在上文已經(jīng)詳細(xì)講解了Audio Unit框架的原理和設(shè)計模式,本文將開始分析如何構(gòu)建一個APP
1. 構(gòu)建過程認(rèn)識
構(gòu)建步驟:
- 配置音頻會話
- 指定音頻單元
- 創(chuàng)建音頻處理graph,然后獲取音頻單元
- 配置音頻單元
- 連接音頻單元節(jié)點
- 提供用戶界面
- 初始化然后啟動音頻處理graph
2. 配置音頻會話
音頻會話的特性在很大程度上決定了app的音頻功能以及與系統(tǒng)其它部分的交互性。
1、指定要在app中使用的采樣率:
self.graphSampleRate = 44100.0; // Hertz
2、設(shè)置首選采樣率為硬件采樣率:
音頻會話對象請求系統(tǒng)使用您的首選采樣率作為設(shè)備硬件的采樣率。這里的目的是避免硬件和app之間的采樣率轉(zhuǎn)換。這可以最大程度的提高cpu的性能和音質(zhì),并最大限度的減少電池消耗。
NSError *audioSessionError = nil;
AVAudioSession *mySession = [AVAudioSession sharedInstance]; // 1
[mySession setPreferredHardwareSampleRate: graphSampleRate // 2
error: &audioSessionError];
[mySession setCategory: AVAudioSessionCategoryPlayAndRecord // 3
error: &audioSessionError];
[mySession setActive: YES // 4
error: &audioSessionError];
self.graphSampleRate = [mySession currentHardwareSampleRate]; // 5
代碼解釋:
- 獲取app 的單例音頻會話對象的引用
- 請求硬件的采樣率
- 請求我們想要的音頻會話category。這里是錄制和播放
- 激活音頻會話
- 音頻會話激活后,根據(jù)系統(tǒng)提供的實際采樣率更新自己的采樣率變量。
3、音頻硬件I/O 緩沖區(qū)持續(xù)時間
self.ioBufferDuration = 0.005;
[mySession setPreferredIOBufferDuration: ioBufferDuration
error: &audioSessionError];
說明:
- 在44.1KHZ采樣率下,模式持續(xù)時間約為23ms。相當(dāng)于1024個樣本的slice大小
- 如果app的I/O 延遲至關(guān)重要,那么我們需要請求較小的持續(xù)時間,最低月為0.005毫秒
3. 指定音頻單元
在運(yùn)行時,音頻會話配置代碼后,其實app還是沒有獲取音頻單元。
我們可以使用AudioComponentDescription指定音頻單元。
獲取到音頻單元說明符,然后根據(jù)我們選擇的模式構(gòu)建音頻處理graph。
AudioComponentDescription ioUnitDescription;
ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
ioUnitDescription.componentFlags = 0;
ioUnitDescription.componentFlagsMask = 0;
4. 創(chuàng)建音頻處理graph,然后獲取音頻單元
AUGraph processingGraph;
NewAUGraph (&processingGraph);
AUNode ioNode;
AUNode mixerNode;
AUGraphAddNode (processingGraph, &ioUnitDesc, &ioNode);
AUGraphAddNode (processingGraph, &mixerDesc, &mixerNode)
步驟:
- 實例化AUGraph對象。該對象代表音頻處理grahp
- 實例化一個或者多個AUNode 對象。每個類型代表grahp中的音頻單元
- 將 AUNode 加入到 AUGraph中
- 打開圖形并實例化音頻單元
- 獲取對音頻單元的引用
AUGraphAddNode 函數(shù)調(diào)用使用音頻單元說明符ioUnitDesc和mixerDesc。此時,graph 將被實例化,并擁有app中使用的結(jié)點。
打開graph 并實例化音頻單元使用AUGraphOpen。
AUGraphOpen (processingGraph);
通過AUGraphNodeInfo函數(shù)獲取對音頻單元實例的引用
AudioUnit ioUnit;
AudioUnit mixerUnit;
AUGraphNodeInfo (processingGraph, ioNode, NULL, &ioUnit);
AUGraphNodeInfo (processingGraph, mixerNode, NULL, &mixerUnit)
5. 配置音頻單元
每個ios 音頻單元都需要自己的配置。
- 默認(rèn)情況下,遠(yuǎn)程I/O 單元已啟用輸出但是禁止輸入。如果想app同時執(zhí)行I/O 或者僅僅使用輸入,那么必須要重新配置I/O 單元。
- 除了遠(yuǎn)程I/O 和語音處理I/O 單元外,所有的ios音頻單元都需要配置其kAudioUnitProperty_MaximumFramesPerSlice屬性。該屬性確保音頻單元準(zhǔn)好相應(yīng)于渲染調(diào)用產(chǎn)生足夠數(shù)量的音頻數(shù)據(jù)幀。
- 所有的音頻單元都需要再輸入和輸出或者兩者上定義其音頻流格式。
具體可以看Audio Unit框架(一)框架認(rèn)識和使用
寫入并附加渲染回調(diào)函數(shù):
對于采用渲染回調(diào)函數(shù)的設(shè)計模式,我們必須編寫這些函數(shù),然后將他們附加到正確的點上。
當(dāng)音頻不流動,我們可以使用音頻單元API 立即附加渲染回調(diào)如下
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = &renderCallback;
callbackStruct.inputProcRefCon = soundStructArray;
AudioUnitSetProperty (
myIOUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0, // output element
&callbackStruct,
sizeof (callbackStruct)
);
通過使用音頻處理graph API ,我們可以以現(xiàn)場安全的方式附加渲染回調(diào),即使在音頻流動時候也可以這樣做。
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = &renderCallback;
callbackStruct.inputProcRefCon = soundStructArray;
AUGraphSetNodeInputCallback (
processingGraph,
myIONode,
0, // output element
&callbackStruct
);
// ... some time later
Boolean graphUpdated;
AUGraphUpdate (processingGraph, &graphUpdated);
6. 連接音頻單元節(jié)點
在大多數(shù)情況下,使用音頻處理graph API中的AUGraphConnectNodeInput和AUGraphDisconnectNodeInput函數(shù)建立或斷開音頻之間的連接是最好和最容易的。
這些函數(shù)是線程安全的,可以避免顯示定義連接的編碼開銷,因為在不使用graph的時候必須這么做。
下列代碼顯示如何處理使用音頻處理grahp API 混合器結(jié)點的輸出連接到I/O 單元輸出元件的輸入
AudioUnitElement mixerUnitOutputBus = 0;
AudioUnitElement ioUnitOutputElement = 0;
AUGraphConnectNodeInput (
processingGraph,
mixerNode, // source node
mixerUnitOutputBus, // source node bus
iONode, // destination node
ioUnitOutputElement // desinatation node element
);
我們也可以使用音頻單元屬性機(jī)制直接建立和斷開音頻單元之間的連接。我們使用AudioUnitSetProperty函數(shù)的kAudioUnitProperty_MakeConnection屬性,
如下,該方法要求我們?yōu)槊總€連接定義AudioUnitConnection結(jié)構(gòu)以用做其屬性值。。
AudioUnitElement mixerUnitOutputBus = 0;
AudioUnitElement ioUnitOutputElement = 0;
AudioUnitConnection mixerOutToIoUnitIn;
mixerOutToIoUnitIn.sourceAudioUnit = mixerUnitInstance;
mixerOutToIoUnitIn.sourceOutputNumber = mixerUnitOutputBus;
mixerOutToIoUnitIn.destInputNumber = ioUnitOutputElement;
AudioUnitSetProperty (
ioUnitInstance, // connection destination
kAudioUnitProperty_MakeConnection, // property key
kAudioUnitScope_Input, // destination scope
ioUnitOutputElement, // destination element
&mixerOutToIoUnitIn, // connection definition
sizeof (mixerOutToIoUnitIn)
);
7. 提供用戶界面
到這里,我們構(gòu)建的app已經(jīng)完成構(gòu)建和配置。
在許多情況下,我們需要提供一個用戶界面,讓用戶微調(diào)音頻行為。
我們可以定制用戶界面以允許用戶調(diào)整特定的音頻單元參數(shù),并在某些特使情況下調(diào)整音頻單元屬性。
8. 初始化然后啟動音頻處理graph
在開始音頻流之前,我們必須通過調(diào)用AUGraphInitialize函數(shù)來初始化音頻graph
OSStatus result = AUGraphInitialize (processingGraph);
// Check for error. On successful initialization, start the graph...
AUGraphStart (processingGraph);
// Some time later
AUGraphStop (processingGraph);
說明:
- 通過為每個音頻單獨的調(diào)用AudioUnitInitialize函數(shù)來初始化graph所擁有的音頻單元
- 驗證graph的連接和音頻流數(shù)據(jù)格式
- 在音頻單元連接上傳播流格式