Audio Unit框架(四)構(gòu)建APP

音視頻開發(fā):OpenGL + OpenGL ES + Metal 系列文章匯總

在上文已經(jīng)詳細(xì)講解了Audio Unit框架的原理和設(shè)計模式,本文將開始分析如何構(gòu)建一個APP

1. 構(gòu)建過程認(rèn)識

構(gòu)建步驟:

  1. 配置音頻會話
  2. 指定音頻單元
  3. 創(chuàng)建音頻處理graph,然后獲取音頻單元
  4. 配置音頻單元
  5. 連接音頻單元節(jié)點
  6. 提供用戶界面
  7. 初始化然后啟動音頻處理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

代碼解釋:

  1. 獲取app 的單例音頻會話對象的引用
  2. 請求硬件的采樣率
  3. 請求我們想要的音頻會話category。這里是錄制和播放
  4. 激活音頻會話
  5. 音頻會話激活后,根據(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)

步驟:

  1. 實例化AUGraph對象。該對象代表音頻處理grahp
  2. 實例化一個或者多個AUNode 對象。每個類型代表grahp中的音頻單元
  3. 將 AUNode 加入到 AUGraph中
  4. 打開圖形并實例化音頻單元
  5. 獲取對音頻單元的引用

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);

說明:

  1. 通過為每個音頻單獨的調(diào)用AudioUnitInitialize函數(shù)來初始化graph所擁有的音頻單元
  2. 驗證graph的連接和音頻流數(shù)據(jù)格式
  3. 在音頻單元連接上傳播流格式
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容