2014 snapchat source code
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.

460 lines
21 KiB

  1. //
  2. // SCManagedLegacyStillImageCapturer.m
  3. // Snapchat
  4. //
  5. // Created by Chao Pang on 10/4/16.
  6. // Copyright © 2016 Snapchat, Inc. All rights reserved.
  7. //
  8. #import "SCManagedLegacyStillImageCapturer.h"
  9. #import "AVCaptureConnection+InputDevice.h"
  10. #import "SCCameraTweaks.h"
  11. #import "SCLogger+Camera.h"
  12. #import "SCManagedCapturer.h"
  13. #import "SCManagedStillImageCapturer_Protected.h"
  14. #import "SCStillImageCaptureVideoInputMethod.h"
  15. #import <SCCrashLogger/SCCrashLogger.h>
  16. #import <SCFoundation/SCAssertWrapper.h>
  17. #import <SCFoundation/SCLog.h>
  18. #import <SCFoundation/SCPerforming.h>
  19. #import <SCFoundation/SCQueuePerformer.h>
  20. #import <SCFoundation/SCTrace.h>
  21. #import <SCLenses/SCLens.h>
  22. #import <SCLogger/SCCameraMetrics.h>
  23. #import <SCWebP/UIImage+WebP.h>
  24. @import ImageIO;
  25. static NSString *const kSCLegacyStillImageCaptureDefaultMethodErrorDomain =
  26. @"kSCLegacyStillImageCaptureDefaultMethodErrorDomain";
  27. static NSString *const kSCLegacyStillImageCaptureLensStabilizationMethodErrorDomain =
  28. @"kSCLegacyStillImageCaptureLensStabilizationMethodErrorDomain";
  29. static NSInteger const kSCLegacyStillImageCaptureDefaultMethodErrorEncounteredException = 10000;
  30. static NSInteger const kSCLegacyStillImageCaptureLensStabilizationMethodErrorEncounteredException = 10001;
  31. @implementation SCManagedLegacyStillImageCapturer {
  32. #pragma clang diagnostic push
  33. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  34. AVCaptureStillImageOutput *_stillImageOutput;
  35. #pragma clang diagnostic pop
  36. BOOL _shouldCapture;
  37. NSUInteger _retries;
  38. SCStillImageCaptureVideoInputMethod *_videoFileMethod;
  39. }
  40. - (instancetype)initWithSession:(AVCaptureSession *)session
  41. performer:(id<SCPerforming>)performer
  42. lensProcessingCore:(id<SCManagedCapturerLensAPI>)lensProcessingCore
  43. delegate:(id<SCManagedStillImageCapturerDelegate>)delegate
  44. {
  45. SCTraceStart();
  46. self = [super initWithSession:session performer:performer lensProcessingCore:lensProcessingCore delegate:delegate];
  47. if (self) {
  48. [self setupWithSession:session];
  49. }
  50. return self;
  51. }
  52. - (void)setupWithSession:(AVCaptureSession *)session
  53. {
  54. SCTraceStart();
  55. #pragma clang diagnostic push
  56. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  57. _stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
  58. #pragma clang diagnostic pop
  59. _stillImageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
  60. [self setAsOutput:session];
  61. }
  62. - (void)setAsOutput:(AVCaptureSession *)session
  63. {
  64. SCTraceStart();
  65. if ([session canAddOutput:_stillImageOutput]) {
  66. [session addOutput:_stillImageOutput];
  67. }
  68. }
  69. - (void)setHighResolutionStillImageOutputEnabled:(BOOL)highResolutionStillImageOutputEnabled
  70. {
  71. SCTraceStart();
  72. if (_stillImageOutput.isHighResolutionStillImageOutputEnabled != highResolutionStillImageOutputEnabled) {
  73. _stillImageOutput.highResolutionStillImageOutputEnabled = highResolutionStillImageOutputEnabled;
  74. }
  75. }
  76. - (void)setPortraitModeCaptureEnabled:(BOOL)enabled
  77. {
  78. // Legacy capturer only used on devices running versions under 10.2, which don't support depth data
  79. // so this function is never called and does not need to be implemented
  80. }
  81. - (void)enableStillImageStabilization
  82. {
  83. SCTraceStart();
  84. #pragma clang diagnostic push
  85. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  86. if (_stillImageOutput.isLensStabilizationDuringBracketedCaptureSupported) {
  87. _stillImageOutput.lensStabilizationDuringBracketedCaptureEnabled = YES;
  88. }
  89. #pragma clang diagnostic pop
  90. }
  91. - (void)removeAsOutput:(AVCaptureSession *)session
  92. {
  93. SCTraceStart();
  94. [session removeOutput:_stillImageOutput];
  95. }
  96. - (void)captureStillImageWithAspectRatio:(CGFloat)aspectRatio
  97. atZoomFactor:(float)zoomFactor
  98. fieldOfView:(float)fieldOfView
  99. state:(SCManagedCapturerState *)state
  100. captureSessionID:(NSString *)captureSessionID
  101. shouldCaptureFromVideo:(BOOL)shouldCaptureFromVideo
  102. completionHandler:
  103. (sc_managed_still_image_capturer_capture_still_image_completion_handler_t)completionHandler
  104. {
  105. SCTraceStart();
  106. SCAssert(completionHandler, @"completionHandler shouldn't be nil");
  107. _retries = 6; // AVFoundation Unknown Error usually resolves itself within 0.5 seconds
  108. _aspectRatio = aspectRatio;
  109. _zoomFactor = zoomFactor;
  110. _fieldOfView = fieldOfView;
  111. _state = state;
  112. _captureSessionID = captureSessionID;
  113. _shouldCaptureFromVideo = shouldCaptureFromVideo;
  114. SCAssert(!_completionHandler, @"We shouldn't have a _completionHandler at this point otherwise we are destroying "
  115. @"current completion handler.");
  116. _completionHandler = [completionHandler copy];
  117. [[SCLogger sharedInstance] logCameraExposureAdjustmentDelayStart];
  118. if (!_adjustingExposureManualDetect) {
  119. SCLogCoreCameraInfo(@"Capturing still image now");
  120. [self _captureStillImageWithExposureAdjustmentStrategy:kSCCameraExposureAdjustmentStrategyNo];
  121. _shouldCapture = NO;
  122. } else {
  123. SCLogCoreCameraInfo(@"Wait adjusting exposure (or after 0.4 seconds) and then capture still image");
  124. _shouldCapture = YES;
  125. [self _deadlineCaptureStillImage];
  126. }
  127. }
  128. #pragma mark - SCManagedDeviceCapacityAnalyzerListener
  129. - (void)managedDeviceCapacityAnalyzer:(SCManagedDeviceCapacityAnalyzer *)managedDeviceCapacityAnalyzer
  130. didChangeAdjustingExposure:(BOOL)adjustingExposure
  131. {
  132. SCTraceStart();
  133. @weakify(self);
  134. [_performer performImmediatelyIfCurrentPerformer:^{
  135. // Since this is handled on a different thread, therefore, dispatch back to the queue we operated on.
  136. @strongify(self);
  137. SC_GUARD_ELSE_RETURN(self);
  138. self->_adjustingExposureManualDetect = adjustingExposure;
  139. [self _didChangeAdjustingExposure:adjustingExposure
  140. withStrategy:kSCCameraExposureAdjustmentStrategyManualDetect];
  141. }];
  142. }
  143. - (void)managedDeviceCapacityAnalyzer:(SCManagedDeviceCapacityAnalyzer *)managedDeviceCapacityAnalyzer
  144. didChangeLightingCondition:(SCCapturerLightingConditionType)lightingCondition
  145. {
  146. SCTraceStart();
  147. @weakify(self);
  148. [_performer performImmediatelyIfCurrentPerformer:^{
  149. @strongify(self);
  150. SC_GUARD_ELSE_RETURN(self);
  151. self->_lightingConditionType = lightingCondition;
  152. }];
  153. }
  154. #pragma mark - SCManagedCapturerListener
  155. - (void)managedCapturer:(id<SCCapturer>)managedCapturer didChangeAdjustingExposure:(SCManagedCapturerState *)state
  156. {
  157. SCTraceStart();
  158. @weakify(self);
  159. [_performer performImmediatelyIfCurrentPerformer:^{
  160. @strongify(self);
  161. SC_GUARD_ELSE_RETURN(self);
  162. // Since this is handled on a different thread, therefore, dispatch back to the queue we operated on.
  163. [self _didChangeAdjustingExposure:state.adjustingExposure withStrategy:kSCCameraExposureAdjustmentStrategyKVO];
  164. }];
  165. }
  166. #pragma mark - Private methods
  167. - (void)_didChangeAdjustingExposure:(BOOL)adjustingExposure withStrategy:(NSString *)strategy
  168. {
  169. if (!adjustingExposure && self->_shouldCapture) {
  170. SCLogCoreCameraInfo(@"Capturing after adjusting exposure using strategy: %@", strategy);
  171. [self _captureStillImageWithExposureAdjustmentStrategy:strategy];
  172. self->_shouldCapture = NO;
  173. }
  174. }
  175. - (void)_deadlineCaptureStillImage
  176. {
  177. SCTraceStart();
  178. // Use the SCManagedCapturer's private queue.
  179. [_performer perform:^{
  180. if (_shouldCapture) {
  181. [self _captureStillImageWithExposureAdjustmentStrategy:kSCCameraExposureAdjustmentStrategyDeadline];
  182. _shouldCapture = NO;
  183. }
  184. }
  185. after:SCCameraTweaksExposureDeadline()];
  186. }
  187. - (void)_captureStillImageWithExposureAdjustmentStrategy:(NSString *)strategy
  188. {
  189. SCTraceStart();
  190. [[SCLogger sharedInstance] logCameraExposureAdjustmentDelayEndWithStrategy:strategy];
  191. if (_shouldCaptureFromVideo) {
  192. [self captureStillImageFromVideoBuffer];
  193. return;
  194. }
  195. SCAssert(_stillImageOutput, @"stillImageOutput shouldn't be nil");
  196. #pragma clang diagnostic push
  197. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  198. AVCaptureStillImageOutput *stillImageOutput = _stillImageOutput;
  199. #pragma clang diagnostic pop
  200. AVCaptureConnection *captureConnection = [self _captureConnectionFromStillImageOutput:stillImageOutput];
  201. SCManagedCapturerState *state = [_state copy];
  202. dispatch_block_t legacyStillImageCaptureBlock = ^{
  203. SCCAssertMainThread();
  204. // If the application is not in background, and we have still image connection, do thecapture. Otherwise fail.
  205. if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
  206. [_performer performImmediatelyIfCurrentPerformer:^{
  207. sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler =
  208. _completionHandler;
  209. _completionHandler = nil;
  210. completionHandler(nil, nil,
  211. [NSError errorWithDomain:kSCManagedStillImageCapturerErrorDomain
  212. code:kSCManagedStillImageCapturerApplicationStateBackground
  213. userInfo:nil]);
  214. }];
  215. return;
  216. }
  217. #if !TARGET_IPHONE_SIMULATOR
  218. if (!captureConnection) {
  219. [_performer performImmediatelyIfCurrentPerformer:^{
  220. sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler =
  221. _completionHandler;
  222. _completionHandler = nil;
  223. completionHandler(nil, nil, [NSError errorWithDomain:kSCManagedStillImageCapturerErrorDomain
  224. code:kSCManagedStillImageCapturerNoStillImageConnection
  225. userInfo:nil]);
  226. }];
  227. return;
  228. }
  229. #endif
  230. // Select appropriate image capture method
  231. if ([_delegate managedStillImageCapturerShouldProcessFileInput:self]) {
  232. if (!_videoFileMethod) {
  233. _videoFileMethod = [[SCStillImageCaptureVideoInputMethod alloc] init];
  234. }
  235. [[SCLogger sharedInstance] logStillImageCaptureApi:@"SCStillImageCapture"];
  236. [[SCCoreCameraLogger sharedInstance]
  237. logCameraCreationDelaySplitPointStillImageCaptureApi:@"SCStillImageCapture"];
  238. [_videoFileMethod captureStillImageWithCapturerState:state
  239. successBlock:^(NSData *imageData, NSDictionary *cameraInfo, NSError *error) {
  240. [self _legacyStillImageCaptureDidSucceedWithImageData:imageData
  241. sampleBuffer:nil
  242. cameraInfo:cameraInfo
  243. error:error];
  244. }
  245. failureBlock:^(NSError *error) {
  246. [self _legacyStillImageCaptureDidFailWithError:error];
  247. }];
  248. } else {
  249. #pragma clang diagnostic push
  250. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  251. if (stillImageOutput.isLensStabilizationDuringBracketedCaptureSupported && !state.flashActive) {
  252. [self _captureStabilizedStillImageWithStillImageOutput:stillImageOutput
  253. captureConnection:captureConnection
  254. capturerState:state];
  255. } else {
  256. [self _captureStillImageWithStillImageOutput:stillImageOutput
  257. captureConnection:captureConnection
  258. capturerState:state];
  259. }
  260. #pragma clang diagnostic pop
  261. }
  262. };
  263. // We need to call this on main thread and blocking.
  264. [[SCQueuePerformer mainQueuePerformer] performAndWait:legacyStillImageCaptureBlock];
  265. }
  266. #pragma clang diagnostic push
  267. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  268. - (void)_captureStillImageWithStillImageOutput:(AVCaptureStillImageOutput *)stillImageOutput
  269. captureConnection:(AVCaptureConnection *)captureConnection
  270. capturerState:(SCManagedCapturerState *)state
  271. {
  272. [[SCLogger sharedInstance] logStillImageCaptureApi:@"AVStillImageCaptureAsynchronous"];
  273. [[SCCoreCameraLogger sharedInstance]
  274. logCameraCreationDelaySplitPointStillImageCaptureApi:@"AVStillImageCaptureAsynchronous"];
  275. @try {
  276. [stillImageOutput
  277. captureStillImageAsynchronouslyFromConnection:captureConnection
  278. completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
  279. if (imageDataSampleBuffer) {
  280. NSData *imageData = [AVCaptureStillImageOutput
  281. jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
  282. [self
  283. _legacyStillImageCaptureDidSucceedWithImageData:imageData
  284. sampleBuffer:
  285. imageDataSampleBuffer
  286. cameraInfo:
  287. cameraInfoForBuffer(
  288. imageDataSampleBuffer)
  289. error:error];
  290. } else {
  291. if (error.domain == AVFoundationErrorDomain && error.code == -11800) {
  292. // iOS 7 "unknown error"; works if we retry
  293. [self _legacyStillImageCaptureWillRetryWithError:error];
  294. } else {
  295. [self _legacyStillImageCaptureDidFailWithError:error];
  296. }
  297. }
  298. }];
  299. } @catch (NSException *e) {
  300. [SCCrashLogger logHandledException:e];
  301. [self _legacyStillImageCaptureDidFailWithError:
  302. [NSError errorWithDomain:kSCLegacyStillImageCaptureDefaultMethodErrorDomain
  303. code:kSCLegacyStillImageCaptureDefaultMethodErrorEncounteredException
  304. userInfo:@{
  305. @"exception" : e
  306. }]];
  307. }
  308. }
  309. - (void)_captureStabilizedStillImageWithStillImageOutput:(AVCaptureStillImageOutput *)stillImageOutput
  310. captureConnection:(AVCaptureConnection *)captureConnection
  311. capturerState:(SCManagedCapturerState *)state
  312. {
  313. [[SCLogger sharedInstance] logStillImageCaptureApi:@"AVStillImageOutputCaptureBracketAsynchronously"];
  314. [[SCCoreCameraLogger sharedInstance]
  315. logCameraCreationDelaySplitPointStillImageCaptureApi:@"AVStillImageOutputCaptureBracketAsynchronously"];
  316. NSArray *bracketArray = [self _bracketSettingsArray:captureConnection];
  317. @try {
  318. [stillImageOutput
  319. captureStillImageBracketAsynchronouslyFromConnection:captureConnection
  320. withSettingsArray:bracketArray
  321. completionHandler:^(CMSampleBufferRef imageDataSampleBuffer,
  322. AVCaptureBracketedStillImageSettings *settings,
  323. NSError *err) {
  324. if (!imageDataSampleBuffer) {
  325. [self _legacyStillImageCaptureDidFailWithError:err];
  326. return;
  327. }
  328. NSData *jpegData = [AVCaptureStillImageOutput
  329. jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
  330. [self
  331. _legacyStillImageCaptureDidSucceedWithImageData:jpegData
  332. sampleBuffer:
  333. imageDataSampleBuffer
  334. cameraInfo:
  335. cameraInfoForBuffer(
  336. imageDataSampleBuffer)
  337. error:nil];
  338. }];
  339. } @catch (NSException *e) {
  340. [SCCrashLogger logHandledException:e];
  341. [self _legacyStillImageCaptureDidFailWithError:
  342. [NSError errorWithDomain:kSCLegacyStillImageCaptureLensStabilizationMethodErrorDomain
  343. code:kSCLegacyStillImageCaptureLensStabilizationMethodErrorEncounteredException
  344. userInfo:@{
  345. @"exception" : e
  346. }]];
  347. }
  348. }
  349. #pragma clang diagnostic pop
  350. - (NSArray *)_bracketSettingsArray:(AVCaptureConnection *)stillImageConnection
  351. {
  352. NSInteger const stillCount = 1;
  353. NSMutableArray *bracketSettingsArray = [NSMutableArray arrayWithCapacity:stillCount];
  354. AVCaptureDevice *device = [stillImageConnection inputDevice];
  355. AVCaptureManualExposureBracketedStillImageSettings *settings = [AVCaptureManualExposureBracketedStillImageSettings
  356. manualExposureSettingsWithExposureDuration:device.exposureDuration
  357. ISO:AVCaptureISOCurrent];
  358. for (NSInteger i = 0; i < stillCount; i++) {
  359. [bracketSettingsArray addObject:settings];
  360. }
  361. return [bracketSettingsArray copy];
  362. }
  363. - (void)_legacyStillImageCaptureDidSucceedWithImageData:(NSData *)imageData
  364. sampleBuffer:(CMSampleBufferRef)sampleBuffer
  365. cameraInfo:(NSDictionary *)cameraInfo
  366. error:(NSError *)error
  367. {
  368. [[SCLogger sharedInstance] logPreCaptureOperationFinishedAt:CACurrentMediaTime()];
  369. [[SCCoreCameraLogger sharedInstance]
  370. logCameraCreationDelaySplitPointPreCaptureOperationFinishedAt:CACurrentMediaTime()];
  371. if (sampleBuffer) {
  372. CFRetain(sampleBuffer);
  373. }
  374. [_performer performImmediatelyIfCurrentPerformer:^{
  375. UIImage *fullScreenImage = [self imageFromData:imageData
  376. currentZoomFactor:_zoomFactor
  377. targetAspectRatio:_aspectRatio
  378. fieldOfView:_fieldOfView
  379. state:_state
  380. sampleBuffer:sampleBuffer];
  381. sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler = _completionHandler;
  382. _completionHandler = nil;
  383. completionHandler(fullScreenImage, cameraInfo, error);
  384. if (sampleBuffer) {
  385. CFRelease(sampleBuffer);
  386. }
  387. }];
  388. }
  389. - (void)_legacyStillImageCaptureDidFailWithError:(NSError *)error
  390. {
  391. [_performer performImmediatelyIfCurrentPerformer:^{
  392. sc_managed_still_image_capturer_capture_still_image_completion_handler_t completionHandler = _completionHandler;
  393. _completionHandler = nil;
  394. completionHandler(nil, nil, error);
  395. }];
  396. }
  397. - (void)_legacyStillImageCaptureWillRetryWithError:(NSError *)error
  398. {
  399. if (_retries-- > 0) {
  400. [_performer perform:^{
  401. [self _captureStillImageWithExposureAdjustmentStrategy:kSCCameraExposureAdjustmentStrategyNo];
  402. }
  403. after:kSCCameraRetryInterval];
  404. } else {
  405. [self _legacyStillImageCaptureDidFailWithError:error];
  406. }
  407. }
  408. #pragma clang diagnostic push
  409. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  410. - (AVCaptureConnection *)_captureConnectionFromStillImageOutput:(AVCaptureStillImageOutput *)stillImageOutput
  411. #pragma clang diagnostic pop
  412. {
  413. SCTraceStart();
  414. SCAssert([_performer isCurrentPerformer], @"");
  415. NSArray *connections = [stillImageOutput.connections copy];
  416. for (AVCaptureConnection *connection in connections) {
  417. for (AVCaptureInputPort *port in [connection inputPorts]) {
  418. if ([[port mediaType] isEqual:AVMediaTypeVideo]) {
  419. return connection;
  420. }
  421. }
  422. }
  423. return nil;
  424. }
  425. @end