0

I was making a video out of an array of images and an audio file

but i can't seem to save it to the camera roll

some times it saves but some times it doesn't and gives me a strange error

outputFile.mp4 cannot be saved to the saved photos album: Error Domain=NSOSStatusErrorDomain Code=-12848 "Movie could not be played." UserInfo=0x7bd7370 {NSLocalizedDescription=Movie could not be played.}

This function creates video from array of images :

-(void) writeImagesToMovieAtPath:(NSString *) path withSize:(CGSize) size
{
    NSLog(@"Write Started");

    NSError *error = nil;

    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:
                                  [NSURL fileURLWithPath:path] fileType:AVFileTypeMPEG4
                                                              error:&error];
    NSParameterAssert(videoWriter);

    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                   AVVideoCodecH264, AVVideoCodecKey,
                                   [NSNumber numberWithInt:size.width], AVVideoWidthKey,
                                   [NSNumber numberWithInt:size.height], AVVideoHeightKey,
                                   nil];

    AVAssetWriterInput* videoWriterInput = [[AVAssetWriterInput
                                             assetWriterInputWithMediaType:AVMediaTypeVideo
                                             outputSettings:videoSettings] retain];


    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
                                                     assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput
                                                     sourcePixelBufferAttributes:nil];

    NSParameterAssert(videoWriterInput);
    NSParameterAssert([videoWriter canAddInput:videoWriterInput]);
    videoWriterInput.expectsMediaDataInRealTime = YES;
    [videoWriter addInput:videoWriterInput];

    //Start a session:
    [videoWriter startWriting];
    [videoWriter startSessionAtSourceTime:kCMTimeZero];

    CVPixelBufferRef buffer = NULL;

    //convert uiimage to CGImage.

    int frameCount = 16;
    int kRecordingFPS = 30;
    UIImage * im = [UIImage imageNamed:@"IMG_0103.JPG"];

    NSArray * imageArray = [[NSArray alloc]initWithObjects:im,im,im,im,im,im,im,im,im,im,im,im,im,im,im,im, nil];

    for(UIImage * img in imageArray)
    {
        buffer = [self pixelBufferFromCGImage:[img CGImage] andSize:size];

        BOOL append_ok = NO;
        int j = 0;
        while (!append_ok && j < 30)
        {
            if (adaptor.assetWriterInput.readyForMoreMediaData)
            {
                printf("appending %d attemp %d\n", frameCount, j);

                CMTime frameTime = CMTimeMake(frameCount,(int32_t) kRecordingFPS);
                append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime];

                if(buffer)
                    CVBufferRelease(buffer);
                [NSThread sleepForTimeInterval:0.05];
            }
            else
            {
                printf("adaptor not ready %d, %d\n", frameCount, j);
                [NSThread sleepForTimeInterval:0.1];
            }
            j++;
        }
        if (!append_ok) {
            printf("error appending image %d times %d\n", frameCount, j);
        }
        frameCount++;
    }


    //Finish the session:
    [videoWriterInput markAsFinished];
    [videoWriter finishWriting];
    NSLog(@"Write Ended");
    [videoWriterInput release];
    [videoWriter release];
    [imageArray release];
}

