swiftavassetwriteravassetwriterinput

Getting an error when I try to compress/down-sample the audio file using AVAssetWriterInput swift


I'm trying to take a local m4a file and compress/down-sample this file (For the purposes of making a smaller file).

Now I stuck with error when I try to append sample buffer.

Error description:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVAssetWriterInput appendSampleBuffer:] Cannot append sample buffer: Input buffer must be in an uncompressed format when outputSettings is not nil'

Code where I try compress original audio file into same file format but with lower bitrate:

@objc func nextTapped() {
        let audioURL = RecordWhistleViewController.getWhistleURL()
        
        var asset = AVAsset.init(url: audioURL)
        let exportPath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("out.m4a").path
        print("export PATH IS \(exportPath)")
        
        let exportURL = URL(fileURLWithPath: exportPath)
        
        var readerError: Error? = nil
        var reader: AVAssetReader? = nil
        do {
            reader = try AVAssetReader(asset: asset)
        } catch {
            print("error in reader \(error)")
        }
        
        let track = asset.tracks(withMediaType: .audio)[0]
        let readerOutput = AVAssetReaderTrackOutput(track: track, outputSettings: nil)
        reader?.add(readerOutput)
        
        var writerError: Error? = nil
        var writer: AVAssetWriter? = nil
        do {
            writer = try AVAssetWriter(outputURL: exportURL, fileType: .m4a)
        } catch {
            print("ERROR IN writer \(error)")
        }
        
        var channelLayout = AudioChannelLayout()
        memset(&channelLayout, 0, MemoryLayout<AudioChannelLayout>.size)
        channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo
        
        let outputSettings = [
            AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
            AVSampleRateKey: 12000,
            AVNumberOfChannelsKey: 2,
            AVEncoderBitRateKey: 128000,
            AVChannelLayoutKey: Data(bytes: &channelLayout, count: MemoryLayout<AudioChannelLayout>.size)
            
        ] as [String : Any]
        
        let writerInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings as? [String: Any])
        writerInput.expectsMediaDataInRealTime = false
        writer?.add(writerInput)
        
        writer?.startWriting()
        writer?.startSession(atSourceTime: .zero)
        
        reader?.startReading()
        let mediaInputQueue = DispatchQueue(label: "mediaInputQueue")
        writerInput.requestMediaDataWhenReady(on: mediaInputQueue) {
            print("Asset writer ready: \(writerInput.isReadyForMoreMediaData)")
            while writerInput.isReadyForMoreMediaData {
                var nextBuffer: CMSampleBuffer?
                nextBuffer = readerOutput.copyNextSampleBuffer()
                if nextBuffer != nil {
                    if let nextBuffer = nextBuffer {
                        print("adding buffer")
                        writerInput.append(nextBuffer)
                    }
                } else {
                    writerInput.markAsFinished()
                    reader?.cancelReading()
                    writer?.finishWriting {
                        print("Asset writer finished writing")
                    }
                    break
                }
            }
        }
        
    }

The original audio have settings:

let settings = [
            AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
            AVSampleRateKey: 12000,
            AVNumberOfChannelsKey: 1,
            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
        ]

Code for URL to original file:

class func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let documentsDirectory = paths[0]
        return documentsDirectory
    }

Edited:

here is full description of Error:

assetWriter_finishBuildingAudioTrackWithSourceFormatDescription signalled err=-12413 (kFigAssetWriterError_InappropriateSourceFormat) (AssetWriter can only compress LPCM audio) at /Library/Caches/com.apple.xbs/Sources/EmbeddedCoreMedia_Sim/EmbeddedCoreMedia-2765.6/Prototypes/Export/FigAssetWriter.c:636

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVAssetWriterInput appendSampleBuffer:] Cannot append sample buffer: Input buffer must be in an uncompressed format when outputSettings is not nil'


Solution

  • The errors says that asset writer wants uncompressed sample buffers, but the AVAssetReaderTrackOutput header file says:

    A value of nil for outputSettings configures the output to vend samples in their original format as stored by the specified track.

    Your file is an .m4a file so the samples are likely going to be compressed as AAC.

    To get uncompressed linear PCM, pass kAudioFormatLinearPCM as the AVFormatIDKey in outputSettings:

    let readerOutputSettings = [
        AVFormatIDKey: kAudioFormatLinearPCM
    ]
    
    let readerOutput = AVAssetReaderTrackOutput(track: track, outputSettings: readerOutputSettings)
    

    Note that you are decoding and re-encoding or transcoding your file.