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.

294 lines
13 KiB

  1. //
  2. // SCManagedDeviceCapacityAnalyzer.m
  3. // Snapchat
  4. //
  5. // Created by Liu Liu on 5/1/15.
  6. // Copyright (c) 2015 Liu Liu. All rights reserved.
  7. //
  8. #import "SCManagedDeviceCapacityAnalyzer.h"
  9. #import "SCCameraSettingUtils.h"
  10. #import "SCCameraTweaks.h"
  11. #import "SCManagedCaptureDevice+SCManagedDeviceCapacityAnalyzer.h"
  12. #import "SCManagedCaptureDevice.h"
  13. #import "SCManagedDeviceCapacityAnalyzerListenerAnnouncer.h"
  14. #import <SCFoundation/SCAppEnvironment.h>
  15. #import <SCFoundation/SCDeviceName.h>
  16. #import <SCFoundation/SCLog.h>
  17. #import <SCFoundation/SCPerforming.h>
  18. #import <SCFoundation/SCTrace.h>
  19. #import <FBKVOController/FBKVOController.h>
  20. @import ImageIO;
  21. @import QuartzCore;
  22. NSInteger const kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor6WithHRSI = 500;
  23. NSInteger const kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor6S = 800;
  24. NSInteger const kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor7 = 640;
  25. NSInteger const kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor8 = 800;
  26. // After this much frames we haven't changed exposure time or ISO, we will assume that the adjustingExposure is ended.
  27. static NSInteger const kExposureUnchangedHighWatermark = 5;
  28. // If deadline reached, and we still haven't reached high watermark yet, we will consult the low watermark and at least
  29. // give the system a chance to take not-so-great pictures.
  30. static NSInteger const kExposureUnchangedLowWatermark = 1;
  31. static NSTimeInterval const kExposureUnchangedDeadline = 0.2;
  32. // It seems that between ISO 500 to 640, the brightness value is always somewhere around -0.4 to -0.5.
  33. // Therefore, this threshold probably will work fine.
  34. static float const kBrightnessValueThreshold = -2.25;
  35. // Give some margins between recognized as bright enough and not enough light.
  36. // If the brightness is lower than kBrightnessValueThreshold - kBrightnessValueThresholdConfidenceInterval,
  37. // and then we count the frame as low light frame. Only if the brightness is higher than
  38. // kBrightnessValueThreshold + kBrightnessValueThresholdConfidenceInterval, we think that we
  39. // have enough light, and reset low light frame count to 0. 0.5 is choosing because in dark
  40. // environment, the brightness value changes +-0.3 with minor orientation changes.
  41. static float const kBrightnessValueThresholdConfidenceInterval = 0.5;
  42. // If we are at good light condition for 5 frames, ready to change back
  43. static NSInteger const kLowLightBoostUnchangedLowWatermark = 7;
  44. // Requires we are at low light condition for ~2 seconds (assuming 20~30fps)
  45. static NSInteger const kLowLightBoostUnchangedHighWatermark = 25;
  46. static NSInteger const kSCLightingConditionDecisionWatermark = 15; // For 30 fps, it is 0.5 second
  47. static float const kSCLightingConditionNormalThreshold = 0;
  48. static float const kSCLightingConditionDarkThreshold = -3;
  49. @implementation SCManagedDeviceCapacityAnalyzer {
  50. float _lastExposureTime;
  51. int _lastISOSpeedRating;
  52. NSTimeInterval _lastAdjustingExposureStartTime;
  53. NSInteger _lowLightBoostLowLightCount;
  54. NSInteger _lowLightBoostEnoughLightCount;
  55. NSInteger _exposureUnchangedCount;
  56. NSInteger _maxISOPresetHigh;
  57. NSInteger _normalLightingConditionCount;
  58. NSInteger _darkLightingConditionCount;
  59. NSInteger _extremeDarkLightingConditionCount;
  60. SCCapturerLightingConditionType _lightingCondition;
  61. BOOL _lowLightCondition;
  62. BOOL _adjustingExposure;
  63. SCManagedDeviceCapacityAnalyzerListenerAnnouncer *_announcer;
  64. FBKVOController *_observeController;
  65. id<SCPerforming> _performer;
  66. float
  67. _lastBrightnessToLog; // Remember last logged brightness, only log again if it changes greater than a threshold
  68. }
  69. - (instancetype)initWithPerformer:(id<SCPerforming>)performer
  70. {
  71. SCTraceStart();
  72. self = [super init];
  73. if (self) {
  74. _performer = performer;
  75. _maxISOPresetHigh = kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor6WithHRSI;
  76. if ([SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone8orNewer]) {
  77. _maxISOPresetHigh = kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor8;
  78. } else if ([SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone7orNewer]) {
  79. _maxISOPresetHigh = kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor7;
  80. } else if ([SCDeviceName isIphone] && [SCDeviceName isSimilarToIphone6SorNewer]) {
  81. // iPhone 6S supports higher ISO rate for video recording, accommadating that.
  82. _maxISOPresetHigh = kSCManagedDeviceCapacityAnalyzerMaxISOPresetHighFor6S;
  83. }
  84. _announcer = [[SCManagedDeviceCapacityAnalyzerListenerAnnouncer alloc] init];
  85. _observeController = [[FBKVOController alloc] initWithObserver:self];
  86. }
  87. return self;
  88. }
  89. - (void)addListener:(id<SCManagedDeviceCapacityAnalyzerListener>)listener
  90. {
  91. SCTraceStart();
  92. [_announcer addListener:listener];
  93. }
  94. - (void)removeListener:(id<SCManagedDeviceCapacityAnalyzerListener>)listener
  95. {
  96. SCTraceStart();
  97. [_announcer removeListener:listener];
  98. }
  99. - (void)setLowLightConditionEnabled:(BOOL)lowLightConditionEnabled
  100. {
  101. SCTraceStart();
  102. if (_lowLightConditionEnabled != lowLightConditionEnabled) {
  103. _lowLightConditionEnabled = lowLightConditionEnabled;
  104. if (!lowLightConditionEnabled) {
  105. _lowLightBoostLowLightCount = 0;
  106. _lowLightBoostEnoughLightCount = 0;
  107. _lowLightCondition = NO;
  108. [_announcer managedDeviceCapacityAnalyzer:self didChangeLowLightCondition:_lowLightCondition];
  109. }
  110. }
  111. }
  112. - (void)managedVideoDataSource:(id<SCManagedVideoDataSource>)managedVideoDataSource
  113. didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
  114. devicePosition:(SCManagedCaptureDevicePosition)devicePosition
  115. {
  116. SCTraceStart();
  117. SampleBufferMetadata metadata = {
  118. .isoSpeedRating = _lastISOSpeedRating, .brightness = 0, .exposureTime = _lastExposureTime,
  119. };
  120. retrieveSampleBufferMetadata(sampleBuffer, &metadata);
  121. if ((SCIsDebugBuild() || SCIsMasterBuild())
  122. // Enable this on internal build only (excluding alpha)
  123. && fabs(metadata.brightness - _lastBrightnessToLog) > 0.5f) {
  124. // Log only when brightness change is greater than 0.5
  125. _lastBrightnessToLog = metadata.brightness;
  126. SCLogCoreCameraInfo(@"ExposureTime: %f, ISO: %ld, Brightness: %f", metadata.exposureTime,
  127. (long)metadata.isoSpeedRating, metadata.brightness);
  128. }
  129. [self _automaticallyDetectAdjustingExposure:metadata.exposureTime ISOSpeedRating:metadata.isoSpeedRating];
  130. _lastExposureTime = metadata.exposureTime;
  131. _lastISOSpeedRating = metadata.isoSpeedRating;
  132. if (!_adjustingExposure && _lastISOSpeedRating <= _maxISOPresetHigh &&
  133. _lowLightConditionEnabled) { // If we are not recording, we are not at ISO higher than we needed
  134. [self _automaticallyDetectLowLightCondition:metadata.brightness];
  135. }
  136. [self _automaticallyDetectLightingConditionWithBrightness:metadata.brightness];
  137. [_announcer managedDeviceCapacityAnalyzer:self didChangeBrightness:metadata.brightness];
  138. }
  139. - (void)setAsFocusListenerForDevice:(SCManagedCaptureDevice *)captureDevice
  140. {
  141. SCTraceStart();
  142. [_observeController observe:captureDevice.device
  143. keyPath:@keypath(captureDevice.device, adjustingFocus)
  144. options:NSKeyValueObservingOptionNew
  145. action:@selector(_adjustingFocusingChanged:)];
  146. }
  147. - (void)removeFocusListener
  148. {
  149. SCTraceStart();
  150. [_observeController unobserveAll];
  151. }
  152. #pragma mark - Private methods
  153. - (void)_automaticallyDetectAdjustingExposure:(float)currentExposureTime ISOSpeedRating:(NSInteger)currentISOSpeedRating
  154. {
  155. SCTraceStart();
  156. if (currentISOSpeedRating != _lastISOSpeedRating || fabsf(currentExposureTime - _lastExposureTime) > FLT_MIN) {
  157. _exposureUnchangedCount = 0;
  158. } else {
  159. ++_exposureUnchangedCount;
  160. }
  161. NSTimeInterval currentTime = CACurrentMediaTime();
  162. if (_exposureUnchangedCount >= kExposureUnchangedHighWatermark ||
  163. (currentTime - _lastAdjustingExposureStartTime > kExposureUnchangedDeadline &&
  164. _exposureUnchangedCount >= kExposureUnchangedLowWatermark)) {
  165. // The exposure values haven't changed for kExposureUnchangedHighWatermark times, considering the adjustment
  166. // as done. Otherwise, if we waited long enough, and the exposure unchange count at least reached low
  167. // watermark, we will call it done and give it a shot.
  168. if (_adjustingExposure) {
  169. _adjustingExposure = NO;
  170. SCLogGeneralInfo(@"Adjusting exposure is done, unchanged count: %zd", _exposureUnchangedCount);
  171. [_announcer managedDeviceCapacityAnalyzer:self didChangeAdjustingExposure:_adjustingExposure];
  172. }
  173. } else {
  174. // Otherwise signal that we have adjustments on exposure
  175. if (!_adjustingExposure) {
  176. _adjustingExposure = YES;
  177. _lastAdjustingExposureStartTime = currentTime;
  178. [_announcer managedDeviceCapacityAnalyzer:self didChangeAdjustingExposure:_adjustingExposure];
  179. }
  180. }
  181. }
  182. - (void)_automaticallyDetectLowLightCondition:(float)brightness
  183. {
  184. SCTraceStart();
  185. if (!_lowLightCondition && _lastISOSpeedRating == _maxISOPresetHigh) {
  186. // If we are at the stage that we need to use higher ISO (because current ISO is maxed out)
  187. // and the brightness is lower than the threshold
  188. if (brightness < kBrightnessValueThreshold - kBrightnessValueThresholdConfidenceInterval) {
  189. // Either count how many frames like this continuously we encountered
  190. // Or if reached the watermark, change the low light boost mode
  191. if (_lowLightBoostLowLightCount >= kLowLightBoostUnchangedHighWatermark) {
  192. _lowLightCondition = YES;
  193. [_announcer managedDeviceCapacityAnalyzer:self didChangeLowLightCondition:_lowLightCondition];
  194. } else {
  195. ++_lowLightBoostLowLightCount;
  196. }
  197. } else if (brightness >= kBrightnessValueThreshold + kBrightnessValueThresholdConfidenceInterval) {
  198. // If the brightness is consistently better, reset the low light boost unchanged count to 0
  199. _lowLightBoostLowLightCount = 0;
  200. }
  201. } else if (_lowLightCondition) {
  202. // Check the current ISO to see if we can disable low light boost
  203. if (_lastISOSpeedRating <= _maxISOPresetHigh &&
  204. brightness >= kBrightnessValueThreshold + kBrightnessValueThresholdConfidenceInterval) {
  205. if (_lowLightBoostEnoughLightCount >= kLowLightBoostUnchangedLowWatermark) {
  206. _lowLightCondition = NO;
  207. [_announcer managedDeviceCapacityAnalyzer:self didChangeLowLightCondition:_lowLightCondition];
  208. _lowLightBoostEnoughLightCount = 0;
  209. } else {
  210. ++_lowLightBoostEnoughLightCount;
  211. }
  212. }
  213. }
  214. }
  215. - (void)_adjustingFocusingChanged:(NSDictionary *)change
  216. {
  217. SCTraceStart();
  218. BOOL adjustingFocus = [change[NSKeyValueChangeNewKey] boolValue];
  219. [_performer perform:^{
  220. [_announcer managedDeviceCapacityAnalyzer:self didChangeAdjustingFocus:adjustingFocus];
  221. }];
  222. }
  223. - (void)_automaticallyDetectLightingConditionWithBrightness:(float)brightness
  224. {
  225. if (brightness >= kSCLightingConditionNormalThreshold) {
  226. if (_normalLightingConditionCount > kSCLightingConditionDecisionWatermark) {
  227. if (_lightingCondition != SCCapturerLightingConditionTypeNormal) {
  228. _lightingCondition = SCCapturerLightingConditionTypeNormal;
  229. [_announcer managedDeviceCapacityAnalyzer:self
  230. didChangeLightingCondition:SCCapturerLightingConditionTypeNormal];
  231. }
  232. } else {
  233. _normalLightingConditionCount++;
  234. }
  235. _darkLightingConditionCount = 0;
  236. _extremeDarkLightingConditionCount = 0;
  237. } else if (brightness >= kSCLightingConditionDarkThreshold) {
  238. if (_darkLightingConditionCount > kSCLightingConditionDecisionWatermark) {
  239. if (_lightingCondition != SCCapturerLightingConditionTypeDark) {
  240. _lightingCondition = SCCapturerLightingConditionTypeDark;
  241. [_announcer managedDeviceCapacityAnalyzer:self
  242. didChangeLightingCondition:SCCapturerLightingConditionTypeDark];
  243. }
  244. } else {
  245. _darkLightingConditionCount++;
  246. }
  247. _normalLightingConditionCount = 0;
  248. _extremeDarkLightingConditionCount = 0;
  249. } else {
  250. if (_extremeDarkLightingConditionCount > kSCLightingConditionDecisionWatermark) {
  251. if (_lightingCondition != SCCapturerLightingConditionTypeExtremeDark) {
  252. _lightingCondition = SCCapturerLightingConditionTypeExtremeDark;
  253. [_announcer managedDeviceCapacityAnalyzer:self
  254. didChangeLightingCondition:SCCapturerLightingConditionTypeExtremeDark];
  255. }
  256. } else {
  257. _extremeDarkLightingConditionCount++;
  258. }
  259. _normalLightingConditionCount = 0;
  260. _darkLightingConditionCount = 0;
  261. }
  262. }
  263. @end