2

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!

Dimitar08
  • 450
  • 7
  • 15
  • Is there any pattern to this behavior? ie Does it record successfully only the first time you record per launch? Does it record successfully only the first time you install the app fresh after deleting it? Does it record successfully every time per launch until you play it back? – Jesse Black May 10 '12 at 23:10
  • There seems to be no pattern that I can see. It seems to occur on about 50% of tries. But it'll easy work 3 attemps in a row,not work for 2 attempts, and then miraculously work again (all without restarting the app) – Dimitar08 May 10 '12 at 23:23

4 Answers4

6

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.

Dimitar08
  • 450
  • 7
  • 15
  • Movie writing should work just fine without setting a fragment interval - I think your real problem lies elsewhere. – Rhythmic Fistman May 11 '12 at 08:31
  • I agree. It doesnt make sense that the fragmentInterval would fix it. It's probably more of a band-aid solution than anything else... – Dimitar08 May 11 '12 at 16:36
  • 1
    came across same thing today, in case of a very short recording the [self.movieFileOutput stopRecording] is called before the delegate method didStartRecordingToOutputFileAtURL is called. so the solution is to wait for didStartRecordingToOutputFileAtURL call after startRecordingToOutputFileURL before stopping. – Valentyn Mar 17 '15 at 13:13
  • @Valentyn thank you! That was the real answer to me. I have blank frames at the end of the recording. Waiting for delegate call to stop capturing inputs resolved the problem! – Ossir Aug 13 '15 at 12:07
  • This just saved me from going completely insane, thank you – LoganRx Sep 01 '17 at 19:47
1

Call this before recording,

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];

and this before playback,

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];

I think it should help considering you do not experience any issues unless trying to record audio.

Since you are using the same URL every time, you should delete it first

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *pathString = [documentsDirectory stringByAppendingPathComponent:@"movie.mov"];
NSURL *pathUrl = [NSURL fileURLWithPath:pathString];
[[NSFileManager defaultManager] removeItemAtURL:pathUrl error:nil];
[movieOutput startRecordingToOutputFileURL:pathUrl recordingDelegate:self];
Jesse Black
  • 7,966
  • 3
  • 34
  • 45
  • Worked 3 times in a row but then failed again unfortunately. – Dimitar08 May 10 '12 at 23:26
  • And yeah, before recording, I delete the movie.mov file if it already exists – Dimitar08 May 10 '12 at 23:39
  • Looks like the problem was with the AVCaptureMovieFileOutput fragmentInterval variable. Lowering the fragment to 5 seconds from the default 10 secs fixed the error. I'll post a more detailed explanation in 6 hours when stackoverflow lets me answer my own question. Thanks for the help! – Dimitar08 May 11 '12 at 00:41
1

You should set the fragmentInterval property of AVCaptureMovieFileOutput to a a higher limit. By default it is 10 sec.

I also had the same issue of my video recording using AVCaptureSession anything above 10 sec was not getting audio recorded. When i set the fragment interval to 300 sec which is the maximum second I allow to take video , and it recorded with audio.

AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
CMTime fragmentInterval = CMTimeMake(300,1);

[aMovieFileOutput setMovieFragmentInterval:fragmentInterval];
bencripps
  • 2,025
  • 3
  • 19
  • 27
0

Its a repeat answer, but adding this to help someone starting out in ios like me, I tried KCMTimeInvalid but it just wasn't working for me. One frazzled hour later, I realised that I am setting movieFragmentInterval after calling startRecordingToOutputFileURL.

So for newbies who type blindly like me - keep the logical and very obvious sequence in mind

videoFileOutput.movieFragmentInterval = kCMTimeInvalid
videoFileOutput.startRecordingToOutputFileURL(filePath, recordingDelegate: recordingDelegate)
lvin
  • 290
  • 3
  • 11