AVAudioConverter с AVAudioConverterInputBlock останавливает звук после обработки
Я пытаюсь конвертировать аудио буферы в другой формат, и я использую AVAudioConverter. AVAudioConverter выполняет эту работу, когда у вас одинаковая частота дискретизации, и вам не нужно использовать AVAudioConverterInputBlock.
Но если я имею дело с той же частотой дискретизации, в моих аудиоданных возникает странное заикание. У меня такое чувство, что я плохо справляюсь с блоком ввода. В выводе есть слова, повторяющиеся два или три раза. Ниже приведен полный метод:
func sendAudio(audioFile: URL, completionHandler: @escaping (Bool, Bool, Data?)->Void) {
createSession(){ sessionUrl, observeURL, session in
let file = try! AVAudioFile(forReading: audioFile)
let formatOfAudio = file.processingFormat
self.engine = AVAudioEngine()
guard let input = self.engine.inputNode else {
print("no input")
return
}
//The audio in format in this case is: <AVAudioFormat 0x61800009d010: 2 ch, 44100 Hz, Float32, non-inter>
let formatIn = formatOfAudio
let formatOut = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: true)
let mixer = AVAudioMixerNode()
self.engine.attach(mixer)
mixer.volume = 0.0
self.engine.attach(self.audioPlayerNode)
self.engine.connect(self.audioPlayerNode, to: mixer, format: formatIn)
self.engine.connect(input, to: mixer, format: input.outputFormat(forBus: 0))
self.engine.connect(mixer, to: self.engine.mainMixerNode, format: formatIn)
let audioConverter = AVAudioConverter(from: formatIn, to: formatOut)
mixer.installTap(onBus: 0, bufferSize: 32000, format: formatIn, block: {
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
let convertedBuffer = AVAudioPCMBuffer(pcmFormat: formatOut, frameCapacity: buffer.frameCapacity)
let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
var error: NSError? = nil
let status = audioConverter.convert(to: convertedBuffer, error: &error, withInputFrom: inputBlock)
let myData = convertedBuffer.toData()
completionHandler(true, false, myData)
})
self.audioPlayerNode.scheduleFile(file, at: nil){
self.delayWithSeconds(3.0){
self.engine.stop()
mixer.removeTap(onBus: 0)
completionHandler(true, true, nil)
}
}
do {
try self.engine.start()
} catch {
print(error)
}
self.audioPlayerNode.play()
}
}
Какие-нибудь мысли? Я получил этот код из образца слайда Apple:
// Create an input block that’s called when converter needs input
let inputBlock : AVAudioConverterInputBlock = {inNumPackets, outStatus in
if (<no_data_available>) {
outStatus.memory = AVAudioConverterInputStatus.NoDataNow;
return nil;
} else if (<end_of_stream>) {
outStatus.memory = AVAudioConverterInputStatus.EndOfStream;
return nil;
} else {
..outStatus.memory = AVAudioConverterInputStatus.HaveData;
return inBuffer; // fill and return input buffer
}
}
1 ответ
Поэтому я считаю, что я понял это. Емкость преобразованного кадра буфера должна быть разделена на отношение преобразованных частот выборки. Итак, полный ответ выглядит так:
func sendAudio(audioFile: URL, completionHandler: @escaping (Bool, Bool, Data?)->Void) {
createSession(){ sessionUrl, observeURL, session in
let file = try! AVAudioFile(forReading: audioFile)
let formatOfAudio = file.processingFormat
self.engine = AVAudioEngine()
guard let input = self.engine.inputNode else {
print("no input")
return
}
//The audio in format in this case is: <AVAudioFormat 0x61800009d010: 2 ch, 44100 Hz, Float32, non-inter>
let formatIn = formatOfAudio
let formatOut = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: true)
let mixer = AVAudioMixerNode()
self.engine.attach(mixer)
mixer.volume = 0.0
self.engine.attach(self.audioPlayerNode)
self.engine.connect(self.audioPlayerNode, to: mixer, format: formatIn)
self.engine.connect(input, to: mixer, format: input.outputFormat(forBus: 0))
self.engine.connect(mixer, to: self.engine.mainMixerNode, format: formatIn)
let audioConverter = AVAudioConverter(from: formatIn, to: formatOut)
//Here is where I adjusted for the sample rate. It's hard coded here, but you would want to adjust so that you're dividing the input sample rate by your chosen sample rate.
let sampleRateConversionRatio: Float = 44100.0/16000.0
mixer.installTap(onBus: 0, bufferSize: 32000, format: formatIn, block: {
(buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in
//And this is where you set the appropriate capacity!
let capacity = UInt32(Float(buffer.frameCapacity)/ratio)
let convertedBuffer = AVAudioPCMBuffer(pcmFormat: formatOut, frameCapacity: capacity)
let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
var error: NSError? = nil
let status = audioConverter.convert(to: convertedBuffer, error: &error, withInputFrom: inputBlock)
let myData = convertedBuffer.toData()
completionHandler(true, false, myData)
})
self.audioPlayerNode.scheduleFile(file, at: nil){
self.delayWithSeconds(3.0){
self.engine.stop()
mixer.removeTap(onBus: 0)
completionHandler(true, true, nil)
}
}
do {
try self.engine.start()
} catch {
print(error)
}
self.audioPlayerNode.play()
}
}
Для любого, кто обнаружит это, настоящая основная причина - неправильное использование AVAudioConverterInputBlock
. Емкость целевого буфера не имеет значения, пока она достаточно велика, однако блок будет вызываться повторно, пока целевой буфер не будет заполнен.
Если ваш исходный буфер содержит ABC
, он заполнит пункт назначения ABCABCABC...
. Затем, если вы подключите его к воспроизведению в реальном времени, фрагменты будут отсечены случайным образом, чтобы соответствовать времени воспроизведения, что приведет к этому странному потрескиванию.
Фактическое решение - правильно установить AVAudioConverterInputStatus
к .noDataNow
как только буфер будет отправлен в конвертер. Обратите внимание, что возвращение.endOfStream
заблокирует объект-преобразователь навсегда.
var gotData = false
self.converter.convert(to: convertedBuffer, error: nil, withInputFrom: { (_, outStatus) -> AVAudioBuffer? in
if gotData {
outStatus.pointee = .noDataNow
return nil
}
gotData = true
outStatus.pointee = .haveData
return inputBuffer
})