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.

243 lines
7.3 KiB

  1. //
  2. // SCFileAudioCaptureSession.m
  3. // Snapchat
  4. //
  5. // Created by Xiaomu Wu on 2/2/17.
  6. // Copyright © 2017 Snapchat, Inc. All rights reserved.
  7. //
  8. #import "SCFileAudioCaptureSession.h"
  9. #import <SCFoundation/SCAssertWrapper.h>
  10. #import <SCFoundation/SCLog.h>
  11. #import <SCFoundation/SCQueuePerformer.h>
  12. #import <SCFoundation/SCSentinel.h>
  13. @import AudioToolbox;
  14. static float const kAudioBufferDurationInSeconds = 0.2; // same as SCAudioCaptureSession
  15. static char *const kSCFileAudioCaptureSessionQueueLabel = "com.snapchat.file-audio-capture-session";
  16. @implementation SCFileAudioCaptureSession {
  17. SCQueuePerformer *_performer;
  18. SCSentinel *_sentinel;
  19. NSURL *_fileURL;
  20. AudioFileID _audioFile; // audio file
  21. AudioStreamBasicDescription _asbd; // audio format (core audio)
  22. CMAudioFormatDescriptionRef _formatDescription; // audio format (core media)
  23. SInt64 _readCurPacket; // current packet index to read
  24. UInt32 _readNumPackets; // number of packets to read every time
  25. UInt32 _readNumBytes; // number of bytes to read every time
  26. void *_readBuffer; // data buffer to hold read packets
  27. }
  28. @synthesize delegate = _delegate;
  29. #pragma mark - Public
  30. - (instancetype)init
  31. {
  32. self = [super init];
  33. if (self) {
  34. _performer = [[SCQueuePerformer alloc] initWithLabel:kSCFileAudioCaptureSessionQueueLabel
  35. qualityOfService:QOS_CLASS_UNSPECIFIED
  36. queueType:DISPATCH_QUEUE_SERIAL
  37. context:SCQueuePerformerContextCamera];
  38. _sentinel = [[SCSentinel alloc] init];
  39. }
  40. return self;
  41. }
  42. - (void)dealloc
  43. {
  44. if (_audioFile) {
  45. AudioFileClose(_audioFile);
  46. }
  47. if (_formatDescription) {
  48. CFRelease(_formatDescription);
  49. }
  50. if (_readBuffer) {
  51. free(_readBuffer);
  52. }
  53. }
  54. - (void)setFileURL:(NSURL *)fileURL
  55. {
  56. [_performer perform:^{
  57. _fileURL = fileURL;
  58. }];
  59. }
  60. #pragma mark - SCAudioCaptureSession
  61. - (void)beginAudioRecordingAsynchronouslyWithSampleRate:(double)sampleRate // `sampleRate` ignored
  62. completionHandler:(audio_capture_session_block)completionHandler
  63. {
  64. [_performer perform:^{
  65. BOOL succeeded = [self _setup];
  66. int32_t sentinelValue = [_sentinel value];
  67. if (completionHandler) {
  68. completionHandler(nil);
  69. }
  70. if (succeeded) {
  71. [_performer perform:^{
  72. SC_GUARD_ELSE_RETURN([_sentinel value] == sentinelValue);
  73. [self _read];
  74. }
  75. after:kAudioBufferDurationInSeconds];
  76. }
  77. }];
  78. }
  79. - (void)disposeAudioRecordingSynchronouslyWithCompletionHandler:(dispatch_block_t)completionHandler
  80. {
  81. [_performer performAndWait:^{
  82. [self _teardown];
  83. if (completionHandler) {
  84. completionHandler();
  85. }
  86. }];
  87. }
  88. #pragma mark - Private
  89. - (BOOL)_setup
  90. {
  91. SCAssert([_performer isCurrentPerformer], @"");
  92. [_sentinel increment];
  93. OSStatus status = noErr;
  94. status = AudioFileOpenURL((__bridge CFURLRef)_fileURL, kAudioFileReadPermission, 0, &_audioFile);
  95. if (noErr != status) {
  96. SCLogGeneralError(@"Cannot open file at URL %@, error code %d", _fileURL, (int)status);
  97. return NO;
  98. }
  99. _asbd = (AudioStreamBasicDescription){0};
  100. UInt32 asbdSize = sizeof(_asbd);
  101. status = AudioFileGetProperty(_audioFile, kAudioFilePropertyDataFormat, &asbdSize, &_asbd);
  102. if (noErr != status) {
  103. SCLogGeneralError(@"Cannot get audio data format, error code %d", (int)status);
  104. AudioFileClose(_audioFile);
  105. _audioFile = NULL;
  106. return NO;
  107. }
  108. if (kAudioFormatLinearPCM != _asbd.mFormatID) {
  109. SCLogGeneralError(@"Linear PCM is required");
  110. AudioFileClose(_audioFile);
  111. _audioFile = NULL;
  112. _asbd = (AudioStreamBasicDescription){0};
  113. return NO;
  114. }
  115. UInt32 aclSize = 0;
  116. AudioChannelLayout *acl = NULL;
  117. status = AudioFileGetPropertyInfo(_audioFile, kAudioFilePropertyChannelLayout, &aclSize, NULL);
  118. if (noErr == status) {
  119. acl = malloc(aclSize);
  120. status = AudioFileGetProperty(_audioFile, kAudioFilePropertyChannelLayout, &aclSize, acl);
  121. if (noErr != status) {
  122. aclSize = 0;
  123. free(acl);
  124. acl = NULL;
  125. }
  126. }
  127. status = CMAudioFormatDescriptionCreate(NULL, &_asbd, aclSize, acl, 0, NULL, NULL, &_formatDescription);
  128. if (acl) {
  129. free(acl);
  130. acl = NULL;
  131. }
  132. if (noErr != status) {
  133. SCLogGeneralError(@"Cannot create format description, error code %d", (int)status);
  134. AudioFileClose(_audioFile);
  135. _audioFile = NULL;
  136. _asbd = (AudioStreamBasicDescription){0};
  137. return NO;
  138. }
  139. _readCurPacket = 0;
  140. _readNumPackets = ceil(_asbd.mSampleRate * kAudioBufferDurationInSeconds);
  141. _readNumBytes = _asbd.mBytesPerPacket * _readNumPackets;
  142. _readBuffer = malloc(_readNumBytes);
  143. return YES;
  144. }
  145. - (void)_read
  146. {
  147. SCAssert([_performer isCurrentPerformer], @"");
  148. OSStatus status = noErr;
  149. UInt32 numBytes = _readNumBytes;
  150. UInt32 numPackets = _readNumPackets;
  151. status = AudioFileReadPacketData(_audioFile, NO, &numBytes, NULL, _readCurPacket, &numPackets, _readBuffer);
  152. if (noErr != status) {
  153. SCLogGeneralError(@"Cannot read audio data, error code %d", (int)status);
  154. return;
  155. }
  156. if (0 == numPackets) {
  157. return;
  158. }
  159. CMTime PTS = CMTimeMakeWithSeconds(_readCurPacket / _asbd.mSampleRate, 600);
  160. _readCurPacket += numPackets;
  161. CMBlockBufferRef dataBuffer = NULL;
  162. status = CMBlockBufferCreateWithMemoryBlock(NULL, NULL, numBytes, NULL, NULL, 0, numBytes, 0, &dataBuffer);
  163. if (kCMBlockBufferNoErr == status) {
  164. if (dataBuffer) {
  165. CMBlockBufferReplaceDataBytes(_readBuffer, dataBuffer, 0, numBytes);
  166. CMSampleBufferRef sampleBuffer = NULL;
  167. CMAudioSampleBufferCreateWithPacketDescriptions(NULL, dataBuffer, true, NULL, NULL, _formatDescription,
  168. numPackets, PTS, NULL, &sampleBuffer);
  169. if (sampleBuffer) {
  170. [_delegate audioCaptureSession:self didOutputSampleBuffer:sampleBuffer];
  171. CFRelease(sampleBuffer);
  172. }
  173. CFRelease(dataBuffer);
  174. }
  175. } else {
  176. SCLogGeneralError(@"Cannot create data buffer, error code %d", (int)status);
  177. }
  178. int32_t sentinelValue = [_sentinel value];
  179. [_performer perform:^{
  180. SC_GUARD_ELSE_RETURN([_sentinel value] == sentinelValue);
  181. [self _read];
  182. }
  183. after:kAudioBufferDurationInSeconds];
  184. }
  185. - (void)_teardown
  186. {
  187. SCAssert([_performer isCurrentPerformer], @"");
  188. [_sentinel increment];
  189. if (_audioFile) {
  190. AudioFileClose(_audioFile);
  191. _audioFile = NULL;
  192. }
  193. _asbd = (AudioStreamBasicDescription){0};
  194. if (_formatDescription) {
  195. CFRelease(_formatDescription);
  196. _formatDescription = NULL;
  197. }
  198. _readCurPacket = 0;
  199. _readNumPackets = 0;
  200. _readNumBytes = 0;
  201. if (_readBuffer) {
  202. free(_readBuffer);
  203. _readBuffer = NULL;
  204. }
  205. }
  206. @end