Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVCaptureMovieFileOutput NSInvalidArgumentException no active/enabled connections

I am occasionally getting an NSInvalidArgumentException exception when I start recording video in a viewController but only after taking photos in a previous view controller. I've tried a couple suggestions from Google and So but still get this error at the startRecordingToOutputFileURL:fileURL call.

I never get the error if I don't visit the other view controller that takes photos - it only occurs when I take photos, and then switch to the new view controller which does the video recording.

I think there is some cruft left behind from taking photos, but when I initialize my Video recorder view controller I get no errors setting up the sessions and whatnot. Any ideas what is going on or how to recover from this? Why is it an NSInvalidArgumentException exception? Thanks!

Here is my code:

dispatch_async(dispatch_get_main_queue(), ^{

            // Try to Fix bug:
            // http://stackoverflow.com/questions/5979962/error-while-recording-video-on-iphone-using-avfoundation

            [self.captureSession beginConfiguration];

            // Ensure session is running
            if ( [self.captureSession isRunning] == NO ) {
                NSLog(@"Capture session is NOT running... Starting it now!");
                [self.captureSession startRunning];
            }
            else {
                NSLog(@"Capture session is ALREADY running...");
            }

            NSLog(@"File URL is: %@",fileURL);
            NSLog(@"FileOutput is: %@",self.fileOutput);

            [self.fileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:self];

            // Try to Fix bug:
            // http://stackoverflow.com/questions/5979962/error-while-recording-video-on-iphone-using-avfoundation
            [self.captureSession commitConfiguration];

        });

Here is the error traceback:

2014-05-18 16:01:38.818 app[1699:60b] *** Start recording
2014-05-18 16:01:38.820 app[1699:60b] Capture session is ALREADY running...
2014-05-18 16:01:38.827 app[1699:60b] Capture session is ALREADY running...
2014-05-18 16:01:38.828 app[1699:60b] File URL is: file:////var/mobile/Applications/73FFC590-05A8-4D74-82D9-EBA122B00A20/Documents/2014-05-18-16-01-38-0.mp4
2014-05-18 16:01:38.828 app[1699:60b] FileOutput is: <AVCaptureMovieFileOutput: 0x16513b10>
2014-05-18 16:01:38.829 app[1699:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVCaptureMovieFileOutput startRecordingToOutputFileURL:recordingDelegate:] - no active/enabled connections.'
*** First throw call stack:
(0x2fe5ff0b 0x3a5f6ce7 0x2ed5751d 0xfb4b5 0x3aadfd53 0x3aadfd3f 0x3aae26c3 0x2fe2a681 0x2fe28f4d 0x2fd93769 0x2fd9354b 0x34d006d3 0x326f2891 0xe40c9 0x3aaf4ab7)
libc++abi.dylib: terminating with uncaught exception of type NSException

This is how the captureSession is initialized ( from the OpenSource project here: https://github.com/shu223/SlowMotionVideoRecorder ):

- (id)initWithPreviewView:(UIView *)previewView {

    self = [super init];

    if (self) {

        NSError *error;

        self.captureSession = [[AVCaptureSession alloc] init];
        self.captureSession.sessionPreset = AVCaptureSessionPresetInputPriority;

        AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        AVCaptureDeviceInput *videoIn = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];

        if (error) {
            NSLog(@"Video input creation failed");
            return nil;
        }

        if (![self.captureSession canAddInput:videoIn]) {
            NSLog(@"Video input add-to-session failed");
            return nil;
        }
        [self.captureSession addInput:videoIn];


        // save the default format
        self.defaultFormat = videoDevice.activeFormat;
        defaultVideoMaxFrameDuration = videoDevice.activeVideoMaxFrameDuration;


        AVCaptureDevice *audioDevice= [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
        AVCaptureDeviceInput *audioIn = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
        [self.captureSession addInput:audioIn];

        self.fileOutput = [[AVCaptureMovieFileOutput alloc] init];
        [self.captureSession addOutput:self.fileOutput];


        self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
        self.previewLayer.frame = previewView.bounds;
        self.previewLayer.contentsGravity = kCAGravityResizeAspectFill;
        self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        [previewView.layer insertSublayer:self.previewLayer atIndex:0];

        [self.captureSession startRunning];
    }
    return self;
}

My code utilizes this initialization code like this in viewDidLoad:

self.captureManager = [[AVCaptureManager alloc] initWithPreviewView:self.view];
self.captureManager.delegate = self;

The code that actually starts and stops recording is done from an IBAction method like this:

- (IBAction)recButtonTapped:(id)sender {

    // REC START
    if (self.captureManager.isRecording == NO ) {

        NSLog(@"*** Start recording");

        // change UI
        [self.recBtn setImage:self.recStopImage
                     forState:UIControlStateNormal];
        self.fpsControl.enabled = NO;

        // timer start
        startTime = [[NSDate date] timeIntervalSince1970];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01
                                                      target:self
                                                    selector:@selector(timerHandler:)
                                                    userInfo:nil
                                                     repeats:YES];

        [self.captureManager startRecording];

    }
    // REC STOP
    else {

        NSLog(@"*** Stop recording");

        isNeededToSave = YES;
        [self.captureManager stopRecording];

        [self.timer invalidate];
        self.timer = nil;

        // change UI
        [self.recBtn setImage:self.recStartImage
                     forState:UIControlStateNormal];
        self.fpsControl.enabled = YES;

    }
}

