'Swift/Cocoa: How do I export an AVMutableComposition as wav or aiff audio file

I have a AVMutableComposition containing only audio that I want to export to a .wav audio file.

The simplest solution for exporting audio I found was using AVAssetExportSession like in this simplified example:

let composition = AVMutableComposition()
// add tracks...

let exportSession = AVAssetExportSession(asset: composition,
                                         presetName: AVAssetExportPresetAppleM4A)!
exportSession.outputFileType = .m4a
exportSession.outputURL = someOutUrl
exportSession.exportAsynchronously {
    // done
}

But it only works for .m4a

This post mentions that in order to export to other formats, one would have to use AVAssetReader and AVAssetWriter, unfortunately though it does not go into further details.

I have tried to implement it but got stuck in the process.
This is what I have so far (again simplified):

let composition = AVMutableComposition()
let outputSettings: [String : Any] = [
    AVFormatIDKey: kAudioFormatLinearPCM,
    AVLinearPCMIsBigEndianKey: false,
    AVLinearPCMIsFloatKey: false,
    AVLinearPCMBitDepthKey: 32,
    AVLinearPCMIsNonInterleaved: false,
    AVSampleRateKey: 44100.0,
    AVChannelLayoutKey: NSData(),
]

let assetWriter = try! AVAssetWriter(outputURL: someOutUrl, fileType: .wav)
let input = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings)
assetWriter.add(input)
assetWriter.startWriting()
assetWriter.startSession(atSourceTime: CMTime.zero)
input.requestMediaDataWhenReady(on: .main) {

    // as I understand, I need to bring in data from my
    // AVMutableComposition here...

    let sampleBuffer: CMSampleBuffer = ???
    input.append(sampleBuffer)
}

assetWriter.finishWriting {
    // done
}

It boils down to my question:

Can you provide a working example for exporting audio from a AVMutableComposition to a wav file?



Solution 1:[1]

After some more research I came up with the following solution.
The missing piece was the usage of AVAssetReader.

(simplified code)

// composition
let composition = AVMutableComposition()
// add stuff to composition

// reader
guard let assetReader = try? AVAssetReader(asset: composition) else { return }
assetReader.timeRange = CMTimeRange(start: .zero, duration: CMTime(value: composition.duration.value, timescale: composition.duration.timescale))
let assetReaderAudioMixOutput = AVAssetReaderAudioMixOutput(audioTracks: composition.tracks(withMediaType: .audio), audioSettings: nil)
assetReader.add(assetReaderAudioMixOutput)
guard assetReader.startReading() else { return }

// writer
let outputSettings: [String : Any] = [
    AVFormatIDKey: kAudioFormatLinearPCM,
    AVLinearPCMIsBigEndianKey: false,
    AVLinearPCMIsFloatKey: false,
    AVLinearPCMBitDepthKey: 32,
    AVLinearPCMIsNonInterleaved: false,
    AVSampleRateKey: 44100.0,
    AVChannelLayoutKey: NSData(),
]
guard let assetWriter = try? AVAssetWriter(outputURL: someOutUrl, fileType: .wav) else { return }
let writerInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings)
assetWriter.add(writerInput)
guard assetWriter.startWriting() else { return }
assetWriter.startSession(atSourceTime: CMTime.zero)

let queue = DispatchQueue(label: "my.queue.id")
writerInput.requestMediaDataWhenReady(on: queue) {

    // capture assetReader in my block to prevent it being released
    let readerOutput = assetReader.outputs.first!

    while writerInput.isReadyForMoreMediaData {
        if let nextSampleBuffer = readerOutput.copyNextSampleBuffer() {
            writerInput.append(nextSampleBuffer)
        } else {
            writerInput.markAsFinished()
            assetWriter.endSession(atSourceTime: composition.duration)
            assetWriter.finishWriting() {
                DispatchQueue.main.async {

                    // done, call my completion
                }
            }
            break;
        }
    }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 wristbands