简体   繁体   English

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

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

I am creating a cross platform VOIP application which uses UDP to send and receive data.我正在创建一个使用 UDP 发送和接收数据的跨平台 VOIP 应用程序。 I am using audio units for the real time recording and playback.我正在使用音频单元进行实时录制和播放。 The communication is fast and smooth when working with raw data but when I involve a codec like OPUS , the data which is being encoded and sent from iPhone to Android has clicking and popping sounds in between.处理原始数据时,通信快速顺畅,但当我涉及像OPUS这样的编解码器时,正在编码并从 iPhone 发送到 Android 的数据之间有咔哒声和爆裂声。 I have been pulling my hair out trying to solve this issue.我一直在努力解决这个问题。

The encoded data which is coming from Android to iPhone plays perfectly and there are no issues with that.从 Android 到 iPhone 的编码数据可以完美播放,没有任何问题。 I am using TPCircularBuffer to handle the data when recording and playback.我在录制和播放时使用TPCircularBuffer来处理数据。

This is what I have so far in the recording callback:这是我到目前为止在录音回调中的内容:

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()
        }
    }

This is the encoding function:这是编码函数:

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)
        }

I have tried to handle the heavy processing inside another thread by using DispatchGroups , DispatchSourceTimers , DispatchSemaphores , DispatchQueues but I just cannot get the result that I need.我试图通过使用DispatchGroupsDispatchSourceTimersDispatchSemaphoresDispatchQueues来处理另一个线程内的繁重处理,但我就是无法获得我需要的结果。 Can anyone help?谁能帮忙?

Can anyone guide me how to make the encoding independent of the real time audio thread , I tried to create a polling thread but even that did not work.任何人都可以指导我如何使编码独立于实时音频线程,我试图创建一个轮询线程,但即使这样也没有用。 I need assistance on transferring data between 2 threads with different data size requirements.我需要帮助在具有不同数据大小要求的 2 个线程之间传输数据。 I am receiving 341-342 bytes from the mic but I need to send 640 bytes to the encoder hence I am combining 2 samples and reusing the left over bytes for later.我从麦克风接收到 341-342 字节,但我需要将 640 字节发送到编码器,因此我合并了 2 个样本并重新使用剩下的字节供以后使用。

@hotpaw2 recommends this https://stackoverflow.com/a/58947295/12020007 but I just need a little more guidance. @hotpaw2 推荐这个https://stackoverflow.com/a/58947295/12020007但我只需要多一点指导。

Updated code as per @hotpaw2's answer:根据@hotpaw2 的回答更新了代码:

Recording callback:录音回调:

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

        if !shouldStartSending {
            startLooping()
        }

Updated polling thread:更新的投票主题:

    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 recommends against using semaphores or calling Swift methods (such as encoders) inside any real-time Audio Unit callback. Apple 建议不要在任何实时音频单元回调中使用信号量或调用 Swift 方法(例如编码器)。 Just copy the data into a pre-allocated circular buffer inside the audio unit callback.只需将数据复制到音频单元回调内的预分配循环缓冲区中。 Period.时期。 Do everything else outside the callback.执行回调之外的所有其他操作。 Semaphores and signals included.包括信号量和信号。

So, you need to create a polling thread.因此,您需要创建一个轮询线程。

Do everything inside a polling loop, timer callback, or network ready callback.在轮询循环、计时器回调或网络就绪回调中执行所有操作。 Do your work anytime there is enough data in the FIFO.只要 FIFO 中有足够的数据,就可以开始工作。 Call (poll) often enough (high enough polling frequency or timer callback rate) that you do not lose data.足够频繁地调用(轮询)(足够高的轮询频率或计时器回调率),您不会丢失数据。 Handle all the data you can (perhaps multiple buffers at a time, if available) in each iteration of the polling loop.在轮询循环的每次迭代中处理您可以处理的所有数据(可能一次处理多个缓冲区,如果可用的话)。

You may need to pre-fill the circular buffer a bit (perhaps a few multiples of your 640 UDP frame size) before starting to send, to account for network and timer jitter.在开始发送之前,您可能需要稍微预填充循环缓冲区(可能是 640 UDP 帧大小的几倍),以解决网络和计时器抖动问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM