5

I'm trying to take 5 pictures every second with AVCaptureSession and I'm not sure I understand what AVFrameRange means. Currently I have some code that sets up the device:

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

and tries to set the activeVideoMinFrameDuration and activeVideoMaxFrameDuration to a custom value of CMTimeMake(1, 5). Apple tells me I can only use one of the AVFrameRanges that they've provided.

When I NSLogged them, I get (2, 30), (2,60), and (2,24). I first want to know what this means? Is this the frame rate at which the camera will run or an interval for capturing frames (i.e. what I'm trying to do)?

If it isn't, what can I do to save 5 frames every second on my sampleBufferDelegate method? Currently it gives me every single frame because the method is called every single time there is a frame, so I just need some pointer on how I can grab just 5 at each second.

steve
  • 3,878
  • 7
  • 34
  • 49
  • Don't you just set the `min` and `max` frame rate as stated here: https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVCaptureDevice_Class/Reference/Reference.html#//apple_ref/occ/instp/AVCaptureDevice/activeVideoMinFrameDuration – Tom Feb 06 '14 at 19:45
  • I don't think they'd allow me to set it to a custom value such as 5 frames per second. – steve Feb 06 '14 at 20:56

2 Answers2

8

Here is working code we have used that sets the frame rate at 5 per second.

If you measure calls to CaptureOutput while using this code, you can see that the camera frames are called every 200 msecs (i.e. which is 5 frames per second.) (We just tested this to confirm.)

Change desiredFrameRate to get other camera frame rates.

- (void)attemptToConfigure5FPS
{
    NSError *error;
    if (![self lockForConfiguration:&error]) {
        NSLog(@"Could not lock device %@ for configuration: %@", self, error);
        return;
    }

    AVCaptureDeviceFormat *format = self.activeFormat;
    double epsilon = 0.00000001;

    int desiredFrameRate = 5;

    for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {

            if (range.minFrameRate <= (desiredFrameRate + epsilon) &&
            range.maxFrameRate >= (desiredFrameRate - epsilon)) {

            self.activeVideoMaxFrameDuration = (CMTime){
                .value = 1,
                .timescale = desiredFrameRate,
                .flags = kCMTimeFlags_Valid,
                .epoch = 0,
            };
            self.activeVideoMinFrameDuration = (CMTime){
                .value = 1,
                .timescale = desiredFrameRate,
                .flags = kCMTimeFlags_Valid,
                .epoch = 0,
            };           
            break;
        }
    }

    [self unlockForConfiguration];
}
Praxiteles
  • 5,802
  • 9
  • 47
  • 78
  • 2
    It's important to remember to call `-[AVCaptureDevice lockForConfiguration:]` around the frame rate change. I was getting exceptions until I did that. – Greg Oct 31 '14 at 19:34
4

Code for choosing custom frame rate is as below - Added checks to Apple RosyWriter to verify if current format supports FPS chosen

- (void)configureCamera:(AVCaptureDevice *)videoDevice withFrameRate:(int)desiredFrameRate
{
    BOOL isFPSSupported = NO;
    AVCaptureDeviceFormat *currentFormat = [videoDevice activeFormat];
    for ( AVFrameRateRange *range in currentFormat.videoSupportedFrameRateRanges ) {
        if ( range.maxFrameRate >= desiredFrameRate && range.minFrameRate <= desiredFrameRate )        {
            isFPSSupported = YES;
            break;
        }
    }

    if( isFPSSupported ) {
        if ( [videoDevice lockForConfiguration:NULL] ) {
            videoDevice.activeVideoMaxFrameDuration = CMTimeMake( 1, desiredFrameRate );
            videoDevice.activeVideoMinFrameDuration = CMTimeMake( 1, desiredFrameRate );
            [videoDevice unlockForConfiguration];
        }
    }
}

In case current format (activeFormat) didn't support your chosen FPS, use below code to change activeFormat and then chose FPS. Will need to get format dimension to match your needs though.

- (void)configureCamera:(AVCaptureDevice *)device withFrameRate:(int)desiredFrameRate
{
    AVCaptureDeviceFormat *desiredFormat = nil;
    for ( AVCaptureDeviceFormat *format in [device formats] ) {
        for ( AVFrameRateRange *range in format.videoSupportedFrameRateRanges ) {
            if ( range.maxFrameRate >= desiredFrameRate && range.minFrameRate <= desiredFrameRate ) {
                desiredFormat = format;
                goto desiredFormatFound;
            }
        }
    }

    desiredFormatFound:
    if ( desiredFormat ) {
        if ( [device lockForConfiguration:NULL] == YES ) {
            device.activeFormat = desiredFormat ;
            device.activeVideoMinFrameDuration = CMTimeMake ( 1, desiredFrameRate );
            device.activeVideoMaxFrameDuration = CMTimeMake ( 1, desiredFrameRate );
            [device unlockForConfiguration];
        }
    }
}

Note: Usage of AVCaptureConnection videoMinFrameDuration to set FPS is deprecated.

kiranpradeep
  • 10,859
  • 4
  • 50
  • 82
  • 1
    This is the right answer for me. A tip is that after calling avSession.addInput, the activeFormat will be changed. So I call the method after addInput. For me, it works normally on iphone7 for resolution 640x480, but it's not ok on resolution 1280x720, in which case the image seems to be splitted and wrongly filled. – Xiaoqi Aug 31 '18 at 04:59