【问题标题】:Playing a stereo audio buffer from memory with AVAudioEngine使用 AVAudioEngine 从内存中播放立体声音频缓冲区
【发布时间】:2020-12-19 17:53:10
【问题描述】:

我正在尝试在我的 iOS 应用程序中从内存(而不是文件)播放 stereo 音频缓冲区,但是当我尝试将 AVAudioPlayerNode 'playerNode' 附加到 AVAudioEngine 'audioEngine 时,我的应用程序崩溃了'。我得到的错误代码如下:

Thread 1: Exception: "required condition is false: _outputFormat.channelCount == buffer.format.channelCount"

我不知道这是否是由于我声明 AVAudioEngine、AVAudioPlayerNode 的方式,我生成的缓冲区是否有问题,或者我是否错误地附加了节点(或其他原因!) .我有一种感觉,这与我如何创建新缓冲区有关。我正在尝试从两个单独的“单声道”数组制作立体声缓冲区,也许它的格式不正确。

我已经声明了audioEngine:AVAudioEngine!和 playerNode:AVAudioPlayerNode!全局:

var audioEngine: AVAudioEngine!
var playerNode: AVAudioPlayerNode!

然后我加载我的应用程序将要处理的单声道源音频文件(不会播放此文件中的数据,它将被加载到数组中,处理并然后加载到一个缓冲区):

    // Read audio file
    let audioFileFormat = audioFile.processingFormat
    let frameCount = UInt32(audioFile.length)
    let audioBuffer = AVAudioPCMBuffer(pcmFormat: audioFileFormat, frameCapacity: frameCount)!
    
    // Read audio data into buffer
    do {
        try audioFile.read(into: audioBuffer)
    } catch let error {
        print(error.localizedDescription)
    }
    // Convert buffer to array of floats
    let input: [Float] = Array(UnsafeBufferPointer(start: audioBuffer.floatChannelData![0], count: Int(audioBuffer.frameLength)))

然后将数组发送到一个卷积函数两次,该函数每次都返回一个新数组。这是因为单声道源文件需要成为立体声音频缓冲区:

    maxSignalLength = input.count + 256
    let leftAudioArray: [Float] = convolve(inputAudio: input, impulse: normalisedLeftImpulse)
    let rightAudioArray: [Float] = convolve(inputAudio: input, impulse: normalisedRightImpulse)

maxSignalLength 变量当前是输入信号的长度 + 正在与之卷积的脉冲响应 (normalisedImpulseResponse) 的长度,目前为 256。这将成为一个合适的变量。

然后我声明并加载新缓冲区及其格式,我感觉错误出在此处,因为这将是播放的缓冲区:

    let bufferFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: hrtfSampleRate, channels: 2, interleaved: false)!
    let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: AVAudioFrameCount(maxSignalLength))!

请注意,我没有创建交错缓冲区,我将立体声音频数据加载到缓冲区中,如下所示(我认为这也可能是错误的):

        for ch in 0 ..< 2 {
        for i in 0 ..< maxSignalLength {
            
            var val: Float!
            
            if ch == 0 { // Left
                
                val = leftAudioArray[i]
                // Limit
                if val > 1 {
                    val = 1
                }
                if val < -1 {
                    val = -1
                }
                
            } else if ch == 1 { // Right
                
                val = rightAudioArray[i]
                // Limit
                if val < 1 {
                    val = 1
                }
                if val < -1 {
                    val = -1
                }
            }
            
            outputBuffer.floatChannelData![ch][i] = val
        }
    }

音频也限制在 -1 和 1 之间的值。

然后我终于来(尝试)将缓冲区加载到音频节点,将音频节点附加到音频引擎,启动音频引擎然后播放节点。

    let frameCapacity = AVAudioFramePosition(outputBuffer.frameCapacity)
    let frameLength = outputBuffer.frameLength
    
    playerNode.scheduleBuffer(outputBuffer, at: nil, options: AVAudioPlayerNodeBufferOptions.interrupts, completionHandler: nil)
    playerNode.prepare(withFrameCount: frameLength)
    let time = AVAudioTime(sampleTime: frameCapacity, atRate: hrtfSampleRate)
    
    audioEngine.attach(playerNode)
    audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: outputBuffer.format)
    audioEngine.prepare()
    do {
        try audioEngine.start()
    } catch let error {
        print(error.localizedDescription)
    }
    
    playerNode.play(at: time)

我在运行时遇到的错误是:

AVAEInternal.h:76    required condition is false: [AVAudioPlayerNode.mm:712:ScheduleBuffer: (_outputFormat.channelCount == buffer.format.channelCount)]

它没有显示发生此错误的行。我已经坚持了一段时间,并且尝试了很多不同的东西,但似乎没有很清楚的信息关于从内存播放音频而不是从我能找到的 AVAudioEngine 文件中播放音频。任何帮助将不胜感激。

谢谢!

编辑#1: 更好的标题

编辑#2: 更新 - 我已经找到了我收到错误的原因。这似乎是由于在将 playerNode 附加到 audioEngine 之前设置它引起的。交换顺序阻止程序崩溃并抛出错误:

    let frameCapacity = AVAudioFramePosition(outputBuffer.frameCapacity)
    let frameLength = outputBuffer.frameLength
    
    audioEngine.attach(playerNode)
    audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: outputBuffer.format)
    audioEngine.prepare()
    
    playerNode.scheduleBuffer(outputBuffer, at: nil, options: AVAudioPlayerNodeBufferOptions.interrupts, completionHandler: nil)
    playerNode.prepare(withFrameCount: frameLength)
    let time = AVAudioTime(sampleTime: frameCapacity, atRate: hrtfSampleRate)
    
    do {
        try audioEngine.start()
    } catch let error {
        print(error.localizedDescription)
    }

    playerNode.play(at: time)

但是,我没有任何声音。使用与输入信号相同的方法创建 outputBuffer 的浮点数组后,并用断点查看它的内容,它似乎是空的,所以我也必须错误地将数据存储到 outputBuffer。

【问题讨论】:

    标签: ios swift audio avaudioengine avaudioplayernode


    【解决方案1】:

    您可能正在错误地创建和填充缓冲区。尝试这样做:

    let fileURL = Bundle.main.url(forResource: "my_file", withExtension: "aiff")!
    let file = try! AVAudioFile(forReading: fileURL)
    let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: UInt32(file.length))!
    try! file.read(into: buffer)
    

    【讨论】:

    • 是的,我认为这是缓冲区初始化的问题,但是这个解决方案对我不起作用,因为我需要立体声输出并且用于输入的音频文件的格式是单声道.尝试此操作时加载到缓冲区时出现错误,可能是因为缓冲区不接受多个通道
    【解决方案2】:

    我已经解决了这个问题!

    我尝试了很多解决方案,最终完全重写了我的应用程序的音频引擎部分,现在我在 ViewController 类中声明了 AVAudioEngine 和 AVAudioPlayerNode,如下所示:

    class ViewController: UIViewController {
    
    var audioEngine: AVAudioEngine = AVAudioEngine()
    var playerNode: AVAudioPlayerNode = AVAudioPlayerNode()
    
    ...
    

    我仍然不清楚在 iOS 中将它们声明为全局变量还是作为类变量更好,但是我可以确认我的应用程序正在播放音频,这些声明在 ViewController 类中声明。我知道它们不应该在函数中声明为they will disappear and stop playing when the function goes out of scope.

    但是,直到我将 AVAudioPCMBuffer.frameLength 设置为 frameCapacity,我仍然没有得到任何音频输出。

    关于从浮点数数组创建新的 AVAudioPCMBuffer 的在线信息很少,但这似乎是使 outputBuffer 可播放所需的缺失步骤。在我设置之前,默认为 0。

    在 AVAudioFormat 类声明中不需要 frameLength 成员。但这很重要,直到我手动设置它并且在类实例声明之后,我的缓冲区才可播放:

    let bufferFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: hrtfSampleRate, channels: 2, interleaved: false)!
    let frameCapacity = UInt32(audioFile.length)
    guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: frameCapacity) else {
        fatalError("Could not create output buffer.")
    }
    outputBuffer.frameLength = frameCapacity // Important!
    

    这花了很长时间才发现,希望这会在未来对其他人有所帮助。

    【讨论】:

    • 我也遇到了同样的问题。结果发现format 我提供的AVAudioEngine 实例与提供的缓冲区格式不匹配。所以我只需要在设置我的 PCM 缓冲区并从中为引擎提供一致的格式后创建引擎,例如engine.connect(player, to: engine.mainMixerNode, format: formatUsedByMyPCMBuffers)。但现在我遇到了另一个错误:ca_require: ValidFormat(inScope, inElement, newDesc) InvalidFormat
    猜你喜欢
    • 2013-11-22
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多