Overwrite
個(gè)人理解的名詞解釋:
- Audio Unit:音頻單元,通常指一個(gè)音頻單元實(shí)例,或者 Audio Unit 技術(shù)。
- Audio Component:音頻組件,指音頻單元(Audio Unit)類的類型。
- Audio Processing Graph:音頻處理圖,即音頻圖。
- Audio Node:音頻結(jié)點(diǎn),在音頻處理圖中擔(dān)任音頻單元的標(biāo)識(shí),在音頻處理圖執(zhí)行打開操作后間接實(shí)例化對(duì)應(yīng)的音頻單元
- Element:音頻單元中的具有特定功能的元件
- Input element:特指連接音頻輸入硬件(如麥克風(fēng))的元件
- Output element:特指連接音頻輸出硬件(如揚(yáng)聲器)的元件
- Bus:與 element 的概念相同,不過在強(qiáng)調(diào)信號(hào)流的時(shí)候使用
- Scope:音頻單元中音頻流的端口或范圍
- Input scope:音頻流的輸入端口
- Output scope : 音頻流的輸出端口
- Global scope:音頻單元的全局范圍
- 音頻流流向:Input scope -> Output scope

如上圖所示,AudioUnit是iOS中音頻最底層的API,僅在高性能,專業(yè)處理聲音的需求下使用.
1. Audio Unit 提供快速、模塊化的音頻處理
使用場(chǎng)景
以最低延遲的方式同步音頻的輸入輸出,如VoIP應(yīng)用
手動(dòng)合成音視頻,如音樂游戲、樂器音樂合成軟件
使用特定的 Audio Unit 功能,如回聲消除、混音、音調(diào)均衡
將多種音頻處理模塊靈活組裝應(yīng)用
1.1 Audio Unit 的用途
| 用途 | Audio Unit |
|---|---|
| 音效(Effect) | iPod Equalizer |
| 音頻混合(Mixing) | 3D Mixer、Multichannel Mixer |
| 音頻輸入輸出(I/O) | Remote I/O、Voice-Processing I/O、Generic Output |
| 格式轉(zhuǎn)換(Format conversion) | Format Converter |
-
Effect Unit
- iPod Equalizer:提供一組預(yù)設(shè)的均衡曲線,如低音增強(qiáng)(Bass Booster)、流行(Pop)和口語(Spoken Word)等等
-
Mixer Units
3D Mixer:OpenAL底層構(gòu)建的基礎(chǔ),如果需要3D Mixer unit特性,建議直接使用OpenAL,因?yàn)樗峁┝撕芏喾庋b好的功能強(qiáng)大的API。
Multichannel Mixer:為多個(gè)音頻提供混音功能,而且支持不同聲道聲音混合,最后以立體聲輸出。你可以單獨(dú)打開或關(guān)閉其中一個(gè)輸入音頻的聲音、調(diào)節(jié)音量、播放速度等等??偟膩碚f,這就是一個(gè)多音頻輸入,單音頻輸出的 Audio Unit。
-
I/O Units
(常用)Remote I/O:它直接連接輸入和輸出的音頻硬件,以低延遲的方式訪問單個(gè)接收或發(fā)出的音頻采樣數(shù)據(jù)。并且提供了硬件音頻格式到應(yīng)用設(shè)置音頻格式的格式轉(zhuǎn)換功能(Format Converter)。
Voice-Processing I/O:通過聲學(xué)的回聲消除拓展了Remote I/O unit,常用于VoIP或語音通信的應(yīng)用。它還提供了自動(dòng)增益校正、語音處理質(zhì)量調(diào)整和靜音功能。
Generic Output:它不連接任何音頻硬件而是提供一個(gè)將處理鏈的輸出傳遞給應(yīng)用程序的途徑,通常用于離線音頻處理。
-
Format Converter Unit
Format Converter:在 I/O Units 中間接使用于的音頻格式轉(zhuǎn)換部分。
Converter unit:支持轉(zhuǎn)換線性PCM音頻數(shù)據(jù)類型(linear PCM)
1.2 Audio Unit 的兩套 API
iOS 中操控 Audio Unit 的有兩套API,一套是用于直接操控單個(gè) Audio Unit,另一套通過音頻處理圖(audio processing graphs)的方式操控多個(gè)Audio Unit。
-
兩套API有部分相同的功能的函數(shù),開發(fā)者在使用的時(shí)候可以混合使用:
獲取 Audio Units 的動(dòng)態(tài)可鏈接庫的引用
實(shí)例化 Audio Units
連接 Audio Units 并注冊(cè)回調(diào)函數(shù)
啟動(dòng)和停止音頻流
1.3 獲取 Audio Units 實(shí)例
1 - 1 創(chuàng)建音頻組件描述來標(biāo)識(shí)一個(gè) Audio Unit
AudioComponentDescription ioUnitDescription;
ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
ioUnitDescription.componentFlags = 0;
ioUnitDescription.componentFlagsMask = 0;
AudioComponentDescription:一個(gè)用來描述并唯一標(biāo)識(shí)一個(gè)音頻組件的結(jié)構(gòu)體
標(biāo)識(shí)字段(Identifier keys):
- componentType:類型,audio unit 的主要功能
- componentSubType:子類型,audio unit 的詳細(xì)功能
- componentManufacturer:制造商,一般都是
kAudioUnitManufacturer_Apple - componentFlags & componentFlagsMask:提供位移枚舉的方式標(biāo)識(shí)特定描述,一般都設(shè)0忽略
更多 Identifier keys 見 Identifier Keys for Audio Units
1 - 2 獲取 Audio Unit 對(duì)象實(shí)例
Audio Unit API
AudioComponent foundIoUnitReference = AudioComponentFindNext (
NULL,
&ioUnitDescription
);
AudioUnit ioUnitInstance;
AudioComponentInstanceNew (
foundIoUnitReference,
&ioUnitInstance
);
AudioComponent類型:音頻組件類型,本質(zhì)是一個(gè)指定特定音頻組件的類指針。一個(gè)單獨(dú)的音頻組件,可用于實(shí)例化多個(gè)相同類型的音頻單元實(shí)例。
AudioComponentFindNext函數(shù):該函數(shù)用于在系統(tǒng)設(shè)備可用組件中查找最相近的組件,并將其返回其類指針??捎媒M件的順序可能會(huì)根據(jù)系統(tǒng)或者Audio Unit 的版本進(jìn)行變化,詳情見kAudioComponentRegistrationsChangedNotification
- 第一個(gè)參數(shù)(
inComponent):設(shè)置為NULL表示使用系統(tǒng)定義的順序查找第一個(gè)匹配的音頻組件。如果你將上一個(gè)使用的音頻組件傳給該參數(shù),則該函數(shù)將從這個(gè)組件開始繼續(xù)尋找下一個(gè)與之描述匹配的音頻組件。根據(jù)可用列表的順序進(jìn)行實(shí)例化,有助于性能提升。 - 第二個(gè)參數(shù)(
inDesc):音頻組件描述結(jié)構(gòu)體地址,用做組件查找的依據(jù)。 - 返回值:符合條件的音頻組件,即音頻單元類指針。
AudioComponentInstanceNew函數(shù):該函數(shù)用于創(chuàng)建音頻組件實(shí)例,即一個(gè)音頻單元(audio unit)。
- 第一個(gè)參數(shù)(
inComponent):音頻組件,用于告訴系統(tǒng)需要的音頻單元類型,該參數(shù)不可為空。 - 第二個(gè)參數(shù)(
outInstance):音頻單元實(shí)例,用于接收實(shí)例化的音頻單元。 - 返回值(
OSStatus):結(jié)果狀態(tài)返回值,無錯(cuò)誤返回 noErr(即0),否則返回錯(cuò)誤碼。
Audio Graph API
// 聲明并實(shí)例化一個(gè)音頻處理圖
AUGraph processingGraph;
NewAUGraph(&processingGraph);
// 添加音頻單元結(jié)點(diǎn)到圖中,然后實(shí)例化這些結(jié)點(diǎn)
AUNode ioNode;
AUGraphAddNode (
processingGraph,
&ioUnitDescription,
&ioNode
);
// 間接執(zhí)行音頻單元的實(shí)例化
AUGraphOpen (processingGraph);
// 獲取新實(shí)例化的 I/O 單元引用
AudioUnit ioUnit;
AUGraphNodeInfo (
processingGraph,
ioNode,
NULL,
&ioUnit
);
NewAUGraph函數(shù):實(shí)例化一個(gè)音頻處理圖
AUGraphAddNode函數(shù):通過音頻組件描述添加一個(gè)音頻結(jié)點(diǎn)到音頻處理圖中。
AUGraphOpen函數(shù):打開指定的音頻處理圖(頭文件中表示音頻單元在此時(shí)并沒有初始化,該操作并不會(huì)有資源被申請(qǐng)?)。
AUGraphNodeInfo函數(shù):返回指定音頻結(jié)點(diǎn)的信息。
- 輸入?yún)?shù)(
inGraph&inNode):音頻處理圖與輸入結(jié)點(diǎn) - 輸出參數(shù)(
outDescription&outAudioUnit):音頻組件描述與音頻單元,設(shè)置NULL表示不獲取該信息。 - 返回值(
OSStatus):函數(shù)的執(zhí)行成功與錯(cuò)誤碼。
1.4 Audio Units 的 Scopes 與 Elements
一個(gè)音頻單元由以下的Scopes和Elements組成:

