// // SCManagedCaptureDeviceFaceDetectionAutoFocusHandler.m // Snapchat // // Created by Jiyang Zhu on 3/7/18. // Copyright © 2018 Snapchat, Inc. All rights reserved. // #import "SCManagedCaptureDeviceFaceDetectionAutoFocusHandler.h" #import "AVCaptureDevice+ConfigurationLock.h" #import "SCCameraTweaks.h" #import "SCManagedCaptureFaceDetectionAdjustingPOIResource.h" #import "SCManagedCapturer.h" #import "SCManagedCapturerListener.h" #import #import #import @interface SCManagedCaptureDeviceFaceDetectionAutoFocusHandler () @property (nonatomic, strong) AVCaptureDevice *device; @property (nonatomic, weak) id managedCapturer; @property (nonatomic, assign) CGPoint focusPointOfInterest; @property (nonatomic, assign) BOOL isVisible; @property (nonatomic, assign) BOOL isContinuousAutofocus; @property (nonatomic, assign) BOOL focusLock; @property (nonatomic, copy) NSDictionary *faceBoundsByFaceID; @property (nonatomic, strong) SCManagedCaptureFaceDetectionAdjustingPOIResource *resource; @end @implementation SCManagedCaptureDeviceFaceDetectionAutoFocusHandler - (instancetype)initWithDevice:(AVCaptureDevice *)device pointOfInterest:(CGPoint)pointOfInterest managedCapturer:(id)managedCapturer { if (self = [super init]) { SCAssert(device, @"AVCaptureDevice should not be nil."); SCAssert(managedCapturer, @"id should not be nil."); _device = device; _focusPointOfInterest = pointOfInterest; SCManagedCaptureDevicePosition position = (device.position == AVCaptureDevicePositionFront ? SCManagedCaptureDevicePositionFront : SCManagedCaptureDevicePositionBack); _resource = [[SCManagedCaptureFaceDetectionAdjustingPOIResource alloc] initWithDefaultPointOfInterest:pointOfInterest shouldTargetOnFaceAutomatically:SCCameraTweaksTurnOnFaceDetectionFocusByDefault(position)]; _managedCapturer = managedCapturer; } return self; } - (CGPoint)getFocusPointOfInterest { return self.focusPointOfInterest; } // 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 self.focusLock in this method. - (void)setAutofocusPointOfInterest:(CGPoint)pointOfInterest { SCTraceODPCompatibleStart(2); pointOfInterest = [self.resource updateWithNewProposedPointOfInterest:pointOfInterest fromUser:YES]; SC_GUARD_ELSE_RETURN(!CGPointEqualToPoint(pointOfInterest, self.focusPointOfInterest) || self.isContinuousAutofocus); [self _actuallySetFocusPointOfInterestIfNeeded:pointOfInterest withFocusMode:AVCaptureFocusModeAutoFocus taskName:@"set autofocus"]; } - (void)continuousAutofocus { SCTraceODPCompatibleStart(2); SC_GUARD_ELSE_RETURN(!self.isContinuousAutofocus); CGPoint pointOfInterest = [self.resource updateWithNewProposedPointOfInterest:CGPointMake(0.5, 0.5) fromUser:NO]; [self _actuallySetFocusPointOfInterestIfNeeded:pointOfInterest withFocusMode:AVCaptureFocusModeContinuousAutoFocus taskName:@"set continuous autofocus"]; } - (void)setFocusLock:(BOOL)focusLock { // Disabled focus lock for face detection and focus handler. } - (void)setSmoothFocus:(BOOL)smoothFocus { SCTraceODPCompatibleStart(2); SC_GUARD_ELSE_RETURN(smoothFocus != self.device.smoothAutoFocusEnabled); [self.device runTask:@"set smooth autofocus" withLockedConfiguration:^() { [self.device setSmoothAutoFocusEnabled:smoothFocus]; }]; } - (void)setVisible:(BOOL)visible { SCTraceODPCompatibleStart(2); SC_GUARD_ELSE_RETURN(_isVisible != visible); self.isVisible = visible; if (visible) { [[SCManagedCapturer sharedInstance] addListener:self]; } else { [[SCManagedCapturer sharedInstance] removeListener:self]; [self.resource reset]; } } - (void)_actuallySetFocusPointOfInterestIfNeeded:(CGPoint)pointOfInterest withFocusMode:(AVCaptureFocusMode)focusMode taskName:(NSString *)taskName { SCTraceODPCompatibleStart(2); SC_GUARD_ELSE_RETURN(!CGPointEqualToPoint(pointOfInterest, self.focusPointOfInterest) && [self.device isFocusModeSupported:focusMode] && [self.device isFocusPointOfInterestSupported]); [self.device runTask:taskName withLockedConfiguration:^() { // Set focus point before changing focus mode // Be noticed that order does matter self.device.focusPointOfInterest = pointOfInterest; self.device.focusMode = focusMode; }]; self.focusPointOfInterest = pointOfInterest; self.isContinuousAutofocus = (focusMode == AVCaptureFocusModeContinuousAutoFocus); } #pragma mark - SCManagedCapturerListener - (void)managedCapturer:(id)managedCapturer didDetectFaceBounds:(NSDictionary *)faceBoundsByFaceID { SCTraceODPCompatibleStart(2); SC_GUARD_ELSE_RETURN(self.isVisible); CGPoint pointOfInterest = [self.resource updateWithNewDetectedFaceBounds:faceBoundsByFaceID]; // If pointOfInterest is equal to CGPointMake(0.5, 0.5), it means no valid face is found, so that we should reset to // AVCaptureFocusModeContinuousAutoFocus. Otherwise, focus on the point and set the mode as // AVCaptureFocusModeAutoFocus. // TODO(Jiyang): Refactor SCManagedCaptureFaceDetectionAdjustingPOIResource to include focusMode and exposureMode. AVCaptureFocusMode focusMode = CGPointEqualToPoint(pointOfInterest, CGPointMake(0.5, 0.5)) ? AVCaptureFocusModeContinuousAutoFocus : AVCaptureFocusModeAutoFocus; [self _actuallySetFocusPointOfInterestIfNeeded:pointOfInterest withFocusMode:focusMode taskName:@"set autofocus from face detection"]; } @end