? ? 前幾篇文章提到過使用audiotool來實現錄音,其中所使用的就是audio queue來具體的實現其中的相關功能。這篇文章就詳細的講一下,如何使用audio queue來進行錄音。
其實說起來很簡單,總共有七步:
????1.自定義一個結構體來管理錄音的狀態,音頻的格式、路徑等各種信息。
????2.寫一個audio queue的回調函數來實現真正的錄音。
? ? 3.寫代碼來計算出audio queue的緩存的大小、處理音頻的magic cookie。
????4.填充我們自定義的結構體.
????5.創建一個錄音的audio queue并且讓他創建一系列的緩存.并且創建一個因為文件來寫入。
? ? 6.告訴audio queue開始錄音。
????7.錄音結束后,告訴audio queue停止,并且清理他的緩存。
下面我們就結合蘋果的官方示例SpeakHere來一步一步的講解和實現:
1.自定義一個結構體。
#definekNumberRecordBuffers3//1
class AQRecorder
{
????UInt64startTime;
????private:
????CFStringRefmFileName;//2
????AudioQueueRefmQueue;//3
????AudioQueueBufferRefmBuffers[kNumberRecordBuffers];//4
????AudioFileIDmRecordFile;//5
????SInt64mRecordPacket;//6
????CAStreamBasicDescriptionmRecordFormat;//7
????BooleanmIsRunning;//8
};
下面詳細解釋一下各個字段的含義:
????1.設置audio queue的緩存的大小
????2.錄音文件的名稱
????3.App所創建的錄音audioqueue
? ? 4.Audio queue的緩存數組
????5.你錄制的音頻所要寫入的文件對象
????6.當前audio queue的緩存所要寫入文件的第一個包的序號
????7.AudioStreamBasicDescription表示音頻文件的各種參數和格式,他會在mQueue中被使用到。
????8.表示是否正在錄音。
2.寫一個audio queue的回調函數來實現真正的錄音。
????這個回調做兩件事情:
????· 將已經被寫滿的audio queue的緩存數據寫到相應的錄音文件中
????· 將這個剛剛寫入到文件中的audio queue的緩存返回給audio queue的緩存數組
????錄音audio queue的回調的定義:
????void AQRecorder::MyInputBufferHandler(void *inUserData,\\1
????????AudioQueueRefinAQ,\\2
????????AudioQueueBufferRefinBuffer,\\3
????????constAudioTimeStamp *inStartTime,\\4
????????UInt32inNumPackets,\\5
????????constAudioStreamPacketDescription*inPacketDesc)\\6
其中的參數含義:
????1.通常他就是我們定義的這個錄音的類AQRecorder
????2.擁有這個回調的audio queue
????3.這個回調所對應的audio queue的緩存,包含著已經錄制好的音頻
????4.緩存中的音頻數據的抽樣時間
????5.在參數inPacketDesc中描述的包的數量
????6.對于壓縮的音頻數據保存時需要AudioStreamPacketDescription,這個數據結構是由緩存中的編碼器來提供的。
將audioqueue的一個緩存數據保存到文件中
? ? 錄音audio queue的第一個任務就是將緩存audio queue的緩存數據寫入到磁盤中。這個緩存中的數據就是從音頻輸入設備剛剛獲取到的并寫入到緩存中的。這個回調中會使用AudioFileWritePackets這個方法,這個方法的定義如下:
????AudioFileWritePackets (// 1
????????pAqData->mRecordFile,// 2
????????false,// 3
????????inBuffer->mAudioDataByteSize,// 4
????????inPacketDesc,// 5
????????pAqData->mCurrentPacket,//6
????????&inNumPackets,// 7
????????inBuffer->mAudioData// 8
????);
?????1. 這個方法將緩存中的數據寫入到磁盤中
?????2. 這個表示將要寫入的音頻文件對象。
????3. False表示不需要緩存需要寫入的數據
????4. 需要寫入的音頻數據的大小。inBuffer表示的是audioqueue回調中傳入的緩存對象。
????5. 一個數組,數組中的每個元素是對每個音頻數據包的描述。NULL意味著不需要包描述(對于CBR來說就是不需要的)
????6. 需要寫入的第一個包的序號
? ? 7. 需要寫入的包的數量
????8.需要寫入單磁盤上的新的音頻數據
將Audio Queue的緩存重新返回給audio queue
? ? 現在緩存中所有的音頻數據已經寫入到磁盤中,需要將緩存重新傳遞給audio queue。這樣audio queue就可以繼續使用他來接收新的數據。
????AudioQueueEnqueueBuffer(// 1
????????pAqData->mQueue,// 2
????????inBuffer,// 3
????????0,// 4
????????NULL// 5
????);
????1. AudioQueueEnqueueBuffer將一個audio queue的緩存加入到audio queue的緩存隊列中
????2.緩存將要加入到的audio queue
????3. 將要返回的緩存
????4. audio queue的緩存數據中的包描述的數量,設置為0是因為他在錄音中是沒有用到的。
????5. 包描述的數組,用來描述錄音數據的audio queue緩存數據的。NULL是因為他在錄音中沒有被用到。
一個完整的錄音audio queue的回調的實現
// AudioQueue callback function, called when an input buffers hasbeen filled.
void AQRecorder::MyInputBufferHandler(void *inUserData,
????AudioQueueRefinAQ,
????AudioQueueBufferRefinBuffer,
????constAudioTimeStamp *inStartTime,
????UInt32inNumPackets,
????constAudioStreamPacketDescription*inPacketDesc)
{
????AQRecorder *aqr =(AQRecorder *)inUserData;
????try {
????????if(inNumPackets > 0) {
????????// writepackets to file
????????XThrowIfError(AudioFileWritePackets(aqr->mRecordFile,FALSE, inBuffer->mAudioDataByteSize,
inPacketDesc, aqr->mRecordPacket,&inNumPackets, inBuffer->mAudioData),
"AudioFileWritePackets failed");
????????aqr->mRecordPacket+= inNumPackets;
????}
????// if we're notstopping, re-enqueue the buffe so that it gets filled again
????if(aqr->IsRunning())
????????XThrowIfError(AudioQueueEnqueueBuffer(inAQ,inBuffer, 0, NULL), "AudioQueueEnqueueBuffer failed");
????} catch (CAXExceptione) {
????char buf[256];
????fprintf(stderr,"Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
}
}
????1. 獲取我們傳遞進去的類
? ? 2. 如果需要寫入的包的數量超過0,將包的數據寫入到文件中
????3. 如果寫入成功,增加已經寫入的音頻包的序號
????4. 如果audio queue正在運行中,將緩存交還給audio queue加入到緩存隊列中
寫一個函數來計算audio queue的緩存的大小
????Audio Queue服務希望你的應用可以提供一個你需要使用的audio queue的緩存的大小。下面的方法提供了如何來提供一個足夠大的緩存來保存指定時間長度內的音頻數據。計算的時候需要考慮你所錄制的音頻的格式。它包含了所有能夠影響大小的因素,包括音頻通道的數量等等。
int AQRecorder::ComputeRecordBufferSize(constAudioStreamBasicDescription *format, float seconds)
{
????int packets, frames,bytes = 0;
????try {
????????frames =(int)ceil(seconds * format->mSampleRate);
????????if(format->mBytesPerFrame > 0)
????????????bytes =frames * format->mBytesPerFrame;
????????else {
????????????UInt32maxPacketSize;
????????????if(format->mBytesPerPacket > 0)
????????????????maxPacketSize= format->mBytesPerPacket;//constant packet size
????????else {
????????????UInt32propertySize = sizeof(maxPacketSize);
????????????XThrowIfError(AudioQueueGetProperty(mQueue,kAudioQueueProperty_MaximumOutputPacketSize, &maxPacketSize,
????????????&propertySize), "couldn't get queue'smaximum output packet size");
????????}
????????if(format->mFramesPerPacket > 0)
????????????packets= frames / format->mFramesPerPacket;
????????else
????????????packets= frames;// worst-case scenario: 1frame in a packet
????????if(packets == 0)// sanitycheck
????????????packets= 1;
????????bytes =packets * maxPacketSize;
????????}
????} catch (CAXExceptione) {
????????char buf[256];
????????fprintf(stderr,"Error: %s (%s)\n", e.mOperation, e.FormatError(buf));
????????return 0;
????}
????return bytes;
}
計算的主要方式是:
????1. 首先設定每個緩存需要存儲的音頻的長度。
????2. 計算給定的時間內音頻所占用的空間的計算方法:
????????a.如果可以獲取到幀的大小:
????????????時間*每秒鐘的采樣率*每一幀的大小,在蘋果的文檔中,將mSampleRate就定義為每秒鐘采樣到的幀的數目
????????b.如果可以獲取到package大小:
????????????時間*(每秒鐘的采樣率/每package中Frame的數量)*每package的大小
????????c.以上的這些方法對于CRV是使用的,因為每個package中的frame數量,每個frame的大小都是相同的。但是對于其他的可能就有問題,就需要采用如下的形式獲取。如果獲取不到package的大小:
????????????通過AudioQueueGetProperty獲取他的kAudioQueueProperty_MaximumOutputPacketSize得大小,然后使用公式:
????????????時間*(每秒鐘的采樣率/每package中Frame的數量)*每package的最大的大小
????????d.實際上還有一種方式來計算規定的時間內錄制的音頻的數據大小:
????????????時間*采樣率*采樣聲道數*聲道的采樣位數
給音頻文件增加MagicCookies
????一些壓縮的音頻格式,例如MPEG 4 AAC會使用一些結構來保存文件信息保存在文件頭中,這些結構被叫做magic cookies。當你使用audio queue服務來錄制音頻的時候,你必須在錄制開始之前從audio queue中獲取這些信息,并且把他加到文件頭中。
????下面展示了如何獲取magic cookie并把他加入到文件中,并且在錄制結束以后,你也可能需要更新magic cookie中的信息。
OSStatusSetMagicCookieForFile (
? ? AudioQueueRef inQueue,// 1
????AudioFileIDinFile// 2
) {
????OSStatus result = noErr;// 3
????UInt32 cookieSize;// 4
????if (
????????AudioQueueGetPropertySize (// 5
????????????inQueue,
????????????kAudioQueueProperty_MagicCookie,
????????????&cookieSize
????????) == noErr
????) {
????????????char* magicCookie =
????????????(char *) malloc (cookieSize);// 6
????????if (
????????????AudioQueueGetProperty (// 7
????????????????inQueue,
????????????????kAudioQueueProperty_MagicCookie,
????????????????magicCookie,
????????????????&cookieSize
????????????) == noErr
????????)
????????result =AudioFileSetProperty (// 8
????????????inFile,
????????????kAudioFilePropertyMagicCookieData,
????????????cookieSize,
????????????magicCookie
????????);
????????free (magicCookie);// 9
????}
????return result;// 10
}
? ? 1.錄音所使用的audio queue.
????2.音頻要寫入的文件.
????3. 一個返回值,代表著寫入是否成功.
????4. 持有magic cookie數據大小的變量.
????5.從audio queue獲取magic cookie的大小并保存到變量cookieSize.
????6. 開辟內存空間來持有magic cookie信息.
????7. 通過查詢audio queue的kAudioQueueProperty_MagicCookie屬性來獲得magic cookie.
????8. 將magic cookie寫入到你的錄音文件中.AudioFileSetProperty方法在AudioFile.h文件中聲明.
????9. 釋放cookie變量所占有的控件.
????10. 返回這個方法是否成功.
設置錄音的音頻格式
????這一部分描述你如何為audio queue設置錄音的音頻格式。這個audio queue使用這個設置來錄進音頻。為了設置音頻格式,你需要設置:
????· 音頻數據的格式(例如linear PCM, AAC,等等.)
????· 采樣頻率(例如:44.1 kHz)
????· 音頻的聲道數量(例如對于多媒體是2)
????· 位深(例如16bits)
????· 每一個package所包含的Fram數量(對于linear PCM,每一個package里面包含一個frame)
????· 音頻文件的格式(例如CAF, AIFF等)
????· 其他的音頻文件格式所需要的音頻設置
????下面設置了一個音頻的屬性來進行錄音。在實際的代碼中,你可能需要允許用戶來設置其中的很多屬性,而不是寫死在代碼里面。兩種方式都是為了填充mDataFormat的AQRecorderState字段。
????AQRecorderStateaqData;// 1
????aqData.mDataFormat.mFormatID= kAudioFormatLinearPCM; // 2
????aqData.mDataFormat.mSampleRate= 44100.0;// 3
????aqData.mDataFormat.mChannelsPerFrame= 2;// 4
????aqData.mDataFormat.mBitsPerChannel= 16;// 5
????aqData.mDataFormat.mBytesPerPacket=// 6
????aqData.mDataFormat.mBytesPerFrame=
????aqData.mDataFormat.mChannelsPerFrame *sizeof (SInt16);
????aqData.mDataFormat.mFramesPerPacket= 1;// 7
????AudioFileTypeIDfileType=kAudioFileAIFFType;// 8
????aqData.mDataFormat.mFormatFlags=// 9
????????kLinearPCMFormatFlagIsBigEndian
????????| kLinearPCMFormatFlagIsSignedInteger
????????| kLinearPCMFormatFlagIsPacked;
????1. 創建自定義的結構體AQRecorderState的實例,他的mDataFormat中包含AudioStreamBasicDescription結構體.mDataFormat為audio queue提供了一個初始的音頻文件格式的設置和音頻文件的保存格式的設置.
????2.設置音頻的格式為linear PCM.
????3.設置采樣率為44.1 kHz.
????4.設置聲道的數目為2.
????5.設置每個聲道的位深為16.
????6.設置每個package的大小,每個frame的大小為4(2個聲道乘以每個采樣的位深2).
????7. 設置每個package中包含的frame的數量為1.
????8. 設置文件類型為AIFF.
????9. 為每個特定的文件類型設置特定的參數
創建一個錄音audioqueue
現在設置完錄音的回調和音頻的格式,你可以創建并設置一個audio queue。
創建一個audioqueue
????AudioQueueNewInput(// 1
????????&aqData.mDataFormat,// 2
????????HandleInputBuffer,// 3
????????&aqData,// 4
????????NULL,// 5
????????kCFRunLoopCommonModes,// 6
????????0,//7
????????&aqData.mQueue// 8
);
????1. AudioQueueNewInput方法創建一個新的錄音audioqueue.
????2.所需要錄制的音頻數據格式
????3. 錄音audio queue所使用的回調
????4. 錄音audio queue所使用的自定義結構體。
????5. 這個回調會在哪個runloop上被回調.NULL表示默認會在和audio queue同一個線程中被回調.通常我們都使用這個設置—它允許你的audio queue錄音的同時,用戶可以手動的在界面上停止錄音
????6. 回調所在的runloop的模式.通常使用kCFRunLoopCommonModes
? ? 7. 保留.必須是0.
????8. 新創建的audio queue.
從audio queue獲取完整的音頻格式
????當audio queue已經被創建,通常他都會比你填充AudioStreamBasicDescription中更多的參數。為了獲取參數,你可以調用AudioQueueGetProperty,
????UInt32dataFormatSize = sizeof (aqData.mDataFormat);// 1
????AudioQueueGetProperty(// 2
????aqData.mQueue,// 3
????kAudioQueueProperty_StreamDescription,// 4
????&aqData.mDataFormat,// 5
????&dataFormatSize// 6
);
????1. 從audio queue中獲取你想要知道的屬性的大小
????2. AudioQueueGetProperty方法從audio queue中獲取你想要知道的特定的屬性的值
????3. 你想要獲取屬性的audio queue.
????4. 你想要獲取的屬性的ID.
????5. 從audio queue獲取的完整的音頻格式,以AudioStreamBasicDescription格式輸出
????6. 你所想要獲取的AudioStreamBasicDescription結構體的大小.
創建一個音頻文件
創建并設置完audio queue之后,你需要創建一個音頻文件將音頻寫入進去。這個文件會使用你前面的各種設置來創建。
CFURLRefaudioFileURL =
????CFURLCreateFromFileSystemRepresentation(// 1
????????NULL,//2
????????(const UInt8 *) filePath,// 3
????????strlen (filePath),// 4
????????false//5
????);
????AudioFileCreateWithURL(// 6
????????audioFileURL,// 7
????????fileType,// 8
????????&aqData.mDataFormat,// 9
????????kAudioFileFlags_EraseFile,// 10
????????&aqData.mAudioFile// 11
????);
????1. CFURLCreateFromFileSystemRepresentation方法創建一個CFURL object表示你將要錄制的文件
????2. 使用NULL來使用默認的內存分配方式.
????3. 你希望創建的文件的路徑.
????4. 文件路徑的長度.
????5.False表示filePath是一個文件而不是目錄
????6. AudioFileCreateWithURL方法創建一個新的音頻文件如果已經存在就初始化已經存在的文件.
????7. 所需要創建的新的音頻文件的URL.這個URL可以從第一步的CFURLCreateFromFileSystemRepresentation中獲取到.
????8. 新文件的文件格式.在這個例子中已經在前面的代碼中被設置為kAudioFileAIFFType
????9.一個AudioStreamBasicDescription結構體,表示的你你將要寫入的音頻的格式.
????10. 如果這個文件已經存在,就刪除他.
? ? 11. 音頻文件對象,你將要錄制的
設置audioqueue的緩存大小
????你可以使用我們上文提到的方法ComputeRecordBufferSize來計算audio queue的緩存大小。
ComputeRecordBufferSize(// 1
????aqData.mQueue,// 2
????aqData.mDataFormat,// 3
????0.5,// 4
????&aqData.bufferByteSize// 5
);
????1. ComputeRecordBufferSize方法來計算緩存的大小.
????2. 你所要設置的audio queue.
????3. 你所要設置的音頻格式
????4. 每一個音頻緩存所應該保存的音頻的長度。0.5是一個通常很好的值。
????5. 每一個audio queue的緩存的大小
準備一系列的緩存
????現在你告訴audio queue你已經準備好創建一系列的緩存。
????for(int i = 0; i < kNumberBuffers; ++i) {// 1
????????AudioQueueAllocateBuffer (// 2
????????aqData.mQueue,// 3
????????aqData.bufferByteSize,// 4
????????&aqData.mBuffers[i]// 5
????);
????AudioQueueEnqueueBuffer (// 6
????????aqData.mQueue,// 7
????????aqData.mBuffers[i],// 8
????????0,// 9
????????NULL// 10
????);
}
????1.[endif]循環的給每一個audio queue的緩存,分配空間并插入到緩存隊列中.
????2. AudioQueueAllocateBuffer告訴audio queue來分配一個audio queue的緩存控件.
????3. 需要這個空間的audio queue.
????4. 需要創建的緩存的大小
????5. 新創建的緩存
? ? 6. AudioQueueEnqueueBuffer將緩存插入到緩存隊列的尾部
????7. 你所要操作的audio queue.
????8. 你想要插入的audio queue緩存
????9. 在創建錄音緩存的時候無用.
????10. 在創建錄音緩存的時候無用.
錄制音頻
????上面準備的代碼可以讓你很輕松的錄制音頻。
????aqData.mCurrentPacket= 0;// 1
????aqData.mIsRunning= true;// 2
????AudioQueueStart(// 3
????????aqData.mQueue,// 4
????????NULL//5
????);
????//Wait, on user interface thread, until user stops the recording
????AudioQueueStop(//6
????????aqData.mQueue,// 7
????????true//8
????);
????aqData.mIsRunning= false;// 9
????1 初始化包的序列號為0來開始錄制一個音頻文件
????2. 設置一個標志位來表示正在錄音.這個會在回調中被使用
????3. AudioQueueStart在自己的線程中開始錄音
????4. 所要開始的audio queue.
????5. NULL說明audio queue應該立即開始錄音.
????6. AudioQueueStop停止并重置audioqueue.
????7. 要停止的audio queue.
????8. True表示同步停止
????9. 設置標志位表示錄制已經結束.
錄音完成之后的清理
????AudioQueueDispose(// 1
????????aqData.mQueue,// 2
????????true//3
????);
????AudioFileClose(aqData.mAudioFile);// 4
????1. AudioQueueDispose銷毀audio queue和他的資源包括各種緩存
????2. 你要銷毀的audio queue.
????3. True同步的銷毀.
????4. 關閉錄制的音頻文件。