音頻采集相關(guān)知識(shí)休里,AduioToolBox 的音頻采集原理,音頻隊(duì)列架構(gòu)琉朽,iOS音頻錄制的過程。
為啥莫名其妙地開始看音頻的東西稚铣?
工作相關(guān)箱叁,梳理相關(guān)模塊需要先了解原理和知識(shí)
1. 音頻采集原理
音頻的采集工作涉及諸多生疏的概念,例如編碼
惕医、格式
耕漱、緩沖
、隊(duì)列
等抬伺,首先需要將這來生疏的概念梳理清楚螟够。
音頻的采集由三階段工作組成,首先是錄音的硬件設(shè)備沛简,這部分我們不需要關(guān)心齐鲤,系統(tǒng)提供的API幫我們處理了模擬信號(hào)采集到數(shù)字信號(hào)的過程。
1.1 音頻隊(duì)列 AudioQueue
所有的音頻播放和錄制都是通過操作 AudioQueue 來完成的椒楣,通過 AudioQueueRef
這個(gè)數(shù)據(jù)結(jié)構(gòu)來表示给郊,聲明在 AudioQueue.h 中,作用如下:
- 連接音頻相關(guān)硬件
- 管理內(nèi)存
- 為不同的壓縮格式提供編碼器
- 錄制
- 播放
錄制的音頻隊(duì)列創(chuàng)建使用AudioQueueNewInput
函數(shù)捧灰,播放的隊(duì)列創(chuàng)建使用AudioQueueNewOutput
函數(shù)淆九。
實(shí)際開發(fā)需要做的是將 AudioQueue 組合其他的 CoreAduio 接口,已實(shí)現(xiàn)符合我們規(guī)范的音頻方案毛俏。
音頻隊(duì)列內(nèi)部包含三個(gè)重要的東西:
- 多個(gè)(默認(rèn)3個(gè))音頻隊(duì)列緩沖區(qū):audio queue buffers
- 一個(gè)緩沖隊(duì)列:buffer queue
- 一個(gè)用戶自己實(shí)現(xiàn)的回調(diào)函數(shù):audio queue callback function
下圖引用自Audio Queue Services Programming Guide
炭庙,展示了這三部分的工作原理
需要注意的是:
AudioQueue 向前的輸入設(shè)備,默認(rèn)使用的是用戶連接的設(shè)備煌寇,可 iOS 默認(rèn)是連接的耳機(jī)焕蹄、內(nèi)置麥克風(fēng)等。
AudioQueue 向后的 CallbackFunction 之后的行為完全是用戶自定義的阀溶,是上傳還是發(fā)送還是存儲(chǔ)到 disk 中用戶可以自己實(shí)現(xiàn)腻脏,這種場(chǎng)景非常多鸦泳,例如錄音完直接發(fā)送、錄音 app 保存到磁盤永品、噪音實(shí)時(shí)監(jiān)測(cè)不需要任何數(shù)據(jù)保存等...
1.2 音頻隊(duì)列緩沖區(qū) AudioQueueBuffer
音頻隊(duì)列緩沖區(qū)
的數(shù)據(jù)結(jié)構(gòu)做鹰,聲明于:AudioQueue.h
,如下
typedef struct AudioQueueBuffer {
const UInt32 mAudioDataBytesCapacity;
void *const mAudioData; // 指向緩沖區(qū)
UInt32 mAudioDataByteSize;
void *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;
這個(gè)結(jié)構(gòu)中的 mAudioData 指針指向的就是真正的緩沖區(qū)數(shù)據(jù)鼎姐。
1.3 緩沖區(qū)隊(duì)列 BufferQueue
緩沖區(qū)隊(duì)列就像任務(wù)隊(duì)列一樣钾麸,它是一個(gè)緩沖區(qū)的有序列表,這里我們需要知道炕桨,隊(duì)列在實(shí)際錄制中的作用饭尝。
一個(gè)音頻隊(duì)列可以使用任意數(shù)量的緩沖區(qū),但是一般來說我們使用3個(gè)就可以献宫,實(shí)際的錄制過程是這樣的:
上圖中步驟:
- 錄音設(shè)備開始用捕獲的數(shù)據(jù)填充緩沖區(qū)芋肠。
- 第一個(gè)緩沖區(qū)數(shù)據(jù)填滿之后,音頻隊(duì)列調(diào)用了 CallBack 將其交出遵蚜,然后將新的數(shù)據(jù)向下一個(gè)緩沖區(qū)進(jìn)行填充。
- 交出的緩沖區(qū)被CallBack函數(shù)處理(不一定是寫入磁盤奈惑,而已程序自定義邏輯)
- Callback 函數(shù)交回緩沖區(qū)將其重新利用吭净。
- 重復(fù)第二步。
- 重復(fù)第三步肴甸。
1.4 音頻隊(duì)列回調(diào)函數(shù) Callback function
大部分的編程工作都是在回調(diào)函數(shù)上寂殉,因?yàn)檫@里是我們編寫的錄制組件接收數(shù)據(jù)的地方,也是調(diào)用最頻繁的地方原在。
根據(jù)緩沖區(qū)的設(shè)置友扰,根據(jù)容量,會(huì)計(jì)算出調(diào)用回調(diào)函數(shù)的間隔時(shí)間庶柿,一般在半秒到幾秒之間村怪。
另外就是向上圖的過程4中記錄的那樣,我們需要將目前返回的這個(gè)緩沖區(qū)在函數(shù)結(jié)束的時(shí)候加入到緩沖區(qū)的末尾浮庐,方法是通過調(diào)用函數(shù) AudioQueueEnqueueBuffer
實(shí)現(xiàn)將緩沖區(qū)加入到緩沖區(qū)的末尾甚负。
錄制的 CallBack 和播放的 CallBack 結(jié)構(gòu)上不同,錄制音頻的 CallBack 如下:
AudioQueueInputCallback (
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumberPacketDescriptions,
const AudioStreamPacketDescription *inPacketDescs
);
各字段解釋說明如下:
- inUserData:通常是一個(gè)穿件用來保存音頻隊(duì)列和它的緩沖區(qū)狀態(tài)信息的自定義結(jié)構(gòu)审残、音頻文件(AudioFileID類型)代表寫入的文件梭域、以及音頻格式的信息
- inAQ 調(diào)用回調(diào)函數(shù)的音頻隊(duì)列
- inBuffer 進(jìn)入回調(diào)函數(shù)的剛剛被填滿的緩沖,根據(jù)inUserData中指定的格式格式化搅轿。
- inStartTime 采樣的參考時(shí)間病涨,正常的錄制不會(huì)使用這個(gè)參數(shù)
- inNumberPacketDescriptions 是inPacketDescs下一個(gè)參數(shù)中描述符的數(shù)量。加入在錄制一個(gè)VBR可變比特率音頻(variable bitrate)璧坟,音頻隊(duì)列會(huì)提供這個(gè)參數(shù)給回調(diào)函數(shù)既穆,這個(gè)參數(shù)里的值可以讓程序傳遞給AudioFileWritePackets函數(shù)赎懦。CBR(常量比特率)的錄制不使用包描述符,會(huì)將inPacketDescs設(shè)置為NULL
- inPacketDescs 一組對(duì)應(yīng)于緩沖區(qū)采樣信息的包描述符循衰。
播放音頻時(shí)的回調(diào)函數(shù):
AudioQueueOutputCallback (
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer
);
1.5 編碼和音頻數(shù)據(jù)格式
采集和播放必然涉及編碼解碼組件铲敛,這個(gè)處理是在回調(diào)函數(shù)之前的,所以回調(diào)函數(shù)不需要處理編碼解碼的過程会钝。
AudioQueue 的 AudioStreamBasicDescription 中有一個(gè)域用來描述音頻數(shù)據(jù)格式伐蒋。當(dāng)你在 MFormatID 域中指定了格式之后,音頻隊(duì)列就會(huì)使用相應(yīng)的解碼器迁酸,然后指定相應(yīng)的采樣率和聲道數(shù)先鱼。
下圖是音頻錄制中進(jìn)行音頻轉(zhuǎn)換的過程
- 程序告訴音頻隊(duì)列開始錄制,并指定數(shù)據(jù)格式
- 音頻隊(duì)列獲取新的音頻數(shù)據(jù)奸鬓,并根據(jù)指定的格式使用編解碼器對(duì)其進(jìn)行轉(zhuǎn)換焙畔。然后,音頻隊(duì)列將調(diào)用該回調(diào)串远,并將其交給包含適當(dāng)格式的音頻數(shù)據(jù)的緩沖區(qū)
- 回調(diào)將格式化的音頻數(shù)據(jù)寫入磁盤宏多。這樣回調(diào)不需要了解數(shù)據(jù)格式
2. 實(shí)現(xiàn)步驟
總覽:
- 定義一個(gè)自定義結(jié)構(gòu),包括狀態(tài)澡罚、格式伸但、緩沖區(qū)大小、保存路勁
- 編寫回調(diào)函數(shù)
- 可選:指定每個(gè)緩沖區(qū)的大小
- 填充第一步的自定義結(jié)構(gòu)
- 創(chuàng)建AudioQueue以及緩沖區(qū)留搔,以及寫入的文件
- 通知AudioQueue開始錄制
- 錄制完畢的時(shí)候通知停止錄制更胖,然后釋放它,以及釋放緩沖區(qū)
2.1 自定義結(jié)構(gòu)
static const int kNumberBuffers = 3; // 1
struct AQRecorderState {
AudioStreamBasicDescription mDataFormat; // 2
AudioQueueRef mQueue; // 3
AudioQueueBufferRef mBuffers[kNumberBuffers]; // 4
AudioFileID mAudioFile; // 5
UInt32 bufferByteSize; // 6
SInt64 mCurrentPacket; // 7
bool mIsRunning; // 8
};
使用這個(gè)結(jié)構(gòu)來管理音頻格式和音頻隊(duì)列狀態(tài)信息隔显。
- 設(shè)置使用的音頻隊(duì)列緩沖區(qū)的數(shù)量却妨,一般是3
- 一個(gè)AudioStreamBasicDescription結(jié)構(gòu)(CoreAudioTypes.h),標(biāo)識(shí)寫入磁盤的音頻數(shù)據(jù)的格式括眠,音頻隊(duì)列也會(huì)使用它來指定mQueue域彪标,mDataFormat域是由app業(yè)務(wù)測(cè)來初始化的。
- 創(chuàng)建的音頻隊(duì)列AudioQueueRef
- 音頻隊(duì)列所管理的音頻隊(duì)列緩沖區(qū)的指針數(shù)組
- 程序錄制音頻時(shí)寫入的文件的音頻文件對(duì)象
- 每個(gè)音頻隊(duì)列緩沖區(qū)的字節(jié)大小掷豺,它的值在隨后的例子中的DeriveBufferSize函數(shù)中計(jì)算出來捐下,它在音頻隊(duì)列創(chuàng)建之后,開始錄制音頻之前計(jì)算出來
- 從當(dāng)前音頻隊(duì)列緩沖區(qū)寫入文件的第一個(gè)包(packet)的索引
- 布爾值萌业,用來指示音頻隊(duì)列是否在運(yùn)行中
2.2 編寫回調(diào)函數(shù)
接下來要編寫回調(diào)函數(shù)坷襟,這個(gè)回調(diào)函數(shù)做兩件事情:
- 接受剛剛填充好的緩沖區(qū),取數(shù)據(jù)
- 將這個(gè)緩沖區(qū)返回緩沖隊(duì)列重新利用
2.2.1 回調(diào)函數(shù)聲明
在AudioQueue.h頭文件中聲明的AudioQueueInputCallback生年。
錄制用音頻隊(duì)列回調(diào)函數(shù)聲明:
static void HandleInputBuffer (
void *aqData, // 1
AudioQueueRef inAQ, // 2
AudioQueueBufferRef inBuffer, // 3
const AudioTimeStamp *inStartTime, // 4
UInt32 inNumPackets, // 5
const AudioStreamPacketDescription *inPacketDesc // 6
)
- 一般來說婴程,aqData是一個(gè)自定義的數(shù)據(jù)結(jié)構(gòu),他包含了音頻隊(duì)列的狀態(tài)信息抱婉,就像“Define a Custom Structure to Manage State.”中的一樣档叔。
- 擁有這個(gè)回調(diào)函數(shù)的音頻隊(duì)列
- 包含錄制數(shù)據(jù)的音頻隊(duì)列緩沖區(qū)
- 音頻隊(duì)列緩沖區(qū)中第一個(gè)采樣的的時(shí)間(對(duì)于簡(jiǎn)單的錄制桌粉,這個(gè)是不需要的)
- inPacketDesc域中packet descriptions的數(shù)量,如果是0衙四,表明這是個(gè)CBR數(shù)據(jù)
- 對(duì)于壓縮數(shù)據(jù)格式如果需要packet descriptions铃肯,這個(gè)packet descriptions是由編碼器產(chǎn)生的
2.2.2 緩沖區(qū)數(shù)據(jù)寫入磁盤
這個(gè)回調(diào)函數(shù)使用AudioFile.h頭文件中聲明的AudioFileWritePackets函,
AudioFileWritePackets ( // 1
pAqData->mAudioFile, // 2
false, // 3
inBuffer->mAudioDataByteSize, // 4
inPacketDesc, // 5
pAqData->mCurrentPacket, // 6
&inNumPackets, // 7
inBuffer->mAudioData // 8
);
- AudioFileWritePackets 將緩沖區(qū)的內(nèi)容寫入音頻數(shù)據(jù)文件
- pAqData表示音頻文件對(duì)象(類型為:AudioFileID)传蹈,pAqData變量是指向自定義結(jié)構(gòu)的指針
- false表示在寫入時(shí)不處理任何緩存
- 正在寫入的音頻數(shù)據(jù)的字節(jié)數(shù)押逼。該inBuffer變量表示音頻隊(duì)列傳遞給回調(diào)的音頻隊(duì)列緩沖區(qū)
- 音頻數(shù)據(jù)包描述的副本。值NULL表示不需要數(shù)據(jù)包描述(例如惦界,對(duì)于CBR音頻數(shù)據(jù))
- 要寫入的第一個(gè)數(shù)據(jù)包的數(shù)據(jù)包索引
- 輸入時(shí)挑格,要寫入的數(shù)據(jù)包數(shù)。輸出時(shí)沾歪,實(shí)際寫入的數(shù)據(jù)包數(shù)
- 將新的音頻數(shù)據(jù)寫入音頻文件
2.2.3 緩沖區(qū)入隊(duì)
使用完緩沖區(qū)的數(shù)據(jù)之后漂彤,使得緩沖區(qū)重新入隊(duì)
AudioQueueEnqueueBuffer ( // 1
pAqData->mQueue, // 2
inBuffer, // 3
0, // 4
NULL // 5
);
- 該AudioQueueEnqueueBuffer函數(shù)將音頻體重閾值添加到音頻長(zhǎng)度的閾值。
- 將指定的音頻隊(duì)列緩沖區(qū)添加到的音頻隊(duì)列
- buffer
- 音頻為音頻數(shù)據(jù)的數(shù)據(jù)中的數(shù)據(jù)包描述數(shù)灾搏。設(shè)置為挫望,0因?yàn)榇藚?shù)未使用記錄
- 數(shù)據(jù)包描述摘要,描述音頻編碼附件的數(shù)據(jù)狂窑。設(shè)置為士骤,NULL因?yàn)榇藚?shù)未使用記錄
2.2.4 完整的 CallBack
static void HandleInputBuffer (
void *aqData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc
) {
AQRecorderState *pAqData = (AQRecorderState *) aqData; // 1
if (inNumPackets == 0 && // 2
pAqData->mDataFormat.mBytesPerPacket != 0)
inNumPackets =
inBuffer->mAudioDataByteSize / pAqData->mDataFormat.mBytesPerPacket;
if (AudioFileWritePackets ( // 3
pAqData->mAudioFile,
false,
inBuffer->mAudioDataByteSize,
inPacketDesc,
pAqData->mCurrentPacket,
&inNumPackets,
inBuffer->mAudioData
) == noErr) {
pAqData->mCurrentPacket += inNumPackets; // 4
}
if (pAqData->mIsRunning == 0) // 5
return;
AudioQueueEnqueueBuffer ( // 6
pAqData->mQueue,
inBuffer,
0,
NULL
);
}
- 自定義結(jié)構(gòu)的實(shí)例化
- 數(shù)據(jù)包的數(shù)量
- 緩沖區(qū)的內(nèi)容寫入文件
- 如果成功寫入數(shù)據(jù),增加音頻數(shù)據(jù)文件的數(shù)據(jù)包index值蕾域,準(zhǔn)備寫入下一個(gè)
- 如果已經(jīng)停止就返回
- 把緩沖區(qū)入隊(duì)
2.3 計(jì)算音頻緩沖區(qū)大小
音頻隊(duì)列服務(wù)要求應(yīng)用程序?yàn)槟褂玫囊纛l隊(duì)列緩沖區(qū)指定大小。下面的函數(shù)展示了一種方法到旦。
它衍生出一個(gè)足夠大的緩沖區(qū)來保存給定的音頻數(shù)據(jù)旨巷。
這里的計(jì)算考慮了要錄制的音頻數(shù)據(jù)格式。格式包括可能影響緩沖區(qū)大小的所有因素添忘,例如音頻通道的數(shù)量采呐。
void DeriveBufferSize (
AudioQueueRef audioQueue, // 1
AudioStreamBasicDescription &ASBDescription, // 2
Float64 seconds, // 3
UInt32 *outBufferSize // 4
) {
static const int maxBufferSize = 0x50000; // 5
int maxPacketSize = ASBDescription.mBytesPerPacket; // 6
if (maxPacketSize == 0) { // 7
UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
AudioQueueGetProperty (
audioQueue,
kAudioQueueProperty_MaximumOutputPacketSize,
// 在 Mac OS X v10.5 應(yīng)該使用:
// kAudioConverterPropertyMaximumOutputPacketSize
&maxPacketSize,
&maxVBRPacketSize
);
}
Float64 numBytesForTime =
ASBDescription.mSampleRate * maxPacketSize * seconds; // 8
*outBufferSize =
UInt32 (numBytesForTime < maxBufferSize ?
numBytesForTime : maxBufferSize); // 9
}
- 需要操作的這個(gè)音頻隊(duì)列
- 音頻隊(duì)列的AudioStreamBasicDescription結(jié)構(gòu)
- 為每個(gè)音頻隊(duì)列緩沖區(qū)指定的大小,以音頻的秒數(shù)為單位
- 輸出時(shí)搁骑,每個(gè)音頻隊(duì)列緩沖區(qū)的大小斧吐,以字節(jié)為單位
- 音頻隊(duì)列緩沖區(qū)大小的上限,以字節(jié)為單位仲器。在本例中煤率,上限設(shè)置為320 KB。這相當(dāng)于大約5秒鐘的立體聲乏冀、24位音頻蝶糯,采樣率為96 kHz
- 對(duì)于CBR音頻數(shù)據(jù),從AudioStreamBasicDescription結(jié)構(gòu)中獲攘韭佟(常量)數(shù)據(jù)包大小昼捍。使用此值作為最大數(shù)據(jù)包大小识虚。該賦值的副作用是確定要記錄的音頻數(shù)據(jù)是CBR還是VBR。如果是VBR妒茬,則音頻隊(duì)列的AudioStreamBasicDescription結(jié)構(gòu)將每個(gè)數(shù)據(jù)包的字節(jié)值列為0
- 對(duì)于VBR音頻數(shù)據(jù)担锤,查詢音頻隊(duì)列以獲得估計(jì)的最大數(shù)據(jù)包大小
- 派生緩沖區(qū)大小(以字節(jié)為單位)
- 如果需要乍钻,將緩沖區(qū)大小限制為先前設(shè)置的上限
2.4 特殊格式元數(shù)據(jù)的處理
一些壓縮的音頻格式肛循,需要用包含音頻元數(shù)據(jù)的結(jié)構(gòu),稱之為 Magic Cookies团赁,稱之為饃干育拨。如果需要錄制這種格式的音頻文件,必須先處理好 MC 數(shù)據(jù)結(jié)構(gòu)欢摄,先從音頻隊(duì)列中獲取MC熬丧,然后將其添加到音頻文件中,再進(jìn)行錄制的操作怀挠。
下面這個(gè)函數(shù)就是從音頻中途獲取 MC 的信息析蝴,并將其替換為音頻文件,代碼需要在錄制之前就調(diào)用此類函數(shù)绿淋,然后錄制之后再調(diào)用闷畸,某些編碼解碼器會(huì)在錄制停止的時(shí)候更新MC數(shù)據(jù)。
OSStatus SetMagicCookieForFile (
AudioQueueRef inQueue, // 1
AudioFileID inFile // 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
}
- 正在用于錄制的音頻隊(duì)列吞滞。
- 你正在錄制的音頻文件佑菩。
- 指示此函數(shù)成功或失敗的結(jié)果變量。
- 一個(gè)變量來保存神奇的cookie數(shù)據(jù)大小裁赠。
- 從音頻隊(duì)列獲取magic cookie的數(shù)據(jù)大小殿漠,并將其存儲(chǔ)在cookieSize變量中。
- 分配一個(gè)字節(jié)數(shù)組來保存魔法cookie信息佩捞。
- 通過查詢音頻隊(duì)列的kaudioqueproperty\u MagicCookie屬性獲取magic cookie绞幌。
- 設(shè)置要錄制到的音頻文件的魔法cookie。AudioFileSetProperty函數(shù)在AudioFile.h頭文件中聲明一忱。
- 釋放臨時(shí)cookie變量的內(nèi)存莲蜘。
- 返回此函數(shù)的成功或失敗。
2.5 設(shè)置音頻格式進(jìn)行錄制
如何為音頻體制設(shè)置音頻數(shù)據(jù)格式帘营。音頻格式使用此格式記錄到文件票渠。
要設(shè)置音頻數(shù)據(jù)格式,需要指定:
- 音頻數(shù)據(jù)格式類型(例如線性PCM芬迄,AAC等)
- 采樣率(例如44.1 kHz)
- 音頻通道數(shù)(例如2庄新,用于立體聲)
- 位深度(例如16位)
- 每個(gè)包的幀數(shù)(例如,線性PCM每包使用一幀)
- 音頻文件類型(例如,CAF择诈,AIFF等)
- 文件類型所需的音頻數(shù)據(jù)格式的詳細(xì)信息
下面這段代碼說明了如何設(shè)置錄音的音頻格式械蹋,為每個(gè)屬性使用固定的選項(xiàng)。
在產(chǎn)品代碼中羞芍,通常允許用戶指定音頻格式的某些或所有方面哗戈。
無論哪種方法,目標(biāo)都是填充 AQRecorderState 自定義結(jié)構(gòu)的 mDataFormat 字段荷科。
AQRecorderState aqData; // 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
AudioFileTypeID fileType = kAudioFileAIFFType; // 8
aqData.mDataFormat.mFormatFlags = // 9
kLinearPCMFormatFlagIsBigEndian
| kLinearPCMFormatFlagIsSignedInteger
| kLinearPCMFormatFlagIsPacked;
- 創(chuàng)建AQRecorderState自定義結(jié)構(gòu)的實(shí)例唯咬。結(jié)構(gòu)的mDataFormat字段包含AudioStreamBasicDescription結(jié)構(gòu)。mDataFormat字段中設(shè)置的值提供音頻隊(duì)列的音頻格式的初始定義畏浆,該音頻隊(duì)列也是您錄制到的文件的音頻格式胆胰。在清單2-10中,您獲得了一個(gè)更完整的音頻格式規(guī)范刻获,核心音頻根據(jù)格式類型和文件類型提供給您
- 將音頻數(shù)據(jù)格式類型定義為線性PCM蜀涨。有關(guān)可用數(shù)據(jù)格式的完整列表,請(qǐng)參見核心音頻數(shù)據(jù)類型參考
- 將采樣率定義為44.1 kHz
- 將通道數(shù)定義為2
- 將每個(gè)通道的位深度定義為16
- 將每個(gè)數(shù)據(jù)包的字節(jié)數(shù)和每幀的字節(jié)數(shù)定義為4(即蝎毡,每個(gè)采樣2個(gè)通道乘以2個(gè)字節(jié))
- 將每個(gè)數(shù)據(jù)包的幀數(shù)定義為1
- 將文件類型定義為AIFF厚柳。有關(guān)可用文件類型的完整列表,請(qǐng)參見AudioFile.h頭文件中的音頻文件類型枚舉沐兵”鹂澹可以指定已安裝編解碼器的任何文件類型,如使用編解碼器和音頻數(shù)據(jù)格式中所述
- 設(shè)置指定文件類型所需的格式標(biāo)志
2.6 創(chuàng)建音頻隊(duì)列
2.6.1 創(chuàng)建音頻隊(duì)列
創(chuàng)建好了 Callback 和 音頻格式 之后扎谎,就可以創(chuàng)建用于錄制的音頻隊(duì)列了碳想,創(chuàng)建的時(shí)候回使用到前面步驟配置好的回調(diào)、自定義結(jié)構(gòu)和音頻數(shù)據(jù)格式毁靶。
AudioQueueNewInput ( // 1
&aqData.mDataFormat, // 2
HandleInputBuffer, // 3
&aqData, // 4
NULL, // 5
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8
);
- AudioQueueNewInput 函數(shù)創(chuàng)建一個(gè)新的錄制音頻隊(duì)列
- 音頻數(shù)據(jù)格式
- 回調(diào)函數(shù)
- 自定義數(shù)據(jù)結(jié)構(gòu)
- 調(diào)用回調(diào)的運(yùn)行循環(huán)胧奔。使用 NULL 指定默認(rèn)行為,其中回調(diào)將在音頻隊(duì)列內(nèi)部的線程上調(diào)用老充。這是一個(gè)典型的用法,它允許音頻隊(duì)列在應(yīng)用程序的用戶界面線程等待用戶輸入停止錄制時(shí)進(jìn)行錄制
- 可以調(diào)用回調(diào)的運(yùn)行循環(huán)模式螟左。通常使用 kCFRunLoopCommonModes 常量
- 保留啡浊。必須為0
- 輸出時(shí),新分配的錄制音頻隊(duì)列
2.6.2 從音頻隊(duì)列中獲取完整的音頻格式
音頻隊(duì)列中可能比 AudioStreamBasicDescription 結(jié)構(gòu)更完整胶背,尤其是對(duì)于壓縮格式巷嚣。要獲得完整的格式描述,調(diào)用 AudioQueueGetProperty 函數(shù)钳吟。創(chuàng)建要錄制到的音頻文件時(shí)使用完整的音頻格式廷粒。
UInt32 dataFormatSize = sizeof (aqData.mDataFormat); // 1
AudioQueueGetProperty ( // 2
aqData.mQueue, // 3
kAudioQueueProperty_StreamDescription, // 4
// in Mac OS X, instead use
// kAudioConverterCurrentInputStreamDescription
&aqData.mDataFormat, // 5
&dataFormatSize // 6
);
- 獲取在查詢音頻隊(duì)列的音頻數(shù)據(jù)格式時(shí)要使用的預(yù)期屬性值大小
- AudioQueueGetProperty 函數(shù)獲取音頻隊(duì)列中指定屬性的值
- 從中獲取音頻數(shù)據(jù)格式的音頻隊(duì)列
- 用于獲取音頻隊(duì)列數(shù)據(jù)格式值的屬性ID
- 輸出時(shí),以 AudioStreamBasicDescription 結(jié)構(gòu)的形式從音頻隊(duì)列中獲取的完整音頻數(shù)據(jù)格式
- 輸入時(shí),AudioStreamBasicDescription 結(jié)構(gòu)的預(yù)期大小坝茎。輸出時(shí)涤姊,實(shí)際大小。錄制應(yīng)用程序不需要使用此值
2.7 創(chuàng)建音頻文件
音頻數(shù)據(jù)記錄到這個(gè)文件中嗤放,需要使用自定義結(jié)構(gòu)中的文件格式和文件格式規(guī)范
CFURLRef audioFileURL =
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
);
- CFURLCreateFromFileSystemRepresentation函數(shù)在CFURL.h頭文件中聲明思喊,它創(chuàng)建一個(gè)CFURL對(duì)象,表示要記錄到的文件次酌。
- 使用NULL(或kCFAllocatorDefault)使用當(dāng)前默認(rèn)內(nèi)存分配器恨课。
- 要轉(zhuǎn)換為CFURL對(duì)象的文件系統(tǒng)路徑。在生產(chǎn)代碼中岳服,通常會(huì)從用戶處獲取filePath的值剂公。
- 文件系統(tǒng)路徑中的字節(jié)數(shù)。
- 值false表示filePath表示文件吊宋,而不是目錄纲辽。
- AudioFile.h頭文件中的AudioFileCreateWithURL函數(shù)創(chuàng)建新的音頻文件或初始化現(xiàn)有文件。
- 創(chuàng)建新音頻文件或在現(xiàn)有文件的情況下初始化的URL贫母。URL是從步驟1中的CFURLCreateFromFileSystemRepresentation派生的文兑。
- 新文件的文件類型。在本章的示例代碼中腺劣,這是以前通過kAudioFileAIFFType文件類型常量設(shè)置為AIFF的绿贞。請(qǐng)參見設(shè)置錄音的音頻格式。
- 將記錄到文件中的音頻的數(shù)據(jù)格式橘原,指定為AudioStreamBasicDescription結(jié)構(gòu)籍铁。在本章的示例代碼中,還設(shè)置了錄音的音頻格式趾断。
- 如果文件已經(jīng)存在拒名,則刪除該文件。
- 輸出時(shí)芋酌,表示要錄制到的音頻文件的音頻文件對(duì)象(AudioFileID類型)增显。
2.8 設(shè)置音頻緩沖區(qū)Size
DeriveBufferSize ( // 1
aqData.mQueue, // 2
aqData.mDataFormat, // 3
0.5, // 4
&aqData.bufferByteSize // 5
);
- 調(diào)用前面聲明的函數(shù),計(jì)算緩沖區(qū)大小
- 正在為其設(shè)置緩沖區(qū)大小的音頻隊(duì)列
- 正在錄制的文件的音頻數(shù)據(jù)格式脐帝。請(qǐng)參見設(shè)置錄音的音頻格式
- 每個(gè)音頻隊(duì)列緩沖區(qū)應(yīng)保留的音頻秒數(shù)同云。這里設(shè)置的半秒通常是一個(gè)不錯(cuò)的選擇
- 輸出時(shí),每個(gè)音頻隊(duì)列緩沖區(qū)的大小堵腹,以字節(jié)為單位炸站。此值放置在音頻隊(duì)列的自定義結(jié)構(gòu)中
2.9 準(zhǔn)備好一組音頻緩沖區(qū)
讓音頻隊(duì)列準(zhǔn)備好一組音頻緩沖區(qū)。
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
);
}
- 循環(huán)緩沖區(qū)的數(shù)量疚顷,分配并入隊(duì)每個(gè)音頻隊(duì)列緩沖區(qū)
- AudioQueueAllocateBuffer函數(shù)要求音頻隊(duì)列分配音頻隊(duì)列緩沖區(qū)
- 執(zhí)行分配并擁有緩沖區(qū)的音頻隊(duì)列
- 正在分配的新音頻隊(duì)列緩沖區(qū)的大泻狄住(字節(jié))禁偎。請(qǐng)參閱編寫函數(shù)以導(dǎo)出錄音音頻隊(duì)列緩沖區(qū)大小
- 輸出時(shí),新分配的音頻隊(duì)列緩沖區(qū)阀坏。指向緩沖區(qū)的指針放置在音頻隊(duì)列使用的自定義結(jié)構(gòu)中
- audioqueuenbuffer函數(shù)將音頻隊(duì)列緩沖區(qū)添加到緩沖區(qū)隊(duì)列的末尾
- 要向其添加緩沖區(qū)的音頻隊(duì)列
- 正在排隊(duì)的音頻隊(duì)列緩沖區(qū)
- 將緩沖區(qū)入隊(duì)時(shí)如暖,此參數(shù)未使用
- 將緩沖區(qū)入隊(duì)時(shí),此參數(shù)未使用
2.10 錄制
前面都已經(jīng)準(zhǔn)備好全释,錄制的時(shí)候就會(huì)非常簡(jiǎn)單:
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
- 將數(shù)據(jù)包索引初始化為0装处,以便在音頻文件開始時(shí)開始錄制。
- 在自定義結(jié)構(gòu)中設(shè)置標(biāo)志浸船,以指示音頻隊(duì)列正在運(yùn)行妄迁。錄制音頻隊(duì)列回調(diào)使用此標(biāo)志。
- AudioQueueStart函數(shù)在自己的線程上啟動(dòng)音頻隊(duì)列李命。
- 要啟動(dòng)的音頻隊(duì)列登淘。
- 使用NULL表示音頻隊(duì)列應(yīng)立即開始錄制。
- AudioQueueStop函數(shù)停止并重置錄制音頻隊(duì)列封字。
- 要停止的音頻隊(duì)列黔州。
- 使用true使用同步停止。有關(guān)同步和異步停止的說明阔籽,請(qǐng)參閱音頻隊(duì)列控制和狀態(tài)流妻。
- 在自定義結(jié)構(gòu)中設(shè)置標(biāo)志,以指示音頻隊(duì)列未運(yùn)行笆制。
2.11 善后首位
善后工作:
AudioQueueDispose ( // 1
aqData.mQueue, // 2
true // 3
);
AudioFileClose (aqData.mAudioFile); // 4
- AudioQueueDispose 處理音頻隊(duì)列和所有相關(guān)的資源绅这,包括所有緩沖區(qū)
- 需要處理的音頻隊(duì)列
- true 表示 synchronously 立即回收
- 管理用于錄制的文件,這個(gè) AudioFileClose 函數(shù)在 AudioFile.h 頭文件中聲明