// // SCManagedStillImageCapturer.m // Snapchat // // Created by Liu Liu on 4/30/15. // Copyright (c) 2015 Liu Liu. All rights reserved. // #import "SCManagedStillImageCapturer.h" #import "SCCameraSettingUtils.h" #import "SCCameraTweaks.h" #import "SCCaptureResource.h" #import "SCLogger+Camera.h" #import "SCManagedCaptureSession.h" #import "SCManagedCapturer.h" #import "SCManagedCapturerLensAPI.h" #import "SCManagedFrameHealthChecker.h" #import "SCManagedLegacyStillImageCapturer.h" #import "SCManagedPhotoCapturer.h" #import "SCManagedStillImageCapturerHandler.h" #import "SCManagedStillImageCapturer_Protected.h" #import #import #import #import #import #import #import #import #import #import NSString *const kSCManagedStillImageCapturerErrorDomain = @"kSCManagedStillImageCapturerErrorDomain"; NSInteger const kSCCameraShutterSoundID = 1108; #if !TARGET_IPHONE_SIMULATOR NSInteger const kSCManagedStillImageCapturerNoStillImageConnection = 1101; #endif NSInteger const kSCManagedStillImageCapturerApplicationStateBackground = 1102; // We will do the image capture regardless if these is still camera adjustment in progress after 0.4 seconds. NSTimeInterval const kSCManagedStillImageCapturerDeadline = 0.4; NSTimeInterval const kSCCameraRetryInterval = 0.1; BOOL SCPhotoCapturerIsEnabled(void) { // Due to the native crash in https://jira.sc-corp.net/browse/CCAM-4904, we guard it >= 10.2 return SC_AT_LEAST_IOS_10_2; } NSDictionary *cameraInfoForBuffer(CMSampleBufferRef imageDataSampleBuffer) { CFDictionaryRef exifAttachments = (CFDictionaryRef)CMGetAttachment(imageDataSampleBuffer, kCGImagePropertyExifDictionary, NULL); float brightness = [retrieveBrightnessFromEXIFAttachments(exifAttachments) floatValue]; NSInteger ISOSpeedRating = [retrieveISOSpeedRatingFromEXIFAttachments(exifAttachments) integerValue]; return @{ (__bridge NSString *) kCGImagePropertyExifISOSpeedRatings : @(ISOSpeedRating), (__bridge NSString *) kCGImagePropertyExifBrightnessValue : @(brightness) }; } @implementation SCManagedStillImageCapturer + (instancetype)capturerWithCaptureResource:(SCCaptureResource *)captureResource { if (SCPhotoCapturerIsEnabled()) { return [[SCManagedPhotoCapturer alloc] initWithSession:captureResource.managedSession.avSession performer:captureResource.queuePerformer lensProcessingCore:captureResource.lensProcessingCore delegate:captureResource.stillImageCapturerHandler]; } else { return [[SCManagedLegacyStillImageCapturer alloc] initWithSession:captureResource.managedSession.avSession performer:captureResource.queuePerformer lensProcessingCore:captureResource.lensProcessingCore delegate:captureResource.stillImageCapturerHandler]; } } - (instancetype)initWithSession:(AVCaptureSession *)session performer:(id)performer lensProcessingCore:(id)lensAPI delegate:(id)delegate { self = [super init]; if (self) { _session = session; _performer = performer; _lensAPI = lensAPI; _delegate = delegate; } return self; } - (void)setupWithSession:(AVCaptureSession *)session { UNIMPLEMENTED_METHOD; } - (void)setAsOutput:(AVCaptureSession *)session { UNIMPLEMENTED_METHOD; } - (void)setHighResolutionStillImageOutputEnabled:(BOOL)highResolutionStillImageOutputEnabled { UNIMPLEMENTED_METHOD; } - (void)enableStillImageStabilization { UNIMPLEMENTED_METHOD; } - (void)removeAsOutput:(AVCaptureSession *)session { UNIMPLEMENTED_METHOD; } - (void)setPortraitModeCaptureEnabled:(BOOL)enabled { UNIMPLEMENTED_METHOD; } - (void)setPortraitModePointOfInterest:(CGPoint)pointOfInterest { UNIMPLEMENTED_METHOD; } - (void)captureStillImageWithAspectRatio:(CGFloat)aspectRatio atZoomFactor:(float)zoomFactor fieldOfView:(float)fieldOfView state:(SCManagedCapturerState *)state captureSessionID:(NSString *)captureSessionID shouldCaptureFromVideo:(BOOL)shouldCaptureFromVideo completionHandler: (sc_managed_still_image_capturer_capture_still_image_completion_handler_t)completionHandler { UNIMPLEMENTED_METHOD; } #pragma mark - SCManagedDeviceCapacityAnalyzerListener - (void)managedDeviceCapacityAnalyzer:(SCManagedDeviceCapacityAnalyzer *)managedDeviceCapacityAnalyzer didChangeAdjustingExposure:(BOOL)adjustingExposure { UNIMPLEMENTED_METHOD; } - (void)managedDeviceCapacityAnalyzer:(SCManagedDeviceCapacityAnalyzer *)managedDeviceCapacityAnalyzer didChangeLightingCondition:(SCCapturerLightingConditionType)lightingCondition { UNIMPLEMENTED_METHOD; } #pragma mark - SCManagedCapturerListener - (void)managedCapturer:(id)managedCapturer didChangeAdjustingExposure:(SCManagedCapturerState *)state { UNIMPLEMENTED_METHOD; } - (UIImage *)imageFromData:(NSData *)data currentZoomFactor:(float)currentZoomFactor targetAspectRatio:(CGFloat)targetAspectRatio fieldOfView:(float)fieldOfView state:(SCManagedCapturerState *)state sampleBuffer:(CMSampleBufferRef)sampleBuffer { UIImage *capturedImage = [self imageFromImage:[UIImage sc_imageWithData:data] currentZoomFactor:currentZoomFactor targetAspectRatio:targetAspectRatio fieldOfView:fieldOfView state:state]; // Check capture frame health before showing preview NSDictionary *metadata = [[SCManagedFrameHealthChecker sharedInstance] metadataForSampleBuffer:sampleBuffer photoCapturerEnabled:SCPhotoCapturerIsEnabled() lensEnabled:state.lensesActive lensID:[_lensAPI activeLensId]]; [[SCManagedFrameHealthChecker sharedInstance] checkImageHealthForCaptureFrameImage:capturedImage captureSettings:metadata captureSessionID:_captureSessionID]; _captureSessionID = nil; return capturedImage; } - (UIImage *)imageFromData:(NSData *)data currentZoomFactor:(float)currentZoomFactor targetAspectRatio:(CGFloat)targetAspectRatio fieldOfView:(float)fieldOfView state:(SCManagedCapturerState *)state metadata:(NSDictionary *)metadata { UIImage *capturedImage = [self imageFromImage:[UIImage sc_imageWithData:data] currentZoomFactor:currentZoomFactor targetAspectRatio:targetAspectRatio fieldOfView:fieldOfView state:state]; // Check capture frame health before showing preview NSDictionary *newMetadata = [[SCManagedFrameHealthChecker sharedInstance] metadataForMetadata:metadata photoCapturerEnabled:SCPhotoCapturerIsEnabled() lensEnabled:state.lensesActive lensID:[_lensAPI activeLensId]]; [[SCManagedFrameHealthChecker sharedInstance] checkImageHealthForCaptureFrameImage:capturedImage captureSettings:newMetadata captureSessionID:_captureSessionID]; _captureSessionID = nil; return capturedImage; } - (UIImage *)imageFromImage:(UIImage *)image currentZoomFactor:(float)currentZoomFactor targetAspectRatio:(CGFloat)targetAspectRatio fieldOfView:(float)fieldOfView state:(SCManagedCapturerState *)state { UIImage *fullScreenImage = image; if (state.lensesActive && _lensAPI.isLensApplied) { fullScreenImage = [_lensAPI processImage:fullScreenImage maxPixelSize:[_lensAPI maxPixelSize] devicePosition:state.devicePosition fieldOfView:fieldOfView]; } // Resize and crop return [self resizeImage:fullScreenImage currentZoomFactor:currentZoomFactor targetAspectRatio:targetAspectRatio]; } - (UIImage *)resizeImage:(UIImage *)image currentZoomFactor:(float)currentZoomFactor targetAspectRatio:(CGFloat)targetAspectRatio { SCTraceStart(); if (currentZoomFactor == 1) { return SCCropImageToTargetAspectRatio(image, targetAspectRatio); } else { @autoreleasepool { return [self resizeImageUsingCG:image currentZoomFactor:currentZoomFactor targetAspectRatio:targetAspectRatio maxPixelSize:[_lensAPI maxPixelSize]]; } } } - (UIImage *)resizeImageUsingCG:(UIImage *)inputImage currentZoomFactor:(float)currentZoomFactor targetAspectRatio:(CGFloat)targetAspectRatio maxPixelSize:(CGFloat)maxPixelSize { size_t imageWidth = CGImageGetWidth(inputImage.CGImage); size_t imageHeight = CGImageGetHeight(inputImage.CGImage); SCLogGeneralInfo(@"Captured still image at %dx%d", (int)imageWidth, (int)imageHeight); size_t targetWidth, targetHeight; float zoomFactor = currentZoomFactor; if (imageWidth > imageHeight) { targetWidth = maxPixelSize; targetHeight = (maxPixelSize * imageHeight + imageWidth / 2) / imageWidth; // Update zoom factor here zoomFactor *= (float)maxPixelSize / imageWidth; } else { targetHeight = maxPixelSize; targetWidth = (maxPixelSize * imageWidth + imageHeight / 2) / imageHeight; zoomFactor *= (float)maxPixelSize / imageHeight; } if (targetAspectRatio != kSCManagedCapturerAspectRatioUnspecified) { SCCropImageSizeToAspectRatio(targetWidth, targetHeight, inputImage.imageOrientation, targetAspectRatio, &targetWidth, &targetHeight); } CGContextRef context = CGBitmapContextCreate(NULL, targetWidth, targetHeight, CGImageGetBitsPerComponent(inputImage.CGImage), CGImageGetBitsPerPixel(inputImage.CGImage) * targetWidth / 8, CGImageGetColorSpace(inputImage.CGImage), CGImageGetBitmapInfo(inputImage.CGImage)); CGContextSetInterpolationQuality(context, kCGInterpolationHigh); CGContextDrawImage(context, CGRectMake(targetWidth * 0.5 - imageWidth * 0.5 * zoomFactor, targetHeight * 0.5 - imageHeight * 0.5 * zoomFactor, imageWidth * zoomFactor, imageHeight * zoomFactor), inputImage.CGImage); CGImageRef thumbnail = CGBitmapContextCreateImage(context); CGContextRelease(context); UIImage *image = [UIImage imageWithCGImage:thumbnail scale:inputImage.scale orientation:inputImage.imageOrientation]; CGImageRelease(thumbnail); return image; } - (CMTime)adjustedExposureDurationForNightModeWithCurrentExposureDuration:(CMTime)exposureDuration { CMTime adjustedExposureDuration = exposureDuration; if (_lightingConditionType == SCCapturerLightingConditionTypeDark) { adjustedExposureDuration = CMTimeMultiplyByFloat64(exposureDuration, 1.5); } else if (_lightingConditionType == SCCapturerLightingConditionTypeExtremeDark) { adjustedExposureDuration = CMTimeMultiplyByFloat64(exposureDuration, 2.5); } return adjustedExposureDuration; } #pragma mark - SCManagedVideoDataSourceListener - (void)managedVideoDataSource:(id)managedVideoDataSource didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer devicePosition:(SCManagedCaptureDevicePosition)devicePosition { SCTraceStart(); SC_GUARD_ELSE_RETURN(_captureImageFromVideoImmediately); _captureImageFromVideoImmediately = NO; @weakify(self); CFRetain(sampleBuffer); [_performer performImmediatelyIfCurrentPerformer:^{ SCTraceStart(); @strongify(self); SC_GUARD_ELSE_RETURN(self); [self _didCapturePhotoFromVideoBuffer]; UIImageOrientation orientation = devicePosition == SCManagedCaptureDevicePositionBack ? UIImageOrientationRight : UIImageOrientationLeftMirrored; UIImage *videoImage = [UIImage imageWithPixelBufferRef:CMSampleBufferGetImageBuffer(sampleBuffer) backingType:UIImageBackingTypeCGImage orientation:orientation context:[CIContext contextWithOptions:nil]]; UIImage *fullScreenImage = [self imageFromImage:videoImage currentZoomFactor:_zoomFactor targetAspectRatio:_aspectRatio fieldOfView:_fieldOfView state:_state]; NSMutableDictionary *cameraInfo = [cameraInfoForBuffer(sampleBuffer) mutableCopy]; cameraInfo[@"capture_image_from_video_buffer"] = @"enabled"; [self _didFinishProcessingFromVideoBufferWithImage:fullScreenImage cameraInfo:cameraInfo]; CFRelease(sampleBuffer); }]; } - (void)_willBeginCapturePhotoFromVideoBuffer { SCTraceStart(); @weakify(self); [_performer performImmediatelyIfCurrentPerformer:^{ SCTraceStart(); @strongify(self); SC_GUARD_ELSE_RETURN(self); if ([self->_delegate respondsToSelector:@selector(managedStillImageCapturerWillCapturePhoto:)]) { [self->_delegate managedStillImageCapturerWillCapturePhoto:self]; } }]; } - (void)_didCapturePhotoFromVideoBuffer { SCTraceStart(); @weakify(self); [_performer performImmediatelyIfCurrentPerformer:^{ SCTraceStart(); @strongify(self); SC_GUARD_ELSE_RETURN(self); if ([self->_delegate respondsToSelector:@selector(managedStillImageCapturerDidCapturePhoto:)]) { [self->_delegate managedStillImageCapturerDidCapturePhoto:self]; } }]; } - (void)_didFinishProcessingFromVideoBufferWithImage:(UIImage *)image cameraInfo:(NSDictionary *)cameraInfo { SCTraceStart(); @weakify(self); [_performer performImmediatelyIfCurrentPerformer:^{ SCTraceStart(); @strongify(self); SC_GUARD_ELSE_RETURN(self); [[SCLogger sharedInstance] logPreCaptureOperationFinishedAt:CACurrentMediaTime()]; [[SCCoreCameraLogger sharedInstance] logCameraCreationDelaySplitPointPreCaptureOperationFinishedAt:CACurrentMediaTime()]; sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler = _completionHandler; _completionHandler = nil; if (completionHandler) { completionHandler(image, cameraInfo, nil); } }]; } - (void)captureStillImageFromVideoBuffer { SCTraceStart(); @weakify(self); [_performer performImmediatelyIfCurrentPerformer:^{ SCTraceStart(); @strongify(self); SC_GUARD_ELSE_RETURN(self); AudioServicesPlaySystemSoundWithCompletion(kSCCameraShutterSoundID, nil); [self _willBeginCapturePhotoFromVideoBuffer]; self->_captureImageFromVideoImmediately = YES; }]; } @end