luckysmg
luckysmg

Reputation: 369

Using AVFoundation scaleTimeRange API will cause different export file duration

I m validating one thing: The composition's duration is the same as it has been exported.

This is ok when no scaleTimeRange.. I don't know why the exported file duration is not the same as AVMutableComposition after using scaleTimeRange API

Here is demo code


class ViewController2 : UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let asset: AVURLAsset = {
            let url = Bundle.main.url(forResource: "test", withExtension: "caf", subdirectory: nil)
            return AVURLAsset(url: url!, options: [AVURLAssetPreferPreciseDurationAndTimingKey : true])
        }()
        
        let composition = AVMutableComposition()
        let mix = AVAudioMix()
        try! composition.insertTimeRange(CMTimeRange(start: .zero, end: asset.duration), of: asset, at: .zero)
        
        
        // scale time range of whole resource, make it 2x speed
        // After using this, the assert will fail.
        // Comment this line the assert can be true.
        composition.scaleTimeRange(CMTimeRange(start: .zero, end: composition.duration), toDuration: CMTime(value: composition.duration.value / 2, timescale: composition.duration.timescale))
        
        Task {
            let outputURL = URL(fileURLWithPath: NSTemporaryDirectory() + UUID().uuidString + ".caf")
            await TestAudioDemo.export(asset: composition, mix: mix, outputURL: outputURL)
            let exportAsset = AVURLAsset(url: outputURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey : true])
            
            print("export ok \(composition.duration) \(exportAsset.duration)")
            assert(composition.duration == exportAsset.duration)
        }
    }
    
}

func export(asset: AVAsset,
            mix: AVAudioMix,
                   outputURL: URL) async {
    return await withCheckedContinuation { continuation in
        let reader = try! AVAssetReader(asset: asset)
        let readerOutput = AVAssetReaderAudioMixOutput(audioTracks: asset.tracks(withMediaType: .audio), audioSettings: nil)
        readerOutput.audioMix = mix
        reader.add(readerOutput)
        let writer = try! AVAssetWriter(outputURL: outputURL, fileType: .caf)
        let cafOutputSettings:[String:Any] = [
            AVFormatIDKey : kAudioFormatLinearPCM,
            AVSampleRateKey : 44100,
            AVNumberOfChannelsKey : 2,
            AVLinearPCMBitDepthKey : 32,
            AVLinearPCMIsNonInterleaved : false,
            AVLinearPCMIsFloatKey : true,
            AVLinearPCMIsBigEndianKey : true,
        ]
        let writerInput = AVAssetWriterInput(mediaType: .audio,
                                             outputSettings: cafOutputSettings)
        writer.add(writerInput)
        reader.startReading()
        writer.startWriting()
        writer.startSession(atSourceTime: .zero)
        let workQueue = DispatchQueue(label: "Exporter.simpleExportQueue")
        writerInput.requestMediaDataWhenReady(on: workQueue) {
            while writerInput.isReadyForMoreMediaData {
                if let buffer = readerOutput.copyNextSampleBuffer() {
                    writerInput.append(buffer)
                } else {
                    writerInput.markAsFinished()
                    reader.cancelReading()
                    writer.finishWriting {
                        continuation.resume()
                    }
                    break
                }
            }
        }
    }
}

Upvotes: 0

Views: 41

Answers (0)

Related Questions