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.

299 lines
12 KiB

  1. //
  2. // SCManagedVideoScanner.m
  3. // Snapchat
  4. //
  5. // Created by Liu Liu on 5/5/15.
  6. // Copyright (c) 2015 Snapchat, Inc. All rights reserved.
  7. //
  8. #import "SCManagedVideoScanner.h"
  9. #import "SCScanConfiguration.h"
  10. #import <SCFeatureSettings/SCFeatureSettingsManager+Property.h>
  11. #import <SCFoundation/NSData+Base64.h>
  12. #import <SCFoundation/NSString+SCFormat.h>
  13. #import <SCFoundation/SCAssertWrapper.h>
  14. #import <SCFoundation/SCLog.h>
  15. #import <SCFoundation/SCQueuePerformer.h>
  16. #import <SCFoundation/SCThreadHelpers.h>
  17. #import <SCFoundation/SCTrace.h>
  18. #import <SCFoundation/UIDevice+Filter.h>
  19. #import <SCLogger/SCLogger.h>
  20. #import <SCScanTweaks/SCScanTweaks.h>
  21. #import <SCScanner/SCMachineReadableCodeResult.h>
  22. #import <SCScanner/SCSnapScanner.h>
  23. #import <SCVisualProductSearchTweaks/SCVisualProductSearchTweaks.h>
  24. // In seconds
  25. static NSTimeInterval const kDefaultScanTimeout = 60;
  26. static const char *kSCManagedVideoScannerQueueLabel = "com.snapchat.scvideoscanningcapturechannel.video.snapcode-scan";
  27. @interface SCManagedVideoScanner ()
  28. @end
  29. @implementation SCManagedVideoScanner {
  30. SCSnapScanner *_snapScanner;
  31. dispatch_semaphore_t _activeSemaphore;
  32. NSTimeInterval _maxFrameDuration; // Used to restrict how many frames the scanner processes
  33. NSTimeInterval _maxFrameDefaultDuration;
  34. NSTimeInterval _maxFramePassiveDuration;
  35. float _restCycleOfBusyCycle;
  36. NSTimeInterval _scanStartTime;
  37. BOOL _active;
  38. BOOL _shouldEmitEvent;
  39. dispatch_block_t _completionHandler;
  40. NSTimeInterval _scanTimeout;
  41. SCManagedCaptureDevicePosition _devicePosition;
  42. SCQueuePerformer *_performer;
  43. BOOL _adjustingFocus;
  44. NSArray *_codeTypes;
  45. NSArray *_codeTypesOld;
  46. sc_managed_capturer_scan_results_handler_t _scanResultsHandler;
  47. SCUserSession *_userSession;
  48. }
  49. - (instancetype)initWithMaxFrameDefaultDuration:(NSTimeInterval)maxFrameDefaultDuration
  50. maxFramePassiveDuration:(NSTimeInterval)maxFramePassiveDuration
  51. restCycle:(float)restCycle
  52. {
  53. SCTraceStart();
  54. self = [super init];
  55. if (self) {
  56. _snapScanner = [SCSnapScanner sharedInstance];
  57. _performer = [[SCQueuePerformer alloc] initWithLabel:kSCManagedVideoScannerQueueLabel
  58. qualityOfService:QOS_CLASS_UNSPECIFIED
  59. queueType:DISPATCH_QUEUE_SERIAL
  60. context:SCQueuePerformerContextCamera];
  61. _activeSemaphore = dispatch_semaphore_create(0);
  62. SCAssert(restCycle >= 0 && restCycle < 1, @"rest cycle should be between 0 to 1");
  63. _maxFrameDefaultDuration = maxFrameDefaultDuration;
  64. _maxFramePassiveDuration = maxFramePassiveDuration;
  65. _restCycleOfBusyCycle = restCycle / (1 - restCycle); // Give CPU time to rest
  66. }
  67. return self;
  68. }
  69. #pragma mark - Public methods
  70. - (void)startScanAsynchronouslyWithScanConfiguration:(SCScanConfiguration *)configuration
  71. {
  72. SCTraceStart();
  73. [_performer perform:^{
  74. _shouldEmitEvent = YES;
  75. _completionHandler = nil;
  76. _scanResultsHandler = configuration.scanResultsHandler;
  77. _userSession = configuration.userSession;
  78. _scanTimeout = kDefaultScanTimeout;
  79. _maxFrameDuration = _maxFrameDefaultDuration;
  80. _codeTypes = [self _scanCodeTypes];
  81. _codeTypesOld = @[ @(SCCodeTypeSnapcode18x18Old), @(SCCodeTypeQRCode) ];
  82. SCTraceStart();
  83. // Set the scan start time properly, if we call startScan multiple times while it is active,
  84. // This makes sure we can scan long enough.
  85. _scanStartTime = CACurrentMediaTime();
  86. // we are not active, need to send the semaphore to start the scan
  87. if (!_active) {
  88. _active = YES;
  89. // Signal the semaphore that we can start scan!
  90. dispatch_semaphore_signal(_activeSemaphore);
  91. }
  92. }];
  93. }
  94. - (void)stopScanAsynchronously
  95. {
  96. SCTraceStart();
  97. [_performer perform:^{
  98. SCTraceStart();
  99. if (_active) {
  100. SCLogScanDebug(@"VideoScanner:stopScanAsynchronously turn off from active");
  101. _active = NO;
  102. _scanStartTime = 0;
  103. _scanResultsHandler = nil;
  104. _userSession = nil;
  105. } else {
  106. SCLogScanDebug(@"VideoScanner:stopScanAsynchronously off already");
  107. }
  108. }];
  109. }
  110. #pragma mark - Private Methods
  111. - (void)_handleSnapScanResult:(SCSnapScannedData *)scannedData
  112. {
  113. if (scannedData.hasScannedData) {
  114. if (scannedData.codeType == SCCodeTypeSnapcode18x18 || scannedData.codeType == SCCodeTypeSnapcodeBitmoji ||
  115. scannedData.codeType == SCCodeTypeSnapcode18x18Old) {
  116. NSString *data = [scannedData.rawData base64EncodedString];
  117. NSString *version = [NSString sc_stringWithFormat:@"%i", scannedData.codeTypeMeta];
  118. [[SCLogger sharedInstance] logEvent:@"SNAPCODE_18x18_SCANNED_FROM_CAMERA"
  119. parameters:@{
  120. @"version" : version
  121. }
  122. secretParameters:@{
  123. @"data" : data
  124. }];
  125. if (_completionHandler != nil) {
  126. runOnMainThreadAsynchronously(_completionHandler);
  127. _completionHandler = nil;
  128. }
  129. } else if (scannedData.codeType == SCCodeTypeBarcode) {
  130. if (!_userSession || !_userSession.featureSettingsManager.barCodeScanEnabled) {
  131. return;
  132. }
  133. NSString *data = scannedData.data;
  134. NSString *type = [SCSnapScannedData stringFromBarcodeType:scannedData.codeTypeMeta];
  135. [[SCLogger sharedInstance] logEvent:@"BARCODE_SCANNED_FROM_CAMERA"
  136. parameters:@{
  137. @"type" : type
  138. }
  139. secretParameters:@{
  140. @"data" : data
  141. }];
  142. } else if (scannedData.codeType == SCCodeTypeQRCode) {
  143. if (!_userSession || !_userSession.featureSettingsManager.qrCodeScanEnabled) {
  144. return;
  145. }
  146. NSURL *url = [NSURL URLWithString:scannedData.data];
  147. [[SCLogger sharedInstance] logEvent:@"QR_CODE_SCANNED_FROM_CAMERA"
  148. parameters:@{
  149. @"type" : (url) ? @"url" : @"other"
  150. }
  151. secretParameters:@{}];
  152. }
  153. if (_shouldEmitEvent) {
  154. sc_managed_capturer_scan_results_handler_t scanResultsHandler = _scanResultsHandler;
  155. runOnMainThreadAsynchronously(^{
  156. if (scanResultsHandler != nil && scannedData) {
  157. SCMachineReadableCodeResult *result =
  158. [SCMachineReadableCodeResult machineReadableCodeResultWithScannedData:scannedData];
  159. scanResultsHandler(result);
  160. }
  161. });
  162. }
  163. }
  164. }
  165. - (NSArray *)_scanCodeTypes
  166. {
  167. // Scan types are defined by codetypes. SnapScan will scan the frame based on codetype.
  168. NSMutableArray *codeTypes = [[NSMutableArray alloc]
  169. initWithObjects:@(SCCodeTypeSnapcode18x18), @(SCCodeTypeQRCode), @(SCCodeTypeSnapcodeBitmoji), nil];
  170. if (SCSearchEnableBarcodeProductSearch()) {
  171. [codeTypes addObject:@(SCCodeTypeBarcode)];
  172. }
  173. return [codeTypes copy];
  174. }
  175. #pragma mark - SCManagedVideoDataSourceListener
  176. - (void)managedVideoDataSource:(id<SCManagedVideoDataSource>)managedVideoDataSource
  177. didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
  178. devicePosition:(SCManagedCaptureDevicePosition)devicePosition
  179. {
  180. SCTraceStart();
  181. _devicePosition = devicePosition;
  182. if (!_active) {
  183. SCLogScanDebug(@"VideoScanner: Scanner is not active");
  184. return;
  185. }
  186. SCLogScanDebug(@"VideoScanner: Scanner is active");
  187. // If we have the semaphore now, enqueue a new buffer, otherwise drop the buffer
  188. if (dispatch_semaphore_wait(_activeSemaphore, DISPATCH_TIME_NOW) == 0) {
  189. CFRetain(sampleBuffer);
  190. NSTimeInterval startTime = CACurrentMediaTime();
  191. [_performer perform:^{
  192. SCTraceStart();
  193. CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  194. SCLogScanInfo(@"VideoScanner: Scanner will scan a frame");
  195. SCSnapScannedData *scannedData;
  196. SCLogScanInfo(@"VideoScanner:Use new scanner without false alarm check");
  197. scannedData = [_snapScanner scanPixelBuffer:pixelBuffer forCodeTypes:_codeTypes];
  198. if ([UIDevice shouldLogPerfEvents]) {
  199. NSInteger loadingMs = (CACurrentMediaTime() - startTime) * 1000;
  200. // Since there are too many unsuccessful scans, we will only log 1/10 of them for now.
  201. if (scannedData.hasScannedData || (!scannedData.hasScannedData && arc4random() % 10 == 0)) {
  202. [[SCLogger sharedInstance] logEvent:@"SCAN_SINGLE_FRAME"
  203. parameters:@{
  204. @"time_span" : @(loadingMs),
  205. @"has_scanned_data" : @(scannedData.hasScannedData),
  206. }];
  207. }
  208. }
  209. [self _handleSnapScanResult:scannedData];
  210. // If it is not turned off, we will continue to scan if result is not presetn
  211. if (_active) {
  212. _active = !scannedData.hasScannedData;
  213. }
  214. // Clean up if result is reported for scan
  215. if (!_active) {
  216. _scanResultsHandler = nil;
  217. _completionHandler = nil;
  218. }
  219. CFRelease(sampleBuffer);
  220. NSTimeInterval currentTime = CACurrentMediaTime();
  221. SCLogScanInfo(@"VideoScanner:Scan time %f maxFrameDuration:%f timeout:%f", currentTime - startTime,
  222. _maxFrameDuration, _scanTimeout);
  223. // Haven't found the scanned data yet, haven't reached maximum scan timeout yet, haven't turned this off
  224. // yet, ready for the next frame
  225. if (_active && currentTime < _scanStartTime + _scanTimeout) {
  226. // We've finished processing current sample buffer, ready for next one, but before that, we need to rest
  227. // a bit (if possible)
  228. if (currentTime - startTime >= _maxFrameDuration && _restCycleOfBusyCycle < FLT_MIN) {
  229. // If we already reached deadline (used too much time) and don't want to rest CPU, give the signal
  230. // now to grab the next frame
  231. SCLogScanInfo(@"VideoScanner:Signal to get next frame for snapcode scanner");
  232. dispatch_semaphore_signal(_activeSemaphore);
  233. } else {
  234. NSTimeInterval afterTime = MAX((currentTime - startTime) * _restCycleOfBusyCycle,
  235. _maxFrameDuration - (currentTime - startTime));
  236. // If we need to wait more than 0 second, then do that, otherwise grab the next frame immediately
  237. if (afterTime > 0) {
  238. [_performer perform:^{
  239. SCLogScanInfo(
  240. @"VideoScanner:Waited and now signaling to get next frame for snapcode scanner");
  241. dispatch_semaphore_signal(_activeSemaphore);
  242. }
  243. after:afterTime];
  244. } else {
  245. SCLogScanInfo(@"VideoScanner:Now signaling to get next frame for snapcode scanner");
  246. dispatch_semaphore_signal(_activeSemaphore);
  247. }
  248. }
  249. } else {
  250. // We are not active, and not going to be active any more.
  251. SCLogScanInfo(@"VideoScanner:not active anymore");
  252. _active = NO;
  253. _scanResultsHandler = nil;
  254. _completionHandler = nil;
  255. }
  256. }];
  257. }
  258. }
  259. #pragma mark - SCManagedDeviceCapacityAnalyzerListener
  260. - (void)managedDeviceCapacityAnalyzer:(SCManagedDeviceCapacityAnalyzer *)managedDeviceCapacityAnalyzer
  261. didChangeAdjustingFocus:(BOOL)adjustingFocus
  262. {
  263. [_performer perform:^{
  264. _adjustingFocus = adjustingFocus;
  265. }];
  266. }
  267. @end