7

AFTER tapping to take picture, I want to lock exposure and turn off torch as soon as exposure is no longer adjusting. So, I added an observer to handle adjustingExposure:

- (IBAction)configureImageCapture:(id)sender
{
    [self.session beginConfiguration];

    [self.cameraController device:self.inputDevice exposureMode:AVCaptureExposureModeAutoExpose];
    [self.cameraController device:self.inputDevice torchMode:AVCaptureTorchModeOn torchLevel:0.8f];

    [self.session commitConfiguration];

    [(AVCaptureDevice *)self.inputDevice addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:MyAdjustingExposureObservationContext];        
}

Here is the observeValueForKeyPath method:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == MyAdjustingExposureObservationContext) {
        if( [keyPath isEqualToString:@"adjustingExposure"] )
        {
            BOOL adjustingExposure = [ [change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:[NSNumber numberWithInt:1] ];

            if (!adjustingExposure)
            {
                [(AVCaptureDevice *)self.cameraController.inputDevice removeObserver:self forKeyPath:@"adjustingExposure"];

                if ([self.inputDevice isExposureModeSupported:AVCaptureExposureModeLocked]) {
                    dispatch_async(dispatch_get_main_queue(),
                                   ^{
                                       NSError *error = nil;
                                       if ([self.inputDevice lockForConfiguration:&error]) {
                                           // 5) lock the exposure
                                           [self.cameraController device:self.inputDevice exposureMode:AVCaptureExposureModeLocked];

                                           // 6) turn off the Torch
                                           [self.cameraController device:self.inputDevice torchMode:AVCaptureTorchModeOn torchLevel:0.0001f];

                                           [self.inputDevice unlockForConfiguration];
                                       }
                                   });
                }                    
            }
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

@user3115647 posted this information, which is exactly what I am trying to do.

But my picture is taken BEFORE the torch is turned off.

Here is my captureStillImageAsynchronouslyFromConnection:self.captureConnection completionHandler. This block occurs after the image is taken. The observeValueForKeyPath is supposed to occur while the camera is adjusting exposure BEFORE the image is taken. But my torch is not going low BEFORE the image is being taken. Either this is a timing issue or I'm not setting up the camera configuration correctly.

- (void)captureImage
{
    // configureImageCapture has already been done
    [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:self.captureConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
     {
         if (imageSampleBuffer != NULL)
         {
             // Log the image properties
             CFDictionaryRef attachmentsRef = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
             NSDictionary *properties = (__bridge NSDictionary *)(attachmentsRef);
             NSLog(@"Image Properties => %@", (properties.count) ? properties : @"none");
Community
  • 1
  • 1
Patricia
  • 5,019
  • 14
  • 72
  • 152
  • OK. I know this question is about both focus and exposure but I'll settle for just exposure at first. I found an answer for what I am trying to do with exposure here: http://stackoverflow.com/questions/12635446/accessing-ios-6-new-apis-for-camera-exposure-and-shutter-speed/20660981#20660981 – Patricia Feb 28 '14 at 00:44
  • I'm having trouble getting the KVO to perform as expected. The image is being taken before the torch is turned off. – Patricia Feb 28 '14 at 00:47
  • I've updated the code to what I have now. Looking for some help here. Thank you in advance. – Patricia Feb 28 '14 at 01:11
  • Where are you calling `captureStillImageAsynchronouslyFromConnection:completionHandler:`? Maybe it gets called before your block gets executed? – Pranav Feb 28 '14 at 02:43
  • @Pranav - I've added the captureStillImageAsynchronouslyFromConnection:completionHandler: method. I'm thinking that either this is a timing issue or I'm not setting up the camera configuration correctly. – Patricia Feb 28 '14 at 15:28

2 Answers2

1

I got something similar to happen by using flash instead of the torch. I have an observer for @"videoDevice.flashActive" as well. I did try using exposureModeLocked first, but it didn't work for me either.

  1. Take a photo with flash on
  2. Then instantly turn flash off and take another photo before the exposure has time to adjust

The code below probably doesn't just work on its own, but it's simplified from what I did.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context
{
    if (context == AdjustingExposureContext)
    {
        self.isAdjustingExposure = [change[NSKeyValueChangeNewKey] boolValue];
    }
    else if (context == FlashModeChangedContext)
    {
        self.isFlashActive = [change[NSKeyValueChangeNewKey] boolValue];
        if (!self.flashActive)
        {
            [self captureImage];  // QUICKLY! capture 2nd image without
        }                         // flash before exposure adjusts
    }
    if (!self.isAdjustingExposure && self.flashActive)
    {
        [self removeObserver:self forKeyPath:@"videoDevice.adjustingExposure" context:AdjustingExposureContext];
        [self captureImage];  // capture 1st image with the flash on
    }
}

Now in the callback for captureStillImageAsynchronouslyFromConnection:,

if (self.isFlashActive)
    [self.videoDeviceInput.device setFlashMode:NO];

However, if you need to take more than one photo without flash at the lowered exposure, this strategy may not work.

EthanP
  • 1,663
  • 3
  • 22
  • 27
  • I'm not getting into the else if (context == FlashModeChangedContext) condition. Will the camera handle this by simply adding an observer like the others (i.e., [(AVCaptureDevice *)self.inputDevice addObserver:self forKeyPath:@"adjustingFlash" options:NSKeyValueObservingOptionNew context:MyAdjustingFlashObserationContext]; )????? – Patricia Mar 01 '14 at 00:33
  • Yes, exactly, but the keyPath is `flashMode`, see [here](https://developer.apple.com/library/mac/documentation/AVFoundation/Reference/AVCaptureDevice_Class/Reference/Reference.html#//apple_ref/occ/instp/AVCaptureDevice/flashMode) – EthanP Mar 01 '14 at 07:15
  • I finally understand your last statement: However, if you need to take more than one photo without flash at the lowered exposure, this strategy may not work. The number of pictures that are taken exponentially increases with every tap. – Patricia Mar 03 '14 at 16:30
  • I'm not understanding where you get an 'exponential increase'. I was trying to say something simple: **my code doesn't even attempt to lock the camera's exposure level**. It just sets the exposure level for a photo with a flash, then takes a photo without a flash before the exposure level has a chance to adjust. After this photo is taken the iPhone will properly adjust the exposure to one more appropriate for a no-flash photo, so you probably only have time for one or two photos before the adjustment finishes. – EthanP Mar 03 '14 at 19:30
1

It is almost certainly a timing issue. Call captureStillImageAsynchronouslyFromConnection:completionHandler: inside your if block. Then the capture will always be executed after exposure has been locked.

if ([self.inputDevice isExposureModeSupported:AVCaptureExposureModeLocked]) {
    dispatch_async(dispatch_get_main_queue(), 
        ^{
            NSError *error = nil;
            if ([self.inputDevice lockForConfiguration:&error]) {
                //code to lock exposure here
                //take photo here
            }
        });
}
Pranav
  • 680
  • 6
  • 11
  • By the time I get into the observer, I've already called the captureStillImageAsynchronouslyFromConnection:completionHandler method. I don't know how to do what you are suggesting and I can't (for the life of me) find an example of code that uses a completion handler BEFORE calling the captureStillImageAsynchronouslyFromConnection:completionHandler method. – Patricia Mar 03 '14 at 19:17