Search code examples
iosavcapturesession

Weird bug with AVCaptureSession


I use the following code to set up an AVCaptureSession, record a video file, and play it back: Sometimes this works just fine, and other times I get a black screen on playback. As far as I can tell it's completely random.

When the bug happens, if I try to open the file in quicktime, i get a "file cannot be opened, format not recognized" message. This leads me to believe its a recording issue rather than a playback issue.

Also, if comment out the part of the code which adds the microphone input, the bug does not occur (but my video file doesn't have an audio track of course)...so maybe the audio feed randomly corrupts the file for some reason?

- (void)viewDidLoad {
[super viewDidLoad];

....

captureSession = [[AVCaptureSession alloc] init];
[captureSession setSessionPreset:AVCaptureSessionPresetHigh];

NSArray *devices = [AVCaptureDevice devices];
AVCaptureDevice *frontCamera;
AVCaptureDevice *mic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];

for (AVCaptureDevice *device in devices) {

    NSLog(@"Device name: %@", [device localizedName]);

    if ([device hasMediaType:AVMediaTypeVideo]) 
    {
        if ([device position] == AVCaptureDevicePositionFront) {
            NSLog(@"Device position : front");
            frontCamera = device;
        }
    }
}

NSError *error = nil;

AVCaptureDeviceInput * microphone_input = [AVCaptureDeviceInput deviceInputWithDevice:mic error:&error];

AVCaptureDeviceInput *frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error];

if (!error) 
{
    if ([captureSession canAddInput:frontFacingCameraDeviceInput])
    {
        [captureSession addInput:frontFacingCameraDeviceInput];
    }

    if([captureSession canAddInput:microphone_input])
    {
        [captureSession addInput:microphone_input];
    }

}
[captureSession startRunning];
}

When record is pressed, I record to a "movie.mov" file with:

movieOutput = [[AVCaptureMovieFileOutput alloc]init];
[captureSession addOutput:movieOutput];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *pathString = [documentsDirectory stringByAppendingPathComponent:@"movie.mov"];
NSURL *pathUrl = [NSURL fileURLWithPath:pathString];

[movieOutput startRecordingToOutputFileURL:pathUrl recordingDelegate:self];

And when play is pressed, I play back the movie file with:

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *pathString = [documentsDirectory stringByAppendingPathComponent:@"movie.mov"];
NSURL *pathUrl = [NSURL fileURLWithPath:pathString];

mPlayer = [AVPlayer playerWithURL:pathUrl];

[self.video setPlayer:self.mPlayer];
[video setVideoFillMode:@"AVLayerVideoGravityResizeAspectFill"];
[self.mPlayer play];

Ive been at this for a while now and can't figure it out, it really is very random. Any help is appreciated!

EDIT: If the movie.mov file already exists, I delete it before starting the new recording.

EDIT2: Could it have something to do with the line: [movieOutput startRecordingToOutputFileURL:pathUrl recordingDelegate:self]; Xcode gives me a "Sending ViewController *const_strong 'to parameter of incompatible type 'id'" warning on this line

EDIT3: Problem solved, will post answer shortly. Thanks!


Solution

  • I think I've figured it out:

    The AVCaptureMovieFileOutput class has a movieFragmentInterval variable which defaults to 10. So every 10 seconds, a "sample table" is written to the quicktime file. Without a sample table the file is unreadable.

    When I tested this all of my recordings were pretty short, so it seems like the bug happened whenever a sample table wasnt written out to the file. That's weird because I assumed when i call [movieOutput stopRecording] that this would write out the sample table, but I guess it didnt. When I lower the fragmentInterval to 5 seconds with:

    CMTime fragmentInterval = CMTimeMake(5,1);
    
    [movieOutput setMovieFragmentInterval:fragmentInterval];
    [movieOutput startRecordingToOutputFileURL:pathUrl recordingDelegate:self];
    

    The bug seems to have dissapeared. Im still unsure why the error only happened whenever the mic was added as an input device though.