EDIT - I am definitely closing the session in the Photo view, here is that code. I verified that it is being called when I leave the Photo view controller.

        NSLog(@"RELEASE PHOTO SESSION NOW!");

        for(AVCaptureInput *input1 in _mySesh.inputs) {
            [_mySesh removeInput:input1];
        }

        for(AVCaptureOutput *output1 in _mySesh.outputs) {
            [_mySesh removeOutput:output1];
        }


        [_mySesh stopRunning];

        // Fix closing of session
        dispatch_after(
                       dispatch_time(0,500000000),
                       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                       ^{
                           _mySesh = nil;
                       }

        );
UPDATE #####

According to the only answer below, I tried to 'unlink' the file prior to starting recording. It still did not work.

NSURL *fileURL = [NSURL URLWithString:[@"file://" stringByAppendingString:filePath]];

    //NSLog(@"Beginning to record to output file...");

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{

        // Wait for session to start
        //[NSThread sleepForTimeInterval:1.0];

        dispatch_async(dispatch_get_main_queue(), ^{

            // Ensure session is running
            if ( [self.captureSession isRunning] == NO ) {
                NSLog(@"Capture session is NOT running... Starting it now!");
                [self.captureSession startRunning];
            }
            else {
                NSLog(@"Capture session is ALREADY running...");
            }

            NSLog(@"File URL is: %@",fileURL);
            NSLog(@"FileOutput is: %@",self.fileOutput);

            // Delete the file
            unlink([[@"file://" stringByAppendingString:filePath] UTF8String]);

            [self.fileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:self];

        });

    });
UPDATE

Just for posterity, I am calling the 'didFinishRecordingToOutputFileAtURL' delegate method:

- (void)                 captureOutput:(AVCaptureFileOutput *)captureOutput
   didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
                       fromConnections:(NSArray *)connections error:(NSError *)error
{

    // Print any errors
    if ( error ) {
        NSLog(@"Error Recording Video! %@",error.localizedDescription);
    }

    _isRecording = NO;

    if ([self.delegate respondsToSelector:@selector(didFinishRecordingToOutputFileAtURL:error:)]) {
        [self.delegate didFinishRecordingToOutputFileAtURL:outputFileURL error:error];
    }

}
like image 238
PhilBot Avatar asked May 18 '14 21:05

PhilBot


2 Answers

First of all, start AVCaptureSession first. After that you can create AVPreviewLayer and AVCaptureMovieFileOutput.

Second, use -[AVCaptureSession canAddOutput:] and -[AVCaptureSession canAddInput:] before adding anything to the capture session, this will save you a lot of time and frustration.

Third, you only need beginConfiguration and commitConfiguration when you want to change lots of things in captureSession at once, e.g. remove input, change preset. It's useful on front/back camera switch if you're going to implement it. Starting or stopping session in between these calls is no bueno.

like image 141
Rob Zombie Avatar answered Nov 19 '22 21:11

Rob Zombie


From what I see this is due to the file already existing.

Try removing the file before your call to startRecordingToOutputFileURL: with: [[NSFileManager defaultManager] removeItemAtPath:fileURL]; You can double check with: [[NSFileManager defaultManager] fileExistsAtPath:fileURL];

If a file at the given URL already exists when capturing starts, recording to the new file will fail.

Another thing that might cause the crash is if you don't have the delegate method implemented.

It is required to implement: captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:

because that is the only reliable place to get the output.

Another suspicious thing:

When you create fileURL, you do

NSURL *fileURL = [NSURL URLWithString:[@"file://" stringByAppendingString:filePath]];

Can you change that to

NSURL *fileURL = [NSURL fileURLWithPath:filePath];

And make sure filePath is valid.

From the documentation: This method throws an NSInvalidArgumentException if the URL is not a valid file URL.

like image 39
Jack Avatar answered Nov 19 '22 19:11

Jack