3

I am recording video using AVCaptureMovieFileOutput. Instead of retaining the captured video for the entire recording time, however, I would like only to retain the last 2 minutes of video. In essence, I would like to create a trailing buffer of video.

I have tried to implement this by setting movieFragmentInterval equal to 15 seconds. As these 15 seconds buffered, the first 15 seconds of the MOV file would be trimmed off using this code:

//This would be called 7 seconds after the video stream started buffering.
-(void)startTrimTimer
{
    trimTimer = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:@selector(trimFlashbackBuffer) userInfo:nil repeats:YES];
}

    -(void)trimFlashbackBuffer
    {
        //make sure that there is enough video before trimming off 15 seconds
        if(trimReadyCount<3){
            trimReadyCount++;
            return;
        }

        AVURLAsset *videoAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/flashbackBuffer.MOV",tripDirectory]] options:nil]; 

        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:AVAssetExportPresetHighestQuality];
        exportSession.outputURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/flashbackBuffer.MOV",tripDirectory]];
        exportSession.outputFileType = AVFileTypeQuickTimeMovie;
        CMTimeRange timeRange = CMTimeRangeMake(CMTimeMake(15000, 1000), CMTimeMake(120000, 1000));
        exportSession.timeRange = timeRange;

        [exportSession exportAsynchronouslyWithCompletionHandler:^{
            switch (exportSession.status) {
                case AVAssetExportSessionStatusCompleted:
                    // Custom method to import the Exported Video
                    [self loadAssetFromFile:exportSession.outputURL];
                    break;
                case AVAssetExportSessionStatusFailed:
                    //
                    NSLog(@"Failed:%@",exportSession.error);
                    break;
                case AVAssetExportSessionStatusCancelled:
                    //
                    NSLog(@"Canceled:%@",exportSession.error);
                    break;
                default:
                    break;
            }
        }];

    }

However, I am receiving the following error every time trimFlashbackBuffer is called:

Failed:Error Domain=AVFoundationErrorDomain Code=-11823 "Cannot Save" UserInfo=0x12e710 {NSLocalizedRecoverySuggestion=Try saving again., NSLocalizedDescription=Cannot Save}

Is this because the file is already being written to by AVCaptureMovieFileOutput?

How could I achieve the effect of a seamless trailing video buffer, if this method cannot work?

Thanks!

James
  • 2,272
  • 1
  • 21
  • 31

2 Answers2

3

not sure that what you trying to achieve here will work, this is because like you said, you are writing the file while trying to trim it, why cant you record the video and trim it later? If you really want to just keep two minutes of videos at any time you might want to try using AVCaptureVideoDataOutput,using this you will get video frames and you can use AVAssetWriter to write it to compress and write frames to a file, check out this SO question which talks about how to do that This code to write video+audio through AVAssetWriter and AVAssetWriterInputs is not working. Why?

Community
  • 1
  • 1
Daniel
  • 22,363
  • 9
  • 64
  • 71
  • Using your method, wouldn't I run into the same problem as before? I would still be writing to the file at the same time as I was trying to edit it. – James Aug 23 '11 at 19:29
  • well, the output is not writing to a file anymore, now you have the buffers and are compressing them into a file, so you are controlling whats being writen to the file, and are not trying to access a file thats being used by AV foundation, so you wont run into that same problem – Daniel Aug 23 '11 at 20:59
  • I've still not been able to successfully achieve this effect.If you wouldn't mind clarifying your answer a little bit further - you are suggesting that instead of writing directly to a file, which would prevent me from trimming the file as it is already being modified, that I maintain a trailing video buffer in RAM? If so, what would be the best way to do this? The only solution that occurs to me would be to hold an array of CVImageBufferRefs in memory, removing the last ref every time I add a new one on, but I would need to hold on to around 3000 frames at a time. Thoughts? Thanks! – James Aug 25 '11 at 15:33
  • dont keep it in ram, use AVAssetWriter to write it to a file...but i am still not sure what you are trying to do exactly, why do you want to retain only the last 2 minutes of the video, and why is it that u cant do the trimming after the video has stopped capturing? – Daniel Aug 25 '11 at 16:44
  • I am trying the create the ability for the user to decide at any given point that he would like to save the last two minutes of recorded video. I cannot save and trim later because the camera will be active for hours at a time, and it would take up huge amounts of memory to save video for that entire span. Basically, I need the ability to more or less simultaneously cut out the oldest frames when new ones are added, so that same duration of trailing video is available at all times. Does this make sense? – James Aug 25 '11 at 16:55
  • I have also tried saving a series of trailing MOV files, deleting the oldest file when the most recent has finished recording, but I can't figure out how to minimize down time between video segments so that they can later be seamlessly concatenated. Does some varient of this solution make sense? Thoughts? – James Aug 25 '11 at 16:56
  • Well i think the way to go here is with AVCaptureVideoDataOutput and AVCaptureAudioDataOutput like i said, check out this question of how to use AVAssetWriter to compress frames into movies http://stackoverflow.com/questions/4149963/this-code-to-write-videoaudio-through-avassetwriter-and-avassetwriterinputs-is-n – Daniel Aug 25 '11 at 17:10
  • thanks for your persistence. As of now, I am able to grab frames using AVCaptureVideoDataOutput, and then write the frames to a file using AVAssetWriter, as in that post. But doesn't AVAssetWriter write those frames directly to the output file? If so, then I am faced with the same problem as before - I'm trying to modify the same file in two places at once. And if I try writing multiple files instead of modifying the same one, there is significant downtime between videos. – James Aug 25 '11 at 17:17
  • well if you have the sample buffers then i dont see why you would have a "downtime" between videos, basically use two files, write two minutes to one, after that write to the other and keep switching...should work since you are telling the writer what frames to write, so you shouldnt miss any frames – Daniel Aug 25 '11 at 17:38
2

I suspect the error you are getting is because you are trying to overwrite the same file as in the export URL. The documentation says "The export will fail if you try to overwrite an existing file, or write a file outside of the application’s sandbox. If you need to overwrite an existing file, you must remove it first."

To get the last two minutes of the video, you might want to obtain it's duration first using loadValuesAsynchronouslyForKeys which is another asynchronous call. Using this duration, you can create a time range and trim the video by exporting it to a different URL.

CMTime start = CMTimeMakeWithSeconds(durationObtained - 120, 600); 
CMTime duration = CMTimeMakeWithSeconds(120, 600);
CMTimeRange range = CMTimeRangeMake(start, duration);  
exportSession.timeRange = range;
Shyam Bhat
  • 1,600
  • 13
  • 22