I am trying to export an AVMutableComposition with a single audio track. This track has a scaled AVCompositionTrackSegment to simulate speed changes up to 20x. I need to use AVAssetWriter and AVAssetReader classes for this task. When I scale the source duration up to a maximum of 5x, everything works fine. However, when I scale it to higher speeds, such as 20x, the app hangs on the copyNextSampleBuffer method. I'm not sure why this is happening and how to prevent it. Also, this often happens if the exported audio track has segments with different speeds. (The duration of the audio file in the example is 47 seconds.)
Example of code:
class Export {
func startExport() {
let inputURL = Bundle.main.url(forResource: "Piano", withExtension: ".m4a")!
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let outputURL = documentsDirectory.appendingPathComponent("Piano20x.m4a")
try? FileManager.default.removeItem(at: outputURL)
print("Output URL: \(outputURL)")
changeAudioSpeed(inputURL: inputURL, outputURL: outputURL, speed: 20)
}
func changeAudioSpeed(inputURL: URL, outputURL: URL, speed: Float) {
let urlAsset = AVAsset(url: inputURL)
guard let assetTrack = urlAsset.tracks(withMediaType: .audio).first else { return }
let composition = AVMutableComposition()
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
do {
try compositionAudioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: assetTrack.timeRange.duration), of: assetTrack, at: .zero)
} catch {
print("Failed to insert audio track: \(error)")
return
}
let scaledDuration = CMTimeMultiplyByFloat64(assetTrack.timeRange.duration, multiplier: Double(1.0 / speed))
compositionAudioTrack?.scaleTimeRange(CMTimeRangeMake(start: .zero, duration: assetTrack.timeRange.duration), toDuration: scaledDuration)
print("Scaled audio from \(assetTrack.timeRange.duration.seconds)sec to \(scaledDuration.seconds) sec")
compositionAudioTrack?.segments
do {
let compositionAudioTracks = composition.tracks(withMediaType: .audio)
let assetReader = try AVAssetReader(asset: composition)
let audioSettings: [String: Any] = [
AVFormatIDKey: kAudioFormatLinearPCM,
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVLinearPCMBitDepthKey: 16,
AVLinearPCMIsBigEndianKey: false,
AVLinearPCMIsFloatKey: false,
AVLinearPCMIsNonInterleaved: false
]
let readerOutput = AVAssetReaderAudioMixOutput(audioTracks: compositionAudioTracks,
audioSettings: audioSettings)
assetReader.add(readerOutput)
let assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: .m4a)
let writerInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
assetWriter.add(writerInput)
assetReader.startReading()
assetWriter.startWriting()
assetWriter.startSession(atSourceTime: .zero)
let conversionQueue = DispatchQueue(label: "ConversionQueue")
writerInput.requestMediaDataWhenReady(on: conversionQueue) {
while writerInput.isReadyForMoreMediaData {
if let sampleBuffer = readerOutput.copyNextSampleBuffer() { // APP hangs here!!!
writerInput.append(sampleBuffer)
} else {
writerInput.markAsFinished()
assetWriter.finishWriting {
print("Export completed successfully")
}
break
}
}
}
} catch {
print("Failed with error: \(error)")
}
}
}