Обработка данных с помощью обратного вызова записи аудиоустройства [iOS][Swift]
Я создаю кроссплатформенное приложение VOIP, которое использует UDP для отправки и получения данных. Я использую аудиоустройства для записи и воспроизведения в реальном времени. Связь происходит быстро и плавно при работе с необработанными данными, но когда я использую кодек, такой как 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, DispatchQueues, но я просто не могу получить нужный мне результат. Кто-нибудь может помочь?
Может ли кто-нибудь подсказать мне, как сделать кодирование независимым от аудиопотока в реальном времени? Я попытался создать поток опроса, но даже это не сработало. Мне нужна помощь при передаче данных между двумя потоками с разными требованиями к размеру данных. Я получаю с микрофона 341-342 байта, но мне нужно отправить 640 байтов на кодировщик, поэтому я объединяю 2 выборки и повторно использую оставшиеся байты на будущее.
@hotpaw2 рекомендует это /questions/52115451/obrezka-zvuka-s-opusom-na-android-otpravlennaya-s-ios/52115458#52115458, но мне просто нужно немного больше рекомендаций.
Обновленный код в соответствии с ответом @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
}
}
1 ответ
Apple не рекомендует использовать семафоры или вызывать методы Swift (например, кодировщики) внутри любых обратных вызовов Audio Unit в реальном времени. Просто скопируйте данные в заранее выделенный кольцевой буфер внутри обратного вызова аудиоустройства. Период. Все остальное делайте вне обратного вызова. Семафоры и сигналы включены.
Итак, вам нужно создать поток опроса.
Делайте все внутри цикла опроса, обратного вызова таймера или обратного вызова готовности сети. Работайте в любое время, когда в FIFO достаточно данных. Звоните (опрашивайте) достаточно часто (достаточно высокая частота опроса или скорость обратного вызова таймера), чтобы не потерять данные. Обрабатывать все данные, которые вы можете (возможно, несколько буферов одновременно, если они доступны) на каждой итерации цикла опроса.
Перед отправкой вам может потребоваться немного предварительно заполнить кольцевой буфер (возможно, в несколько раз больше размера кадра UDP 640), чтобы учесть джиттер сети и таймера.