1. 程式人生 > >Audio Unit 基礎

Audio Unit 基礎

ren res 狀況 -i proc rda src ios 變化

如圖所示,所有 iOS 音頻技術都是基於 audio units。此處顯示的更高級別的技術,如 Media Player,AV Foundation,OpenAL,AudioToolbox,是對 audio units 的封裝,為特定的任務提供專用且簡化的 API。

技術分享圖片

如在可控性、性能、靈活性有非常高的需求,或者需要實現特定的功能(例如回音消除),直接使用 audio unit 是一個正確的選擇。

Audio Units 提供高效,模塊化音頻處理方案

當你需要實現以下需求時,不使用高級 API,直接使用 audio units

  • 低延時同步音頻輸入輸出,例如 VoIP 應用
  • 響應回放合成聲音,例如音樂遊戲或合成樂器
  • 使用特定的 audio unit 特征,例如回聲消除,混音,色調均衡
  • 處理鏈結構讓你可以將音頻處理模塊組裝到靈活的網絡中。這是 iOS 中唯一提供此功能的音頻 API

iOS 中的 Audio Units

根據不同功能分類,iOS 提供了七種 audio units

  • Effect - iPod Equalizer
  • Mixing - 3D Mixer
  • Mixing - Multichannel Mixer
  • I/O - Remote I/O
  • I/O - Voice-Processing I/O
  • I/O - Generic Output
  • Format conversion - Format Converter

註意:iOS 動態插件結構不支持第三方 audio units,也就是說,動態加載的 audio units 僅能通過操作系統提供。

Effect Unit

iOS 4 提供了一個效果單元,iPod Equalizer,與 iPod 內置應用使用相同的均衡器。查看這個 audio unit 的 iPod 應用用戶界面,進入設置 -> iPod -> EQ。當使用此 audio unit,必須提供自己的用戶界面。此 audio unit 提供了一組預設的均衡曲線,例如低音增強,Pop 和 Spoken Word。

Mixer Units

iOS 提供兩個 mixer units。3D Mixer unit 是 OpenAL 的基礎,如果需要實現 3D Mixer unit 的特征,可以優先使用 OpenAL,它提供了高級 API,並且非常適合遊戲應用程序。關於如何使用 OpenAL,見示例代碼: oalTouch。

Multichannel Mixer unit 為任意數量的單聲道或立體聲提供混音,立體聲輸出。可以打開和關閉每一個輸入,設置輸入增益,並設置立體聲平移位置。見示例代碼:MixerHost。

I/O Units

iOS 提供了三個 I/O units,其中 Remote I/O unit 是最常用的。連接輸入輸出音頻硬件,對傳入和傳出的樣本值低延遲訪問,提供硬件音頻格式和應用音頻格式之間的格式轉化。見示例代碼:aurioTouch。

Voice-Processing I/O unit 是對 Remote I/O unit 的拓展,添加了語音聊天中的回聲消除,還提供了自動增益矯正,語音質量調整,靜音等特性。

Generic Output unit 不連接音頻硬件,而是提供了一種將處理鏈的輸出發送到應用程序的機制。通常會使用做離線音頻處理

Format Converter Unit

iOS 4 提供了 Format Converter Unit,通常通過 I/O unit 間接使用。

兩個 Audio Unit API

iOS 中兩個和 audio units 相關的 API,一個 API 直接處理 audio units ,另一個處理 audio processing graphs,在應用中可以同時使用兩個 API。

  • 直接使用 audio units
  • 創建配置 audio processing graph

這兩個 API 之間有一些功能重疊,可以根據自己編程風格自由組合和搭配,提供的功能如下:

  • 獲取對定義音頻單元動態鏈接庫的引用
  • 實例化 audio unit
  • audio units 互聯和附件回調函數
  • 開始和停止音頻流

Audio Unit 獲取

首先需要在音頻組件描述數據結構中確定其類型、子類型和制造商密匙。下面指定了一個特定的 audio Unit,Remote I/O unit,對 componentManufacturer 字段,所有的 iOS audio units 使用 kAudioUnitManufacturer_Apple。如需創建一個通用描述,可以將類型或者子類型設置為0,例如為了匹配所有的 I/O unit,可以將 componentSubType 設置為0。

    AudioComponentDescription ioUnitDescription;
    ioUnitDescription.componentType = kAudioUnitType_Output;
    ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
    ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    ioUnitDescription.componentFlags = 0;
    ioUnitDescription.componentFlagsMask = 0;

