使用 Audio Unit 录音回调处理数据 [iOS][Swift]

Processing data with Audio Unit recording callback [iOS][Swift]

我正在创建一个使用 UDP 发送和接收数据的跨平台 VOIP 应用程序。我正在使用音频单元进行实时录制和播放。使用原始数据时,通信快速流畅,但当我涉及像 OPUS 这样的编解码器时,正在编码并从 iPhone 发送到 Android 的数据中间有咔哒声和爆裂声。我一直在努力解决这个问题。

从 Android 到 iPhone 的编码数据播放完美,没有任何问题。我正在使用 TPCircularBuffer 来处理录制和播放时的数据。

这是我目前在录音回调中的内容:

var samplesForEncoder: UInt32 = 640
var targetBuffer = [opus_int16](repeating: 0, count: 1500)

    _ = TPCircularBufferProduceBytes(&circularBuffer, mData, inNumberFrames * 2)
    self.samplesSinceLastCall += inNumberFrames

    encodingQueue.async {
        if self.samplesSinceLastCall > self.samplesForEncoder {
            let samplesToCopy = min(self.bytesToCopy, Int(self.availableBytes))
            self.bufferTailPointer = TPCircularBufferTail(&self.circularBuffer, &self.availableBytes)
            memcpy(&self.targetBuffer, self.bufferTailPointer, samplesToCopy)
            self.semaphore.signal()
            self.semaphore.wait()

            self.opusHelper?.encodeStream(of: self.targetBuffer)
            self.semaphore.signal()
            self.semaphore.wait()

            TPCircularBufferConsume(&self.circularBuffer, UInt32(samplesToCopy))
            self.samplesSinceLastCall = 0
            self.semaphore.signal()
            self.semaphore.wait()
        }
    }

这是编码函数:

var encodedData = [UInt8](repeating: 0, count: 1500)

    self.encodedLength = opus_encode(self.encoder!, samples, OpusSettings.FRAME_SIZE, &self.encodedData, 1500)

        let opusSlice = Array(self.encodedData.prefix(Int(self.encodedLength!)))

        self.seqNumber += 1
        self.protoModel.sequenceNumber = self.seqNumber
        self.protoModel.timeStamp = Date().currentTimeInMillis()
        self.protoModel.payload = opusSlice.data

        do {
            _ = try self.udpClient?.send(data: self.protoModel)
        } catch {
            print(error.localizedDescription)
        }

我尝试使用 DispatchGroupsDispatchSourceTimersDispatchSemaphores[= 来处理另一个线程中的繁重处理46=], DispatchQueues 但我无法得到我需要的结果。有人可以帮忙吗?

任何人都可以指导我如何使 编码独立于实时音频线程 ,我尝试创建一个 轮询线程 但是即使那样也行不通。我需要帮助在具有不同数据大小要求的 2 个线程之间传输数据。我从麦克风接收到 341-342 字节,但我需要将 640 字节发送到编码器,因此我合并了 2 个样本并为以后重复使用剩余的字节。

@hotpaw2 推荐这个 但我只需要多一点指导。

根据@hotpaw2 的回答更新了代码:

录音回调:

_ = TPCircularBufferProduceBytes(&circularBuffer, mData, inNumberFrames * 2)
        self.samplesSinceLastCall += inNumberFrames

        if !shouldStartSending {
            startLooping()
        }

更新的投票线程:

    func startLooping() {
        loopingQueue.async {
            repeat {
                if self.samplesSinceLastCall > self.samplesForEncoder {
                    let samplesToCopy = min(self.bytesToCopy, Int(self.availableBytes))
                    self.bufferTailPointer = TPCircularBufferTail(&self.circularBuffer, &self.availableBytes)
                    memcpy(&self.targetBuffer, self.bufferTailPointer, samplesToCopy)
                    self.semaphore.signal()
                    self.semaphore.wait()

                    self.opusEncodedStream = self.opusHelper?.encodeStream(of: self.targetBuffer)
                    self.semaphore.signal()
                    self.semaphore.wait()

                    self.send(stream: self.opusEncodedStream!)
                    self.semaphore.signal()
                    self.semaphore.wait()

                    TPCircularBufferConsume(&self.circularBuffer, UInt32(samplesToCopy))
                    self.samplesSinceLastCall = 0
                }
                self.shouldStartSending = true
            } while true
        }
}

Apple 建议不要在任何实时音频单元回调中使用信号量或调用 Swift 方法(例如编码器)。只需将数据复制到音频单元回调内的预分配循环缓冲区中。时期。执行回调之外的所有其他操作。包括信号量和信号。

因此,您需要创建一个轮询线程。

在轮询循环、计时器回调或网络就绪回调中执行所有操作。只要 FIFO 中有足够的数据,就可以开始工作。足够频繁地调用(轮询)(足够高的轮询频率或计时器回调率),您不会丢失数据。在轮询循环的每次迭代中处理您可以处理的所有数据(可能一次处理多个缓冲区,如果可用的话)。

您可能需要在开始发送之前稍微预填充循环缓冲区(可能是 640 UDP 帧大小的几倍),以解决网络和计时器抖动问题。