I'm adding an an m4a
audio file from the file system, loaded via an AVURLAsset
, into an AVMutableComposition
. If the loaded asset has a duration
of 1s, adding it to the AVMutableComposition
results in the composition also having a duration of 1s
. This makes sense. However, after exporting the composition to a new file using AVAssetExportSession
, the resulting m4a
file has a duration about 0.05s less than the duration of the composition (0.95s vs. 1s).
This bug only happens when working with m4a
files. If I work with caf
, there's no difference in the duration of the exported file vs. the composition.
I originally discovered this bug when I was combining an existing audio file with a new audio file. Despite the AVMutableComposition
reporting the correct duration
, the file exported by AVAssetExportSession
was ~0.05s shorter in duration. To simplify this question, I've removed the code that combines 2 existing audio files together, and made it so that we simply insert a new audio file into an empty mutable composition. The bug still occurs even for this simple case.
Here's my mutable composition code:
let composition = AVMutableComposition()
guard
let compositionTrack = composition.addMutableTrack(
withMediaType: .audio,
preferredTrackID: kCMPersistentTrackID_Invalid)
else
{
fatalError("Could not create an AVMutableCompositionTrack.")
}
let newAudioAsset = AVURLAsset(
url: newAudioFileURL,
options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
guard let newAudioTrack = newAudioAsset.tracks(withMediaType: .audio).first else {
fatalError("Could not get an audio track for the new audio to be inserted.")
}
// Insert the new audio.
try compositionTrack.insertTimeRange(
CMTimeRangeMake(start: .zero, duration: newAudioAsset.duration),
of: newAudioTrack,
at: .zero)
}
Here's my export code:
guard
let exportSession = AVAssetExportSession(
asset: composition,
presetName: AVAssetExportPresetAppleM4A)
else
{
fatalError("Could not create an AVAssetExploreSession.")
}
self.exportSession = exportSession
exportSession.outputURL = audioFileURL
exportSession.outputFileType = .m4a
exportSession.timeRange = CMTimeRange(start: .zero, duration: composition.duration)
exportSession.exportAsynchronously(completionHandler: { [weak self, weak exportSession] in
guard let self = self, let exportSession = exportSession else {
fatalError()
}
switch exportSession.status {
case .completed:
// Roughly 0.05s less
let savedFileAsset = AVURLAsset(
url: audioFileURL,
options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
assert(savedFileAsset.duration == composition.duration) // fails; saved file asset is about 0.05s less
self.exportSession = nil
default:
if let error = exportSession.error {
self.exportSession = nil
print(error.localizedDescription)
}
}
})