使用 audio unit API 獲取 audio unit 實例,對於 AudioComponentFindNext ,如果第一個參數傳空,按照系統定義的排序,找到第一個符合的 audio unit ,如果該參數為先前找到的音頻單元,則該功能找到與描述匹配的下一個 audio unit,例如此用法可以通過重復調用 AudioComponentFindNext 來獲取所有 I/O 單元的引用。

    AudioComponent foundIoUnitReference = AudioComponentFindNext(NULL, &ioUnitDescription);
    AudioUnit ioUnitInstance;
    AudioComponentInstanceNew(foundIoUnitReference, &ioUnitInstance);

使用 audio processing graph API 獲取 audio unit

    AUGraph processingGraph;
    NewAUGraph(&processingGraph);
    
    AUNode ioNode;
    AUGraphAddNode(processingGraph, &ioUnitDescription, &ioNode);
    AUGraphOpen(processingGraph);
    
    AudioUnit ioUnit;
    AUGraphNodeInfo(processingGraph, ioNode, NULL, &ioUnit);

Audio Units 結構

技術分享圖片

如圖所示,Audio Unit 中有一個 input element,一個 output element,這種結構比較常見,但這並不適合所有狀況。例如在 mixer unit 中,會存在多個 input element,一個 output element的情況。

element 1 可以理解為 input element(bus),element 0 理解為output element。Input scope 和 Output scope,直接參與音頻流的流程,音頻從 Input scope 輸入,從 Output scope 輸出,一些參數或屬性適用於 Input scope 或 Output scope。Global scope,應用於整個 audio unit,不與音頻流關聯,它有一個 element,命名為 element 0,一些屬性,像 kAudioUnitProperty_MaximumFramesPerSlice 應用於 Global scope。

Audio Units 屬性

設置屬性,可以使用函數 AudioUnitSetProperty

    UInt32 busCount = 2;
    OSStatus result = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &busCount, sizeof(busCount));

下面是一些常用的屬性:

  • kAudioOutputUnitProperty_EnableIO 啟用或禁止 I/O,默認輸出開啟,輸入禁止。
  • kAudioUnitProperty_ElementCount 配置元素個數
  • kAudioUnitProperty_MaximumFramesPerSlice 設置 audio unit 的最大幀數
  • kAudioUnitProperty_StreamFormat 指定輸入 audio unit 輸入輸出元素的數據格式

大部分屬性可以在 audio unit 未初始化的時候設置,因為這些屬性一般不會發生改變,有些屬性像 iPod EQ unit 中的 kAudioUnitProperty_PresentPreset和 Voice-Processing I/O unit 中的 kAUVoiceIOProperty_MuteOutput 這些屬性會在播放音頻的時候也會發生改變

查找屬性是否可得,訪問屬性值,監聽改變,可以使用一下函數:

  • AudioUnitGetPropertyInfo 查看屬性是否可得,如果可以,會得到值大小和值是否可以改變
  • AudioUnitGetProperty, AudioUnitSetProperty 獲取或設置屬性
  • AudioUnitAddPropertyListener, AudioUnitRemovePropertyListenerWithUserData 安裝或者移除監聽屬性變化回調函數

Audio Units 參數

audio unit 參數是用戶可以在音頻生成的過程中更改,事實上,大部分參數可以在 audio unit 正在執行時實時調整的,例如音量。

  • AudioUnitGetParameter
  • AudioUnitSetParameter

I/O Units 的基本特性

技術分享圖片

  • Input element 和 Output element 都是 I/O unit 的一部分,可以將它們視為一個獨立的個體,單獨啟動或禁止每一個 Element,默認情況下,Element 1 禁用,Element 0 開啟。
  • 音頻輸入硬件麥克風直接連著 Element 1, Element 1 的 Input scope 對你是不可見的,你首次訪問硬件輸入的音頻數據是位於 Element 1 的 Output scope。
  • 音頻輸出硬件揚聲器直接連著 Element 0,Element 0 的 Output scope 對你是不可見的,數據從 Element 1 的 Output scope 傳遞到 Element 0 的 Input scope。

每一個 Element都有自己的 input scope 和 output scope,當描述 I/O unit 的時候可能會有困惑,相當於這樣描述,你收到收據來自 input element 的 output scope,發送數據到 output element 的 input scope。
I/O unit 是唯一能夠在 audio processing graph 中啟動和停止音頻流的 audio unit。I/O unit 負責在音頻單元APP中的音頻流。

Audio Processing Graphs 管理 Audio Units