[圖片上傳中...(IO_unit_2x.png-b104de-1593881466577-0)]
-
scope:音頻單元內(nèi)部的一個(gè)編程上下文,本文主要稱之為音頻流動(dòng)的“端口”或“范圍”。-
Input scope和Output scope直接參與音頻數(shù)據(jù)流通過音頻單元的過程。讓音頻數(shù)據(jù)從Input scope進(jìn)入,并從Global scope離開。可以這兩個(gè)scope中可以配置一些屬性和參數(shù),例如``kAudioUnitProperty_ElementCount、kAudioOutputUnitProperty_EnableIO和kMultiChannelMixerParam_Volume`等等。-
Input scope:音頻輸入端口,在此處都需要向Element輸入音頻數(shù)據(jù); -
Output scope:音頻輸出端口,在此處都需要將Element的音頻數(shù)據(jù)輸出到其他地方;
-
-
Global scope:全局范圍并沒有與Element嵌套,用于配置音頻單元中與輸入輸出概念無關(guān)的屬性,例如kAudioUnitProperty_MaximumFramesPerSlice等。
-
-
element:音頻單元內(nèi)部一個(gè)嵌套在Scope中的編程上下文,本文稱之為“元件”或“總線”。- 嵌套在
Input scope和Output scope的Element類似一個(gè)在物理音頻設(shè)備上的信號(hào)總線。因此在文檔中'element'和'bus'都代表上圖中的ElementX。一般在強(qiáng)調(diào)信號(hào)流時(shí)使用'bus',在強(qiáng)調(diào)音頻單元的特定功能方面時(shí)使用'element'。 - 對(duì)于
Input Output scope中的Element都是從0開始索引的;對(duì)于Global scope直接用0索引。 - 上圖只是表示一個(gè)普通音頻單元的內(nèi)部架構(gòu),但是對(duì)于不同音頻單元會(huì)有不同的內(nèi)部結(jié)構(gòu),比如一個(gè)音頻混合單元,它會(huì)有多個(gè)輸入的
element,一個(gè)輸出的element。
- 嵌套在
1.5 Audio Units 的屬性配置
一個(gè)音頻單元的屬性是通過一個(gè)鍵值對(duì)來設(shè)置的,其中鍵是通過一個(gè)唯一的整數(shù)(UInt32)來標(biāo)識(shí)的。蘋果預(yù)留了0 ~ 63999的區(qū)間標(biāo)識(shí)屬性鍵,剩下的區(qū)間可提供給第三方音頻單元使用。我們可以通過AudioUnitSetProperty函數(shù)進(jìn)行屬性配置,調(diào)用方式由以下代碼塊所示。
UInt32 busCount = 2;
OSStatus result = AudioUnitSetProperty (
mixerUnit, // 屬性設(shè)置目標(biāo)音頻單元
kAudioUnitProperty_ElementCount, // 屬性鍵
kAudioUnitScope_Input, // 屬性設(shè)置所在域
0, // 屬性設(shè)置所在元件
&busCount, // 屬性值地址
sizeof(busCount) // 屬性值的字節(jié)大小
);
常用屬性:
-
kAudioOutputUnitProperty_EnableIO:用于在 I/O Unit 上啟用或禁用輸入或輸出。默認(rèn)輸出已啟用,輸入已禁用。 -
kAudioUnitProperty_ElementCount:配置mixer unit上的輸入elements的數(shù)量 -
kAudioUnitProperty_MaximumFramesPerSlice:為了指定音頻數(shù)據(jù)的最大幀數(shù),音頻單元應(yīng)該準(zhǔn)備好響應(yīng)于回調(diào)函數(shù)調(diào)用而產(chǎn)生。對(duì)于大多數(shù)音頻設(shè)備,在大多數(shù)情況下,您必須按照參考文檔中的說明設(shè)置此屬性。如果不這樣做,屏幕鎖定時(shí)您的音頻將停止。 -
kAudioUnitProperty_StreamFormat:指定特定音頻單元輸入或輸出總線的音頻流數(shù)據(jù)格式。
大多數(shù)屬性只能在音頻單元沒有初始化時(shí)指定,但是某些特定屬性可以在音頻單元運(yùn)行時(shí)設(shè)置,如``Voice-Processing I/O unit的kAUVoiceIOProperty_MuteOutput靜音功能、iPod EQ unit的kAudioUnitProperty_PresentPreset`當(dāng)前模式功能。
屬性相關(guān)函數(shù)
-
AudioUnitGetPropertyInfo:用于判斷指定音頻單元中該屬性是否可用,可用則提供值的大小。 -
AudioUnitGetProperty、AudioUnitSetProperty:獲取、設(shè)置屬性。 -
AudioUnitAddPropertyListener、AudioUnitRemovePropertyListenerWithUserData:監(jiān)聽、移除監(jiān)聽特定屬性。
1.6 Audio Units 的參數(shù)配置
相比與音頻單元屬性,音頻單元參數(shù)在音頻單元工作的過程中都是可以調(diào)整的。音頻單元參數(shù)也是以鍵值對(duì)的方式表示的。
- 鍵是以枚舉值的形式展示,在同一種類的音頻單元中枚舉值是唯一的,但在所有音頻單元中的有重復(fù)。
- 值統(tǒng)一都是32位的浮點(diǎn)數(shù)(Float32)類型的。值的允許范圍以及含義由音頻單元的實(shí)現(xiàn)確定,詳情見以下文檔(Audio Unit Parameters Reference)。
參數(shù)相關(guān)函數(shù)(其使用方式類似屬性獲取與設(shè)置)
-
AudioUnitGetParameter:獲取指定參數(shù)值 -
AudioUnitSetParameter:設(shè)置指定參數(shù)值
我們可以通過UIKit中的UISlider和UISwitch控件在音頻單元工作的過程中改變其參數(shù)值,達(dá)到用戶交互的效果。
1.7 I/O Units 的基本特性
I/O Units 是一個(gè)十分常用的音頻單元,它在許多地方都比較特別。它包含了兩個(gè)element,內(nèi)部結(jié)構(gòu)如下圖所示:

