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.

283 lines
12 KiB

  1. //
  2. // SCManagedVideoNoSoundLogger.m
  3. // Snapchat
  4. //
  5. // Created by Pinlin Chen on 15/07/2017.
  6. //
  7. //
  8. #import "SCManagedVideoNoSoundLogger.h"
  9. #import "SCManagedCapturer.h"
  10. #import "SCManiphestTicketCreator.h"
  11. #import <SCAudio/SCAudioSession+Debug.h>
  12. #import <SCAudio/SCAudioSession.h>
  13. #import <SCFoundation/NSString+Helpers.h>
  14. #import <SCFoundation/SCLog.h>
  15. #import <SCFoundation/SCLogHelper.h>
  16. #import <SCFoundation/SCThreadHelpers.h>
  17. #import <SCFoundation/SCUUID.h>
  18. #import <SCLogger/SCCameraMetrics.h>
  19. #import <SCLogger/SCLogger.h>
  20. @import AVFoundation;
  21. static BOOL s_startCountingVideoNoSoundFixed;
  22. // Count the number of no sound errors for an App session
  23. static NSUInteger s_noSoundCaseCount = 0;
  24. @interface SCManagedVideoNoSoundLogger () {
  25. BOOL _isAudioSessionDeactivated;
  26. int _lenseResumeCount;
  27. }
  28. @property (nonatomic) id<SCManiphestTicketCreator> ticketCreator;
  29. @end
  30. @implementation SCManagedVideoNoSoundLogger
  31. - (instancetype)initWithTicketCreator:(id<SCManiphestTicketCreator>)ticketCreator
  32. {
  33. if (self = [super init]) {
  34. _ticketCreator = ticketCreator;
  35. }
  36. return self;
  37. }
  38. + (NSUInteger)noSoundCount
  39. {
  40. return s_noSoundCaseCount;
  41. }
  42. + (void)increaseNoSoundCount
  43. {
  44. s_noSoundCaseCount += 1;
  45. }
  46. + (void)startCountingVideoNoSoundHaveBeenFixed
  47. {
  48. static dispatch_once_t onceToken;
  49. dispatch_once(&onceToken, ^{
  50. s_startCountingVideoNoSoundFixed = YES;
  51. SCLogGeneralInfo(@"start counting video no sound have been fixed");
  52. });
  53. }
  54. + (NSString *)appSessionIdForNoSound
  55. {
  56. static dispatch_once_t onceToken;
  57. static NSString *s_AppSessionIdForNoSound = @"SCDefaultSession";
  58. dispatch_once(&onceToken, ^{
  59. s_AppSessionIdForNoSound = SCUUID();
  60. });
  61. return s_AppSessionIdForNoSound;
  62. }
  63. + (void)logVideoNoSoundHaveBeenFixedIfNeeded
  64. {
  65. if (s_startCountingVideoNoSoundFixed) {
  66. [[SCLogger sharedInstance] logUnsampledEvent:kSCCameraMetricsVideoNoSoundError
  67. parameters:@{
  68. @"have_been_fixed" : @"true",
  69. @"fixed_type" : @"player_leak",
  70. @"asset_writer_success" : @"true",
  71. @"audio_session_success" : @"true",
  72. @"audio_queue_success" : @"true",
  73. }
  74. secretParameters:nil
  75. metrics:nil];
  76. }
  77. }
  78. + (void)logAudioSessionCategoryHaveBeenFixed
  79. {
  80. [[SCLogger sharedInstance] logUnsampledEvent:kSCCameraMetricsVideoNoSoundError
  81. parameters:@{
  82. @"have_been_fixed" : @"true",
  83. @"fixed_type" : @"audio_session_category_mismatch",
  84. @"asset_writer_success" : @"true",
  85. @"audio_session_success" : @"true",
  86. @"audio_queue_success" : @"true",
  87. }
  88. secretParameters:nil
  89. metrics:nil];
  90. }
  91. + (void)logAudioSessionBrokenMicHaveBeenFixed:(NSString *)type
  92. {
  93. [[SCLogger sharedInstance]
  94. logUnsampledEvent:kSCCameraMetricsVideoNoSoundError
  95. parameters:@{
  96. @"have_been_fixed" : @"true",
  97. @"fixed_type" : @"broken_microphone",
  98. @"asset_writer_success" : @"true",
  99. @"audio_session_success" : @"true",
  100. @"audio_queue_success" : @"true",
  101. @"mic_broken_type" : SC_NULL_STRING_IF_NIL(type),
  102. @"audio_session_debug_info" :
  103. [SCAudioSession sharedInstance].lastRecordingRequestDebugInfo ?: @"(null)",
  104. }
  105. secretParameters:nil
  106. metrics:nil];
  107. }
  108. - (instancetype)init
  109. {
  110. if (self = [super init]) {
  111. [[NSNotificationCenter defaultCenter] addObserver:self
  112. selector:@selector(_audioSessionWillDeactivate)
  113. name:SCAudioSessionWillDeactivateNotification
  114. object:nil];
  115. [[NSNotificationCenter defaultCenter] addObserver:self
  116. selector:@selector(_audioSessionDidActivate)
  117. name:SCAudioSessionActivatedNotification
  118. object:nil];
  119. _firstWrittenAudioBufferDelay = kCMTimeInvalid;
  120. }
  121. return self;
  122. }
  123. - (void)resetAll
  124. {
  125. _audioQueueError = nil;
  126. _audioSessionError = nil;
  127. _assetWriterError = nil;
  128. _retryAudioQueueSuccess = NO;
  129. _retryAudioQueueSuccessSetDataSource = NO;
  130. _brokenMicCodeType = nil;
  131. _lenseActiveWhileRecording = NO;
  132. _lenseResumeCount = 0;
  133. _activeLensId = nil;
  134. self.firstWrittenAudioBufferDelay = kCMTimeInvalid;
  135. }
  136. - (void)checkVideoFileAndLogIfNeeded:(NSURL *)videoURL
  137. {
  138. AVURLAsset *asset = [AVURLAsset assetWithURL:videoURL];
  139. __block BOOL hasAudioTrack = ([asset tracksWithMediaType:AVMediaTypeAudio].count > 0);
  140. dispatch_block_t block = ^{
  141. // Log no audio issues have been fixed
  142. if (hasAudioTrack) {
  143. if (_retryAudioQueueSuccess) {
  144. [SCManagedVideoNoSoundLogger logAudioSessionCategoryHaveBeenFixed];
  145. } else if (_retryAudioQueueSuccessSetDataSource) {
  146. [SCManagedVideoNoSoundLogger logAudioSessionBrokenMicHaveBeenFixed:_brokenMicCodeType];
  147. } else {
  148. [SCManagedVideoNoSoundLogger logVideoNoSoundHaveBeenFixedIfNeeded];
  149. }
  150. } else {
  151. // Log no audio issues caused by no permission into "wont_fixed_type", won't show in Grafana
  152. BOOL isPermissonGranted =
  153. [[SCAudioSession sharedInstance] recordPermission] == AVAudioSessionRecordPermissionGranted;
  154. if (!isPermissonGranted) {
  155. [SCManagedVideoNoSoundLogger increaseNoSoundCount];
  156. [[SCLogger sharedInstance]
  157. logUnsampledEvent:kSCCameraMetricsVideoNoSoundError
  158. parameters:@{
  159. @"wont_fix_type" : @"no_permission",
  160. @"no_sound_count" :
  161. [@([SCManagedVideoNoSoundLogger noSoundCount]) stringValue] ?: @"(null)",
  162. @"session_id" : [SCManagedVideoNoSoundLogger appSessionIdForNoSound] ?: @"(null)"
  163. }
  164. secretParameters:nil
  165. metrics:nil];
  166. }
  167. // Log no audio issues caused by microphone occupied into "wont_fixed_type", for example Phone Call,
  168. // It won't show in Grafana
  169. // TODO: maybe we should prompt the user of these errors in the future
  170. else if (_audioSessionError.code == AVAudioSessionErrorInsufficientPriority ||
  171. _audioQueueError.code == AVAudioSessionErrorInsufficientPriority) {
  172. NSDictionary *parameters = @{
  173. @"wont_fix_type" : @"microphone_in_use",
  174. @"asset_writer_error" : _assetWriterError ? [_assetWriterError description] : @"(null)",
  175. @"audio_session_error" : _audioSessionError.userInfo ?: @"(null)",
  176. @"audio_queue_error" : _audioQueueError.userInfo ?: @"(null)",
  177. @"audio_session_deactivated" : _isAudioSessionDeactivated ? @"true" : @"false",
  178. @"audio_session_debug_info" :
  179. [SCAudioSession sharedInstance].lastRecordingRequestDebugInfo ?: @"(null)",
  180. @"no_sound_count" : [@([SCManagedVideoNoSoundLogger noSoundCount]) stringValue] ?: @"(null)",
  181. @"session_id" : [SCManagedVideoNoSoundLogger appSessionIdForNoSound] ?: @"(null)"
  182. };
  183. [SCManagedVideoNoSoundLogger increaseNoSoundCount];
  184. [[SCLogger sharedInstance] logUnsampledEvent:kSCCameraMetricsVideoNoSoundError
  185. parameters:parameters
  186. secretParameters:nil
  187. metrics:nil];
  188. [_ticketCreator createAndFileBetaReport:JSONStringSerializeObjectForLogging(parameters)];
  189. } else {
  190. // Log other new no audio issues, use "have_been_fixed=false" to show in Grafana
  191. NSDictionary *parameters = @{
  192. @"have_been_fixed" : @"false",
  193. @"asset_writer_error" : _assetWriterError ? [_assetWriterError description] : @"(null)",
  194. @"audio_session_error" : _audioSessionError.userInfo ?: @"(null)",
  195. @"audio_queue_error" : _audioQueueError.userInfo ?: @"(null)",
  196. @"asset_writer_success" : [NSString stringWithBool:_assetWriterError == nil],
  197. @"audio_session_success" : [NSString stringWithBool:_audioSessionError == nil],
  198. @"audio_queue_success" : [NSString stringWithBool:_audioQueueError == nil],
  199. @"audio_session_deactivated" : _isAudioSessionDeactivated ? @"true" : @"false",
  200. @"video_duration" : [NSString sc_stringWithFormat:@"%f", CMTimeGetSeconds(asset.duration)],
  201. @"is_audio_session_nil" :
  202. [[SCAudioSession sharedInstance] noSoundCheckAudioSessionIsNil] ? @"true" : @"false",
  203. @"lenses_active" : [NSString stringWithBool:self.lenseActiveWhileRecording],
  204. @"active_lense_id" : self.activeLensId ?: @"(null)",
  205. @"lense_audio_resume_count" : @(_lenseResumeCount),
  206. @"first_audio_buffer_delay" :
  207. [NSString sc_stringWithFormat:@"%f", CMTimeGetSeconds(self.firstWrittenAudioBufferDelay)],
  208. @"audio_session_debug_info" :
  209. [SCAudioSession sharedInstance].lastRecordingRequestDebugInfo ?: @"(null)",
  210. @"audio_queue_started" : [NSString stringWithBool:_audioQueueStarted],
  211. @"no_sound_count" : [@([SCManagedVideoNoSoundLogger noSoundCount]) stringValue] ?: @"(null)",
  212. @"session_id" : [SCManagedVideoNoSoundLogger appSessionIdForNoSound] ?: @"(null)"
  213. };
  214. [SCManagedVideoNoSoundLogger increaseNoSoundCount];
  215. [[SCLogger sharedInstance] logUnsampledEvent:kSCCameraMetricsVideoNoSoundError
  216. parameters:parameters
  217. secretParameters:nil
  218. metrics:nil];
  219. [_ticketCreator createAndFileBetaReport:JSONStringSerializeObjectForLogging(parameters)];
  220. }
  221. }
  222. };
  223. if (hasAudioTrack) {
  224. block();
  225. } else {
  226. // Wait for all tracks to be loaded, in case of error counting the metric
  227. [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ]
  228. completionHandler:^{
  229. // Return when the tracks couldn't be loaded
  230. NSError *error = nil;
  231. if ([asset statusOfValueForKey:@"tracks" error:&error] != AVKeyValueStatusLoaded ||
  232. error != nil) {
  233. return;
  234. }
  235. // check audio track again
  236. hasAudioTrack = ([asset tracksWithMediaType:AVMediaTypeAudio].count > 0);
  237. runOnMainThreadAsynchronously(block);
  238. }];
  239. }
  240. }
  241. - (void)_audioSessionWillDeactivate
  242. {
  243. _isAudioSessionDeactivated = YES;
  244. }
  245. - (void)_audioSessionDidActivate
  246. {
  247. _isAudioSessionDeactivated = NO;
  248. }
  249. - (void)managedLensesProcessorDidCallResumeAllSounds
  250. {
  251. _lenseResumeCount += 1;
  252. }
  253. @end