AUGraph 用於構建和管理 audio units 處理鏈,可以利用多個 audio units 和多個回調函數功能,創建幾乎任何你可以想象的音頻處理解決方案。
AUGraph 增加了線程安全,讓你可以即時重新配置處理鏈,例如你可以安全插入一個均衡器,甚至在音頻播放時可以交換混音器輸入的其它回調函數。實際上,AUGraph 提供了 iOS 中唯一可以在音頻應用程序中執行這種動態重新配置的 API 。
Audio Processing Graph 使用了 AUNode 表示上 graph 中 一個單獨的 audio unit ,當使用 Audio Processing Graphs,通常與包含 audio units 的代理 AUNode 交互,而不是直接使用 audio unit。
當將 graph 組合時,需要配置每一個 audio unit ,並且必須通過 audio unit API 直接與 audio unit 交互,節點單元本身是不可以配置的,通過這種方式,需要使用這兩種 API。
通常情況下,Audio Processing Graphs 通常需要三個任務,將節點添加到 Graph 中,直接配置由節點表示的 audio unit,互連節點。

#Audio Processing Graph 有一個 I/O Unit

每一個 audio processing graph 有一個 I/O unit,無論你是錄音,播放,同步 I/O。Graphs 通過 AUGraphStart 和 AUGraphStop 啟動和停止音頻流,通過函數 AudioOutputUnitStart 和 AudioOutputUnitStop 傳遞開始和停止消息到 I/O unit。

Audio Processing Graphs 提供線程安全

audio processing graph 使用“待辦事項列表”提供線程安全,API 的一些函數添加工作單元到稍後執行的更改列表中,在你指定完整的更改好,讓 graph 實現他們。
這是一些 audio processing graph 支持的重配置函數

  • 添加或刪除音頻單元節點(AUGraphAddNode,AUGraphRemoveNode)
  • 添加或刪除節點間的連接(AUGraphConnectNodeInput,AUGraphDisconnectNodeInput)
  • 渲染回調函數連接到 aduio unit 的輸入總線(AUGraphSetNodeInputCallback)

下面看一個重配置 audio processing graph 的例子,構建一個 graph 包含 Multichannel Mixer unit 和 Remote I/O unit,將聲音輸入到混頻器的兩個輸入總線上。從混合器輸出數據到 I/O unit 的 Output element 上。

技術分享圖片

現在用戶想插入均衡器到其中一個音頻流上,如何完成動態配置

  • 使用 AUGraphDisconnectNodeInput 斷掉 input 1 到 mixer unit 的回調
  • 添加一個 iPod EQ unit 到 graph 中,需要使用 AudioComponentDescription 指定 iPod EQ unit 的結構,接著調用 AUGraphAddNode,至此,iPod EQ unit 被安裝但是沒有初始化,被 graph 擁有但是沒有參與到音頻流中
  • 重配置和初始化 iPod EQ unit,詳情如下:

調用 AudioUnitGetProperty 函數得到 mixer input 的流格式(kAudioUnitProperty_StreamForamt)
調用 AudioUnitSetProperty 函數兩次,一次設置 iPod EQ unit 的輸入格式,一次設置它的輸出格式
調用 AudioUnitInitialize 函數,給 iPod EQ 分配資源和準備處理音頻,這個函數調用時線程安全的
調用 AUGraphSetNodeInputCallback 函數,設置鼓的回調函數到 iPod EQ unit 的輸入

技術分享圖片

回調函數提供數據給 Audio Units

為了給 audio unit 的輸入總線提供數據,使用遵從 AURenderCallback 原型的回調函數,音頻輸入單元需要一幀數據的時候觸發回調。在處理 audio unit 應用中,寫回調函數可能是最具有創意的工作,你能根據你的意願產生和改變聲音。
回調函數有嚴格的性能要求,回調存在於實時線程上,隨後回調異步到達,回調函數內部所有的工作發生在時間有限的環境中,當下一幀數據到達,你仍在處理之前的回調產生的幀,聲音則會產生間隙,出於這個原因,不得在回調函數主體中執行耗時操作,例如鎖定,分配內存,訪問文件系統,網絡連接等。

理解音頻單元的回調函數

回調函數頭部

static OSStatus MyAURenderCallBack(void                                    *inRefCon,
                                                      AudioUnitRenderActionFlags  *ioActionFlags,
                                                      const AudioTimeStamp         *inTimeStamp,
                                                      UInt32                                inBusNumber,
                                                      UInt32                                inNumberFrames,
                                                      AudioBufferList                   *ioData);
  • inRefCon,參數指向回調附加到 audio unit 輸入時指定的編程上下文

  • ioActionFlags,參數允許提供提示當沒有音頻數據處理時,例如當你的應用程序是合成吉他,用戶當面沒有播放,請執行此操作。當你想要輸出靜音,可以在回調主體中使用如下語句:*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence,並且你必須明確地將 ioData 參數指向的緩沖區設置為0。
  • inTimeStamp,回調函數被觸發時間,包含一個 AudioTimeStamp 結構體。
  • inBusNumber,參數指示調用回調的音頻單元總線
  • inNumberFrames,當前調用的音頻采樣數
  • ioData,指向音頻數據緩存區

技術分享圖片

Audio Unit 基礎