Source-SCCamera/ManagedCapturer/SCManagedCaptureDevice.m

822 lines
28 KiB
Objective-C

//
// SCManagedCaptureDevice.m
// Snapchat
//
// Created by Liu Liu on 4/22/15.
// Copyright (c) 2015 Liu Liu. All rights reserved.
//
#import "SCManagedCaptureDevice.h"
#import "AVCaptureDevice+ConfigurationLock.h"
#import "SCCameraTweaks.h"
#import "SCCaptureCommon.h"
#import "SCCaptureDeviceResolver.h"
#import "SCManagedCaptureDevice+SCManagedCapturer.h"
#import "SCManagedCaptureDeviceAutoExposureHandler.h"
#import "SCManagedCaptureDeviceAutoFocusHandler.h"
#import "SCManagedCaptureDeviceExposureHandler.h"
#import "SCManagedCaptureDeviceFaceDetectionAutoExposureHandler.h"
#import "SCManagedCaptureDeviceFaceDetectionAutoFocusHandler.h"
#import "SCManagedCaptureDeviceFocusHandler.h"
#import "SCManagedCapturer.h"
#import "SCManagedDeviceCapacityAnalyzer.h"
#import <SCFoundation/SCDeviceName.h>
#import <SCFoundation/SCLog.h>
#import <SCFoundation/SCTrace.h>
#import <FBKVOController/FBKVOController.h>
static int32_t const kSCManagedCaptureDeviceMaximumHighFrameRate = 30;
static int32_t const kSCManagedCaptureDeviceMaximumLowFrameRate = 24;
static float const kSCManagedCaptureDevicecSoftwareMaxZoomFactor = 8;
CGFloat const kSCMaxVideoZoomFactor = 100; // the max videoZoomFactor acceptable
CGFloat const kSCMinVideoZoomFactor = 1;
static NSDictionary *SCBestHRSIFormatsForHeights(NSArray *desiredHeights, NSArray *formats, BOOL shouldSupportDepth)
{
NSMutableDictionary *bestHRSIHeights = [NSMutableDictionary dictionary];
for (NSNumber *height in desiredHeights) {
bestHRSIHeights[height] = @0;
}
NSMutableDictionary *bestHRSIFormats = [NSMutableDictionary dictionary];
for (AVCaptureDeviceFormat *format in formats) {
if (@available(ios 11.0, *)) {
if (shouldSupportDepth && format.supportedDepthDataFormats.count == 0) {
continue;
}
}
if (CMFormatDescriptionGetMediaSubType(format.formatDescription) !=
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
continue;
}
CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
NSNumber *height = @(dimensions.height);
NSNumber *bestHRSI = bestHRSIHeights[height];
if (bestHRSI) {
CMVideoDimensions hrsi = format.highResolutionStillImageDimensions;
// If we enabled HSRI, we only intersted in the ones that is good.
if (hrsi.height > [bestHRSI intValue]) {
bestHRSIHeights[height] = @(hrsi.height);
bestHRSIFormats[height] = format;
}
}
}
return [bestHRSIFormats copy];
}
static inline float SCDegreesToRadians(float theta)
{
return theta * (float)M_PI / 180.f;
}
static inline float SCRadiansToDegrees(float theta)
{
return theta * 180.f / (float)M_PI;
}
@implementation SCManagedCaptureDevice {
AVCaptureDevice *_device;
AVCaptureDeviceInput *_deviceInput;
AVCaptureDeviceFormat *_defaultFormat;
AVCaptureDeviceFormat *_nightFormat;
AVCaptureDeviceFormat *_liveVideoStreamingFormat;
SCManagedCaptureDevicePosition _devicePosition;
// Configurations on the device, shortcut to avoid re-configurations
id<SCManagedCaptureDeviceExposureHandler> _exposureHandler;
id<SCManagedCaptureDeviceFocusHandler> _focusHandler;
FBKVOController *_observeController;
// For the private category methods
NSError *_error;
BOOL _softwareZoom;
BOOL _isConnected;
BOOL _flashActive;
BOOL _torchActive;
BOOL _liveVideoStreamingActive;
float _zoomFactor;
BOOL _isNightModeActive;
BOOL _captureDepthData;
}
@synthesize fieldOfView = _fieldOfView;
+ (instancetype)front
{
SCTraceStart();
static dispatch_once_t onceToken;
static SCManagedCaptureDevice *front;
static dispatch_semaphore_t semaphore;
dispatch_once(&onceToken, ^{
semaphore = dispatch_semaphore_create(1);
});
/* You can use the tweak below to intentionally kill camera in debug.
if (SCIsDebugBuild() && SCCameraTweaksKillFrontCamera()) {
return nil;
}
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (!front) {
AVCaptureDevice *device =
[[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionFront];
if (device) {
front = [[SCManagedCaptureDevice alloc] initWithDevice:device
devicePosition:SCManagedCaptureDevicePositionFront];
}
}
dispatch_semaphore_signal(semaphore);
return front;
}
+ (instancetype)back
{
SCTraceStart();
static dispatch_once_t onceToken;
static SCManagedCaptureDevice *back;
static dispatch_semaphore_t semaphore;
dispatch_once(&onceToken, ^{
semaphore = dispatch_semaphore_create(1);
});
/* You can use the tweak below to intentionally kill camera in debug.
if (SCIsDebugBuild() && SCCameraTweaksKillBackCamera()) {
return nil;
}
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (!back) {
AVCaptureDevice *device =
[[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionBack];
if (device) {
back = [[SCManagedCaptureDevice alloc] initWithDevice:device
devicePosition:SCManagedCaptureDevicePositionBack];
}
}
dispatch_semaphore_signal(semaphore);
return back;
}
+ (SCManagedCaptureDevice *)dualCamera
{
SCTraceStart();
static dispatch_once_t onceToken;
static SCManagedCaptureDevice *dualCamera;
static dispatch_semaphore_t semaphore;
dispatch_once(&onceToken, ^{
semaphore = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (!dualCamera) {
AVCaptureDevice *device = [[SCCaptureDeviceResolver sharedInstance] findDualCamera];
if (device) {
dualCamera = [[SCManagedCaptureDevice alloc] initWithDevice:device
devicePosition:SCManagedCaptureDevicePositionBackDualCamera];
}
}
dispatch_semaphore_signal(semaphore);
return dualCamera;
}
+ (instancetype)deviceWithPosition:(SCManagedCaptureDevicePosition)position
{
switch (position) {
case SCManagedCaptureDevicePositionFront:
return [self front];
case SCManagedCaptureDevicePositionBack:
return [self back];
case SCManagedCaptureDevicePositionBackDualCamera:
return [self dualCamera];
}
}
+ (BOOL)is1080pSupported
{
return [SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone6SorNewer];
}
+ (BOOL)isMixCaptureSupported
{
return !![self front] && !![self back];
}
+ (BOOL)isNightModeSupported
{
return [SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone6orNewer];
}
+ (BOOL)isEnhancedNightModeSupported
{
if (SC_AT_LEAST_IOS_11) {
return [SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone6SorNewer];
}
return NO;
}
+ (CGSize)defaultActiveFormatResolution
{
if ([SCDeviceName isIphoneX]) {
return CGSizeMake(kSCManagedCapturerVideoActiveFormatWidth1080p,
kSCManagedCapturerVideoActiveFormatHeight1080p);
}
return CGSizeMake(kSCManagedCapturerDefaultVideoActiveFormatWidth,
kSCManagedCapturerDefaultVideoActiveFormatHeight);
}
+ (CGSize)nightModeActiveFormatResolution
{
if ([SCManagedCaptureDevice isEnhancedNightModeSupported]) {
return CGSizeMake(kSCManagedCapturerNightVideoHighResActiveFormatWidth,
kSCManagedCapturerNightVideoHighResActiveFormatHeight);
}
return CGSizeMake(kSCManagedCapturerNightVideoDefaultResActiveFormatWidth,
kSCManagedCapturerNightVideoDefaultResActiveFormatHeight);
}
- (instancetype)initWithDevice:(AVCaptureDevice *)device devicePosition:(SCManagedCaptureDevicePosition)devicePosition
{
SCTraceStart();
self = [super init];
if (self) {
_device = device;
_devicePosition = devicePosition;
if (SCCameraTweaksEnableFaceDetectionFocus(devicePosition)) {
_exposureHandler = [[SCManagedCaptureDeviceFaceDetectionAutoExposureHandler alloc]
initWithDevice:device
pointOfInterest:CGPointMake(0.5, 0.5)
managedCapturer:[SCManagedCapturer sharedInstance]];
_focusHandler = [[SCManagedCaptureDeviceFaceDetectionAutoFocusHandler alloc]
initWithDevice:device
pointOfInterest:CGPointMake(0.5, 0.5)
managedCapturer:[SCManagedCapturer sharedInstance]];
} else {
_exposureHandler = [[SCManagedCaptureDeviceAutoExposureHandler alloc] initWithDevice:device
pointOfInterest:CGPointMake(0.5, 0.5)];
_focusHandler = [[SCManagedCaptureDeviceAutoFocusHandler alloc] initWithDevice:device
pointOfInterest:CGPointMake(0.5, 0.5)];
}
_observeController = [[FBKVOController alloc] initWithObserver:self];
[self _setAsExposureListenerForDevice:device];
if (SCCameraTweaksEnableExposurePointObservation()) {
[self _observeExposurePointForDevice:device];
}
if (SCCameraTweaksEnableFocusPointObservation()) {
[self _observeFocusPointForDevice:device];
}
_zoomFactor = 1.0;
[self _findSupportedFormats];
}
return self;
}
- (SCManagedCaptureDevicePosition)position
{
return _devicePosition;
}
#pragma mark - Setup and hook up with device
- (BOOL)setDeviceAsInput:(AVCaptureSession *)session
{
SCTraceStart();
AVCaptureDeviceInput *deviceInput = [self deviceInput];
if ([session canAddInput:deviceInput]) {
[session addInput:deviceInput];
} else {
NSString *previousSessionPreset = session.sessionPreset;
session.sessionPreset = AVCaptureSessionPresetInputPriority;
// Now we surely can add input
if ([session canAddInput:deviceInput]) {
[session addInput:deviceInput];
} else {
session.sessionPreset = previousSessionPreset;
return NO;
}
}
[self _enableSubjectAreaChangeMonitoring];
[self _updateActiveFormatWithSession:session fallbackPreset:AVCaptureSessionPreset640x480];
if (_device.activeFormat.videoMaxZoomFactor < 1 + 1e-5) {
_softwareZoom = YES;
} else {
_softwareZoom = NO;
if (_device.videoZoomFactor != _zoomFactor) {
// Reset the zoom factor
[self setZoomFactor:_zoomFactor];
}
}
[_exposureHandler setVisible:YES];
[_focusHandler setVisible:YES];
_isConnected = YES;
return YES;
}
- (void)removeDeviceAsInput:(AVCaptureSession *)session
{
SCTraceStart();
if (_isConnected) {
[session removeInput:_deviceInput];
[_exposureHandler setVisible:NO];
[_focusHandler setVisible:NO];
_isConnected = NO;
}
}
- (void)resetDeviceAsInput
{
_deviceInput = nil;
AVCaptureDevice *deviceFound;
switch (_devicePosition) {
case SCManagedCaptureDevicePositionFront:
deviceFound = [[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionFront];
break;
case SCManagedCaptureDevicePositionBack:
deviceFound = [[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionBack];
break;
case SCManagedCaptureDevicePositionBackDualCamera:
deviceFound = [[SCCaptureDeviceResolver sharedInstance] findDualCamera];
break;
}
if (deviceFound) {
_device = deviceFound;
}
}
#pragma mark - Configurations
- (void)_findSupportedFormats
{
NSInteger defaultHeight = [SCManagedCaptureDevice defaultActiveFormatResolution].height;
NSInteger nightHeight = [SCManagedCaptureDevice nightModeActiveFormatResolution].height;
NSInteger liveVideoStreamingHeight = kSCManagedCapturerLiveStreamingVideoActiveFormatHeight;
NSArray *heights = @[ @(nightHeight), @(defaultHeight), @(liveVideoStreamingHeight) ];
BOOL formatsShouldSupportDepth = _devicePosition == SCManagedCaptureDevicePositionBackDualCamera;
NSDictionary *formats = SCBestHRSIFormatsForHeights(heights, _device.formats, formatsShouldSupportDepth);
_nightFormat = formats[@(nightHeight)];
_defaultFormat = formats[@(defaultHeight)];
_liveVideoStreamingFormat = formats[@(liveVideoStreamingHeight)];
}
- (AVCaptureDeviceFormat *)_bestSupportedFormat
{
if (_isNightModeActive) {
return _nightFormat;
}
if (_liveVideoStreamingActive) {
return _liveVideoStreamingFormat;
}
return _defaultFormat;
}
- (void)setNightModeActive:(BOOL)nightModeActive session:(AVCaptureSession *)session
{
SCTraceStart();
if (![SCManagedCaptureDevice isNightModeSupported]) {
return;
}
if (_isNightModeActive == nightModeActive) {
return;
}
_isNightModeActive = nightModeActive;
[self updateActiveFormatWithSession:session];
}
- (void)setLiveVideoStreaming:(BOOL)liveVideoStreaming session:(AVCaptureSession *)session
{
SCTraceStart();
if (_liveVideoStreamingActive == liveVideoStreaming) {
return;
}
_liveVideoStreamingActive = liveVideoStreaming;
[self updateActiveFormatWithSession:session];
}
- (void)setCaptureDepthData:(BOOL)captureDepthData session:(AVCaptureSession *)session
{
SCTraceStart();
_captureDepthData = captureDepthData;
[self _findSupportedFormats];
[self updateActiveFormatWithSession:session];
}
- (void)updateActiveFormatWithSession:(AVCaptureSession *)session
{
[self _updateActiveFormatWithSession:session fallbackPreset:AVCaptureSessionPreset640x480];
if (_device.videoZoomFactor != _zoomFactor) {
[self setZoomFactor:_zoomFactor];
}
}
- (void)_updateActiveFormatWithSession:(AVCaptureSession *)session fallbackPreset:(NSString *)fallbackPreset
{
AVCaptureDeviceFormat *nextFormat = [self _bestSupportedFormat];
if (nextFormat && [session canSetSessionPreset:AVCaptureSessionPresetInputPriority]) {
session.sessionPreset = AVCaptureSessionPresetInputPriority;
if (nextFormat == _device.activeFormat) {
// Need to reconfigure frame rate though active format unchanged
[_device runTask:@"update frame rate"
withLockedConfiguration:^() {
[self _updateDeviceFrameRate];
}];
} else {
[_device runTask:@"update active format"
withLockedConfiguration:^() {
_device.activeFormat = nextFormat;
[self _updateDeviceFrameRate];
}];
}
} else {
session.sessionPreset = fallbackPreset;
}
[self _updateFieldOfView];
}
- (void)_updateDeviceFrameRate
{
int32_t deviceFrameRate;
if (_liveVideoStreamingActive) {
deviceFrameRate = kSCManagedCaptureDeviceMaximumLowFrameRate;
} else {
deviceFrameRate = kSCManagedCaptureDeviceMaximumHighFrameRate;
}
CMTime frameDuration = CMTimeMake(1, deviceFrameRate);
if (@available(ios 11.0, *)) {
if (_captureDepthData) {
// Sync the video frame rate to the max depth frame rate (24 fps)
if (_device.activeDepthDataFormat.videoSupportedFrameRateRanges.firstObject) {
frameDuration =
_device.activeDepthDataFormat.videoSupportedFrameRateRanges.firstObject.minFrameDuration;
}
}
}
_device.activeVideoMaxFrameDuration = frameDuration;
_device.activeVideoMinFrameDuration = frameDuration;
if (_device.lowLightBoostSupported) {
_device.automaticallyEnablesLowLightBoostWhenAvailable = YES;
}
}
- (void)setZoomFactor:(float)zoomFactor
{
SCTraceStart();
if (_softwareZoom) {
// Just remember the software zoom scale
if (zoomFactor <= kSCManagedCaptureDevicecSoftwareMaxZoomFactor && zoomFactor >= 1) {
_zoomFactor = zoomFactor;
}
} else {
[_device runTask:@"set zoom factor"
withLockedConfiguration:^() {
if (zoomFactor <= _device.activeFormat.videoMaxZoomFactor && zoomFactor >= 1) {
_zoomFactor = zoomFactor;
if (_device.videoZoomFactor != _zoomFactor) {
_device.videoZoomFactor = _zoomFactor;
}
}
}];
}
[self _updateFieldOfView];
}
- (void)_updateFieldOfView
{
float fieldOfView = _device.activeFormat.videoFieldOfView;
if (_zoomFactor > 1.f) {
// Adjust the field of view to take the zoom factor into account.
// Note: this assumes the zoom factor linearly affects the focal length.
fieldOfView = 2.f * SCRadiansToDegrees(atanf(tanf(SCDegreesToRadians(0.5f * fieldOfView)) / _zoomFactor));
}
self.fieldOfView = fieldOfView;
}
- (void)setExposurePointOfInterest:(CGPoint)pointOfInterest fromUser:(BOOL)fromUser
{
[_exposureHandler setExposurePointOfInterest:pointOfInterest fromUser:fromUser];
}
// called when user taps on a point on screen, to re-adjust camera focus onto that tapped spot.
// this re-adjustment is always necessary, regardless of scenarios (recording video, taking photo, etc),
// therefore we don't have to check _focusLock in this method.
- (void)setAutofocusPointOfInterest:(CGPoint)pointOfInterest
{
SCTraceStart();
[_focusHandler setAutofocusPointOfInterest:pointOfInterest];
}
- (void)continuousAutofocus
{
SCTraceStart();
[_focusHandler continuousAutofocus];
}
- (void)setRecording:(BOOL)recording
{
if (SCCameraTweaksSmoothAutoFocusWhileRecording() && [_device isSmoothAutoFocusSupported]) {
[self _setSmoothFocus:recording];
} else {
[self _setFocusLock:recording];
}
[_exposureHandler setStableExposure:recording];
}
- (void)_setFocusLock:(BOOL)focusLock
{
SCTraceStart();
[_focusHandler setFocusLock:focusLock];
}
- (void)_setSmoothFocus:(BOOL)smoothFocus
{
SCTraceStart();
[_focusHandler setSmoothFocus:smoothFocus];
}
- (void)setFlashActive:(BOOL)flashActive
{
SCTraceStart();
if (_flashActive != flashActive) {
if ([_device hasFlash]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (flashActive && [_device isFlashModeSupported:AVCaptureFlashModeOn]) {
[_device runTask:@"set flash active"
withLockedConfiguration:^() {
_device.flashMode = AVCaptureFlashModeOn;
}];
} else if (!flashActive && [_device isFlashModeSupported:AVCaptureFlashModeOff]) {
[_device runTask:@"set flash off"
withLockedConfiguration:^() {
_device.flashMode = AVCaptureFlashModeOff;
}];
}
#pragma clang diagnostic pop
_flashActive = flashActive;
} else {
_flashActive = NO;
}
}
}
- (void)setTorchActive:(BOOL)torchActive
{
SCTraceStart();
if (_torchActive != torchActive) {
if ([_device hasTorch]) {
if (torchActive && [_device isTorchModeSupported:AVCaptureTorchModeOn]) {
[_device runTask:@"set torch active"
withLockedConfiguration:^() {
[_device setTorchMode:AVCaptureTorchModeOn];
}];
} else if (!torchActive && [_device isTorchModeSupported:AVCaptureTorchModeOff]) {
[_device runTask:@"set torch off"
withLockedConfiguration:^() {
_device.torchMode = AVCaptureTorchModeOff;
}];
}
_torchActive = torchActive;
} else {
_torchActive = NO;
}
}
}
#pragma mark - Utilities
- (BOOL)isFlashSupported
{
return _device.hasFlash;
}
- (BOOL)isTorchSupported
{
return _device.hasTorch;
}
- (CGPoint)convertViewCoordinates:(CGPoint)viewCoordinates
viewSize:(CGSize)viewSize
videoGravity:(NSString *)videoGravity
{
SCTraceStart();
CGPoint pointOfInterest = CGPointMake(.5f, .5f);
CGRect cleanAperture;
AVCaptureDeviceInput *deviceInput = [self deviceInput];
NSArray *ports = [deviceInput.ports copy];
if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) {
// Scale, switch x and y, and reverse x
return CGPointMake(viewCoordinates.y / viewSize.height, 1.f - (viewCoordinates.x / viewSize.width));
}
for (AVCaptureInputPort *port in ports) {
if ([port mediaType] == AVMediaTypeVideo && port.formatDescription) {
cleanAperture = CMVideoFormatDescriptionGetCleanAperture(port.formatDescription, YES);
CGSize apertureSize = cleanAperture.size;
CGPoint point = viewCoordinates;
CGFloat apertureRatio = apertureSize.height / apertureSize.width;
CGFloat viewRatio = viewSize.width / viewSize.height;
CGFloat xc = .5f;
CGFloat yc = .5f;
if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) {
if (viewRatio > apertureRatio) {
CGFloat y2 = viewSize.height;
CGFloat x2 = viewSize.height * apertureRatio;
CGFloat x1 = viewSize.width;
CGFloat blackBar = (x1 - x2) / 2;
// If point is inside letterboxed area, do coordinate conversion; otherwise, don't change the
// default value returned (.5,.5)
if (point.x >= blackBar && point.x <= blackBar + x2) {
// Scale (accounting for the letterboxing on the left and right of the video preview),
// switch x and y, and reverse x
xc = point.y / y2;
yc = 1.f - ((point.x - blackBar) / x2);
}
} else {
CGFloat y2 = viewSize.width / apertureRatio;
CGFloat y1 = viewSize.height;
CGFloat x2 = viewSize.width;
CGFloat blackBar = (y1 - y2) / 2;
// If point is inside letterboxed area, do coordinate conversion. Otherwise, don't change the
// default value returned (.5,.5)
if (point.y >= blackBar && point.y <= blackBar + y2) {
// Scale (accounting for the letterboxing on the top and bottom of the video preview),
// switch x and y, and reverse x
xc = ((point.y - blackBar) / y2);
yc = 1.f - (point.x / x2);
}
}
} else if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) {
// Scale, switch x and y, and reverse x
if (viewRatio > apertureRatio) {
CGFloat y2 = apertureSize.width * (viewSize.width / apertureSize.height);
xc = (point.y + ((y2 - viewSize.height) / 2.f)) / y2; // Account for cropped height
yc = (viewSize.width - point.x) / viewSize.width;
} else {
CGFloat x2 = apertureSize.height * (viewSize.height / apertureSize.width);
yc = 1.f - ((point.x + ((x2 - viewSize.width) / 2)) / x2); // Account for cropped width
xc = point.y / viewSize.height;
}
}
pointOfInterest = CGPointMake(xc, yc);
break;
}
}
return pointOfInterest;
}
#pragma mark - SCManagedCapturer friendly methods
- (AVCaptureDevice *)device
{
return _device;
}
- (AVCaptureDeviceInput *)deviceInput
{
SCTraceStart();
if (!_deviceInput) {
NSError *error = nil;
_deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:_device error:&error];
if (!_deviceInput) {
_error = [error copy];
}
}
return _deviceInput;
}
- (NSError *)error
{
return _error;
}
- (BOOL)softwareZoom
{
return _softwareZoom;
}
- (BOOL)isConnected
{
return _isConnected;
}
- (BOOL)flashActive
{
return _flashActive;
}
- (BOOL)torchActive
{
return _torchActive;
}
- (float)zoomFactor
{
return _zoomFactor;
}
- (BOOL)isNightModeActive
{
return _isNightModeActive;
}
- (BOOL)liveVideoStreamingActive
{
return _liveVideoStreamingActive;
}
- (BOOL)isAvailable
{
return [_device isConnected];
}
#pragma mark - Private methods
- (void)_enableSubjectAreaChangeMonitoring
{
SCTraceStart();
[_device runTask:@"enable SubjectAreaChangeMonitoring"
withLockedConfiguration:^() {
_device.subjectAreaChangeMonitoringEnabled = YES;
}];
}
- (AVCaptureDeviceFormat *)activeFormat
{
return _device.activeFormat;
}
#pragma mark - Observe -adjustingExposure
- (void)_setAsExposureListenerForDevice:(AVCaptureDevice *)device
{
SCTraceStart();
SCLogCoreCameraInfo(@"Set exposure adjustment KVO for device: %ld", (long)device.position);
[_observeController observe:device
keyPath:@keypath(device, adjustingExposure)
options:NSKeyValueObservingOptionNew
action:@selector(_adjustingExposureChanged:)];
}
- (void)_adjustingExposureChanged:(NSDictionary *)change
{
SCTraceStart();
BOOL adjustingExposure = [change[NSKeyValueChangeNewKey] boolValue];
SCLogCoreCameraInfo(@"KVO exposure changed to %d", adjustingExposure);
if ([self.delegate respondsToSelector:@selector(managedCaptureDevice:didChangeAdjustingExposure:)]) {
[self.delegate managedCaptureDevice:self didChangeAdjustingExposure:adjustingExposure];
}
}
#pragma mark - Observe -exposurePointOfInterest
- (void)_observeExposurePointForDevice:(AVCaptureDevice *)device
{
SCTraceStart();
SCLogCoreCameraInfo(@"Set exposure point KVO for device: %ld", (long)device.position);
[_observeController observe:device
keyPath:@keypath(device, exposurePointOfInterest)
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
action:@selector(_exposurePointOfInterestChanged:)];
}
- (void)_exposurePointOfInterestChanged:(NSDictionary *)change
{
SCTraceStart();
CGPoint exposurePoint = [change[NSKeyValueChangeNewKey] CGPointValue];
SCLogCoreCameraInfo(@"KVO exposure point changed to %@", NSStringFromCGPoint(exposurePoint));
if ([self.delegate respondsToSelector:@selector(managedCaptureDevice:didChangeExposurePoint:)]) {
[self.delegate managedCaptureDevice:self didChangeExposurePoint:exposurePoint];
}
}
#pragma mark - Observe -focusPointOfInterest
- (void)_observeFocusPointForDevice:(AVCaptureDevice *)device
{
SCTraceStart();
SCLogCoreCameraInfo(@"Set focus point KVO for device: %ld", (long)device.position);
[_observeController observe:device
keyPath:@keypath(device, focusPointOfInterest)
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
action:@selector(_focusPointOfInterestChanged:)];
}
- (void)_focusPointOfInterestChanged:(NSDictionary *)change
{
SCTraceStart();
CGPoint focusPoint = [change[NSKeyValueChangeNewKey] CGPointValue];
SCLogCoreCameraInfo(@"KVO focus point changed to %@", NSStringFromCGPoint(focusPoint));
if ([self.delegate respondsToSelector:@selector(managedCaptureDevice:didChangeFocusPoint:)]) {
[self.delegate managedCaptureDevice:self didChangeFocusPoint:focusPoint];
}
}
- (void)dealloc
{
[_observeController unobserveAll];
}
@end