5

I have problems with running iPhone X flash in torch mode.

Back AVCaptureDeviceTypeBuiltInTelephotoCamera selected as capture device:

com.apple.avfoundation.avcapturedevice.built-in_video:2' -
AVCaptureDeviceTypeBuiltInTelephotoCamera

After checking touch mode availability with:

[self.captureDevice isTorchModeSupported:AVCaptureTorchModeOn]

I'm trying to switch flash light into torch mode with

[self.captureDevice lockForConfiguration:nil];
BOOL result = [self.captureDevice setTorchModeOnWithLevel:1 error:&error];
[self.captureDevice unlockForConfiguration];

This call is successful. result == YES and error == nil. But flash light blinks once then turns off.

I saw this behaviour on iPhone X myself and there is a reports of the same behaviour from iPhone 8 and iPhone 8 Plus owners. Some users say that this problem appeared after update to iOS 11.1. But I couldn't reproduce it with iPhone 8 myself.

Is there any ideas how to fix or debug this problem?

Full code snippet from my app listed below:

// Retrieve the back camera
    if ([AVCaptureDeviceDiscoverySession class]) {
        DDLogDebug(@"Search camera with AVCaptureDeviceDiscoverySession");
        AVCaptureDevice* camera =
        [AVCaptureDeviceDiscoverySession
         discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInTelephotoCamera]
         mediaType:AVMediaTypeVideo
         position:AVCaptureDevicePositionBack].devices.firstObject;

        if (!camera) {
            camera = [AVCaptureDeviceDiscoverySession
                      discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInTelephotoCamera]
                      mediaType:AVMediaTypeVideo
                      position:AVCaptureDevicePositionBack].devices.firstObject;
        }
        DDLogDebug(@"Did find %@ camera", camera);
        self.captureDevice = camera;
    } else {
        DDLogDebug(@"Haven't found camera device with AVCaptureDeviceDiscoverySession");
    }

    if (!self.captureDevice) {
        DDLogDebug(@"Searching at [AVCaptureDevice devices], where %lu devices available", (unsigned long)AVCaptureDevice.devices.count);
        for (AVCaptureDevice *device in [AVCaptureDevice devices]) {
            if ([device hasMediaType:AVMediaTypeVideo] && [device hasTorch]) {
                self.captureDevice = device;
                break;
            }
        }
    }

    if (!self.captureDevice) {
        NSError* error = [NSError buildError:^(MRErrorBuilder *builder) {
            builder.localizedDescription = NSLocalizedString(@"There is no camera devices able to measure heart rate", nil);
            builder.domain               = kWTCameraHeartRateMonitorError;
            builder.code                 = 27172;
        }];
        DDLogError(@"%@", error);
        self.session = nil;
        self.handler(0, 0, error);
        return NO;
    }

    NSError *error;
    AVCaptureDeviceInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:self.captureDevice
                                                                         error:&error];
    if (error) {
        DDLogError(@"%@", error);
        self.session = nil;
        self.handler(0, 0, error);
        return NO;
    }


    NSString* deviceType = [self.captureDevice respondsToSelector:@selector(deviceType)] ? self.captureDevice.deviceType : @"Unknown";

    DDLogDebug(@"Configurating camera '%@'/'%@' - %@ id %@ at %ld connected: %@", self.captureDevice.localizedName, self.captureDevice.modelID, deviceType, self.captureDevice.uniqueID, (long)self.captureDevice.position, self.captureDevice.connected?@"YES":@"NO");

    self.session               = [[AVCaptureSession alloc] init];
    NSString* preset = [self.session canSetSessionPreset:AVCaptureSessionPresetLow] ? AVCaptureSessionPresetLow : nil;
    if (preset) {
        self.session.sessionPreset = preset;
    }

    [self.session beginConfiguration];
    [self.session addInput:input];

    // Find the max frame rate we can get from the given device
    AVCaptureDeviceFormat *currentFormat;
    for (AVCaptureDeviceFormat *format in self.captureDevice.formats)
    {
        NSArray *ranges = format.videoSupportedFrameRateRanges;
        AVFrameRateRange *frameRates = ranges[0];

        // Find the lowest resolution format at the frame rate we want.
        if (frameRates.maxFrameRate == FRAMES_PER_SECOND && (!currentFormat || (CMVideoFormatDescriptionGetDimensions(format.formatDescription).width < CMVideoFormatDescriptionGetDimensions(currentFormat.formatDescription).width && CMVideoFormatDescriptionGetDimensions(format.formatDescription).height < CMVideoFormatDescriptionGetDimensions(currentFormat.formatDescription).height)))
        {
            currentFormat = format;
        }
    }

    if (![self.captureDevice isTorchModeSupported:AVCaptureTorchModeOn]) {
        NSError* error = [NSError buildError:^(MRErrorBuilder *builder) {
            builder.localizedDescription = NSLocalizedString(@"Torch mode is not supported for your camera", nil);
            builder.domain               = kWTCameraHeartRateMonitorError;
            builder.code                 = 28633;
        }];
        self.session = nil;
        DDLogError(@"%@", error);
        self.session = nil;
        self.handler(0, 0, error);
        return NO;
    }

    // Tell the device to use the max frame rate.
    [self.captureDevice lockForConfiguration:nil];
    DDLogVerbose(@"Turn on tourch mode with level 0.5");
    self.captureDevice.flashMode = AVCaptureFlashModeOff;
    BOOL result = [self.captureDevice setTorchModeOnWithLevel:0.5 error:&error];
    if (!result) {
        DDLogError(@"%@", error);
        self.session = nil;
        self.handler(0, 0, error);
        return NO;
    }
    [self.captureDevice setFocusMode:AVCaptureFocusModeLocked];
    [self.captureDevice setFocusModeLockedWithLensPosition:1.0
                                         completionHandler:nil];
    self.captureDevice.activeFormat = currentFormat;
    self.captureDevice.activeVideoMinFrameDuration = CMTimeMake(1, FRAMES_PER_SECOND);
    self.captureDevice.activeVideoMaxFrameDuration = CMTimeMake(1, FRAMES_PER_SECOND);
    [self.captureDevice unlockForConfiguration];

    // Set the output
    AVCaptureVideoDataOutput* videoOutput = [AVCaptureVideoDataOutput new];

    // create a queue to run the capture on
    dispatch_queue_t captureQueue=dispatch_queue_create("catpureQueue", DISPATCH_QUEUE_SERIAL);

    // setup our delegate
    [videoOutput setSampleBufferDelegate:self queue:captureQueue];

    // configure the pixel format

    videoOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};
    videoOutput.alwaysDiscardsLateVideoFrames = NO;

    [self.session addOutput:videoOutput];

    if (debugPath) {
        NSError* error;
        [[NSFileManager defaultManager] removeItemAtPath:debugPath
                                                   error:nil];

        BOOL result =
        [[NSFileManager defaultManager] createDirectoryAtPath:debugPath
                                  withIntermediateDirectories:YES
                                                   attributes:nil
                                                        error:&error];
        if (result) {
            [self setupDebugRecordAt:debugPath withFormat:currentFormat];
        } else {
            DDLogError(@"%@", error);
        }

        const char* path = [debugPath cStringUsingEncoding:NSUTF8StringEncoding];
        self.filter->setDebugPath(path);
    }


    // Start the video session
    [self.session commitConfiguration];

    self.frameNumber = 0;
    [self.assetWriter startWriting];
    [self.assetWriter startSessionAtSourceTime:kCMTimeZero];
    [self.session startRunning];