I/O Unit 的兩個(gè)elements是兩個(gè)獨(dú)立的實(shí)體,在使用的時(shí)候我們需要通過設(shè)置屬性(kAudioOutputUnitProperty_EnableIO)單獨(dú)啟用和禁用element。
-
Element 1:Input scope部分是對(duì)開發(fā)者不透明的,它直接連接著音頻輸入的硬件設(shè)備(麥克風(fēng)),可在其Output scope部分獲取音頻數(shù)據(jù)。一般 I/O unit 的Element 1又稱為input element。 -
Element 0:Ouput scope部分是對(duì)開發(fā)者不透明的,它直接連接著音頻輸出的硬件設(shè)備(揚(yáng)聲器),可在其Input scope部分傳入音頻數(shù)據(jù)。一般 I/O unit 的Element 0又稱為output element。
因此 I/O unit 在音頻處理圖中擔(dān)任著音頻流處理的起始點(diǎn)和終點(diǎn),擁有開啟和關(guān)閉音頻流的能力。
2. Audio Processing Grapha 管理 Audio Units
Audio Processing Grapha 即一個(gè)音頻處理圖,是 Core-Foundation風(fēng)格的數(shù)據(jù)結(jié)構(gòu)——AUGrapha。通過 AUGrapha 我們可以構(gòu)建和管理一個(gè)音頻處理鏈條。一個(gè)音頻處理圖可以利用多個(gè)音頻單元和多個(gè)呈現(xiàn)回調(diào)函數(shù)創(chuàng)建任何你所想象的音頻處理方案。
-
AUGraph:音頻圖,本質(zhì)是一個(gè)音頻圖的指針類型,該類保證了線程安全。例如播放音頻時(shí),可以保證安全地插入一個(gè)均衡器(equalizer)或者在混合器(mixer)輸入端更換回調(diào)函數(shù)。事實(shí)上,AUGraph提供了iOS平臺(tái)上用于音頻應(yīng)用程序的動(dòng)態(tài)配置API。- 更多:在構(gòu)建一個(gè)音頻圖的時(shí)候,必須配置好圖中的所有音頻單元,AUGraph相關(guān)的API并不能完全勝任,因此需要同時(shí)使用音頻圖和音頻單元兩套API。
-
AUNode:音頻結(jié)點(diǎn),本質(zhì)是一個(gè)SInt32類型,在音頻圖中的標(biāo)識(shí)一個(gè)獨(dú)立的音頻單元。在配置使用音頻圖時(shí),為了可讀性,一般使用一個(gè)音頻結(jié)點(diǎn)來代表圖中的包含的音頻單元,而不是直接使用音頻單元。- 更多:音頻結(jié)點(diǎn)除了可以代表一個(gè)音頻單元外,還能代表音頻圖里面的子圖,但是I/O unit連接的子圖必須只能使用 Generic Output unit 而不是 I/O unit。因?yàn)橐粋€(gè)物理設(shè)備最多只能只能連接一個(gè)音頻單元。
總的來說,構(gòu)建音頻處理圖需要以下三步:
- 添加音頻結(jié)點(diǎn)到音頻圖中
- 通過音頻結(jié)點(diǎn)獲取音頻單元,直接配置音頻單元的屬性參數(shù)
- 將音頻結(jié)點(diǎn)連接起來
2.1 Audio Processing Graphs 中的 I/O Unit
無論是錄音、回放和同步I/O流,所有音頻處理圖都有一個(gè) I/O unit。音頻圖中的 I/O unit 負(fù)責(zé)音頻流的輸入和輸出,其他音頻單元負(fù)責(zé)其他音頻流處理工作。
- 音頻圖通過
AUGraphStart和AUGraphStop方法啟動(dòng)和停止音頻流 - 通過
AudioOutputUnitStart和AudioOutputUnitStop方法傳達(dá)啟動(dòng)和停止信息給I/O unit
2.2 Audio Processing Graphs 線程安全
音頻處理圖API提供一個(gè)“to-do list”保存需要做的操作,在開發(fā)者配置完成后再告訴音頻圖去實(shí)現(xiàn)。以下是音頻處理圖支持的一些常見重新配置以及相關(guān)功能:
-
AUGraphAddNode、AUGraphRemoveNode:添加移除音頻結(jié)點(diǎn) -
AUGraphConnectNodeInput、AUGraphDisconnectNodeInput:添加移除音頻結(jié)點(diǎn)之間的連接 -
AUGraphSetNodeInputCallback:設(shè)置連接音頻單元輸入總線(input bus)的回調(diào)函數(shù)
下面將以一個(gè)音頻合成播放的音頻處理圖重新配置為例,講述音頻處理圖運(yùn)行中的線程安全。首先構(gòu)建一個(gè)音頻處理圖包含 Multichannel Mixer unit 和 Remote I/O unit,用于播放合成兩種輸入源的混音效果。運(yùn)行中的音頻處理圖中,開發(fā)者將兩個(gè)輸入源的數(shù)據(jù)送給 Mixer unit 的 input bus,mixer 的輸出端連接著 I/O unit 的output element,最終將音頻傳給硬件輸出。音頻處理圖如下:

