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.

289 lines
11 KiB

  1. //
  2. // SCAudioCaptureSession.m
  3. // Snapchat
  4. //
  5. // Created by Liu Liu on 3/5/15.
  6. // Copyright (c) 2015 Snapchat, Inc. All rights reserved.
  7. //
  8. #import "SCAudioCaptureSession.h"
  9. #import <SCAudio/SCAudioSession.h>
  10. #import <SCFoundation/SCLog.h>
  11. #import <SCFoundation/SCQueuePerformer.h>
  12. #import <SCFoundation/SCTrace.h>
  13. #import <mach/mach.h>
  14. #import <mach/mach_time.h>
  15. @import AVFoundation;
  16. double const kSCAudioCaptureSessionDefaultSampleRate = 44100;
  17. NSString *const SCAudioCaptureSessionErrorDomain = @"SCAudioCaptureSessionErrorDomain";
  18. static NSInteger const kNumberOfAudioBuffersInQueue = 15;
  19. static float const kAudioBufferDurationInSeconds = 0.2;
  20. static char *const kSCAudioCaptureSessionQueueLabel = "com.snapchat.audio-capture-session";
  21. @implementation SCAudioCaptureSession {
  22. SCQueuePerformer *_performer;
  23. AudioQueueRef _audioQueue;
  24. AudioQueueBufferRef _audioQueueBuffers[kNumberOfAudioBuffersInQueue];
  25. CMAudioFormatDescriptionRef _audioFormatDescription;
  26. }
  27. @synthesize delegate = _delegate;
  28. - (instancetype)init
  29. {
  30. SCTraceStart();
  31. self = [super init];
  32. if (self) {
  33. _performer = [[SCQueuePerformer alloc] initWithLabel:kSCAudioCaptureSessionQueueLabel
  34. qualityOfService:QOS_CLASS_USER_INTERACTIVE
  35. queueType:DISPATCH_QUEUE_SERIAL
  36. context:SCQueuePerformerContextCamera];
  37. }
  38. return self;
  39. }
  40. - (void)dealloc
  41. {
  42. [self disposeAudioRecordingSynchronouslyWithCompletionHandler:NULL];
  43. }
  44. static AudioStreamBasicDescription setupAudioFormat(UInt32 inFormatID, Float64 sampleRate)
  45. {
  46. SCTraceStart();
  47. AudioStreamBasicDescription recordFormat = {0};
  48. recordFormat.mSampleRate = sampleRate;
  49. recordFormat.mChannelsPerFrame = (UInt32)[SCAudioSession sharedInstance].inputNumberOfChannels;
  50. recordFormat.mFormatID = inFormatID;
  51. if (inFormatID == kAudioFormatLinearPCM) {
  52. // if we want pcm, default to signed 16-bit little-endian
  53. recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
  54. recordFormat.mBitsPerChannel = 16;
  55. recordFormat.mBytesPerPacket = recordFormat.mBytesPerFrame =
  56. (recordFormat.mBitsPerChannel / 8) * recordFormat.mChannelsPerFrame;
  57. recordFormat.mFramesPerPacket = 1;
  58. }
  59. return recordFormat;
  60. }
  61. static int computeRecordBufferSize(const AudioStreamBasicDescription *format, const AudioQueueRef audioQueue,
  62. float seconds)
  63. {
  64. SCTraceStart();
  65. int packets, frames, bytes = 0;
  66. frames = (int)ceil(seconds * format->mSampleRate);
  67. if (format->mBytesPerFrame > 0) {
  68. bytes = frames * format->mBytesPerFrame;
  69. } else {
  70. UInt32 maxPacketSize;
  71. if (format->mBytesPerPacket > 0)
  72. maxPacketSize = format->mBytesPerPacket; // constant packet size
  73. else {
  74. UInt32 propertySize = sizeof(maxPacketSize);
  75. AudioQueueGetProperty(audioQueue, kAudioQueueProperty_MaximumOutputPacketSize, &maxPacketSize,
  76. &propertySize);
  77. }
  78. if (format->mFramesPerPacket > 0)
  79. packets = frames / format->mFramesPerPacket;
  80. else
  81. packets = frames; // worst-case scenario: 1 frame in a packet
  82. if (packets == 0) // sanity check
  83. packets = 1;
  84. bytes = packets * maxPacketSize;
  85. }
  86. return bytes;
  87. }
  88. static NSTimeInterval machHostTimeToSeconds(UInt64 mHostTime)
  89. {
  90. static dispatch_once_t onceToken;
  91. static mach_timebase_info_data_t timebase_info;
  92. dispatch_once(&onceToken, ^{
  93. (void)mach_timebase_info(&timebase_info);
  94. });
  95. return (double)mHostTime * timebase_info.numer / timebase_info.denom / NSEC_PER_SEC;
  96. }
  97. static void audioQueueBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
  98. const AudioTimeStamp *nStartTime, UInt32 inNumPackets,
  99. const AudioStreamPacketDescription *inPacketDesc)
  100. {
  101. SCTraceStart();
  102. SCAudioCaptureSession *audioCaptureSession = (__bridge SCAudioCaptureSession *)inUserData;
  103. if (inNumPackets > 0) {
  104. CMTime PTS = CMTimeMakeWithSeconds(machHostTimeToSeconds(nStartTime->mHostTime), 600);
  105. [audioCaptureSession appendAudioQueueBuffer:inBuffer
  106. numPackets:inNumPackets
  107. PTS:PTS
  108. packetDescriptions:inPacketDesc];
  109. }
  110. AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
  111. }
  112. - (void)appendAudioQueueBuffer:(AudioQueueBufferRef)audioQueueBuffer
  113. numPackets:(UInt32)numPackets
  114. PTS:(CMTime)PTS
  115. packetDescriptions:(const AudioStreamPacketDescription *)packetDescriptions
  116. {
  117. SCTraceStart();
  118. CMBlockBufferRef dataBuffer = NULL;
  119. CMBlockBufferCreateWithMemoryBlock(NULL, NULL, audioQueueBuffer->mAudioDataByteSize, NULL, NULL, 0,
  120. audioQueueBuffer->mAudioDataByteSize, 0, &dataBuffer);
  121. if (dataBuffer) {
  122. CMBlockBufferReplaceDataBytes(audioQueueBuffer->mAudioData, dataBuffer, 0,
  123. audioQueueBuffer->mAudioDataByteSize);
  124. CMSampleBufferRef sampleBuffer = NULL;
  125. CMAudioSampleBufferCreateWithPacketDescriptions(NULL, dataBuffer, true, NULL, NULL, _audioFormatDescription,
  126. numPackets, PTS, packetDescriptions, &sampleBuffer);
  127. if (sampleBuffer) {
  128. [self processAudioSampleBuffer:sampleBuffer];
  129. CFRelease(sampleBuffer);
  130. }
  131. CFRelease(dataBuffer);
  132. }
  133. }
  134. - (void)processAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
  135. {
  136. SCTraceStart();
  137. [_delegate audioCaptureSession:self didOutputSampleBuffer:sampleBuffer];
  138. }
  139. - (NSError *)_generateErrorForType:(NSString *)errorType
  140. errorCode:(int)errorCode
  141. format:(AudioStreamBasicDescription)format
  142. {
  143. NSDictionary *errorInfo = @{
  144. @"error_type" : errorType,
  145. @"error_code" : @(errorCode),
  146. @"record_format" : @{
  147. @"format_id" : @(format.mFormatID),
  148. @"format_flags" : @(format.mFormatFlags),
  149. @"sample_rate" : @(format.mSampleRate),
  150. @"bytes_per_packet" : @(format.mBytesPerPacket),
  151. @"frames_per_packet" : @(format.mFramesPerPacket),
  152. @"bytes_per_frame" : @(format.mBytesPerFrame),
  153. @"channels_per_frame" : @(format.mChannelsPerFrame),
  154. @"bits_per_channel" : @(format.mBitsPerChannel)
  155. }
  156. };
  157. SCLogGeneralInfo(@"Audio queue error occured. ErrorInfo: %@", errorInfo);
  158. return [NSError errorWithDomain:SCAudioCaptureSessionErrorDomain code:errorCode userInfo:errorInfo];
  159. }
  160. - (NSError *)beginAudioRecordingWithSampleRate:(Float64)sampleRate
  161. {
  162. SCTraceStart();
  163. if ([SCAudioSession sharedInstance].inputAvailable) {
  164. // SCAudioSession should be activated already
  165. SCTraceSignal(@"Set audio session to be active");
  166. AudioStreamBasicDescription recordFormat = setupAudioFormat(kAudioFormatLinearPCM, sampleRate);
  167. OSStatus audioQueueCreationStatus = AudioQueueNewInput(&recordFormat, audioQueueBufferHandler,
  168. (__bridge void *)self, NULL, NULL, 0, &_audioQueue);
  169. if (audioQueueCreationStatus != 0) {
  170. NSError *error = [self _generateErrorForType:@"audio_queue_create_error"
  171. errorCode:audioQueueCreationStatus
  172. format:recordFormat];
  173. return error;
  174. }
  175. SCTraceSignal(@"Initialize audio queue with new input");
  176. UInt32 bufferByteSize = computeRecordBufferSize(
  177. &recordFormat, _audioQueue, kAudioBufferDurationInSeconds); // Enough bytes for half a second
  178. for (int i = 0; i < kNumberOfAudioBuffersInQueue; i++) {
  179. AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioQueueBuffers[i]);
  180. AudioQueueEnqueueBuffer(_audioQueue, _audioQueueBuffers[i], 0, NULL);
  181. }
  182. SCTraceSignal(@"Allocate audio buffer");
  183. UInt32 size = sizeof(recordFormat);
  184. audioQueueCreationStatus =
  185. AudioQueueGetProperty(_audioQueue, kAudioQueueProperty_StreamDescription, &recordFormat, &size);
  186. if (0 != audioQueueCreationStatus) {
  187. NSError *error = [self _generateErrorForType:@"audio_queue_get_property_error"
  188. errorCode:audioQueueCreationStatus
  189. format:recordFormat];
  190. [self disposeAudioRecording];
  191. return error;
  192. }
  193. SCTraceSignal(@"Audio queue sample rate %lf", recordFormat.mSampleRate);
  194. AudioChannelLayout acl;
  195. bzero(&acl, sizeof(acl));
  196. acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
  197. audioQueueCreationStatus = CMAudioFormatDescriptionCreate(NULL, &recordFormat, sizeof(acl), &acl, 0, NULL, NULL,
  198. &_audioFormatDescription);
  199. if (0 != audioQueueCreationStatus) {
  200. NSError *error = [self _generateErrorForType:@"audio_queue_audio_format_error"
  201. errorCode:audioQueueCreationStatus
  202. format:recordFormat];
  203. [self disposeAudioRecording];
  204. return error;
  205. }
  206. SCTraceSignal(@"Start audio queue");
  207. audioQueueCreationStatus = AudioQueueStart(_audioQueue, NULL);
  208. if (0 != audioQueueCreationStatus) {
  209. NSError *error = [self _generateErrorForType:@"audio_queue_start_error"
  210. errorCode:audioQueueCreationStatus
  211. format:recordFormat];
  212. [self disposeAudioRecording];
  213. return error;
  214. }
  215. }
  216. return nil;
  217. }
  218. - (void)disposeAudioRecording
  219. {
  220. SCTraceStart();
  221. SCLogGeneralInfo(@"dispose audio recording");
  222. if (_audioQueue) {
  223. AudioQueueStop(_audioQueue, true);
  224. AudioQueueDispose(_audioQueue, true);
  225. for (int i = 0; i < kNumberOfAudioBuffersInQueue; i++) {
  226. _audioQueueBuffers[i] = NULL;
  227. }
  228. _audioQueue = NULL;
  229. }
  230. if (_audioFormatDescription) {
  231. CFRelease(_audioFormatDescription);
  232. _audioFormatDescription = NULL;
  233. }
  234. }
  235. #pragma mark - Public methods
  236. - (void)beginAudioRecordingAsynchronouslyWithSampleRate:(double)sampleRate
  237. completionHandler:(audio_capture_session_block)completionHandler
  238. {
  239. SCTraceStart();
  240. // Request audio session change for recording mode.
  241. [_performer perform:^{
  242. SCTraceStart();
  243. NSError *error = [self beginAudioRecordingWithSampleRate:sampleRate];
  244. if (completionHandler) {
  245. completionHandler(error);
  246. }
  247. }];
  248. }
  249. - (void)disposeAudioRecordingSynchronouslyWithCompletionHandler:(dispatch_block_t)completionHandler
  250. {
  251. SCTraceStart();
  252. [_performer performAndWait:^{
  253. SCTraceStart();
  254. [self disposeAudioRecording];
  255. if (completionHandler) {
  256. completionHandler();
  257. }
  258. }];
  259. }
  260. @end