user1832894
user1832894

Reputation: 35

Swift code to use AVWriter/AVReader to extract .wav audio from .mp4 video writing zero byte file

I've gone through the documentation and all the posts here, and I've gotten this far.

The function below should take an AVAsset and write out a .wav file. However, the file written out is of zero bytes. I'm not sure I can even inspect what the writer is writing at each step.

What am I missing?

    static func writeAudioTrackToUrl(asset: AVAsset, _ url: URL) throws {
    // initialize asset reader, writer
    let assetReader = try AVAssetReader(asset: asset)
    let assetWriter = try AVAssetWriter(outputURL: URL(fileURLWithPath: "/tmp/audiowav.wav"), fileType: .wav)
    
    // get audio track
    let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first!
    
    // configure output audio settings
    let audioSettings: [String : Any] = [
        AVFormatIDKey: kAudioFormatLinearPCM,
        AVSampleRateKey: 22050.0,
        AVNumberOfChannelsKey: 1,
        AVLinearPCMBitDepthKey: 16,
        AVLinearPCMIsFloatKey: false,
        AVLinearPCMIsBigEndianKey: false,
        AVLinearPCMIsNonInterleaved: false
    ]
    let assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: audioSettings)
    
    if assetReader.canAdd(assetReaderAudioOutput) {
        assetReader.add(assetReaderAudioOutput)
    } else {
        fatalError("could not add audio output reader")
    }
    
    let inputAudioSettings: [String:Any] = [AVFormatIDKey : kAudioFormatLinearPCM]
    let audioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: inputAudioSettings, sourceFormatHint: (audioTrack.formatDescriptions[0] as! CMFormatDescription))
    
    let audioInputQueue = DispatchQueue(label: "audioQueue")
    assetWriter.add(audioInput)
    
    assetWriter.startWriting()
    assetReader.startReading()
    assetWriter.startSession(atSourceTime: CMTime.zero)
    
    audioInput.requestMediaDataWhenReady(on: audioInputQueue) {
        while (audioInput.isReadyForMoreMediaData) {
            let sample = assetReaderAudioOutput.copyNextSampleBuffer()
            if (sample != nil) {
                audioInput.append(sample!)
            } else {
                audioInput.markAsFinished()
                DispatchQueue.main.async {
                    assetWriter.finishWriting {
                        assetReader.cancelReading()
                    }
                }
                break
            }
        }
    }
}

Upvotes: 2

Views: 532

Answers (1)

Gordon Childs
Gordon Childs

Reputation: 36074

The problem here is that you convert the input audio to the LPCM format described by audioSettings, but then you give a sourceFormatHint of audioTrack.formatDescriptions[0] to the AVAssetWriterInput.

This is a problem because the audio track format descriptions are not going to be LPCM but a compressed format, like kAudioFormatMPEG4AAC.

Just drop the hint, I think it's for passing through compressed formats anyway.

Further, the LPCM in inputAudioSettings is under specified - why not pass audioSettings directly?

In summary, try this:

let audioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: audioSettings)

p.s. don't forget to delete the output file before running, AVAssetWriter doesn't seem to overwrite existing files

Upvotes: 2

Related Questions