How do you convert and audio file from two (2) channels (stereo) to one channel (1)?

Currently, I'm recording audio on the Apple Watch using the presentAudioRecorderController and sending it to the corresponding iPhone app.


The audio format by default seems to be recorded on the Apple Watch using two channels and there doesn't seem to be a way to change that.


For various reasons, I need the audio format to be only one channel. I was hoping there would be something on iOS to convert the audio file from stereo (two channels) to mono (one channel)


Right now, I'm investigating if this is possible using AVAudioEngine and other various related classes. I'm not sure if it's possible and it seems very complicated at the moment.

Add a Comment

Replies

I found a way to accomplish this using AVAudioConverter:

let audioFileInputBuffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: UInt32(audioFile.length))!
try? audioFile.read(into: audioFileInputBuffer)

let outputFormat = AVAudioFormat(commonFormat: audioFile.fileFormat.commonFormat, sampleRate: audioFile.fileFormat.sampleRate, channels: 1, interleaved: false)!
let audioConverter = AVAudioConverter(from: audioFileInputBuffer.format, to: outputFormat)!
audioConverter.channelMap = [0]
let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: UInt32(audioFile.length))!

var error: NSError?
let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
    outStatus.pointee = AVAudioConverterInputStatus.haveData
    return audioFileInputBuffer
}

audioConverter.convert(to: outputBuffer, error: &error, withInputFrom: inputBlock)
if let error = error {
    print("AUDIO CONVERSION ERROR: \(error.localizedDescription)")
}

var settings: [String: Any] = [:]
settings[AVFormatIDKey] = kAudioFormatLinearPCM
settings[AVAudioFileTypeKey] = kAudioFileWAVEType
settings[AVSampleRateKey] = outputBuffer.format.sampleRate
settings[AVNumberOfChannelsKey] = 1
settings[AVLinearPCMIsFloatKey] = false

let outputURL = audioURL.deletingLastPathComponent().appendingPathComponent("mono.wav")
let outputAudioFile = try? AVAudioFile(forWriting: outputURL, settings: settings, commonFormat: outputFormat.commonFormat, interleaved: false)
try? outputAudioFile?.write(from: outputBuffer)

let outputData = (try? Data(contentsOf: outputURL))!

But isn't there a simpler way to get the converted mono audio data? Going via a file is slow. I'd like to extract the audio data directly from outputBuffer.

I just want to mention that in outputFormat above you're specifying the common format to be whatever the input format is, but if that's .otherFormat then the initializer will fail. I think it's better to specify one of the other values (e.g., .pcmFormatFloat32)