AVAssetExportSession intermittent error 11820 “Cannot Complete Export” Suggestion=Try exporting again

EXPORT STATUS 4 Error Domain=AVFoundationErrorDomain Code=-11820 "Cannot Complete Export" UserInfo={NSLocalizedDescription=Cannot Complete Export, NSLocalizedRecoverySuggestion=Try exporting again.}

I'm experiencing an intermittent error when trying to export an

AVMutableComposition
containing
AVMutableVideoCompositionLayerInstruction
(s) and an
AVMutableVideoComposition
using an
AVAssetExportSession
.


The objective is to merge an unlimited number of videos and applying transitions between clips using layerInstructions.


P.S. The error is not consistent. It works when attempting to merge 5 clips and 18 clips, but doesn't work when attempting to merge 17 clips.

I've posted my code below. Any help is greatly appreciated.


EDIT: It seems the issue is related to the creation of multiple AVMutableCompositionTrack(s). If more than 15 or 16 are created, the error occurs. However, creating multiple AVMutableCompositionTrack(s), I believe, is necessary to overlap all the videos and create overlapping transitions.


EDIT 2: When shorter videos are selected, more videos are processed before the error occurs. Accordingly, it looks like a memory issue whereby tracks are being deallocated. However, there doesn't seem to be a memory leak based on the memory management tool.


