You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
16 KiB
399 lines
16 KiB
//
|
|
// 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 <SCFoundation/NSException+Exceptions.h>
|
|
#import <SCFoundation/SCLog.h>
|
|
#import <SCFoundation/SCPerforming.h>
|
|
#import <SCFoundation/SCQueuePerformer.h>
|
|
#import <SCFoundation/SCTrace.h>
|
|
#import <SCFoundation/UIImage+CVPixelBufferRef.h>
|
|
#import <SCLenses/SCLens.h>
|
|
#import <SCLogger/SCCameraMetrics.h>
|
|
#import <SCWebP/UIImage+WebP.h>
|
|
|
|
#import <ImageIO/ImageIO.h>
|
|
|
|
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<SCPerforming>)performer
|
|
lensProcessingCore:(id<SCManagedCapturerLensAPI>)lensAPI
|
|
delegate:(id<SCManagedStillImageCapturerDelegate>)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<SCCapturer>)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<SCManagedVideoDataSource>)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
|