使用 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)
}
我尝试使用 DispatchGroups、DispatchSourceTimers、DispatchSemaphores[= 来处理另一个线程中的繁重处理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 帧大小的几倍),以解决网络和计时器抖动问题。
我正在创建一个使用 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)
}
我尝试使用 DispatchGroups、DispatchSourceTimers、DispatchSemaphores[= 来处理另一个线程中的繁重处理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 帧大小的几倍),以解决网络和计时器抖动问题。