From 09e5602464b2b5e2cbbef62962aad68e9597e197 Mon Sep 17 00:00:00 2001 From: Daniel Cohen Gindi Date: Wed, 17 Dec 2014 16:07:58 +0200 Subject: [PATCH] Allow recording a specific DataSource to AAC-LC --- StreamingKit/StreamingKit/STKAudioPlayer.m | 248 ++++++++++++++++++++- StreamingKit/StreamingKit/STKDataSource.h | 1 + 2 files changed, 246 insertions(+), 3 deletions(-) diff --git a/StreamingKit/StreamingKit/STKAudioPlayer.m b/StreamingKit/StreamingKit/STKAudioPlayer.m index ba695a5..4cdb5a1 100755 --- a/StreamingKit/StreamingKit/STKAudioPlayer.m +++ b/StreamingKit/StreamingKit/STKAudioPlayer.m @@ -63,6 +63,9 @@ #define STK_DEFAULT_PACKET_BUFFER_SIZE (2048) #define STK_DEFAULT_GRACE_PERIOD_AFTER_SEEK_SECONDS (0.5) +#define OSSTATUS_PRINTF_PLACEHOLDER @"%c%c%c%c" +#define OSSTATUS_PRINTF_VALUE(status) ((status) >> 24) & 0xFF, ((status) >> 16) & 0xFF, ((status) >> 8) & 0xFF, (status) & 0xFF + #define LOGINFO(x) [self logInfo:[NSString stringWithFormat:@"%s %@", sel_getName(_cmd), x]]; static void PopulateOptionsWithDefault(STKAudioPlayerOptions* options) @@ -173,6 +176,7 @@ static AudioComponentDescription nbandUnitDescription; static AudioComponentDescription outputUnitDescription; static AudioComponentDescription convertUnitDescription; static AudioStreamBasicDescription canonicalAudioStreamBasicDescription; +static AudioStreamBasicDescription recordAudioStreamBasicDescription; @interface STKAudioPlayer() { @@ -241,7 +245,16 @@ static AudioStreamBasicDescription canonicalAudioStreamBasicDescription; AudioFileStreamID audioFileStream; NSConditionLock* threadStartedLock; NSConditionLock* threadFinishedCondLock; - + + AudioFileID recordAudioFileId; + UInt32 recordFilePacketPosition; + AudioConverterRef recordAudioConverterRef; + UInt32 recordOutputBufferSize; + UInt8 *recordOutputBuffer; + UInt32 recordPacketsPerBuffer; + UInt32 recordPacketSize; + AudioStreamPacketDescription *recordPacketDescriptions; + void(^stopBackBackgroundTaskBlock)(); int32_t seekVersion; @@ -311,7 +324,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn .mBitsPerChannel = 8 * bytesPerSample, .mBytesPerPacket = (bytesPerSample * 2) }; - + outputUnitDescription = (AudioComponentDescription) { .componentType = kAudioUnitType_Output, @@ -542,6 +555,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn OSSpinLockUnlock(¤tEntryReferencesLock); } + [self closeRecordAudioFile]; + [self stopAudioUnitWithReason:STKAudioPlayerStopReasonDisposed]; [self clearQueue]; @@ -1099,6 +1114,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn [currentlyReadingEntry.dataSource registerForEvents:[NSRunLoop currentRunLoop]]; [currentlyReadingEntry.dataSource seekToOffset:0]; + [self closeRecordAudioFile]; + if (startPlaying) { if (clearQueue) @@ -1401,6 +1418,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn currentlyReadingEntry = nil; OSSpinLockUnlock(¤tEntryReferencesLock); pthread_mutex_unlock(&playerMutex); + + [self closeRecordAudioFile]; self.internalState = STKAudioPlayerInternalStateDisposed; @@ -1463,6 +1482,11 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn AudioConverterReset(audioConverterRef); } + if (recordAudioConverterRef) + { + AudioConverterReset(recordAudioConverterRef); + } + [currentEntry reset]; [currentEntry.dataSource seekToOffset:seekByteOffset]; @@ -1581,7 +1605,9 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn } NSObject* queueItemId = currentlyReadingEntry.queueItemId; - + + [self closeRecordAudioFile]; + [self dispatchSyncOnMainThread:^ { [self.delegate audioPlayer:self didFinishBufferingSourceWithQueueItemId:queueItemId]; @@ -1706,6 +1732,8 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn return; } + [self closeRecordAudioFile]; + [self stopAudioUnitWithReason:STKAudioPlayerStopReasonUserAction]; [self resetPcmBuffers]; @@ -1800,6 +1828,35 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn self.muted = NO; } +-(void) closeRecordAudioFile +{ + if (recordAudioFileId) + { + AudioFileClose(recordAudioFileId); + recordAudioFileId = NULL; + } + + if (recordAudioConverterRef) + { + AudioConverterDispose(recordAudioConverterRef); + recordAudioConverterRef = nil; + } + + if (recordOutputBuffer) + { + free(recordOutputBuffer); + recordOutputBuffer = NULL; + } + + if (recordPacketDescriptions) + { + free(recordPacketDescriptions); + recordPacketDescriptions = NULL; + } + + recordFilePacketPosition = 0; +} + -(void) dispose { [self stop]; @@ -1878,6 +1935,11 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl { AudioConverterReset(audioConverterRef); + if (recordAudioConverterRef) + { + AudioConverterReset(recordAudioConverterRef); + } + return; } @@ -1885,6 +1947,25 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl canonicalAudioStreamBasicDescription.mChannelsPerFrame = asbd->mChannelsPerFrame; + BOOL isRecording = currentlyReadingEntry.dataSource.recordToFileUrl != nil; + if (isRecording) + { + recordAudioStreamBasicDescription = (AudioStreamBasicDescription) + { + .mFormatID = kAudioFormatMPEG4AAC, + .mFormatFlags = kMPEG4Object_AAC_LC, + .mChannelsPerFrame = canonicalAudioStreamBasicDescription.mChannelsPerFrame, + .mSampleRate = canonicalAudioStreamBasicDescription.mSampleRate, + }; + + UInt32 dataSize = sizeof(recordAudioStreamBasicDescription); + AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, + 0, + NULL, + &dataSize, + &recordAudioStreamBasicDescription); + } + AudioClassDescription classDesc; if (GetHardwareCodecClassDesc(asbd->mFormatID, &classDesc)) @@ -1903,6 +1984,16 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl return; } } + + if (isRecording && !recordAudioConverterRef) + { + status = AudioConverterNew(&canonicalAudioStreamBasicDescription, &recordAudioStreamBasicDescription, &recordAudioConverterRef); + + if (status) + { + NSLog(@"STKAudioPlayer failed to create a recording audio converter"); + } + } audioConverterAudioStreamBasicDescription = *asbd; @@ -1933,6 +2024,82 @@ static BOOL GetHardwareCodecClassDesc(UInt32 formatId, AudioClassDescription* cl return; } } + + if (recordAudioConverterRef) + { + if (recordAudioFileId) + { + AudioFileClose(recordAudioFileId); + recordAudioFileId = NULL; + } + + if (recordOutputBuffer) + { + free(recordOutputBuffer); + recordOutputBuffer = NULL; + } + + if (recordPacketDescriptions) + { + free(recordPacketDescriptions); + recordPacketDescriptions = NULL; + } + + recordOutputBufferSize = 32 * 1024; + recordPacketSize = canonicalAudioStreamBasicDescription.mBytesPerPacket; + + if (recordPacketSize == 0) + { + UInt32 size = sizeof(recordPacketSize); + if (0 == AudioConverterGetProperty(recordAudioConverterRef, kAudioConverterPropertyMaximumOutputPacketSize, &size, &recordPacketSize)) + { + if (recordPacketSize > recordOutputBufferSize) + { + recordOutputBufferSize = recordPacketSize; + } + + recordPacketsPerBuffer = recordOutputBufferSize / recordPacketSize; + } + else + { + AudioConverterDispose(recordAudioConverterRef); + recordAudioConverterRef = NULL; + + NSLog(@"STKAudioPlayer: Can't support this output format for recording"); + } + } + else + { + recordPacketsPerBuffer = recordOutputBufferSize / recordPacketSize; + } + + UInt32 propertySize = sizeof(UInt32); + UInt32 externallyFramed = 0; + OSStatus error = AudioFormatGetProperty(kAudioFormatProperty_FormatIsExternallyFramed, sizeof(recordAudioStreamBasicDescription), &recordAudioStreamBasicDescription, &propertySize, &externallyFramed); + + if (externallyFramed) + { + recordPacketDescriptions = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription) * recordPacketsPerBuffer); + } + + recordOutputBuffer = (UInt8 *)malloc(sizeof(UInt8) * recordOutputBufferSize); + + error = AudioFileCreateWithURL( + (__bridge CFURLRef)(currentlyReadingEntry.dataSource.recordToFileUrl), + kAudioFileCAFType, + &recordAudioStreamBasicDescription, + kAudioFileFlags_EraseFile, + &recordAudioFileId); + + recordFilePacketPosition = 0; + + if (error) + { + NSLog(@"STKAudioPlayer failed to create a recording audio file at %@", currentlyReadingEntry.dataSource.recordToFileUrl); + + [self closeRecordAudioFile]; + } + } } -(void) createOutputUnit @@ -2424,6 +2591,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu status = AudioConverterFillComplexBuffer(audioConverterRef, AudioConverterCallback, (void*)&convertInfo, &framesToDecode, &localPcmBufferList, NULL); framesAdded = framesToDecode; + + if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef) + { + [self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]]; + } if (status == 100) { @@ -2467,6 +2639,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu framesAdded += framesToDecode; + if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef) + { + [self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]]; + } + if (status == 100) { OSSpinLockLock(&pcmBufferSpinLock); @@ -2511,6 +2688,11 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu framesAdded = framesToDecode; + if ((status == 100 || status == 0) && recordAudioFileId && recordAudioConverterRef) + { + [self handleRecordingOfAudioPackets:framesToDecode audioBuffer:&localPcmBufferList.mBuffers[0]]; + } + if (status == 100) { OSSpinLockLock(&pcmBufferSpinLock); @@ -2545,6 +2727,66 @@ OSStatus AudioConverterCallback(AudioConverterRef inAudioConverter, UInt32* ioNu } } +- (void)handleRecordingOfAudioPackets:(UInt32)numberOfPackets audioBuffer:(AudioBuffer *)audioBuffer +{ + if (recordAudioFileId && recordAudioConverterRef) + { + AudioConvertInfo recordConvertInfo; + recordConvertInfo.done = NO; + recordConvertInfo.numberOfPackets = numberOfPackets; + recordConvertInfo.packetDescriptions = NULL; + recordConvertInfo.audioBuffer = *audioBuffer; + + AudioBufferList convertedData; + convertedData.mNumberBuffers = 1; + convertedData.mBuffers[0].mNumberChannels = recordAudioStreamBasicDescription.mChannelsPerFrame; + convertedData.mBuffers[0].mDataByteSize = recordOutputBufferSize; + convertedData.mBuffers[0].mData = recordOutputBuffer; + + UInt32 ioOutputDataPackets; + OSStatus status; + + while (1) + { + ioOutputDataPackets = recordPacketsPerBuffer; + + status = AudioConverterFillComplexBuffer(recordAudioConverterRef, AudioConverterCallback, (void*)&recordConvertInfo, &ioOutputDataPackets, &convertedData, recordPacketDescriptions); + + if (status == 100 || status == 0) + { + if (ioOutputDataPackets > 0) + { + OSStatus writeError = AudioFileWritePackets(recordAudioFileId, + NO, + convertedData.mBuffers[0].mDataByteSize, + recordPacketDescriptions, + recordFilePacketPosition, + &ioOutputDataPackets, + convertedData.mBuffers[0].mData); + + if (writeError) + { + NSLog(@"STKAudioPlayer:handleRecordingOfAudioPackets failed on AudioFileWritePackets with error \"" OSSTATUS_PRINTF_PLACEHOLDER "\"", OSSTATUS_PRINTF_VALUE(writeError)); + } + else + { + recordFilePacketPosition += ioOutputDataPackets; + } + } + } + else + { + NSLog(@"STKAudioPlayer: Unexpected error during recording audio file conversion"); + } + + if (status == 100) + { + break; + } + } + } +} + static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { STKAudioPlayer* audioPlayer = (__bridge STKAudioPlayer*)inRefCon; diff --git a/StreamingKit/StreamingKit/STKDataSource.h b/StreamingKit/StreamingKit/STKDataSource.h index 2565f5d..8ca4150 100755 --- a/StreamingKit/StreamingKit/STKDataSource.h +++ b/StreamingKit/StreamingKit/STKDataSource.h @@ -51,6 +51,7 @@ @property (readonly) BOOL hasBytesAvailable; @property (nonatomic, readwrite, assign) double durationHint; @property (readwrite, unsafe_unretained) id delegate; +@property (nonatomic, strong) NSURL *recordToFileUrl; -(BOOL) registerForEvents:(NSRunLoop*)runLoop; -(void) unregisterForEvents;