I've looked all over the place and have not yet found a working example that can use AVAudioEngine and do a sample-rate conversion when writing to an output file. Here are the things that I've tried:
1) Change the output format on the inputNode. Although the docs say that this is possible (ref: https://developer.apple.com/documentation/avfoundation/avaudioinputnode, text: "The format of the output scope is initially the same as that of the input, but you may set it to a different format, in which case the node will convert."), this actually results in a crash: "'required condition is false: format.sampleRate == hwFormat.sampleRate'"
Also, according to theanalogkid here: https://forums.developer.apple.com/message/201324#201324, this isn't possible anyways: "We currently don't provide sample rate conversion on an input node - if one destination is a file, use AVAudioFile and let it perform the conversion and move your tap to the input node.", so possibly, the docs are out of date or refer to another situation that I have seen, where inserting a microphone will cause the input node's input format to change to 44100 Hz, but leave the output format as 48000 Hz.
I'm not sure how AVAudioFile can be used to perform the conversion, since it wants samples in the 'processingFormat' which might not match the input node's format especially due to these microphone changes.
2) Changing the format of the input node's tap results in the same error.
3) I've tried adding a tap on the mainMixerNode instead with the sample rate that I want, but then the audio engine breaks and my tap callback never gets called.
The only solution that I have found so far is to use AVAudioConverter, which is an API that is undocumented in Apple's online docs. This solution, used inside a tap callback block, feels ham-fisted:
writerSerialQueue.async {
do {
let outBuffer = AVAudioPCMBuffer(pcmFormat: processingFormat, frameCapacity: pcmBuffer.frameCapacity)!
var error : NSError?
var numCalls = 0
converter.convert(to: outBuffer, error: &error, withInputFrom: { (inNumPackets, outStatus) -> AVAudioBuffer? in
if numCalls == 0 {
outStatus.pointee = AVAudioConverterInputStatus.haveData
numCalls += 1
return pcmBuffer
} else {
outStatus.pointee = AVAudioConverterInputStatus.noDataNow
return nil
}
})
try audioFile.write(buffer: pcmBuffer)
} catch {
/
}
}
Please tell me it's not supposed to be this difficult and I've missed an easy or obvious solution?