Search code examples
iosswiftavfoundationavaudioengineavaudioconverter

AVAudioConverter corrupts data


I am trying to convert AVAudioPCMBuffer from Float32 44.1kHz to Int16 8kHz. I use AVAudioConverter. But after the conversion I get corrupted data. When I read data every 100ms from microphone I get every 800th sample broken (first sample in the read data). Broken sample looks as follows: broken sample

It's just one sample out of 800 but it sounds like really annoying click every 100ms. Here is a piece of my code:

...
    let outputFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
                                     sampleRate: 8000,
                                     channels: 1,
                                     interleaved: false)!
    let input = self.engine.inputNode
    let bus = 0
    let inputFormat = input.outputFormat(forBus: bus)
        
    let bufferSize = inputFormat.sampleRate * 0.1 // Read every 100ms
        
    input.installTap(onBus: bus, bufferSize: UInt32(bufferSize), format: inputFormat) { (buffer, time) -> Void in
                
        let convertedBuffer = self.convertBuffer(buffer: buffer, from: inputFormat, to: outputFormat)
        if self.isRecording {
            self.inFile?.seekToEndOfFile()
            let data = Data(buffer: UnsafeBufferPointer(start: convertedBuffer.int16ChannelData![0], count: Int(convertedBuffer.frameLength)))
            self.inFile?.write(data)
        }
...

func convertBuffer(buffer: AVAudioPCMBuffer,
                   from inputFormat: AVAudioFormat,
                   to outputFormat: AVAudioFormat) -> AVAudioPCMBuffer {
        
    let converter = AVAudioConverter(from: inputFormat, to: outputFormat)!
        
    let inputCallback: AVAudioConverterInputBlock = { inNumPackets, outStatus in
        outStatus.pointee = .haveData
        return buffer
    }
        
    let convertedBuffer = AVAudioPCMBuffer(
        pcmFormat: outputFormat,
        frameCapacity: AVAudioFrameCount(outputFormat.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate))!
        
    var error: NSError?
    let status = converter.convert(to: convertedBuffer, error: &error, withInputFrom: inputCallback)
    assert(status != .error)
        
    return convertedBuffer
}

When I write buffer without any conversion I get clear sound without corruption. If I read data every 200ms every 1600th sample will be corrupted. So it's only the first sample from every buffer I get is corrupted.

I really don't know what is wrong with my code and why it works like that. Is there any error?

P.S. I don't really care about the conversion, all I need is get 16 bit PCM 8kHz raw audio data from microphone. If anyone knows how to do that the other way I'd really appreciate your help.


Solution

  • Audio converters have internal state. Creating a single converter instead of one in each call to convertBuffer should fix the issue.