iosswiftaudioavfoundationavaudioconverter

AVAudioConverter.convertToBuffer throwing error code -50


I need to convert a .wav file recorded with 2 audio channels to a .wav that has only 1 channel, as well as reduce the bit depth from 32 to 16. I've been trying to use AVAudioConverter.convertToBuffer However, the conversion is throwing an error: Error Domain=NSOSStatusErrorDomain Code=-50 "(null)"

Basically, the only thing that really needs to change is to strip the audio down to a single channel, and the bit depth. I'm getting these files from a different tool, so I can't just change the way the files are recorded.

I'm not that awesome at working with audio, and I'm a bit stumped. The code I'm working on is below - is there anything I'm missing?

let inAudioFileURL:NSURL = <url_to_wav_file>

var inAudioFile:AVAudioFile?
do
{
    inAudioFile = try AVAudioFile(forReading: inAudioFileURL)
}
catch let error
{
    print ("error: \(error)")
}

let inAudioFormat:AVAudioFormat = inAudioFile!.processingFormat
let inFrameCount:UInt32 = UInt32(inAudioFile!.length)

let inAudioBuffer:AVAudioPCMBuffer = AVAudioPCMBuffer(PCMFormat: inAudioFormat, frameCapacity: inFrameCount)

do
{
    try inAudioFile!.readIntoBuffer(inAudioBuffer)
}
catch let error
{
    print ("readError: \(error)")
}

let startFormat:AVAudioFormat = AVAudioFormat.init(settings: inAudioFile!.processingFormat.settings)
print ("startFormat: \(startFormat.settings)")

var endFormatSettings = startFormat.settings
endFormatSettings[AVLinearPCMBitDepthKey] = 16
endFormatSettings[AVNumberOfChannelsKey] = 1
endFormatSettings[AVEncoderAudioQualityKey] = AVAudioQuality.Medium.rawValue
print ("endFormatSettings: \(endFormatSettings)")


let endFormat:AVAudioFormat = AVAudioFormat.init(settings: endFormatSettings)
let outBuffer = AVAudioPCMBuffer(PCMFormat: endFormat, frameCapacity: inFrameCount)

let avConverter:AVAudioConverter = AVAudioConverter.init(fromFormat: startFormat, toFormat: endFormat)

do
{
    try avConverter.convertToBuffer(outBuffer, fromBuffer: inAudioBuffer)
}
catch let error
{
    print ("avconverterError: \(error)")
}

As for the output:

startFormat: 
  ["AVSampleRateKey": 16000,
  "AVLinearPCMBitDepthKey": 32,
  "AVLinearPCMIsFloatKey": 1,
  "AVNumberOfChannelsKey": 2,
  "AVFormatIDKey": 1819304813,
  "AVLinearPCMIsNonInterleaved": 0,
  "AVLinearPCMIsBigEndianKey": 0]

endFormatSettings:
["AVSampleRateKey": 16000,
"AVLinearPCMBitDepthKey": 16,
"AVLinearPCMIsFloatKey": 1,
"AVNumberOfChannelsKey": 1,
"AVFormatIDKey": 1819304813,
"AVLinearPCMIsNonInterleaved": 0,
"AVLinearPCMIsBigEndianKey": 0,
"AVEncoderQualityKey": 64]

avconverterError: Error Domain=NSOSStatusErrorDomain Code=-50 "(null)"

Solution

  • I'm not 100% sure why this is the case, but I found a solution that got this working for me, so here's how I understand the problem. I found this solution by trying to use the alternate convert(to:error:withInputFrom:) method. Using this was giving me a different error:

    `ERROR:    AVAudioConverter.mm:526: FillComplexProc: required condition is false: [impl->_inputBufferReceived.format isEqual: impl->_inputFormat]`
    

    The problem was caused in the line where I setup the AVAudioConverter:

    let avConverter:AVAudioConverter = AVAudioConverter.init(fromFormat: startFormat, toFormat: endFormat)
    

    It appears that the audio converter wants to use the same AVAudioFormat that the input buffer is using, instead of using a copy based on the original's settings. Once I swapped startFormat out for inAudioFormat, the convert(to:error:withInputFrom:) error was dismissed, and things worked as expected. I was then able to go back to using the simpler convert(to:fromBuffer:) method, and the original error I was dealing with also went away.

    To recap, the line setting up the converter now looks like:

    let avConverter:AVAudioConverter = AVAudioConverter.init(fromFormat: inAudioFormat, toFormat: endFormat)
    

    As for the lack of docs on how to use AVAudioConverter, I have no idea why the API reference has next to nothing. Instead, in Xcode, CMD-click on AVAudioConverter in your code to go to it's header file. There's plenty of comments and info there. Not full sample code or anything, but it's at least something.