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.

821 lines
28 KiB

  1. //
  2. // SCManagedCaptureDevice.m
  3. // Snapchat
  4. //
  5. // Created by Liu Liu on 4/22/15.
  6. // Copyright (c) 2015 Liu Liu. All rights reserved.
  7. //
  8. #import "SCManagedCaptureDevice.h"
  9. #import "AVCaptureDevice+ConfigurationLock.h"
  10. #import "SCCameraTweaks.h"
  11. #import "SCCaptureCommon.h"
  12. #import "SCCaptureDeviceResolver.h"
  13. #import "SCManagedCaptureDevice+SCManagedCapturer.h"
  14. #import "SCManagedCaptureDeviceAutoExposureHandler.h"
  15. #import "SCManagedCaptureDeviceAutoFocusHandler.h"
  16. #import "SCManagedCaptureDeviceExposureHandler.h"
  17. #import "SCManagedCaptureDeviceFaceDetectionAutoExposureHandler.h"
  18. #import "SCManagedCaptureDeviceFaceDetectionAutoFocusHandler.h"
  19. #import "SCManagedCaptureDeviceFocusHandler.h"
  20. #import "SCManagedCapturer.h"
  21. #import "SCManagedDeviceCapacityAnalyzer.h"
  22. #import <SCFoundation/SCDeviceName.h>
  23. #import <SCFoundation/SCLog.h>
  24. #import <SCFoundation/SCTrace.h>
  25. #import <FBKVOController/FBKVOController.h>
  26. static int32_t const kSCManagedCaptureDeviceMaximumHighFrameRate = 30;
  27. static int32_t const kSCManagedCaptureDeviceMaximumLowFrameRate = 24;
  28. static float const kSCManagedCaptureDevicecSoftwareMaxZoomFactor = 8;
  29. CGFloat const kSCMaxVideoZoomFactor = 100; // the max videoZoomFactor acceptable
  30. CGFloat const kSCMinVideoZoomFactor = 1;
  31. static NSDictionary *SCBestHRSIFormatsForHeights(NSArray *desiredHeights, NSArray *formats, BOOL shouldSupportDepth)
  32. {
  33. NSMutableDictionary *bestHRSIHeights = [NSMutableDictionary dictionary];
  34. for (NSNumber *height in desiredHeights) {
  35. bestHRSIHeights[height] = @0;
  36. }
  37. NSMutableDictionary *bestHRSIFormats = [NSMutableDictionary dictionary];
  38. for (AVCaptureDeviceFormat *format in formats) {
  39. if (@available(ios 11.0, *)) {
  40. if (shouldSupportDepth && format.supportedDepthDataFormats.count == 0) {
  41. continue;
  42. }
  43. }
  44. if (CMFormatDescriptionGetMediaSubType(format.formatDescription) !=
  45. kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
  46. continue;
  47. }
  48. CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
  49. NSNumber *height = @(dimensions.height);
  50. NSNumber *bestHRSI = bestHRSIHeights[height];
  51. if (bestHRSI) {
  52. CMVideoDimensions hrsi = format.highResolutionStillImageDimensions;
  53. // If we enabled HSRI, we only intersted in the ones that is good.
  54. if (hrsi.height > [bestHRSI intValue]) {
  55. bestHRSIHeights[height] = @(hrsi.height);
  56. bestHRSIFormats[height] = format;
  57. }
  58. }
  59. }
  60. return [bestHRSIFormats copy];
  61. }
  62. static inline float SCDegreesToRadians(float theta)
  63. {
  64. return theta * (float)M_PI / 180.f;
  65. }
  66. static inline float SCRadiansToDegrees(float theta)
  67. {
  68. return theta * 180.f / (float)M_PI;
  69. }
  70. @implementation SCManagedCaptureDevice {
  71. AVCaptureDevice *_device;
  72. AVCaptureDeviceInput *_deviceInput;
  73. AVCaptureDeviceFormat *_defaultFormat;
  74. AVCaptureDeviceFormat *_nightFormat;
  75. AVCaptureDeviceFormat *_liveVideoStreamingFormat;
  76. SCManagedCaptureDevicePosition _devicePosition;
  77. // Configurations on the device, shortcut to avoid re-configurations
  78. id<SCManagedCaptureDeviceExposureHandler> _exposureHandler;
  79. id<SCManagedCaptureDeviceFocusHandler> _focusHandler;
  80. FBKVOController *_observeController;
  81. // For the private category methods
  82. NSError *_error;
  83. BOOL _softwareZoom;
  84. BOOL _isConnected;
  85. BOOL _flashActive;
  86. BOOL _torchActive;
  87. BOOL _liveVideoStreamingActive;
  88. float _zoomFactor;
  89. BOOL _isNightModeActive;
  90. BOOL _captureDepthData;
  91. }
  92. @synthesize fieldOfView = _fieldOfView;
  93. + (instancetype)front
  94. {
  95. SCTraceStart();
  96. static dispatch_once_t onceToken;
  97. static SCManagedCaptureDevice *front;
  98. static dispatch_semaphore_t semaphore;
  99. dispatch_once(&onceToken, ^{
  100. semaphore = dispatch_semaphore_create(1);
  101. });
  102. /* You can use the tweak below to intentionally kill camera in debug.
  103. if (SCIsDebugBuild() && SCCameraTweaksKillFrontCamera()) {
  104. return nil;
  105. }
  106. */
  107. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  108. if (!front) {
  109. AVCaptureDevice *device =
  110. [[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionFront];
  111. if (device) {
  112. front = [[SCManagedCaptureDevice alloc] initWithDevice:device
  113. devicePosition:SCManagedCaptureDevicePositionFront];
  114. }
  115. }
  116. dispatch_semaphore_signal(semaphore);
  117. return front;
  118. }
  119. + (instancetype)back
  120. {
  121. SCTraceStart();
  122. static dispatch_once_t onceToken;
  123. static SCManagedCaptureDevice *back;
  124. static dispatch_semaphore_t semaphore;
  125. dispatch_once(&onceToken, ^{
  126. semaphore = dispatch_semaphore_create(1);
  127. });
  128. /* You can use the tweak below to intentionally kill camera in debug.
  129. if (SCIsDebugBuild() && SCCameraTweaksKillBackCamera()) {
  130. return nil;
  131. }
  132. */
  133. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  134. if (!back) {
  135. AVCaptureDevice *device =
  136. [[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionBack];
  137. if (device) {
  138. back = [[SCManagedCaptureDevice alloc] initWithDevice:device
  139. devicePosition:SCManagedCaptureDevicePositionBack];
  140. }
  141. }
  142. dispatch_semaphore_signal(semaphore);
  143. return back;
  144. }
  145. + (SCManagedCaptureDevice *)dualCamera
  146. {
  147. SCTraceStart();
  148. static dispatch_once_t onceToken;
  149. static SCManagedCaptureDevice *dualCamera;
  150. static dispatch_semaphore_t semaphore;
  151. dispatch_once(&onceToken, ^{
  152. semaphore = dispatch_semaphore_create(1);
  153. });
  154. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  155. if (!dualCamera) {
  156. AVCaptureDevice *device = [[SCCaptureDeviceResolver sharedInstance] findDualCamera];
  157. if (device) {
  158. dualCamera = [[SCManagedCaptureDevice alloc] initWithDevice:device
  159. devicePosition:SCManagedCaptureDevicePositionBackDualCamera];
  160. }
  161. }
  162. dispatch_semaphore_signal(semaphore);
  163. return dualCamera;
  164. }
  165. + (instancetype)deviceWithPosition:(SCManagedCaptureDevicePosition)position
  166. {
  167. switch (position) {
  168. case SCManagedCaptureDevicePositionFront:
  169. return [self front];
  170. case SCManagedCaptureDevicePositionBack:
  171. return [self back];
  172. case SCManagedCaptureDevicePositionBackDualCamera:
  173. return [self dualCamera];
  174. }
  175. }
  176. + (BOOL)is1080pSupported
  177. {
  178. return [SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone6SorNewer];
  179. }
  180. + (BOOL)isMixCaptureSupported
  181. {
  182. return !![self front] && !![self back];
  183. }
  184. + (BOOL)isNightModeSupported
  185. {
  186. return [SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone6orNewer];
  187. }
  188. + (BOOL)isEnhancedNightModeSupported
  189. {
  190. if (SC_AT_LEAST_IOS_11) {
  191. return [SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone6SorNewer];
  192. }
  193. return NO;
  194. }
  195. + (CGSize)defaultActiveFormatResolution
  196. {
  197. if ([SCDeviceName isIphoneX]) {
  198. return CGSizeMake(kSCManagedCapturerVideoActiveFormatWidth1080p,
  199. kSCManagedCapturerVideoActiveFormatHeight1080p);
  200. }
  201. return CGSizeMake(kSCManagedCapturerDefaultVideoActiveFormatWidth,
  202. kSCManagedCapturerDefaultVideoActiveFormatHeight);
  203. }
  204. + (CGSize)nightModeActiveFormatResolution
  205. {
  206. if ([SCManagedCaptureDevice isEnhancedNightModeSupported]) {
  207. return CGSizeMake(kSCManagedCapturerNightVideoHighResActiveFormatWidth,
  208. kSCManagedCapturerNightVideoHighResActiveFormatHeight);
  209. }
  210. return CGSizeMake(kSCManagedCapturerNightVideoDefaultResActiveFormatWidth,
  211. kSCManagedCapturerNightVideoDefaultResActiveFormatHeight);
  212. }
  213. - (instancetype)initWithDevice:(AVCaptureDevice *)device devicePosition:(SCManagedCaptureDevicePosition)devicePosition
  214. {
  215. SCTraceStart();
  216. self = [super init];
  217. if (self) {
  218. _device = device;
  219. _devicePosition = devicePosition;
  220. if (SCCameraTweaksEnableFaceDetectionFocus(devicePosition)) {
  221. _exposureHandler = [[SCManagedCaptureDeviceFaceDetectionAutoExposureHandler alloc]
  222. initWithDevice:device
  223. pointOfInterest:CGPointMake(0.5, 0.5)
  224. managedCapturer:[SCManagedCapturer sharedInstance]];
  225. _focusHandler = [[SCManagedCaptureDeviceFaceDetectionAutoFocusHandler alloc]
  226. initWithDevice:device
  227. pointOfInterest:CGPointMake(0.5, 0.5)
  228. managedCapturer:[SCManagedCapturer sharedInstance]];
  229. } else {
  230. _exposureHandler = [[SCManagedCaptureDeviceAutoExposureHandler alloc] initWithDevice:device
  231. pointOfInterest:CGPointMake(0.5, 0.5)];
  232. _focusHandler = [[SCManagedCaptureDeviceAutoFocusHandler alloc] initWithDevice:device
  233. pointOfInterest:CGPointMake(0.5, 0.5)];
  234. }
  235. _observeController = [[FBKVOController alloc] initWithObserver:self];
  236. [self _setAsExposureListenerForDevice:device];
  237. if (SCCameraTweaksEnableExposurePointObservation()) {
  238. [self _observeExposurePointForDevice:device];
  239. }
  240. if (SCCameraTweaksEnableFocusPointObservation()) {
  241. [self _observeFocusPointForDevice:device];
  242. }
  243. _zoomFactor = 1.0;
  244. [self _findSupportedFormats];
  245. }
  246. return self;
  247. }
  248. - (SCManagedCaptureDevicePosition)position
  249. {
  250. return _devicePosition;
  251. }
  252. #pragma mark - Setup and hook up with device
  253. - (BOOL)setDeviceAsInput:(AVCaptureSession *)session
  254. {
  255. SCTraceStart();
  256. AVCaptureDeviceInput *deviceInput = [self deviceInput];
  257. if ([session canAddInput:deviceInput]) {
  258. [session addInput:deviceInput];
  259. } else {
  260. NSString *previousSessionPreset = session.sessionPreset;
  261. session.sessionPreset = AVCaptureSessionPresetInputPriority;
  262. // Now we surely can add input
  263. if ([session canAddInput:deviceInput]) {
  264. [session addInput:deviceInput];
  265. } else {
  266. session.sessionPreset = previousSessionPreset;
  267. return NO;
  268. }
  269. }
  270. [self _enableSubjectAreaChangeMonitoring];
  271. [self _updateActiveFormatWithSession:session fallbackPreset:AVCaptureSessionPreset640x480];
  272. if (_device.activeFormat.videoMaxZoomFactor < 1 + 1e-5) {
  273. _softwareZoom = YES;
  274. } else {
  275. _softwareZoom = NO;
  276. if (_device.videoZoomFactor != _zoomFactor) {
  277. // Reset the zoom factor
  278. [self setZoomFactor:_zoomFactor];
  279. }
  280. }
  281. [_exposureHandler setVisible:YES];
  282. [_focusHandler setVisible:YES];
  283. _isConnected = YES;
  284. return YES;
  285. }
  286. - (void)removeDeviceAsInput:(AVCaptureSession *)session
  287. {
  288. SCTraceStart();
  289. if (_isConnected) {
  290. [session removeInput:_deviceInput];
  291. [_exposureHandler setVisible:NO];
  292. [_focusHandler setVisible:NO];
  293. _isConnected = NO;
  294. }
  295. }
  296. - (void)resetDeviceAsInput
  297. {
  298. _deviceInput = nil;
  299. AVCaptureDevice *deviceFound;
  300. switch (_devicePosition) {
  301. case SCManagedCaptureDevicePositionFront:
  302. deviceFound = [[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionFront];
  303. break;
  304. case SCManagedCaptureDevicePositionBack:
  305. deviceFound = [[SCCaptureDeviceResolver sharedInstance] findAVCaptureDevice:AVCaptureDevicePositionBack];
  306. break;
  307. case SCManagedCaptureDevicePositionBackDualCamera:
  308. deviceFound = [[SCCaptureDeviceResolver sharedInstance] findDualCamera];
  309. break;
  310. }
  311. if (deviceFound) {
  312. _device = deviceFound;
  313. }
  314. }
  315. #pragma mark - Configurations
  316. - (void)_findSupportedFormats
  317. {
  318. NSInteger defaultHeight = [SCManagedCaptureDevice defaultActiveFormatResolution].height;
  319. NSInteger nightHeight = [SCManagedCaptureDevice nightModeActiveFormatResolution].height;
  320. NSInteger liveVideoStreamingHeight = kSCManagedCapturerLiveStreamingVideoActiveFormatHeight;
  321. NSArray *heights = @[ @(nightHeight), @(defaultHeight), @(liveVideoStreamingHeight) ];
  322. BOOL formatsShouldSupportDepth = _devicePosition == SCManagedCaptureDevicePositionBackDualCamera;
  323. NSDictionary *formats = SCBestHRSIFormatsForHeights(heights, _device.formats, formatsShouldSupportDepth);
  324. _nightFormat = formats[@(nightHeight)];
  325. _defaultFormat = formats[@(defaultHeight)];
  326. _liveVideoStreamingFormat = formats[@(liveVideoStreamingHeight)];
  327. }
  328. - (AVCaptureDeviceFormat *)_bestSupportedFormat
  329. {
  330. if (_isNightModeActive) {
  331. return _nightFormat;
  332. }
  333. if (_liveVideoStreamingActive) {
  334. return _liveVideoStreamingFormat;
  335. }
  336. return _defaultFormat;
  337. }
  338. - (void)setNightModeActive:(BOOL)nightModeActive session:(AVCaptureSession *)session
  339. {
  340. SCTraceStart();
  341. if (![SCManagedCaptureDevice isNightModeSupported]) {
  342. return;
  343. }
  344. if (_isNightModeActive == nightModeActive) {
  345. return;
  346. }
  347. _isNightModeActive = nightModeActive;
  348. [self updateActiveFormatWithSession:session];
  349. }
  350. - (void)setLiveVideoStreaming:(BOOL)liveVideoStreaming session:(AVCaptureSession *)session
  351. {
  352. SCTraceStart();
  353. if (_liveVideoStreamingActive == liveVideoStreaming) {
  354. return;
  355. }
  356. _liveVideoStreamingActive = liveVideoStreaming;
  357. [self updateActiveFormatWithSession:session];
  358. }
  359. - (void)setCaptureDepthData:(BOOL)captureDepthData session:(AVCaptureSession *)session
  360. {
  361. SCTraceStart();
  362. _captureDepthData = captureDepthData;
  363. [self _findSupportedFormats];
  364. [self updateActiveFormatWithSession:session];
  365. }
  366. - (void)updateActiveFormatWithSession:(AVCaptureSession *)session
  367. {
  368. [self _updateActiveFormatWithSession:session fallbackPreset:AVCaptureSessionPreset640x480];
  369. if (_device.videoZoomFactor != _zoomFactor) {
  370. [self setZoomFactor:_zoomFactor];
  371. }
  372. }
  373. - (void)_updateActiveFormatWithSession:(AVCaptureSession *)session fallbackPreset:(NSString *)fallbackPreset
  374. {
  375. AVCaptureDeviceFormat *nextFormat = [self _bestSupportedFormat];
  376. if (nextFormat && [session canSetSessionPreset:AVCaptureSessionPresetInputPriority]) {
  377. session.sessionPreset = AVCaptureSessionPresetInputPriority;
  378. if (nextFormat == _device.activeFormat) {
  379. // Need to reconfigure frame rate though active format unchanged
  380. [_device runTask:@"update frame rate"
  381. withLockedConfiguration:^() {
  382. [self _updateDeviceFrameRate];
  383. }];
  384. } else {
  385. [_device runTask:@"update active format"
  386. withLockedConfiguration:^() {
  387. _device.activeFormat = nextFormat;
  388. [self _updateDeviceFrameRate];
  389. }];
  390. }
  391. } else {
  392. session.sessionPreset = fallbackPreset;
  393. }
  394. [self _updateFieldOfView];
  395. }
  396. - (void)_updateDeviceFrameRate
  397. {
  398. int32_t deviceFrameRate;
  399. if (_liveVideoStreamingActive) {
  400. deviceFrameRate = kSCManagedCaptureDeviceMaximumLowFrameRate;
  401. } else {
  402. deviceFrameRate = kSCManagedCaptureDeviceMaximumHighFrameRate;
  403. }
  404. CMTime frameDuration = CMTimeMake(1, deviceFrameRate);
  405. if (@available(ios 11.0, *)) {
  406. if (_captureDepthData) {
  407. // Sync the video frame rate to the max depth frame rate (24 fps)
  408. if (_device.activeDepthDataFormat.videoSupportedFrameRateRanges.firstObject) {
  409. frameDuration =
  410. _device.activeDepthDataFormat.videoSupportedFrameRateRanges.firstObject.minFrameDuration;
  411. }
  412. }
  413. }
  414. _device.activeVideoMaxFrameDuration = frameDuration;
  415. _device.activeVideoMinFrameDuration = frameDuration;
  416. if (_device.lowLightBoostSupported) {
  417. _device.automaticallyEnablesLowLightBoostWhenAvailable = YES;
  418. }
  419. }
  420. - (void)setZoomFactor:(float)zoomFactor
  421. {
  422. SCTraceStart();
  423. if (_softwareZoom) {
  424. // Just remember the software zoom scale
  425. if (zoomFactor <= kSCManagedCaptureDevicecSoftwareMaxZoomFactor && zoomFactor >= 1) {
  426. _zoomFactor = zoomFactor;
  427. }
  428. } else {
  429. [_device runTask:@"set zoom factor"
  430. withLockedConfiguration:^() {
  431. if (zoomFactor <= _device.activeFormat.videoMaxZoomFactor && zoomFactor >= 1) {
  432. _zoomFactor = zoomFactor;
  433. if (_device.videoZoomFactor != _zoomFactor) {
  434. _device.videoZoomFactor = _zoomFactor;
  435. }
  436. }
  437. }];
  438. }
  439. [self _updateFieldOfView];
  440. }
  441. - (void)_updateFieldOfView
  442. {
  443. float fieldOfView = _device.activeFormat.videoFieldOfView;
  444. if (_zoomFactor > 1.f) {
  445. // Adjust the field of view to take the zoom factor into account.
  446. // Note: this assumes the zoom factor linearly affects the focal length.
  447. fieldOfView = 2.f * SCRadiansToDegrees(atanf(tanf(SCDegreesToRadians(0.5f * fieldOfView)) / _zoomFactor));
  448. }
  449. self.fieldOfView = fieldOfView;
  450. }
  451. - (void)setExposurePointOfInterest:(CGPoint)pointOfInterest fromUser:(BOOL)fromUser
  452. {
  453. [_exposureHandler setExposurePointOfInterest:pointOfInterest fromUser:fromUser];
  454. }
  455. // called when user taps on a point on screen, to re-adjust camera focus onto that tapped spot.
  456. // this re-adjustment is always necessary, regardless of scenarios (recording video, taking photo, etc),
  457. // therefore we don't have to check _focusLock in this method.
  458. - (void)setAutofocusPointOfInterest:(CGPoint)pointOfInterest
  459. {
  460. SCTraceStart();
  461. [_focusHandler setAutofocusPointOfInterest:pointOfInterest];
  462. }
  463. - (void)continuousAutofocus
  464. {
  465. SCTraceStart();
  466. [_focusHandler continuousAutofocus];
  467. }
  468. - (void)setRecording:(BOOL)recording
  469. {
  470. if (SCCameraTweaksSmoothAutoFocusWhileRecording() && [_device isSmoothAutoFocusSupported]) {
  471. [self _setSmoothFocus:recording];
  472. } else {
  473. [self _setFocusLock:recording];
  474. }
  475. [_exposureHandler setStableExposure:recording];
  476. }
  477. - (void)_setFocusLock:(BOOL)focusLock
  478. {
  479. SCTraceStart();
  480. [_focusHandler setFocusLock:focusLock];
  481. }
  482. - (void)_setSmoothFocus:(BOOL)smoothFocus
  483. {
  484. SCTraceStart();
  485. [_focusHandler setSmoothFocus:smoothFocus];
  486. }
  487. - (void)setFlashActive:(BOOL)flashActive
  488. {
  489. SCTraceStart();
  490. if (_flashActive != flashActive) {
  491. if ([_device hasFlash]) {
  492. #pragma clang diagnostic push
  493. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  494. if (flashActive && [_device isFlashModeSupported:AVCaptureFlashModeOn]) {
  495. [_device runTask:@"set flash active"
  496. withLockedConfiguration:^() {
  497. _device.flashMode = AVCaptureFlashModeOn;
  498. }];
  499. } else if (!flashActive && [_device isFlashModeSupported:AVCaptureFlashModeOff]) {
  500. [_device runTask:@"set flash off"
  501. withLockedConfiguration:^() {
  502. _device.flashMode = AVCaptureFlashModeOff;
  503. }];
  504. }
  505. #pragma clang diagnostic pop
  506. _flashActive = flashActive;
  507. } else {
  508. _flashActive = NO;
  509. }
  510. }
  511. }
  512. - (void)setTorchActive:(BOOL)torchActive
  513. {
  514. SCTraceStart();
  515. if (_torchActive != torchActive) {
  516. if ([_device hasTorch]) {
  517. if (torchActive && [_device isTorchModeSupported:AVCaptureTorchModeOn]) {
  518. [_device runTask:@"set torch active"
  519. withLockedConfiguration:^() {
  520. [_device setTorchMode:AVCaptureTorchModeOn];
  521. }];
  522. } else if (!torchActive && [_device isTorchModeSupported:AVCaptureTorchModeOff]) {
  523. [_device runTask:@"set torch off"
  524. withLockedConfiguration:^() {
  525. _device.torchMode = AVCaptureTorchModeOff;
  526. }];
  527. }
  528. _torchActive = torchActive;
  529. } else {
  530. _torchActive = NO;
  531. }
  532. }
  533. }
  534. #pragma mark - Utilities
  535. - (BOOL)isFlashSupported
  536. {
  537. return _device.hasFlash;
  538. }
  539. - (BOOL)isTorchSupported
  540. {
  541. return _device.hasTorch;
  542. }
  543. - (CGPoint)convertViewCoordinates:(CGPoint)viewCoordinates
  544. viewSize:(CGSize)viewSize
  545. videoGravity:(NSString *)videoGravity
  546. {
  547. SCTraceStart();
  548. CGPoint pointOfInterest = CGPointMake(.5f, .5f);
  549. CGRect cleanAperture;
  550. AVCaptureDeviceInput *deviceInput = [self deviceInput];
  551. NSArray *ports = [deviceInput.ports copy];
  552. if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) {
  553. // Scale, switch x and y, and reverse x
  554. return CGPointMake(viewCoordinates.y / viewSize.height, 1.f - (viewCoordinates.x / viewSize.width));
  555. }
  556. for (AVCaptureInputPort *port in ports) {
  557. if ([port mediaType] == AVMediaTypeVideo && port.formatDescription) {
  558. cleanAperture = CMVideoFormatDescriptionGetCleanAperture(port.formatDescription, YES);
  559. CGSize apertureSize = cleanAperture.size;
  560. CGPoint point = viewCoordinates;
  561. CGFloat apertureRatio = apertureSize.height / apertureSize.width;
  562. CGFloat viewRatio = viewSize.width / viewSize.height;
  563. CGFloat xc = .5f;
  564. CGFloat yc = .5f;
  565. if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) {
  566. if (viewRatio > apertureRatio) {
  567. CGFloat y2 = viewSize.height;
  568. CGFloat x2 = viewSize.height * apertureRatio;
  569. CGFloat x1 = viewSize.width;
  570. CGFloat blackBar = (x1 - x2) / 2;
  571. // If point is inside letterboxed area, do coordinate conversion; otherwise, don't change the
  572. // default value returned (.5,.5)
  573. if (point.x >= blackBar && point.x <= blackBar + x2) {
  574. // Scale (accounting for the letterboxing on the left and right of the video preview),
  575. // switch x and y, and reverse x
  576. xc = point.y / y2;
  577. yc = 1.f - ((point.x - blackBar) / x2);
  578. }
  579. } else {
  580. CGFloat y2 = viewSize.width / apertureRatio;
  581. CGFloat y1 = viewSize.height;
  582. CGFloat x2 = viewSize.width;
  583. CGFloat blackBar = (y1 - y2) / 2;
  584. // If point is inside letterboxed area, do coordinate conversion. Otherwise, don't change the
  585. // default value returned (.5,.5)
  586. if (point.y >= blackBar && point.y <= blackBar + y2) {
  587. // Scale (accounting for the letterboxing on the top and bottom of the video preview),
  588. // switch x and y, and reverse x
  589. xc = ((point.y - blackBar) / y2);
  590. yc = 1.f - (point.x / x2);
  591. }
  592. }
  593. } else if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) {
  594. // Scale, switch x and y, and reverse x
  595. if (viewRatio > apertureRatio) {
  596. CGFloat y2 = apertureSize.width * (viewSize.width / apertureSize.height);
  597. xc = (point.y + ((y2 - viewSize.height) / 2.f)) / y2; // Account for cropped height
  598. yc = (viewSize.width - point.x) / viewSize.width;
  599. } else {
  600. CGFloat x2 = apertureSize.height * (viewSize.height / apertureSize.width);
  601. yc = 1.f - ((point.x + ((x2 - viewSize.width) / 2)) / x2); // Account for cropped width
  602. xc = point.y / viewSize.height;
  603. }
  604. }
  605. pointOfInterest = CGPointMake(xc, yc);
  606. break;
  607. }
  608. }
  609. return pointOfInterest;
  610. }
  611. #pragma mark - SCManagedCapturer friendly methods
  612. - (AVCaptureDevice *)device
  613. {
  614. return _device;
  615. }
  616. - (AVCaptureDeviceInput *)deviceInput
  617. {
  618. SCTraceStart();
  619. if (!_deviceInput) {
  620. NSError *error = nil;
  621. _deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:_device error:&error];
  622. if (!_deviceInput) {
  623. _error = [error copy];
  624. }
  625. }
  626. return _deviceInput;
  627. }
  628. - (NSError *)error
  629. {
  630. return _error;
  631. }
  632. - (BOOL)softwareZoom
  633. {
  634. return _softwareZoom;
  635. }
  636. - (BOOL)isConnected
  637. {
  638. return _isConnected;
  639. }
  640. - (BOOL)flashActive
  641. {
  642. return _flashActive;
  643. }
  644. - (BOOL)torchActive
  645. {
  646. return _torchActive;
  647. }
  648. - (float)zoomFactor
  649. {
  650. return _zoomFactor;
  651. }
  652. - (BOOL)isNightModeActive
  653. {
  654. return _isNightModeActive;
  655. }
  656. - (BOOL)liveVideoStreamingActive
  657. {
  658. return _liveVideoStreamingActive;
  659. }
  660. - (BOOL)isAvailable
  661. {
  662. return [_device isConnected];
  663. }
  664. #pragma mark - Private methods
  665. - (void)_enableSubjectAreaChangeMonitoring
  666. {
  667. SCTraceStart();
  668. [_device runTask:@"enable SubjectAreaChangeMonitoring"
  669. withLockedConfiguration:^() {
  670. _device.subjectAreaChangeMonitoringEnabled = YES;
  671. }];
  672. }
  673. - (AVCaptureDeviceFormat *)activeFormat
  674. {
  675. return _device.activeFormat;
  676. }
  677. #pragma mark - Observe -adjustingExposure
  678. - (void)_setAsExposureListenerForDevice:(AVCaptureDevice *)device
  679. {
  680. SCTraceStart();
  681. SCLogCoreCameraInfo(@"Set exposure adjustment KVO for device: %ld", (long)device.position);
  682. [_observeController observe:device
  683. keyPath:@keypath(device, adjustingExposure)
  684. options:NSKeyValueObservingOptionNew
  685. action:@selector(_adjustingExposureChanged:)];
  686. }
  687. - (void)_adjustingExposureChanged:(NSDictionary *)change
  688. {
  689. SCTraceStart();
  690. BOOL adjustingExposure = [change[NSKeyValueChangeNewKey] boolValue];
  691. SCLogCoreCameraInfo(@"KVO exposure changed to %d", adjustingExposure);
  692. if ([self.delegate respondsToSelector:@selector(managedCaptureDevice:didChangeAdjustingExposure:)]) {
  693. [self.delegate managedCaptureDevice:self didChangeAdjustingExposure:adjustingExposure];
  694. }
  695. }
  696. #pragma mark - Observe -exposurePointOfInterest
  697. - (void)_observeExposurePointForDevice:(AVCaptureDevice *)device
  698. {
  699. SCTraceStart();
  700. SCLogCoreCameraInfo(@"Set exposure point KVO for device: %ld", (long)device.position);
  701. [_observeController observe:device
  702. keyPath:@keypath(device, exposurePointOfInterest)
  703. options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
  704. action:@selector(_exposurePointOfInterestChanged:)];
  705. }
  706. - (void)_exposurePointOfInterestChanged:(NSDictionary *)change
  707. {
  708. SCTraceStart();
  709. CGPoint exposurePoint = [change[NSKeyValueChangeNewKey] CGPointValue];
  710. SCLogCoreCameraInfo(@"KVO exposure point changed to %@", NSStringFromCGPoint(exposurePoint));
  711. if ([self.delegate respondsToSelector:@selector(managedCaptureDevice:didChangeExposurePoint:)]) {
  712. [self.delegate managedCaptureDevice:self didChangeExposurePoint:exposurePoint];
  713. }
  714. }
  715. #pragma mark - Observe -focusPointOfInterest
  716. - (void)_observeFocusPointForDevice:(AVCaptureDevice *)device
  717. {
  718. SCTraceStart();
  719. SCLogCoreCameraInfo(@"Set focus point KVO for device: %ld", (long)device.position);
  720. [_observeController observe:device
  721. keyPath:@keypath(device, focusPointOfInterest)
  722. options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
  723. action:@selector(_focusPointOfInterestChanged:)];
  724. }
  725. - (void)_focusPointOfInterestChanged:(NSDictionary *)change
  726. {
  727. SCTraceStart();
  728. CGPoint focusPoint = [change[NSKeyValueChangeNewKey] CGPointValue];
  729. SCLogCoreCameraInfo(@"KVO focus point changed to %@", NSStringFromCGPoint(focusPoint));
  730. if ([self.delegate respondsToSelector:@selector(managedCaptureDevice:didChangeFocusPoint:)]) {
  731. [self.delegate managedCaptureDevice:self didChangeFocusPoint:focusPoint];
  732. }
  733. }
  734. - (void)dealloc
  735. {
  736. [_observeController unobserveAll];
  737. }
  738. @end