Source-SCCamera/ManagedCapturer/SCManagedLegacyStillImageCa...

461 lines
21 KiB
Objective-C

//
// SCManagedLegacyStillImageCapturer.m
// Snapchat
//
// Created by Chao Pang on 10/4/16.
// Copyright © 2016 Snapchat, Inc. All rights reserved.
//
#import "SCManagedLegacyStillImageCapturer.h"
#import "AVCaptureConnection+InputDevice.h"
#import "SCCameraTweaks.h"
#import "SCLogger+Camera.h"
#import "SCManagedCapturer.h"
#import "SCManagedStillImageCapturer_Protected.h"
#import "SCStillImageCaptureVideoInputMethod.h"
#import <SCCrashLogger/SCCrashLogger.h>
#import <SCFoundation/SCAssertWrapper.h>
#import <SCFoundation/SCLog.h>
#import <SCFoundation/SCPerforming.h>
#import <SCFoundation/SCQueuePerformer.h>
#import <SCFoundation/SCTrace.h>
#import <SCLenses/SCLens.h>
#import <SCLogger/SCCameraMetrics.h>
#import <SCWebP/UIImage+WebP.h>
@import ImageIO;
static NSString *const kSCLegacyStillImageCaptureDefaultMethodErrorDomain =
@"kSCLegacyStillImageCaptureDefaultMethodErrorDomain";
static NSString *const kSCLegacyStillImageCaptureLensStabilizationMethodErrorDomain =
@"kSCLegacyStillImageCaptureLensStabilizationMethodErrorDomain";
static NSInteger const kSCLegacyStillImageCaptureDefaultMethodErrorEncounteredException = 10000;
static NSInteger const kSCLegacyStillImageCaptureLensStabilizationMethodErrorEncounteredException = 10001;
@implementation SCManagedLegacyStillImageCapturer {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AVCaptureStillImageOutput *_stillImageOutput;
#pragma clang diagnostic pop
BOOL _shouldCapture;
NSUInteger _retries;
SCStillImageCaptureVideoInputMethod *_videoFileMethod;
}
- (instancetype)initWithSession:(AVCaptureSession *)session
performer:(id<SCPerforming>)performer
lensProcessingCore:(id<SCManagedCapturerLensAPI>)lensProcessingCore
delegate:(id<SCManagedStillImageCapturerDelegate>)delegate
{
SCTraceStart();
self = [super initWithSession:session performer:performer lensProcessingCore:lensProcessingCore delegate:delegate];
if (self) {
[self setupWithSession:session];
}
return self;
}
- (void)setupWithSession:(AVCaptureSession *)session
{
SCTraceStart();
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
#pragma clang diagnostic pop
_stillImageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
[self setAsOutput:session];
}
- (void)setAsOutput:(AVCaptureSession *)session
{
SCTraceStart();
if ([session canAddOutput:_stillImageOutput]) {
[session addOutput:_stillImageOutput];
}
}
- (void)setHighResolutionStillImageOutputEnabled:(BOOL)highResolutionStillImageOutputEnabled
{
SCTraceStart();
if (_stillImageOutput.isHighResolutionStillImageOutputEnabled != highResolutionStillImageOutputEnabled) {
_stillImageOutput.highResolutionStillImageOutputEnabled = highResolutionStillImageOutputEnabled;
}
}
- (void)setPortraitModeCaptureEnabled:(BOOL)enabled
{
// Legacy capturer only used on devices running versions under 10.2, which don't support depth data
// so this function is never called and does not need to be implemented
}
- (void)enableStillImageStabilization
{
SCTraceStart();
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (_stillImageOutput.isLensStabilizationDuringBracketedCaptureSupported) {
_stillImageOutput.lensStabilizationDuringBracketedCaptureEnabled = YES;
}
#pragma clang diagnostic pop
}
- (void)removeAsOutput:(AVCaptureSession *)session
{
SCTraceStart();
[session removeOutput:_stillImageOutput];
}
- (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
{
SCTraceStart();
SCAssert(completionHandler, @"completionHandler shouldn't be nil");
_retries = 6; // AVFoundation Unknown Error usually resolves itself within 0.5 seconds
_aspectRatio = aspectRatio;
_zoomFactor = zoomFactor;
_fieldOfView = fieldOfView;
_state = state;
_captureSessionID = captureSessionID;
_shouldCaptureFromVideo = shouldCaptureFromVideo;
SCAssert(!_completionHandler, @"We shouldn't have a _completionHandler at this point otherwise we are destroying "
@"current completion handler.");
_completionHandler = [completionHandler copy];
[[SCLogger sharedInstance] logCameraExposureAdjustmentDelayStart];
if (!_adjustingExposureManualDetect) {
SCLogCoreCameraInfo(@"Capturing still image now");
[self _captureStillImageWithExposureAdjustmentStrategy:kSCCameraExposureAdjustmentStrategyNo];
_shouldCapture = NO;
} else {
SCLogCoreCameraInfo(@"Wait adjusting exposure (or after 0.4 seconds) and then capture still image");
_shouldCapture = YES;
[self _deadlineCaptureStillImage];
}
}
#pragma mark - SCManagedDeviceCapacityAnalyzerListener
- (void)managedDeviceCapacityAnalyzer:(SCManagedDeviceCapacityAnalyzer *)managedDeviceCapacityAnalyzer
didChangeAdjustingExposure:(BOOL)adjustingExposure
{
SCTraceStart();
@weakify(self);
[_performer performImmediatelyIfCurrentPerformer:^{
// Since this is handled on a different thread, therefore, dispatch back to the queue we operated on.
@strongify(self);
SC_GUARD_ELSE_RETURN(self);
self->_adjustingExposureManualDetect = adjustingExposure;
[self _didChangeAdjustingExposure:adjustingExposure
withStrategy:kSCCameraExposureAdjustmentStrategyManualDetect];
}];
}
- (void)managedDeviceCapacityAnalyzer:(SCManagedDeviceCapacityAnalyzer *)managedDeviceCapacityAnalyzer
didChangeLightingCondition:(SCCapturerLightingConditionType)lightingCondition
{
SCTraceStart();
@weakify(self);
[_performer performImmediatelyIfCurrentPerformer:^{
@strongify(self);
SC_GUARD_ELSE_RETURN(self);
self->_lightingConditionType = lightingCondition;
}];
}
#pragma mark - SCManagedCapturerListener
- (void)managedCapturer:(id<SCCapturer>)managedCapturer didChangeAdjustingExposure:(SCManagedCapturerState *)state
{
SCTraceStart();
@weakify(self);
[_performer performImmediatelyIfCurrentPerformer:^{
@strongify(self);
SC_GUARD_ELSE_RETURN(self);
// Since this is handled on a different thread, therefore, dispatch back to the queue we operated on.
[self _didChangeAdjustingExposure:state.adjustingExposure withStrategy:kSCCameraExposureAdjustmentStrategyKVO];
}];
}
#pragma mark - Private methods
- (void)_didChangeAdjustingExposure:(BOOL)adjustingExposure withStrategy:(NSString *)strategy
{
if (!adjustingExposure && self->_shouldCapture) {
SCLogCoreCameraInfo(@"Capturing after adjusting exposure using strategy: %@", strategy);
[self _captureStillImageWithExposureAdjustmentStrategy:strategy];
self->_shouldCapture = NO;
}
}
- (void)_deadlineCaptureStillImage
{
SCTraceStart();
// Use the SCManagedCapturer's private queue.
[_performer perform:^{
if (_shouldCapture) {
[self _captureStillImageWithExposureAdjustmentStrategy:kSCCameraExposureAdjustmentStrategyDeadline];
_shouldCapture = NO;
}
}
after:SCCameraTweaksExposureDeadline()];
}
- (void)_captureStillImageWithExposureAdjustmentStrategy:(NSString *)strategy
{
SCTraceStart();
[[SCLogger sharedInstance] logCameraExposureAdjustmentDelayEndWithStrategy:strategy];
if (_shouldCaptureFromVideo) {
[self captureStillImageFromVideoBuffer];
return;
}
SCAssert(_stillImageOutput, @"stillImageOutput shouldn't be nil");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AVCaptureStillImageOutput *stillImageOutput = _stillImageOutput;
#pragma clang diagnostic pop
AVCaptureConnection *captureConnection = [self _captureConnectionFromStillImageOutput:stillImageOutput];
SCManagedCapturerState *state = [_state copy];
dispatch_block_t legacyStillImageCaptureBlock = ^{
SCCAssertMainThread();
// If the application is not in background, and we have still image connection, do thecapture. Otherwise fail.
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
[_performer performImmediatelyIfCurrentPerformer:^{
sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler =
_completionHandler;
_completionHandler = nil;
completionHandler(nil, nil,
[NSError errorWithDomain:kSCManagedStillImageCapturerErrorDomain
code:kSCManagedStillImageCapturerApplicationStateBackground
userInfo:nil]);
}];
return;
}
#if !TARGET_IPHONE_SIMULATOR
if (!captureConnection) {
[_performer performImmediatelyIfCurrentPerformer:^{
sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler =
_completionHandler;
_completionHandler = nil;
completionHandler(nil, nil, [NSError errorWithDomain:kSCManagedStillImageCapturerErrorDomain
code:kSCManagedStillImageCapturerNoStillImageConnection
userInfo:nil]);
}];
return;
}
#endif
// Select appropriate image capture method
if ([_delegate managedStillImageCapturerShouldProcessFileInput:self]) {
if (!_videoFileMethod) {
_videoFileMethod = [[SCStillImageCaptureVideoInputMethod alloc] init];
}
[[SCLogger sharedInstance] logStillImageCaptureApi:@"SCStillImageCapture"];
[[SCCoreCameraLogger sharedInstance]
logCameraCreationDelaySplitPointStillImageCaptureApi:@"SCStillImageCapture"];
[_videoFileMethod captureStillImageWithCapturerState:state
successBlock:^(NSData *imageData, NSDictionary *cameraInfo, NSError *error) {
[self _legacyStillImageCaptureDidSucceedWithImageData:imageData
sampleBuffer:nil
cameraInfo:cameraInfo
error:error];
}
failureBlock:^(NSError *error) {
[self _legacyStillImageCaptureDidFailWithError:error];
}];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (stillImageOutput.isLensStabilizationDuringBracketedCaptureSupported && !state.flashActive) {
[self _captureStabilizedStillImageWithStillImageOutput:stillImageOutput
captureConnection:captureConnection
capturerState:state];
} else {
[self _captureStillImageWithStillImageOutput:stillImageOutput
captureConnection:captureConnection
capturerState:state];
}
#pragma clang diagnostic pop
}
};
// We need to call this on main thread and blocking.
[[SCQueuePerformer mainQueuePerformer] performAndWait:legacyStillImageCaptureBlock];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (void)_captureStillImageWithStillImageOutput:(AVCaptureStillImageOutput *)stillImageOutput
captureConnection:(AVCaptureConnection *)captureConnection
capturerState:(SCManagedCapturerState *)state
{
[[SCLogger sharedInstance] logStillImageCaptureApi:@"AVStillImageCaptureAsynchronous"];
[[SCCoreCameraLogger sharedInstance]
logCameraCreationDelaySplitPointStillImageCaptureApi:@"AVStillImageCaptureAsynchronous"];
@try {
[stillImageOutput
captureStillImageAsynchronouslyFromConnection:captureConnection
completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer) {
NSData *imageData = [AVCaptureStillImageOutput
jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
[self
_legacyStillImageCaptureDidSucceedWithImageData:imageData
sampleBuffer:
imageDataSampleBuffer
cameraInfo:
cameraInfoForBuffer(
imageDataSampleBuffer)
error:error];
} else {
if (error.domain == AVFoundationErrorDomain && error.code == -11800) {
// iOS 7 "unknown error"; works if we retry
[self _legacyStillImageCaptureWillRetryWithError:error];
} else {
[self _legacyStillImageCaptureDidFailWithError:error];
}
}
}];
} @catch (NSException *e) {
[SCCrashLogger logHandledException:e];
[self _legacyStillImageCaptureDidFailWithError:
[NSError errorWithDomain:kSCLegacyStillImageCaptureDefaultMethodErrorDomain
code:kSCLegacyStillImageCaptureDefaultMethodErrorEncounteredException
userInfo:@{
@"exception" : e
}]];
}
}
- (void)_captureStabilizedStillImageWithStillImageOutput:(AVCaptureStillImageOutput *)stillImageOutput
captureConnection:(AVCaptureConnection *)captureConnection
capturerState:(SCManagedCapturerState *)state
{
[[SCLogger sharedInstance] logStillImageCaptureApi:@"AVStillImageOutputCaptureBracketAsynchronously"];
[[SCCoreCameraLogger sharedInstance]
logCameraCreationDelaySplitPointStillImageCaptureApi:@"AVStillImageOutputCaptureBracketAsynchronously"];
NSArray *bracketArray = [self _bracketSettingsArray:captureConnection];
@try {
[stillImageOutput
captureStillImageBracketAsynchronouslyFromConnection:captureConnection
withSettingsArray:bracketArray
completionHandler:^(CMSampleBufferRef imageDataSampleBuffer,
AVCaptureBracketedStillImageSettings *settings,
NSError *err) {
if (!imageDataSampleBuffer) {
[self _legacyStillImageCaptureDidFailWithError:err];
return;
}
NSData *jpegData = [AVCaptureStillImageOutput
jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
[self
_legacyStillImageCaptureDidSucceedWithImageData:jpegData
sampleBuffer:
imageDataSampleBuffer
cameraInfo:
cameraInfoForBuffer(
imageDataSampleBuffer)
error:nil];
}];
} @catch (NSException *e) {
[SCCrashLogger logHandledException:e];
[self _legacyStillImageCaptureDidFailWithError:
[NSError errorWithDomain:kSCLegacyStillImageCaptureLensStabilizationMethodErrorDomain
code:kSCLegacyStillImageCaptureLensStabilizationMethodErrorEncounteredException
userInfo:@{
@"exception" : e
}]];
}
}
#pragma clang diagnostic pop
- (NSArray *)_bracketSettingsArray:(AVCaptureConnection *)stillImageConnection
{
NSInteger const stillCount = 1;
NSMutableArray *bracketSettingsArray = [NSMutableArray arrayWithCapacity:stillCount];
AVCaptureDevice *device = [stillImageConnection inputDevice];
AVCaptureManualExposureBracketedStillImageSettings *settings = [AVCaptureManualExposureBracketedStillImageSettings
manualExposureSettingsWithExposureDuration:device.exposureDuration
ISO:AVCaptureISOCurrent];
for (NSInteger i = 0; i < stillCount; i++) {
[bracketSettingsArray addObject:settings];
}
return [bracketSettingsArray copy];
}
- (void)_legacyStillImageCaptureDidSucceedWithImageData:(NSData *)imageData
sampleBuffer:(CMSampleBufferRef)sampleBuffer
cameraInfo:(NSDictionary *)cameraInfo
error:(NSError *)error
{
[[SCLogger sharedInstance] logPreCaptureOperationFinishedAt:CACurrentMediaTime()];
[[SCCoreCameraLogger sharedInstance]
logCameraCreationDelaySplitPointPreCaptureOperationFinishedAt:CACurrentMediaTime()];
if (sampleBuffer) {
CFRetain(sampleBuffer);
}
[_performer performImmediatelyIfCurrentPerformer:^{
UIImage *fullScreenImage = [self imageFromData:imageData
currentZoomFactor:_zoomFactor
targetAspectRatio:_aspectRatio
fieldOfView:_fieldOfView
state:_state
sampleBuffer:sampleBuffer];
sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler = _completionHandler;
_completionHandler = nil;
completionHandler(fullScreenImage, cameraInfo, error);
if (sampleBuffer) {
CFRelease(sampleBuffer);
}
}];
}
- (void)_legacyStillImageCaptureDidFailWithError:(NSError *)error
{
[_performer performImmediatelyIfCurrentPerformer:^{
sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler = _completionHandler;
_completionHandler = nil;
completionHandler(nil, nil, error);
}];
}
- (void)_legacyStillImageCaptureWillRetryWithError:(NSError *)error
{
if (_retries-- > 0) {
[_performer perform:^{
[self _captureStillImageWithExposureAdjustmentStrategy:kSCCameraExposureAdjustmentStrategyNo];
}
after:kSCCameraRetryInterval];
} else {
[self _legacyStillImageCaptureDidFailWithError:error];
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (AVCaptureConnection *)_captureConnectionFromStillImageOutput:(AVCaptureStillImageOutput *)stillImageOutput
#pragma clang diagnostic pop
{
SCTraceStart();
SCAssert([_performer isCurrentPerformer], @"");
NSArray *connections = [stillImageOutput.connections copy];
for (AVCaptureConnection *connection in connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([[port mediaType] isEqual:AVMediaTypeVideo]) {
return connection;
}
}
}
return nil;
}
@end