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
})            
Другие вопросы по тегам