I have an application that allows to append multiple video assets and add one or multiple audio tracks to a composition. All seems to work, I can play the resulting composition using AVPlayer
(although the audio level seems low). After exporting the composition to file, the audio track is missing.
My code is largely based on the AVEditDemo sample code from the WWDC10 sessions. I have double checked my code against the AVEditDemo code and cannot find what could be the problem. I have also checked forums but there is not much AVFoundation related posts/solutions.
Any help is most welcome. Cheers,
Jean-Pierre
Method to build the composition with extra audio tracks
Notes:
compositionArray: contains assets to build the composition.
AssetView: object containing a AVURLAsset.
- (AVMutableComposition *)buildCompositionObjects
{
// no assets available, return nil
if ([compositionArray count] < 1)
{
return nil;
}
// get the asset video size
AssetView * view = [compositionArray objectAtIndex:0];
AVURLAsset * asset = view.asset;
CGSize videoSize = [asset naturalSize];
// create new composition
AVMutableComposition * cmp = [AVMutableComposition composition];
// set the size
cmp.naturalSize = videoSize;
// build composition
[self buildComposition:cmp];
// add any extra audio track
[self addAudioTrackToComposition:cmp];
// return the new composition
return cmp;
}
Method to build the base composition
- (void) buildComposition:(AVMutableComposition *)cmp
{
// set the start time of contiguous tracks
CMTime nextClipStartTime = kCMTimeZero;
// clear the composition
[cmp removeTimeRange:CMTimeRangeMake(CMTimeMake(0, 600), cmp.duration)];
// add audio and video tracks
AVMutableCompositionTrack *compositionVideoTrack = [cmp addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *compositionAudioTrack = [cmp addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// loop through all available assets
for (AssetView * view in compositionArray)
{
AVURLAsset *asset = view.asset;
CMTimeRange timeRangeInAsset;
timeRangeInAsset = CMTimeRangeMake(kCMTimeZero, [asset duration]);
AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[compositionVideoTrack insertTimeRange:timeRangeInAsset ofTrack:clipVideoTrack atTime:nextClipStartTime error:nil];
// make sure there is an audio track. Had to do this becaaause of this missing audio track issue. Crashes if check is not done (out of bounds).
if ([[asset tracksWithMediaType:AVMediaTypeAudio] count] > 0)
{
AVAssetTrack *clipAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
[compositionAudioTrack insertTimeRange:timeRangeInAsset ofTrack:clipAudioTrack atTime:nextClipStartTime error:nil];
}
// adjust next asset start
nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRangeInAsset.duration);
}
}
Method to add the additional audio tracks
- (void)addAudioTrackToComposition:(AVMutableComposition *)cmp
{
// no audio track, return
if ([audioTracks count] < 1)
{
return;
}
// base track ID for additional audio tracks
long baseTrackID = 100;
for (AVURLAsset * audioAsset in audioTracks)
{
// make sure the audio track fits in the composition
CMTimeRange commentaryTimeRange = CMTimeRangeMake(kCMTimeZero, audioAsset.duration);
if (CMTIME_COMPARE_INLINE(CMTimeRangeGetEnd(commentaryTimeRange), >, [cmp duration]))
{
commentaryTimeRange.duration = CMTimeSubtract([cmp duration], commentaryTimeRange.start);
}
// Add the audio track.
AVMutableCompositionTrack *compositionCommentaryTrack = [cmp addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:baseTrackID++];
[compositionCommentaryTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, commentaryTimeRange.duration) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:commentaryTimeRange.start error:nil];
}
}
Method to export the composition
- (void) save
{
NSString * eventFolder = [NSString stringWithFormat:@"%@/%@-%@",
DOCUMENTS_FOLDER,
event.title,
[StringUtils stringForDate:event.timeStamp]];
NSString * exportVideoPath = [NSString stringWithFormat:@"%@/Edits/%@.MOV", eventFolder, [StringUtils stringForDate:[NSDate date]]];
video.path = exportVideoPath;
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
NSURL *exportURL = [NSURL fileURLWithPath:exportVideoPath];
exportSession.outputURL = exportURL;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
[exportSession exportAsynchronouslyWithCompletionHandler:^
{
switch (exportSession.status)
{
case AVAssetExportSessionStatusFailed:
{
NSLog (@"FAIL");
[self performSelectorOnMainThread:@selector (doPostExportFailed:)
withObject:nil
waitUntilDone:NO];
break;
}
case AVAssetExportSessionStatusCompleted:
{
NSLog (@"SUCCESS");
[self performSelectorOnMainThread:@selector (doPostExportSuccess:)
withObject:nil
waitUntilDone:NO];
break;
}
case AVAssetExportSessionStatusCancelled:
{
NSLog (@"CANCELED");
[self performSelectorOnMainThread:@selector (doPostExportCancelled:)
withObject:nil
waitUntilDone:NO];
break;
}
};
}];
}
Did not get any reply. I have the thing working by adding one line of code:
exportSession.shouldOptimizeForNetworkUse = YES;
before
[exportSession exportAsynchronouslyWithCompletionHandler:^
I'm not sure why this fixes the problem. since this seems totaly unrelated to the problem but I was able to export a dozen composition with up to 5 extra audio tracks without problems.
I hope this helps other people who have been scratching their heads for days.
Cheers
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With