現(xiàn)在,在其中一個(gè)音頻流中插入一個(gè)“音頻均衡器”,則音頻處理圖如下:

以下是完成重新配置的步驟:
- 通過調(diào)用
AUGraphDisconnectNodeInput斷開mixer unit從input 1的“鼓聲”回調(diào)。 - 通過配置“音頻組件描述”,然后調(diào)用
AUGraphAddNode將一個(gè)包含一個(gè)包含iPod EQ unit的音頻結(jié)點(diǎn)到音頻圖中的音頻結(jié)點(diǎn)添加到音頻圖中。(此時(shí),iPod EQ unit已具有實(shí)例化對(duì)象但未被初始化,新增的音頻結(jié)點(diǎn)也只是存在音頻圖中并未參與音頻流) - 配置和初始化
iPod EQ unit- 調(diào)用
AudioUnitGetProperty函數(shù)從Mixer unit的輸入端獲取當(dāng)前使用的流格式(kAudioUnitProperty_StreamFormat) - 調(diào)用
AudioUnitSetProperty函數(shù)兩次,分別設(shè)置iPod EQ unit輸入端和輸出端的流格式 - 調(diào)用
AudioUnitInitialize函數(shù)為iPod EQ unit分配內(nèi)存和準(zhǔn)備處理音頻使用。(注:這個(gè)函數(shù)是線程不安全的,需要當(dāng)iPod EQ unit尚未主動(dòng)參與進(jìn)音頻處理圖時(shí),即沒有調(diào)用AUGraphUpdate函數(shù)前使用。
- 調(diào)用
- 通過調(diào)用
AUGraphSetNodeInputCallback將“鼓聲”回調(diào)函數(shù)添加到iPod EQ unit的input端。
上面1,2,4步使用AUGraph開頭的函數(shù),都會(huì)被添加到的任務(wù)執(zhí)行列表(“to-do list”)中。然后通過調(diào)用AUGraphUpdate執(zhí)行這些未開始任務(wù)。如果成功返回,則代表音頻圖已經(jīng)被動(dòng)態(tài)重新配置并且iPod EQ unit也已經(jīng)就位正在處理音頻數(shù)據(jù)。
2.3 通過 Graph "pull"音頻流
在音頻處理圖的音頻流流動(dòng)類似生產(chǎn)者消費(fèi)者模式,消費(fèi)者在需要更多音頻數(shù)據(jù)時(shí)通知生產(chǎn)者。請(qǐng)求音頻數(shù)據(jù)流的方向與音頻流提供的方向正好相反。

對(duì)一組音頻數(shù)據(jù)的每個(gè)請(qǐng)求稱為渲染調(diào)用(render call),也稱為拉流(pull)。該圖灰色“控制流”箭頭表示為拉流操作。拉流請(qǐng)求的數(shù)據(jù)本質(zhì)是一組音頻樣本幀(audio sample frames),一組音頻樣本幀也稱為一個(gè)切片(slice)。提供切片的代碼稱為渲染回調(diào)函數(shù)( render callback function)。下列是拉取音頻流的步驟:
- 調(diào)用
AUGraphStart函數(shù)后,虛擬輸出設(shè)備調(diào)用Remote I/O unit上output element的渲染回調(diào)函數(shù)請(qǐng)求一片處理過的音頻數(shù)據(jù)幀。 -
Remote I/O unit的回調(diào)函數(shù)在其輸入緩沖區(qū)中查找要處理的音頻數(shù)據(jù)去滿足渲染請(qǐng)求。如果有數(shù)據(jù)則直接傳遞,否則,調(diào)用連接其輸入端的回調(diào)函數(shù)。上圖中Remote I/O unit的輸入端連接一個(gè)effect unit的輸出端,則I/O unit從effect unit中拉流,請(qǐng)求一片音頻數(shù)據(jù)幀。 -
effect unit的行為與Remote I/O unit一樣。當(dāng)它需要音頻數(shù)據(jù)時(shí),便從輸入連接中獲取它。上圖中,effect unit從應(yīng)用程序的回調(diào)函數(shù)中獲取音頻數(shù)據(jù)。 - 應(yīng)用程序的回調(diào)函數(shù)最終接收了這個(gè)拉流請(qǐng)求,在函數(shù)中提供
effect unit需要的音頻幀數(shù)據(jù)。 -
effect unit從應(yīng)用程序的回調(diào)函數(shù)中獲取的音頻數(shù)據(jù),然后按照步驟2中要求提供音頻數(shù)據(jù)給Remote I/O unit -
Remote I/O unit將從effect unit提供的音頻數(shù)據(jù),按照步驟1中的要求提供給虛擬輸出設(shè)備,完成了一個(gè)拉流周期。
3. 通過回調(diào)函數(shù)將音頻傳遞給 Audio Units
為了將音頻數(shù)據(jù)從內(nèi)存或磁盤中傳遞到音頻單元的input bus,需要使用實(shí)現(xiàn)一個(gè)渲染回調(diào)函數(shù)填充數(shù)據(jù)并通過AURenderCallback屬性配置音頻單元的屬性。這樣當(dāng)音頻單元需要一片音頻幀數(shù)據(jù)時(shí)候,該回調(diào)函數(shù)就會(huì)被調(diào)用。渲染回調(diào)函數(shù)給了我們操作音頻數(shù)據(jù)很高的自由度,我們可以在回調(diào)函數(shù)以任何方式創(chuàng)建或改變音頻數(shù)據(jù)。與此同時(shí),渲染回調(diào)函數(shù)處于實(shí)時(shí)優(yōu)先級(jí)線程上,后續(xù)的函數(shù)調(diào)用都是異步的。因此,我們?cè)阡秩净卣{(diào)函數(shù)中處理時(shí)間是有限的,如果在下一次渲染調(diào)用到達(dá)時(shí),上一個(gè)回調(diào)函數(shù)還沒運(yùn)行完成,那么你的音頻結(jié)果將會(huì)出現(xiàn)缺口,導(dǎo)致你的結(jié)果是不連續(xù)的。因此,不能在渲染回調(diào)函數(shù)中執(zhí)行線程鎖、分配內(nèi)存、訪問文件系統(tǒng)或網(wǎng)絡(luò)連接等耗時(shí)任務(wù)。
以下是渲染回調(diào)函數(shù)的詳細(xì)說明:
static OSStatus MyAURenderCallback (
void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData
) { /* callback body */ }
-
inRefCon:表示注冊(cè)回調(diào)函數(shù)時(shí)傳遞的指針,一般可傳當(dāng)前對(duì)象實(shí)例。因?yàn)榛卣{(diào)函數(shù)是C語言形式,無法直接訪問本類中屬性與方法,所以將實(shí)例化對(duì)象傳入可以間接調(diào)用當(dāng)前對(duì)象中屬性與方法。 -
ioActionFlags:表示音頻渲染處理的行為,本質(zhì)是一個(gè)位移枚舉,告訴 Audio Unit 使用不同的音頻渲染方式。比如在一個(gè)樂器音樂合成應(yīng)用中,用戶當(dāng)前沒有需要播放的音符,則在回調(diào)函數(shù)中使用*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;。 -
inTimeStamp:表示調(diào)用回調(diào)函數(shù)的時(shí)間,可用作音頻同步的時(shí)間戳。它是一個(gè)AudioTimeStamp結(jié)構(gòu)體,每次mSampleTime字段的值都會(huì)根據(jù)inNumberFrames參數(shù)中的數(shù)字遞增。例如,如果你的應(yīng)用是音序器或鼓機(jī),則可以使用mSampleTime的值來調(diào)度聲音。 -
inBusNumber:表示調(diào)用回調(diào)函數(shù)的audio unit bus,可通過該值在回調(diào)函數(shù)中進(jìn)行分支操作。另外,當(dāng)音頻單元注冊(cè)回調(diào)函數(shù)時(shí),可以為每個(gè)bus指定不同的inRefCon。 -
inNumberFrames:表示回調(diào)函數(shù)中的需要填充的音頻幀數(shù)。 -
ioData:表示需要填入的音頻數(shù)據(jù)緩沖,該音頻數(shù)據(jù)緩沖的結(jié)構(gòu)必須與當(dāng)前所在bus的的音頻流格式一致。如果需要在渲染回調(diào)中實(shí)現(xiàn)靜音功能,則需要通過memset函數(shù)將ioData的buffers都設(shè)為0。
下圖描述的是ioData參數(shù)中的一對(duì)非交錯(cuò)(noninterleaved)立體聲緩沖區(qū):

4. 音頻流格式啟用數(shù)據(jù)流
在一個(gè)音頻樣本數(shù)據(jù)中,二進(jìn)制位的布局是有含義的,這不是單純用Float32和UInt16數(shù)據(jù)類型可以表達(dá)的。音頻單元(audio unit)可以使用音頻組件描述(AudioComponentDescription)來表達(dá),音頻流格式則使用音頻流基本描述(AudioStreamBasicDescription,即ASBD)來表達(dá)。
// ASBD 結(jié)構(gòu)體
struct AudioStreamBasicDescription {
Float64 mSampleRate;
UInt32 mFormatID;
UInt32 mFormatFlags;
UInt32 mBytesPerPacket;
UInt32 mFramesPerPacket;
UInt32 mBytesPerFrame;
UInt32 mChannelsPerFrame;
UInt32 mBitsPerChannel;
UInt32 mReserved;
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
// 定義一個(gè)立體聲 ASBD
// AudioUnitSampleType => 8.24 fixed-point integer => SInt32
size_t bytesPerSample = sizeof(AudioUnitSampleType);
AudioStreamBasicDescription stereoStreamFormat = {0};
stereoStreamFormat.mSampleRate = graphSampleRate;
stereoStreamFormat.mFormatID = kAudioFormatLinearPCM; // 未壓縮的音頻數(shù)據(jù)
/*
kAudioFormatFlagsAudioUnitCanonical = kAudioFormatFlagIsFloat |
kAudioFormatFlagsNativeEndian |
kAudioFormatFlagIsPacked |
kAudioFormatFlagIsNonInterleaved
kAudioFormatFlagsAudioUnitCanonical = kAudioFormatFlagIsSignedInteger |
kAudioFormatFlagsNativeEndian |
kAudioFormatFlagIsPacked |
kAudioFormatFlagIsNonInterleaved | (kAudioUnitSampleFractionBits << kLinearPCMFormatFlagsSampleFractionShift)
*/
stereoStreamFormat.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;
stereoStreamFormat.mBytesPerPacket = bytesPerSample;
stereoStreamFormat.mFramesPerPacket = 1;
stereoStreamFormat.mBytesPerFrame = bytesPerSample;
stereoStreamFormat.mChannelsPerFrame = 2; // 2 indicates stereo
stereoStreamFormat.mBitsPerChannel = 8 * bytesPerSample;
stereoStreamFormat.mReserved = 0;
-
mSampleRate:采樣率,每秒鐘音頻流中的樣本幀數(shù) -
mFormatID:格式標(biāo)識(shí),大體的數(shù)據(jù)格式類型 -
mFormatFlags:格式配置,位移枚舉,指定具體的數(shù)據(jù)格式 -
mBytesPerPacket:每個(gè)音頻包中的字節(jié)數(shù) -
mFramesPerPacket:每個(gè)音頻包的幀數(shù)- 未壓縮音頻:一個(gè)音頻包只有一幀的數(shù)據(jù)
- 壓縮音頻:一個(gè)音頻包是一塊壓縮好的數(shù)據(jù),比如一個(gè)ACC音頻包有1024個(gè)樣本幀
-
mBytesPerFrame:每一幀的字節(jié)數(shù)- 非交錯(cuò)型數(shù)據(jù)(
non-interleaved):每一幀只是包含一個(gè)聲道數(shù)據(jù) - 交錯(cuò)型數(shù)據(jù)(
interleaved):每一幀只是包含多個(gè)聲道數(shù)據(jù)
- 非交錯(cuò)型數(shù)據(jù)(
-
mChannelsPerFrame:聲道數(shù),每一幀的聲道數(shù) -
mBitsPerChannel:位深,每一個(gè)聲道的二進(jìn)制數(shù) -
mReserved:保留的?,表示填塞數(shù)據(jù)結(jié)構(gòu)強(qiáng)制8字節(jié)對(duì)齊
注:音頻流格式在創(chuàng)建的時(shí)候需要將事先初始化為0,即不包含任何數(shù)據(jù),否則可能會(huì)出bug。
音頻處理圖中必須在關(guān)鍵點(diǎn)設(shè)置好音頻數(shù)據(jù)格式,其他點(diǎn)系統(tǒng)將會(huì)設(shè)置自動(dòng)格式。iOS 設(shè)備上的音頻輸入和輸出硬件具有系統(tǒng)確定的音頻流格式,該格式始終是未壓縮的,采用交錯(cuò)的線性 PCM 格式。

- 綠色方塊:系統(tǒng)根據(jù)硬件事先設(shè)置好了音頻流格式
- 藍(lán)色方塊:開發(fā)者設(shè)置音頻流格式的關(guān)鍵點(diǎn)
- 紅色方塊:開發(fā)者設(shè)置采樣率的位置