Search code examples
swiftavfoundation

Error convertig mp3 to aac audio with AVFoundation


I am trying to convert a mp3-audio to aac-audio and I am getting the error

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '*** -[AVAssetReaderTrackOutput copyNextSampleBuffer] cannot copy next sample buffer before adding this output to an instance of AVAssetReader (using -addOutput:) and calling -startReading on that asset reader'

when using this code:

let asset = AVAsset(url: tmpURL)
            
let assetReader = try AVAssetReader(asset: asset)
            
if let audioTrack = try await asset.loadTracks(withMediaType: .audio).first {
    let assetReaderOutput = AVAssetReaderTrackOutput(track: audioTrack,
                                                     outputSettings: [
                                                        AVFormatIDKey: Int(kAudioFormatLinearPCM)
                                                     ])
                
    assetReader.add(assetReaderOutput)
    assetReader.startReading()
                
    let assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: .m4a)
    let outputSettings: [String: Any] = [
        AVFormatIDKey: kAudioFormatMPEG4AAC,
        AVSampleRateKey: 44100,
        AVEncoderBitRateKey: 256000,
        AVNumberOfChannelsKey: 2
    ]
    let assetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio,
                                                          outputSettings: outputSettings)
    assetWriter.add(assetWriterInput)
                
    assetWriter.startWriting()
    assetWriter.startSession(atSourceTime: .zero)
                
                
    let processingQueue = DispatchQueue(label: "processingQueue")
                
    assetWriterInput.requestMediaDataWhenReady(on: processingQueue) {
        while assetWriterInput.isReadyForMoreMediaData {
            guard let nextBuffer = assetReaderOutput.copyNextSampleBuffer() else {
                assetWriterInput.markAsFinished()
                assetWriter.finishWriting(completionHandler: {
                    if assetWriter.status == .completed {
                        print("File converted successfully")
                    } else {
                        print("File conversion failed with error: \(assetWriter.error?.localizedDescription ?? "unknown error")")
                    }
                })
                break
            }
            assetWriterInput.append(nextBuffer)
        }
    }
}

I don't understand that, because I am actually adding the output to a reader and then I start assetReader.startReading(). What am I doing wrong?


Solution

  • Your assetReader is going out of scope. I guess the AVAssetReaderOutput has no back pointer to its AVAssetReader. So your code works if you add a "structural print" after appending the buffer, because this extends the AVAssetReader scope:

    assetWriterInput.append(nextBuffer)
    print("structural print \(assetReader)")
    

    Unlike AVAssetWriter, whose lifetime is made obvious by its startWriting and finishWriting methods, AVAssetReader has a startReading but no finishReading method, making it easy to mismanage its scope. API design is hard. You should probably bundle your AVAssetReader and AVAssetReaderOutput together in a class or struct along with a comment warning against refactoring the object to avoid this situation in the future.