-(void)prepareMutableCompositionFor{
    AVMutableComposition *mutableComposition = [[AVMutableComposition alloc] init];
    AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    NSMutableArray *instructionsArray = [[NSMutableArray alloc] init];
   
    videoStartTime = kCMTimeZero;
   
    for(int i = 0; i < videoURLsArrray.count; i++){
        AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:[videoURLsArrray objectAtIndex:i] options:nil];
        CMTime currentVideoDuration = [videoAsset duration];
       
        AVMutableCompositionTrack *videoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
        [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, currentVideoDuration) ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:videoStartTime error:nil];
       
        CGSize videoSize = [videoTrack naturalSize];
       
        if([videoAsset tracksWithMediaType:AVMediaTypeAudio].count > 0){
            AVMutableCompositionTrack *audioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
            [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, currentVideoDuration) ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:videoStartTime error:nil];
        }
       
        /
        AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
       
        int transitionNumber = [[videoTransitionsArray objectAtIndex:i] intValue];
        float transitionDuration = [[videoTransitionsDurationArray objectAtIndex:i] floatValue];
       
        if(i == 0){
            if(i < videoURLsArrray.count - 1){
                [layerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:CMTimeRangeMake(CMTimeSubtract(currentVideoDuration, CMTimeMakeWithSeconds(transitionDuration, 600)), CMTimeMakeWithSeconds(transitionDuration, 600))];
            }
            else{
                [layerInstruction setOpacity:0.0 atTime:CMTimeSubtract(currentVideoDuration, CMTimeMakeWithSeconds(transitionDuration, 600))];
            }
        }
        else{
            int previousTransitionNumber = [[videoTransitionsArray objectAtIndex:i - 1] intValue];
            float previousTransitionDuration = [[videoTransitionsDurationArray objectAtIndex:i - 1] floatValue];
           
            if(i < videoURLsArrray.count - 1){
                [layerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:1.0 timeRange:CMTimeRangeMake(videoStartTime, CMTimeMakeWithSeconds(previousTransitionDuration, 600))];
                [layerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:CMTimeRangeMake(CMTimeAdd(videoStartTime, CMTimeSubtract(currentVideoDuration, CMTimeMakeWithSeconds(transitionDuration, 600))), CMTimeMakeWithSeconds(transitionDuration, 600))];
            }
            else{
                [layerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:1.0 timeRange:CMTimeRangeMake(videoStartTime, CMTimeMakeWithSeconds(previousTransitionDuration, 600))];
               
            }
        }
       
        [instructionsArray addObject:layerInstruction];
       
        if(i < videoURLsArrray.count - 1){
            videoStartTime = CMTimeAdd(videoStartTime, CMTimeSubtract(currentVideoDuration, CMTimeMakeWithSeconds(transitionDuration, 600)));
        }
        else{
            videoStartTime = CMTimeAdd(videoStartTime, currentVideoDuration);
        }
    }
   
    mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero,videoStartTime);
    mainInstruction.layerInstructions = instructionsArray;
   
    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:mutableComposition];
    videoComposition.renderSize = CGSizeMake(1920, 1080);
    videoComposition.frameDuration = CMTimeMake(1, 30);
    videoComposition.instructions = [NSArray arrayWithObjects:mainInstruction,nil];
   
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *videoOutputPath = [documentsDirectory stringByAppendingPathComponent:@"videoRecordingFinalOutput.mov"];
    NSURL *videoOutputURL = [[NSURL alloc] initFileURLWithPath:videoOutputPath];
   
    NSError *error = nil;
    if([[NSFileManager defaultManager] fileExistsAtPath:videoOutputPath]){
        [[NSFileManager defaultManager] removeItemAtPath:videoOutputPath error:&error];
        if(error){
            NSLog(@"VIDEO FILE DELETE FAILED");
        }
        else{
            NSLog(@"VIDEO FILE DELETED");
        }
    }
   
    AVAssetExportSession *videoExportSession = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];
    videoExportSession.outputURL = videoOutputURL;
    videoExportSession.videoComposition = videoComposition;
    /
    videoExportSession.outputFileType = AVFileTypeQuickTimeMovie;
   
    [videoExportSession exportAsynchronouslyWithCompletionHandler:^{
        NSLog(@"EXPORT STATUS %ld %@", (long)videoExportSession.status, videoExportSession.error);
       
        if(videoExportSession.error == NULL){
            NSLog(@"VIDEO EXPORT SUCCESSFUL");
           
            [library writeVideoAtPathToSavedPhotosAlbum:videoOutputURL
                                        completionBlock:^(NSURL *assetURL, NSError *error) {
                                            if(error) {
                                                NSLog(@"SAVING ERROR");
                                               
                                                NSError *error = nil;
                                                if([[NSFileManager defaultManager] fileExistsAtPath:videoOutputPath]){
                                                    [[NSFileManager defaultManager] removeItemAtPath:videoOutputPath error:&error];
                                                    if(error){
                                                        NSLog(@"VIDEO FILE DELETE FAILED");
                                                    }
                                                    else{
                                                        NSLog(@"VIDEO FILE DELETED");
                                                    }
                                                }
                                            }
                                            else{
                                                NSLog(@"SAVING NO ERROR");
                                               
                                                NSError *error = nil;
                                                if([[NSFileManager defaultManager] fileExistsAtPath:videoOutputPath]){
                                                    [[NSFileManager defaultManager] removeItemAtPath:videoOutputPath error:&error];
                                                    if(error){
                                                        NSLog(@"VIDEO FILE DELETE FAILED");
                                                    }
                                                    else{
                                                        NSLog(@"VIDEO FILE DELETED");
                                                    }
                                                }
                                            }
                                        }];
        }
        else{
            NSError *error = nil;
            if([[NSFileManager defaultManager] fileExistsAtPath:videoOutputPath]){
                [[NSFileManager defaultManager] removeItemAtPath:videoOutputPath error:&error];
                if(error){
                    NSLog(@"VIDEO FILE DELETE FAILED");
                }
                else{
                    NSLog(@"VIDEO FILE DELETED");
                }
            }
        }
    }];
}

I have the same issue on iPhone but it's not related to number of tracks. I have this error message even for 4 tracks and total video duration 2.5 sec.

It's pretty rare but once I get this error - it won't disappear for next video export sessions until relaunching the app.

Just as a (late) followup to this thread - I've found that its important to understand the difference between:
VideoAssetWriterInput setExpectsMediaDataInRealTime:YES

and:

VideoAssetWriterInput setExpectsMediaDataInRealTime:NO



.. in terms of the memory pressure that is exerted on the system during processing of the VideoAsset. If YES, the system uses a *lot* of memory for compositions. If NO, it attempts to minimize the memory used during the export. This is a tradeoff that can make or break your Composition: Performance versus Memory pressure.



I believe - but have not verified - that the problem miamitwinz is running into, is related to memory pressure incurred due to inattention to setExpectsMediaDataInRealTime...


j.

AVAssetExportSession intermittent error 11820 “Cannot Complete Export” Suggestion=Try exporting again
 
 
Q