VC.One
  • 14,790
  • 4
  • 25
  • 57
lazarev
  • 839
  • 10
  • 25
  • I have similar problem but on the iPhone 8 iOS 11.1.1. Some of my user report the bug, but since i am not own iPhone 8 or X, it is very hard to solve. Please update when you find a solution. – DzungPV Nov 10 '17 at 08:45
  • Hi, @DzungPV. I've fixed this problem in my project. Read my solution below. Hope this will help you. – lazarev Nov 10 '17 at 09:26

1 Answers1

6

Finally, the problem is fixed. I'm not sure about exact reason. Any information related to this issue are appreciated.

This problem exists on iPhone 8, 8+ and iPhone X running iOS 11.1. I've reproduced this behaviour on iPhone 8 after updating form iOS from 11.0 to 11.1.

What I noticed is that torch turns on after calling

BOOL result = [self.captureDevice setTorchModeOnWithLevel:0.5 error:&error];

and turns off after

[self.captureDevice setFocusMode:AVCaptureFocusModeLocked];

or

[self.session commitConfiguration];

So the solution was to perform torch configuration where ALL other session and device configurations are finished and session is started.

My current implementation is:

// Session configuration ...

[self.session startRunning];

if (![self.captureDevice isTorchModeSupported:AVCaptureTorchModeOn]) {
    NSError* error = [NSError buildError:^(MRErrorBuilder *builder) {
        builder.localizedDescription = NSLocalizedString(@"Torch mode is not supported for your camera", nil);
        builder.domain               = kWTCameraHeartRateMonitorError;
        builder.code                 = 28633;
    }];
    DDLogError(@"%@", error);
    if (self.session) {
       [self.session stopRunning];
    }
    self.session = nil;
    self.handler(0, 0, error);
    return NO;
}

[self.captureDevice lockForConfiguration:nil];
self.captureDevice.flashMode = AVCaptureFlashModeOff;
[self.captureDevice setFocusMode:AVCaptureFocusModeLocked];
[self.captureDevice setFocusModeLockedWithLensPosition:1.0
                                         completionHandler:nil];
self.captureDevice.activeFormat = currentFormat;
self.captureDevice.activeVideoMinFrameDuration = CMTimeMake(1, FRAMES_PER_SECOND);
self.captureDevice.activeVideoMaxFrameDuration = CMTimeMake(1, FRAMES_PER_SECOND);

// This call should be placed AFTER all other configurations

BOOL result = [self.captureDevice setTorchModeOnWithLevel:0.5 error:&error];
if (!result) {
    DDLogError(@"%@", error);
    self.session = nil;
    self.handler(0, 0, error);
    return NO;
}
[self.captureDevice unlockForConfiguration];
lazarev
  • 839
  • 10
  • 25
  • Thank you for the solution, but you use an deprecated property, you can change to self.captureDevice.torchMode=AVCaptureTorchModeOff; – DzungPV Nov 10 '17 at 10:10
  • Which one is deprecated? – lazarev Nov 10 '17 at 13:23
  • This one: https://developer.apple.com/documentation/avfoundation/avcapturedevice/1388116-flashmode – DzungPV Nov 11 '17 at 03:18
  • Thanks for the comment that the torch mode should be turned on after the session is started. That fixed my problem where torch was not staying on when session started on iPhone 6 plus running iOS12. It was working OK on iOS10. – birdman Oct 10 '18 at 15:03