This function adds the audio :

    -(void)CompileFilesToMakeMovie{
        AVMutableComposition* mixComposition = [AVMutableComposition composition];

        NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

        NSString* audio_inputFileName = @"Rihanna-Take a Bow.mp3";

        NSString* audio_inputFilePath = [NSString stringWithFormat:@"%@/%@",documentsDirectoryPath,audio_inputFileName];

        NSURL*    audio_inputFileUrl = [NSURL fileURLWithPath:audio_inputFilePath];

        NSString* video_inputFileName = @"a.mp4";
        NSString* video_inputFilePath = [NSString stringWithFormat:@"%@/%@",documentsDirectoryPath,video_inputFileName];
        NSURL*    video_inputFileUrl = [NSURL fileURLWithPath:video_inputFilePath];

        NSString* outputFileName = @"outputFile.mp4";
        NSString* outputFilePath = [NSString stringWithFormat:@"%@/%@",documentsDirectoryPath,outputFileName];

        NSURL*    outputFileUrl = [NSURL fileURLWithPath:outputFilePath];

        if ([[NSFileManager defaultManager] fileExistsAtPath:outputFilePath])
            [[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];



        CMTime nextClipStartTime = kCMTimeZero;

        AVURLAsset* videoAsset = [[AVURLAsset alloc]initWithURL:video_inputFileUrl options:nil];
        CMTimeRange video_timeRange = CMTimeRangeMake(kCMTimeZero,videoAsset.duration);

        AVMutableCompositionTrack *a_compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
        [a_compositionVideoTrack insertTimeRange:video_timeRange ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:nextClipStartTime error:nil];

        //nextClipStartTime = CMTimeAdd(nextClipStartTime, a_timeRange.duration);

        AVURLAsset* audioAsset = [[AVURLAsset alloc]initWithURL:audio_inputFileUrl options:nil];
        CMTimeRange audio_timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
        AVMutableCompositionTrack *b_compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
        [b_compositionAudioTrack insertTimeRange:audio_timeRange ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:nextClipStartTime error:nil];



        AVAssetExportSession* _assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetMediumQuality];
        _assetExport.outputFileType = AVFileTypeMPEG4;
        _assetExport.outputURL = outputFileUrl;
        [_assetExport setTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)];
        [_assetExport exportAsynchronouslyWithCompletionHandler:nil];

        [audioAsset release];
        [videoAsset release];


        [_assetExport release];
    }

The function calls :

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

    UIImage * i = [UIImage imageNamed:@"IMG_0103.JPG"];

    [self writeImagesToMovieAtPath:[NSString stringWithFormat:@"%@/%@",documentsDirectoryPath,@"a.mp4"] withSize:CGSizeMake(i.size.width, i.size.height)];

    NSString* exportVideoPath1 = [NSString stringWithFormat:@"%@/%@",documentsDirectoryPath,@"a.mp4"];


    NSString* exportVideoPath = [NSString stringWithFormat:@"%@/%@",documentsDirectoryPath,@"outputFile.mp4"];

    UISaveVideoAtPathToSavedPhotosAlbum (exportVideoPath,self, @selector(video:didFinishSavingWithError: contextInfo:), nil);

}
Ashraf Hussein
  • 589
  • 1
  • 8
  • 19
  • Can you be a little more clear. You have made an app, that converts pictures to videos, and then saves the videos? And some times it does not save them? Do you have any code to share as to how your dong it? – nycynik Oct 31 '12 at 21:56
  • I added the code mainly I used the code in this post : http://stackoverflow.com/a/6094407/1708270 – Ashraf Hussein Oct 31 '12 at 22:33
  • and that's exactly waht's happenning some times it saves and sometimes it doesn't also the error code changed to (Code=-12893) on its own – Ashraf Hussein Oct 31 '12 at 22:53
  • not sure if this will help or not. But i had a similar problem when saving images to an ALAssetsGroup (Images to gallery, similar to what you are doing), where it would work sometimes but not others. The problem seemed to be that the OS was blocking the call for some specified time. I got around the problem by adding code to try again on failure after waiting, say, 0.1 seconds. This solved my problem. Try adding a call to try again after some time delay if you get an error. – Bergasms Oct 31 '12 at 22:54
  • 1
    I made it sleep for 1 second then try saving gain but it keeps trying and gives the same error @Bergasms THX any way :D – Ashraf Hussein Oct 31 '12 at 23:10
  • no worries. I suppose if it is always failing for certain movies then you know it is an issue with the actual movie. I'm just wondering, if you are combining images into a movie, does the container format require a certain number of frames to be present, or perhaps it requires a minimum number, or maybe a certain number of keyframes. I would do some profiling on what movies are causing the problems, are they of a consistent type, or all over the place? – Bergasms Oct 31 '12 at 23:28
  • also by sleep do you mean you were actually calling a sleep function? or a perform selector after delay. This is how i did it. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{ [self addAssetURL:assetURL toAlbum:albumName withCompletionBlock:completionBlock]; }); – Bergasms Oct 31 '12 at 23:29

1 Answers1

0

In my case, the reason was that AVAssetWriter hasn't finished writing. Solved it with the following piece of code:

__block BOOL _finished = NO;

[assetWriter finishWritingWithCompletionHandler:^{
    _finished = YES;
}];

while (!_finished) {
    [NSThread sleepForTimeInterval:0.1];
}
Khanh Nguyen
  • 11,112
  • 10
  • 52
  • 65