Audio Unit: iOS中最底層最強大音頻控制API

閱讀前提:

  • Audio Session基礎 - Audio Session
  • Core Audio基本數據結構 - Core Audio
  • 音視頻基礎知識
  • C/C++ 簡單數據結構,函數使用

以下概念是文中常用的詞語,因為其含義一般直接用英文表達, 一般不需中文翻譯,可將其理解為固定名詞詞組.

  • audio unit: 主要介紹的技術名稱
  • audio processing graph: 另一種處理audio unit的技術
  • node: 承載audio unit的容器
  • input scope /output scope : 可理解為音頻流動的位置(比如從input scope流向output scope)
  • input element : 連接輸入端硬件(如麥克風)的一個組件.
  • output element : 連接輸出端硬件(如揚聲器)的一個組件.
  • bus: 與element概念相同,在文中強調信號流時使用“bus”,在強調音頻單元的特定功能方面時使用“element”,\
  • I/O Units: 輸入輸出常用的audio unit類型,其中包括Remote I/O unit, Voice-Processing I/O, Generic Output unit三種類型.

注意每個element還可能具有input scope與output scope.

Overview

Audio Unit : iOS提供音頻處理插件,支持混合,均衡,格式轉換和實時輸入/輸出,用于錄制,播放,離線渲染和實時對話,例如VoIP(互聯網協議語音).可以從iOS應用程序動態加載和使用它.

Audio Unit通常在稱為audio processing graph的封閉對象的上下文中工作,如圖所示。在此示例中,您的應用程序通過一個或多個回調函數將音頻發送到graph中的第一個audio unit,并對每個audio unit進行單獨控制。 最終生成的I / O unit直接輸出給連接的硬件.

1.ex

audio unit是iOS音頻層面中最底層的編碼層,如果要充分利用它需要對audio unit有更深入的了解.除非你需要實時播放同步的聲音,低延遲的輸入輸出或是一些音頻優化的其他特性,否則請使用Media Player, AV Foundation, OpenAL, or Audio Toolbox等上層框架.

優點

Audio Unit提供了更快,模塊化的音頻處理,同時提供了強大的個性化功能,如立體聲聲像,混音,音量控制和音頻電平測量。如果想充分使用它的功能,必須深入了解包括音頻數據流格式,回調函數和音頻單元架構等基礎知識。

  • 出色的響應能力: 可以通過回調函數訪問實時的音頻數據.可以直接使用audio unit合成樂器音,實時同步語音.
  • 動態的重新配置: 圍繞AUGraph opaque類型構建的 audio processing graph API允許以線程安全的方式動態組裝,重新配置和重新排列復雜的音頻處理鏈,同時處理音頻。這是iOS中唯一提供此功能的音頻API。

生命周期

  • 運行時,獲取對動態可鏈接庫的引用,該庫定義您要使用的audio unit
  • 新建一個audio unit實例
  • 根據需求配置audio unit
  • 初始化audio unit以準備處理音頻
  • 開啟audio unit
  • 控制audio unit
  • 用完后釋放audio unit

選擇設計模式

  • 配置audio session負責app與硬件間的交互,配置I/O unit: I/O units有兩個獨立元素(elements)組成,一個從輸入端硬件接收音頻數據,一個將音頻數據送給輸出端硬件.可以根據需求選擇我們要開啟的元素.
  • 構造audio processing graph: 在audio processing graph中,必須指定音頻數據流格式.
  • 控制audio units的生命周期:建立audio unit的連接并注冊回調函數.

一.工作原理

2

如圖所示,audio unit是iOS中音頻最底層的API,audio unit僅在高性能,專業處理聲音的需求下使用才有意義.

1. audio unit提供了快速的,模塊化的音頻處理

使用場景

  • 以最低延遲的方式同步音頻的輸入輸出,如VoIP.
  • 手動同步音視頻,如游戲,直播類軟件
  • 使用特定的audio unit:如回聲消除,混音,音調均衡
  • 一種處理鏈架構:將音頻處理模塊組裝成靈活的網絡。這是iOS中唯一提供此功能的音頻API。

1.1. iOS中的audio unit

功能 Audio units
Effect iPod Equalizer
Mixing 3D Mixer,Multichannel Mixer
I/O Remote I/O, Voice-Processing I/O, Generic Output
Format conversion Format Converter
  • Effect Unit

提供一組預設均衡曲線,如重低音,流行音等等。

  • Mixer Units
    • 3D Mixer unit: OpenAL構建的基礎,如果需要3D Mixer unit特性,建議直接使用OpenAL,因為它提供了很多封裝好的功能強大的API.
    • Multichannel Mixer unit: 為一個或多個聲道的聲音提供混音功能,以立體聲輸出.你可以單獨打開或關閉其中一個聲道的聲音,調節音量,快進快退等.
  • I/O Units
    • Remote I/O unit: 直接連接輸入,輸出的音頻硬件,以低延遲的方式訪問單個接收或發出的音頻采樣點.提供了格式轉換功能,
    • Voice-Processing I/O: 通過聲學的回聲消除拓展了Remote I/O unit,常用于VoIP或語音通信的應用.它還提供了自動增益校正,語音處理質量調整和靜音等功能.
    • Generic Output unit: 不連接音頻硬件而是提供了一種機制:將處理鏈的輸出傳遞給應用程序.通常用來做離線音頻處理.
  • Format Converter Unit

    通常通過I/O unit間接使用.

1.2. 同時使用兩個Audio Unit APIs

iOS有一個用于直接處理audio units的API,另一個用于處理audio processing graphs,可以同時使用這兩種API. 然而這兩種API中有一部分功能是相同的,如下:

  • 獲取audio units的動態可鏈接庫的引用
  • 實例化audio units
  • 連接audio units并注冊回調函數
  • 啟動和停止音頻流

1.3. 指定Audio Unit屬性以獲取其引用對象

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


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

AudioComponentFindNext:第一個參數設置為NULL表示使用系統定義的順序查找第一個匹配的audio unit.如果你將上一個使用的audio unit引用傳給該參數,則該函數將繼續尋找下一個與之描述匹配的audio unit.

還可以使用audio processing graph API初始化audio unit

// Declare and instantiate an audio processing graph
AUGraph processingGraph;
NewAUGraph (&processingGraph);
 
// Add an audio unit node to the graph, then instantiate the audio unit
AUNode ioNode;
AUGraphAddNode (
    processingGraph,
    &ioUnitDescription,
    &ioNode
);
AUGraphOpen (processingGraph); // indirectly performs audio unit instantiation
 
// Obtain a reference to the newly-instantiated I/O unit
AudioUnit ioUnit;
AUGraphNodeInfo (
    processingGraph,
    ioNode,
    NULL,
    &ioUnit
);

1.4. Audio Units的Scopes,Elements.

如下圖,Audio Units由Scopes,Elements組成.

3
  • scope: audio unit內部的編程上下文,scope概念有一點抽象,可以這樣理解,比如input scope表示里面所有的element都需要一個輸入。output scope 表示里面所有的element都會輸出到某個地方。至于global scope應該是用來配置一些和輸入輸出概念無關的屬性。

  • element: 當element是input/output scope的一部分時,它類似于物理音頻設備中的信號總線.因此這兩個術語"element, bus"在audio unit中是一個含義.本文檔在強調信號流時使用“bus”,在強調音頻單元的特定功能方面時使用“element”,例如I / O unit的輸入和輸出element.

上圖展示了audio unit的一種常見架構,其中輸入和輸出上的element數量相同。然而,不同的audio unit使用不同架構。例如,mixer unit可能具有多個輸入element但是具有單個輸出element。

  • global scope:作為整體應用于audio unit并且不與任何特定音頻流相關聯.它只有一個element。該范圍僅適用于個別屬性,比如每個片的最大幀數(kAudioUnitProperty_MaximumFramesPerSlice)

input , output scopes直接參與通過audio unit移動一個或多個音頻流.audio進入input scope并從output scope離開 。屬性或參數可以作為整體應用于input或output scope,例如kAudioUnitProperty_ElementCount.其他屬性和參數(如enable I / O屬性(kAudioOutputUnitProperty_EnableIO)或volume參數(kMultiChannelMixerParam_Volume))適用于特定scope的element.

注意: 可以這樣理解scope,scope就是音頻流動的方位,比如從input scope流動到 output scope, 而element是與硬件掛鉤的,比如input element是跟麥克風連接的,音頻從input element的input scope流入,從它的output scope流出.

1.5. 使用屬性配置Audio Units

UInt32 busCount = 2;
 
OSStatus result = AudioUnitSetProperty (
    mixerUnit,
    kAudioUnitProperty_ElementCount,   // the property key
    kAudioUnitScope_Input,             // the scope to set the property on
    0,                                 // the element to set the property on
    &busCount,                         // the property value
    sizeof (busCount)
);
  • kAudioOutputUnitProperty_EnableIO

用于在I / O unit上啟用或禁用輸入或輸出。默認情況下,輸出已啟用但輸入已禁用。

  • kAudioUnitProperty_ElementCount

配置mixer unit上的輸入elements的數量

  • kAudioUnitProperty_MaximumFramesPerSlice

為了指定音頻數據的最大幀數,audio unit應該準備好響應于回調函數調用而產生。對于大多數音頻設備,在大多數情況下,您必須按照參考文檔中的說明設置此屬性。如果不這樣做,屏幕鎖定時您的音頻將停止。

  • kAudioUnitProperty_StreamFormat

指定特定audio unit輸入或輸出總線的音頻流數據格式。

大多數屬性只能在audio unit沒有初始化時指定,但是某些特定屬性可以在audio unit運行時設置,如kAUVoiceIOProperty_MuteOutput靜音功能.

要測試屬性的可用性,訪問其值以及監視其值的更改,請使用以下函數:

  • AudioUnitGetPropertyInfo: 測試屬性是否可用;如果是,則為其值提供數據大小.
  • AudioUnitGetProperty,AudioUnitSetProperty: 獲取,設置一個屬性值
  • AudioUnitAddPropertyListener,AudioUnitRemovePropertyListenerWithUserData: 監聽,移除監聽對于特定屬性.

1.6. 用戶交互

audio unit parameter是用戶可以在audio unit運行時提交的參數,通過下面的函數實現.

  • AudioUnitGetParameter
  • AudioUnitSetParameter

1.7. I/O Units基本特征

4.

盡管這兩個elements是audio unit的一部分,但你的app應該把它們當做兩個獨立的實體.例如,你可以根據需求使用kAudioOutputUnitProperty_EnableIO屬性獨立啟用或禁用每個element.如上圖,Element 1直接與音頻輸入硬件(麥克風)連接,在藍色區域輸入端的連接范圍對于開發者是不透明的,開發者可以在黃色區域,即Element 1輸出端獲取麥克風采集的音頻數據.同樣地,Element 0的輸出端直接與音頻硬件(揚聲器)連接,開發者可以將音頻數據交給Element 0的輸入端,輸出端是不透明的.

實際使用過程中,我們經常需要選擇使用哪個element,使用時我們常使用它們的編號而不是名稱,input element編號為1,output elemnet編號為0.(可以將input首字母I當做1,ouput首字母O當做0來記憶)

如上圖所示,每個element都有自己的輸入,輸入范圍.這是為了更加清楚的描述.例如,在一個同時具備輸入輸出音頻的app中,你可以收到音頻從input element的 output scope, 發送音頻給output element的input scope.

2. Audio Processing Graphs管理Audio Units

audio processing graph(AUGraph):基于Core Foundation風格的數據結構,常用來管理audio unit處理鏈. graph可以利用多個audio unit與回調函數,以用來解決任意音頻處理方法。

  • AUGraph類型保證了線程安全.例如播放音頻時,允許你添加一個均衡器或者在mixer輸入端更換回調函數.AUGraph提供了音頻動態配置在iOS平臺.
  • AUNode: 代表graph上下文中單個的audio unit.使用graph時,我們常用它作為代理與audio unit交互,而不是直接使用audio unit.

當我們將graph放在一起時,必須使用audio unit的API配置每個audio unit. 而nodes則不能直接配置audio unit.因此,使用graph必須同時使用這兩套API.

使用AUNode實例對象(使用node代表一個完整的audio processing subgraph)作為一個復雜graph中的element.在這種情況下, I/O unit結尾的subgraph必須是Generic Output unit(不能連接音頻硬件的I/O unit).

步驟

  • 向graph中添加nodes
  • 通過nodes直接配置audio units
  • 互相連接nodes
2.1. Audio Processing Graph擁有精確的I/O Unit.

無論你正在錄制,播放或是同步,每個audio processing graph都有一個I/0 unit.通過AUGraphStartAUGraphStop可以開啟或停止音頻流.通過AudioOutputUnitStartAudioOutputUnitStop可以開啟或停止I/O unit.通過這種方式,graph的I / O單元負責graph中的音頻流。

2.2. 線程安全

audio processing graph API保證了線程安全.此API中的某些功能會將一個audio unit添加到稍后要執行的更改列表中.指定完整的更改集后,然后要求graph去實現它們。

以下是audio processing graph API支持的一些常見重新配置及其相關功能:

  • 添加,移除audio unit nodes (AUGraphAddNode, AUGraphRemoveNode)
  • 添加移除nodes間的連接(AUGraphConnectNodeInput, AUGraphDisconnectNodeInput)
  • 連接audio unit input bus的回調函數.(AUGraphSetNodeInputCallback)


    a1

以下是一個重新配置運行中的audio processing graph.例如,構建一個graph包含Multichannel Mixer unit與Remote I/O unit.用于播放合成兩種輸入源的混音效果.將兩個輸入源的數據送給Mix的input buses.mixer的輸出端連接I/OUnit的output element最終將聲音傳給硬件.


a2

在上面這種情況下,用戶如果想在任一一路流前插入一個均衡器.可以添加一個iPod EQ unit在從硬件輸入端到mixer輸入端之前.如圖,以下是重新配置的步驟

  • 1.通過調用AUGraphDisconnectNodeInput斷開mixer unit input 1 的“beats sound”回調。
  • 2.添加一個包含iPod EQ unit的audio unit node到graph中.可以通過配置ASBD以生成iPod EQ unit,然后調用AUGraphAddNode將其加入到graph.此時,iPod EQ unit已具有實例化對象但未初始化,已經存在于graph中但未參與音頻流.
  • 3.配置,初始化iPod EQ unit.
    • 調用AudioUnitGetProperty從mixer的輸入端檢索kAudioUnitProperty_StreamFormat流格式.
    • 調用兩次AudioUnitSetProperty,一次設置iPod EQ unit’s input流格式,一次設置輸出流格式.
    • 調用AudioUnitInitialize以分配內存準備使用.這個函數是線程不安全的.但是,當iPod EQ unit尚未主動參與audio processing graph時,必須在序列時執行它,因為此時沒有調用AUGraphUpdate函數。
  • 4.通過調用AUGraphSetNodeInputCallback將“beats sound”回調函數添加到iPod EQ input端。

上面1,2,4步使用AUGraph*開頭的函數,都會被添加到graph的任務執行列表中.通過調用AUGraphUpdate執行這些未開始任務.如果成功返回,則graph已經被動態重新配置并且iPod EQ也已經就位正在處理音頻數據.

2.3. 通過graph "pull" 音頻流

在audio processing graph可以使用類似生產者消費者模式,消費者在需要更多音頻數據時通知生產者。請求音頻數據流的方向與音頻流提供的方向正好相反.

2.

對一組音頻數據的每個請求稱為渲染調用(render call),也稱為拉流(pull)。該圖表示拉流為灰色“控制流”箭頭。拉流請求的數據更恰當地稱為一組音頻樣本幀(audio sample frames)。反過來,響應拉流而提供的一組音頻樣本幀被稱為slice.提供slice的代碼稱為渲染回調函數( render callback function).

  • 調用AUGraphStart函數.虛擬輸出設備調用Remote I/O unit output element的回調函數.該調用請求一片處理過的音頻數據幀。
  • Remote I/O unit的回調函數在其輸入緩沖區中查找要處理的音頻數據。如果有數據等待處理,Remote I/O unit將使用它.否則,如圖所示,它將調用應用程序連接到其輸入的任何內容的回調函數。Remote I/O unit的輸入端連接一個effect unit的輸出端.I/O uni從effect unit中拉流,請求一組音頻數據幀.
  • effect unit的行為與Remote I/O unit一樣.當它需要音頻數據時,它從輸入連接中獲取它.上例中,effect unit從回調函數中獲取音頻數據
  • effect unit處理回調函數中獲取的音頻數據. effect unit然后將先前請求的(在步驟2中)處理的數據提供給Remote / O unit
  • Remote I/O unit通過effect unit提供的音頻片。然后,Remote I/O unit 將最初請求的處理過的片(在步驟1中)提供給虛擬輸出設備。這完成了一個拉動周期。

3. 回調函數中將音頻傳給audio unit

為了從本地文件或內存中將音頻傳給audio unit的input bus.使用符合AURenderCallback回調函數完成.audio unit input需要一些音頻數據幀將調用回調函數.

回調函數是唯一可以對音頻幀做處理的地方,同時,回調函數必須遵守嚴格的性能要求.以錄制為例,回調函數是按照固定時間間隔進行喚醒調用,如果我們在間隔時間內還沒有處理完上一幀數據,那么下一幀數據到達時將產生一個間隙的效果.因此回調函數內應盡量避免加鎖,分配內存,訪問文件系統或網絡連接,或以其他方式在回調函數的主體中執行耗時的任務。

static OSStatus MyAURenderCallback (
    void                        *inRefCon,
    AudioUnitRenderActionFlags  *ioActionFlags,
    const AudioTimeStamp        *inTimeStamp,
    UInt32                      inBusNumber,
    UInt32                      inNumberFrames,
    AudioBufferList             *ioData
) { /* callback body *
  • inRefCon: 注冊回調函數時傳遞的指針,一般可傳本類對象實例,因為回調函數是C語言形式,無法直接訪問本類中屬性與方法,所以將本例實例化對象傳入可以間接調用本類中屬性與方法.
  • ioActionFlags: 讓回調函數為audio unit提供沒有處理音頻的提示.例如,如果應用程序是合成吉他并且用戶當前沒有播放音符,請執行此操作。在要為其輸出靜默的回調調用期間,在回調體中使用如下語句:*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;當您希望產生靜默時,還必須顯式地將ioData參數指向的緩沖區設置為0。
  • inTimeStamp: 表示調用回調函數的時間,可以用作音頻同步的時間戳.每次調用回調時, mSampleTime 字段的值都會由 inNumberFrames參數中的數字遞增。例如, 如果你的應用是音序器或鼓機, 則可以使用 mSampleTime 值來調度聲音。
  • inBusNumber: 調用回調函數的audio unit bus.允許你通過該值在回調函數中進行分支.另外,當audio unit注冊回調函數時,可以指定不同的inRefCon為每個bus.
  • inNumberFrames: 回調函數中提供的音頻幀數.這些幀的數據保存在ioData參數中.
  • ioData: 真正的音頻數據,如果設置靜音,需要將buffer中內容設置為0.

4. 音頻流格式啟用數據流

  • AudioStreamBasicDescription結構 (簡稱ASBD)
struct AudioStreamBasicDescription {
    Float64 mSampleRate;
    UInt32  mFormatID;
    UInt32  mFormatFlags;
    UInt32  mBytesPerPacket;
    UInt32  mFramesPerPacket;
    UInt32  mBytesPerFrame;
    UInt32  mChannelsPerFrame;
    UInt32  mBitsPerChannel;
    UInt32  mReserved;
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;


size_t bytesPerSample = sizeof (AudioUnitSampleType);
AudioStreamBasicDescription stereoStreamFormat = {0};
 
stereoStreamFormat.mFormatID          = kAudioFormatLinearPCM;
stereoStreamFormat.mFormatFlags       = kAudioFormatFlagsAudioUnitCanonical;
stereoStreamFormat.mBytesPerPacket    = bytesPerSample;
stereoStreamFormat.mBytesPerFrame     = bytesPerSample;
stereoStreamFormat.mFramesPerPacket   = 1;
stereoStreamFormat.mBitsPerChannel    = 8 * bytesPerSample;
stereoStreamFormat.mChannelsPerFrame  = 2;           // 2 indicates stereo
stereoStreamFormat.mSampleRate        = graphSampleRate;

首先,應該確定采樣值的數據類型,上面使用的是AudioUnitSampleType,這是大部分audio units推薦的類型,在iOS平臺上,被定義為8.24位固定的整型數據.接下來定義ASBD類型變量,初始化為0代表不包含任何數據.(注意,不能跳過該步,否則可能產生一些想不到的問題.). 然后就是對ASBD賦值,設備PCM類型表示未壓縮的音頻數據,

  • mFormatFlags(metaflag): 某些audio unit使用不規則的音頻數據格式即不同的音頻數據類型,則mFormatFlags字段需要不同的標志集。 例如:3D Mixer unit需要UInt16類型數據作為采樣值且mFormatFlags需要設置為kAudioFormatFlagsCanonical.

  • mBytesPerPacket: 每個音頻包中有多少字節數

  • mBytesPerFrame: 每一幀中有多少字節

  • mFramesPerPacket: 每個包中有多少幀

  • mBitsPerChannel: 每個聲道有多少位

在audio processing graph中你必須在關鍵點設置音頻數據格式.其他點,系統將會設置這個格式.IOS 設備上的音頻輸入和輸出硬件具有系統確定的音頻流格式,并且格式始終是未壓縮的, 采用交錯的線性 PCM 格式.

5

如上圖, 麥克風表示輸入的音頻硬件。系統確定輸入硬件的音頻流格式, 并將其施加到 Remote I/O unit’s 的input element的output scope內。同樣,揚聲器表示輸出音頻硬件。系統確定輸出硬件的流格式,并將其施加到Remote I/O unit’s output element的output scope。

開發者則負責在兩個Remote I/O unit’s elements之間建立音頻流格式.I/O unit則自動在硬件與自身連接的一端做一個必要的格式轉換.

audio unit連接的一個關鍵功能 (如圖1-8 所示) 是, 該連接將音頻數據流格式從其源音頻單元的輸出傳播到目標音頻單元的輸入。這是一個關鍵點, 因此值得強調的是: 流格式傳播是通過音頻單元連接的方式進行的, 僅在一個方向上進行--從源音頻單元的輸出到目標音頻單元的輸入。

雖然我們通過ASBD靈活的設置音頻數據流的屬性(如采樣率),但是建議還是使用當前設備硬件默認使用值.因為如果保持一致,系統不需要做采樣率轉換,這可以降低能耗同時提高音頻質量.

二.Audio Unit使用步驟

1.選擇設計模式

  • 僅僅只有一個 I/O unit.
  • audio processing graph中使用單一音頻流格式,
  • 要求你去設置流的格式,或則僅僅設置其中一部分.

正確的設置流格式在建立音頻流中顯得至關重要.這些模式大多依賴于音頻流格式從源到目的地的自動傳播,它們之間通過audio unit連接.合理利用傳播的特性可以減少代碼書寫量,同時,你必須保證清楚了解每種模式需要如何進行設置.可以在不使用audio processing graph的情況下實現任一一種模式,但是使用它可以減少代碼書寫量并支持動態重新配置.

1.1. I/O Pass Through

I/O Pass Through傳遞模式在不處理音頻的情況下將傳入的音頻直接發送到輸出硬件.


6

如上圖所示,輸入端硬件將它的格式告訴Remote I/O unit的input element.開發者則在I/O unit的輸出端指定了流格式,audio unit內部將根據需求自動進行轉換,為了避免采樣率轉換,我們在定義ASBD時最好使用硬件使用的采樣率.在上圖,我們不必指定I/O unit的輸出element,因為數據格式會從輸入的element傳給輸出.同理,傳給硬件的流將會根據硬件需要完成一次自動轉換.

1.2. I/O不帶有回調函數

app可以添加一個或多個audio unit在Remote I/O unit’s elements之間.例如使用多通道Mixer unit將傳入的麥克風音頻定位到立體聲域中,或提供輸出音量控制,在這中模式下,仍然沒有用到回調函數.它簡化了模式,但限制了其實用性。如果沒有呈現回調函數,就無法直接操作音頻。


7

在這種模式下,和Pass Through模式相同,你可以配置這兩個Remote I/O unit.為了設置多通道Mixer unit,你必須把你的音頻流采樣率設置給mixer output.mixer的輸入端音頻流格式會自動設置通過接收從I/O unit輸出端傳遞來的音頻流.同理,Output element輸入端的格式也將自動設置通過流傳遞.

使用此模式必須設置kAudioUnitProperty_MaximumFramesPerSlice屬性.

1.3. I/O帶有回調函數

通過注冊回調函數在Remote I/O unit的input,output elements之間,開發者可以在音頻數據送到輸出硬件之前操控它.比如,通過回調函數調節輸出音頻的音量,還可以添加顫音、環調制、回波或其他效果(Accelerate framework中有相關API).

8

如圖所示,這個模式使用兩個Remote I/O unit, 回調函數被附加在output element的input scope.當output element需要音頻數據時,系統會觸發回調,緊接著,回調完成后系統將數據傳給output element的input scope.

必須顯式啟動input在Remote I/O unit.因為它默認是禁止的.

1.4. 僅輸出的回調函數

該模式通常用于游戲,專業音頻app使用.簡單的說,該模式在直接連接在Remote I/O unit的output element的input scope.可以利用此模式完成復雜的音頻結構,如將幾種不同的聲音混合在一起,然后通過輸出硬件播放他們,如下圖.

9

如上圖所示,注意iPod EQ需要開發者設置Input,output兩者的流格式.而 Multichannel Mixer僅僅只需要設置它輸出的采樣率即可.正如前面說到過,完整的音頻流格式信息會在傳遞的過程中自動賦值.

1.5. 其他

audio unit還有兩種主要的設計模式.

  • 錄制與分析音頻: 創建一個帶有回調的僅輸入的app.回調函數會首先被喚醒,隨后將數據傳給Remote I/O unit’s input element.但是大多數情況下直接使用audio queue更為簡單方便. audio queue使用起來更加靈活,因為它的回調函數不在一個實時的線程上.
  • Generic Output unit: 離線音頻處理.不像Remote I/O unit,這個audio unit不連接設備的音頻硬件.當你使用它發送音頻到app時,它僅僅取決于你的應用程序調用它的渲染方法.

2.構建App

  • 配置audio session
  • 指定audio unit
  • 創建audio processing graph,然后獲取audio unit
  • 配置audio unit
  • 連接audio unit nodes
  • 提供用戶界面
  • 初始化然后開啟audio processing graph.

2.1. 配置Audio Session

audio session是app與硬件交互的中介。設置采樣率,

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.獲取audio session單例對象
  • 2.請求當前設備硬件使用的采樣率.
  • 3.設置音頻分類。AVAudioSessionCategoryPlayAndRecord指的是支持音頻輸入與輸出。
  • 4.激活audio session
  • 5.激活audio session后更新采樣率。

你還可以配置一些其他功能,如采樣率為44.1 kHz默認的duration是大概23ms,相當于每次采集1024個采樣點。如果你的app要求延遲很低,你可以最低設置0.005ms(相當于256個采樣點)

self.ioBufferDuration = 0.005;
[mySession setPreferredIOBufferDuration: ioBufferDuration
                                  error: &audioSessionError];

2.2. 指定audio unit

配置完audio session后還無法直接獲得audio unit,通過AudioComponentDescription賦值可以得到一個指定的audio unit. (1.3中講到如何賦值)

2.3. 構建Audio Processing Graph

  • 實例化AUGraph對象(代表 audio processing graph)。
  • 實例化一個或多個AUNode對象(每一個代表一個 audio unit in the graph.)。
  • 添加nodes到graphgraph并且實例化
  • 打開graph并且實例化 audio units
  • 獲得audio unit引用
AUGraph processingGraph;
NewAUGraph (&processingGraph);
 
AUNode ioNode;
AUNode mixerNode;
 
AUGraphAddNode (processingGraph, &ioUnitDesc, &ioNode);
AUGraphAddNode (processingGraph, &mixerDesc, &mixerNode);

調用AUGraphAddNode函數后,graph被實例化并且擁有nodes.為了打開graph并且實例化audio unit需要調用AUGraphOpen
AUGraphOpen (processingGraph);

通過AUGraphNodeInfo函數獲取對audio unit實例的引用

AudioUnit ioUnit;
AudioUnit mixerUnit;
 
AUGraphNodeInfo (processingGraph, ioNode, NULL, &ioUnit);
AUGraphNodeInfo (processingGraph, mixerNode, NULL, &mixerUnit);

2.4.配置audio unit.

iOS中每種audio unit都有其配置的方法,我們應該熟悉一些常用的audio unit配置.

Remote I/O unit, 默認輸入禁用,輸出可用.如果僅僅使用輸入,你必須相應地重新配置 I/O unit.除了Remote I/O與Voice-Processing I/O unit之外所有的audio unit都必須配置kAudioUnitProperty_MaximumFramesPerSlice屬性.該屬性確保audio unit 準備好響應于回調函數產生足夠數量的音頻數據幀。

2.5. 注冊并實現回調函數

對于需要使用回調函數的設計模式,我們必須注冊并實現相應的回調函數.此外,還可以通過回調函數拉取音頻數據流.

AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc        = &renderCallback;
callbackStruct.inputProcRefCon  = soundStructArray;
 
AudioUnitSetProperty (
    myIOUnit,
    kAudioUnitProperty_SetRenderCallback,
    kAudioUnitScope_Input,
    0,                 // output element
    &callbackStruct,
    sizeof (callbackStruct)
);


AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc        = &renderCallback;
callbackStruct.inputProcRefCon  = soundStructArray;
 
AUGraphSetNodeInputCallback (
    processingGraph,
    myIONode,
    0,                 // output element
    &callbackStruct
);
// ... some time later
Boolean graphUpdated;
AUGraphUpdate (processingGraph, &graphUpdated);

2.6. 連接Audio Unit Nodes

使用AUGraphConnectNodeInputAUGraphDisconnectNodeInput函數可以建立,斷開Node.它們是線程安全的并且降低代碼的開銷,因為如果不適用graph我們將必須手動實現.

AudioUnitElement mixerUnitOutputBus  = 0;
AudioUnitElement ioUnitOutputElement = 0;
 
AUGraphConnectNodeInput (
    processingGraph,
    mixerNode,           // source node
    mixerUnitOutputBus,  // source node bus
    iONode,              // destination node
    ioUnitOutputElement  // desinatation node element
);

當然,我們也可以直接使用audio unit的屬性建立連接,如下.

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

2.7. 提供用戶界面

一般而言,我們需要提供一個用戶界面,讓用戶微調音頻行為。可以定制用戶界面以允許用戶調整特定的音頻單元參數,并在某些特殊情況下調整音頻單元屬性。

2.8. 初始化并開啟Audio Processing Graph

調用AUGraphInitialize初始化audio processing graph.

  • 為每個audio units單獨自動調用AudioUnitInitialize函數來初始化graph所擁有的audio units。 (如果要在不使用graph的情況下構建處理鏈,則必須依次顯式初始化每個audio unit)
  • 驗證graph的連接與音頻數據流格式
  • 通過不同audio unit的連接傳播指定格式的音頻流數據。
OSStatus result = AUGraphInitialize (processingGraph);
// Check for error. On successful initialization, start the graph...
AUGraphStart (processingGraph);
 
// Some time later
AUGraphStop (processingGraph);

3.故障排除提示

通過函數返回值可以檢查調用是否成功.

請留意函數調用之間的依賴,例如,僅僅只能在audio processing graph初始化成功后再開啟它.其次,利用CAShow函數可以打印當前graph的狀態.

確保ASBD初始化賦值為0. AudioStreamBasicDescription stereoStreamFormat = {0};.將ASBD的字段初始化為0可確保沒有字段包含垃圾數據。(注意:作為類聲明中的實例變量 - 其字段會自動初始化為0,無需自己初始化它們)

- (void) printASBD: (AudioStreamBasicDescription) asbd {
 
    char formatIDString[5];
    UInt32 formatID = CFSwapInt32HostToBig (asbd.mFormatID);
    bcopy (&formatID, formatIDString, 4);
    formatIDString[4] = '\0';
 
    NSLog (@"  Sample Rate:         %10.0f",  asbd.mSampleRate);
    NSLog (@"  Format ID:           %10s",    formatIDString);
    NSLog (@"  Format Flags:        %10X",    asbd.mFormatFlags);
    NSLog (@"  Bytes per Packet:    %10d",    asbd.mBytesPerPacket);
    NSLog (@"  Frames per Packet:   %10d",    asbd.mFramesPerPacket);
    NSLog (@"  Bytes per Frame:     %10d",    asbd.mBytesPerFrame);
    NSLog (@"  Channels per Frame:  %10d",    asbd.mChannelsPerFrame);
    NSLog (@"  Bits per Channel:    %10d",    asbd.mBitsPerChannel);
}

三.類型對比

1. 使用I/O Unit

iOS提供了三種I/O unit.大部分應用使用Remote I/O unit,它連接到輸入和輸出音頻硬件,并提供對各個傳入和傳出音頻樣本值的低延遲訪問.對于VoIP應用,Voice-Processing I/O unit通過添加聲學回聲消除和其他功能來擴展遠程I / O單元。要將音頻發送回應用程序而不是輸出音頻硬件,請使用通用輸出單元。

1.1. Remote I/O Unit

Remote I/O unit(子類型kAudioUnitSubType_RemoteIO)連接到設備硬件,用于輸入,輸出或同時輸入和輸出。用于播放,錄制或低延遲同時輸入和輸出,不需要回聲消除。

設備的音頻硬件將其音頻流格式強制放置在 Remote I/O unit的外側。audio unit提供硬件音頻格式和應用音頻格式之間的格式轉換,通過附帶的格式轉換器audio unit進行格式轉換.

input element input scope 從輸入硬件獲取音頻格式, output element output scope從輸出硬件獲取音頻格式,

在input元素的輸出范圍上設置應用程序格式。 input元素根據需要在其輸入和輸出范圍之間執行格式轉換。使用應用程序流格式的硬件采樣率。如果輸出元素的輸入范圍由音頻單元連接提供,則它從該連接獲取其流格式。但是,如果它由渲染回調函數提供,請在其上設置應用程序格式。

Audio unit feature Details
Elements 一個input element,一個output element
建議使用屬性 kAudioFormatLinearPCM,kAudioFormatFlags,AudioUnitSampleType,AudioUnitCanonical
注意點 Remote I/O unit朝外側從音頻硬件獲取其格式,input element從輸入端硬件獲取,output element 從輸出端硬件獲取.
屬性注意 不需要設置kAudioUnitProperty_MaximumFramesPerSlice

1.2.Voice-Processing I/O Unit

Voice-Processing I/O unit(子類型kAudioUnitSubType_VoiceProcessingIO)具有 Remote I/O unit 的特性,并且添加了回聲抑制。它還增加了自動增益校正,語音處理質量調整和靜音功能。這是用于VoIP(互聯網協議語音)應用程序的正確I/O unit。

1.3. Generic Output Unit

在將audio processing graph的輸出發送到應用程序而不是輸出音頻硬件時,請使用此類型為kAudioUnitSubType_GenericOutput的音頻單元。通常使用Generic Output Unit進行離線音頻處理。與其他I / O units一樣,Generic Output unit包含格式轉換器。因此可以在audio processing graph中使用的流格式與所需格式之間執行格式轉換。

2. Using Mixer Units

iOS提供兩種mixer units。在大多數情況下,您應該使用Multichannel Mixer unit,它可以為任意數量的單聲道或立體聲流提供混音。如果您需要3D Mixer unit的功能,請使用OpenAL。 OpenAL建立在3D混音器單元之上,提供與簡單API相同的性能,非常適合游戲應用程序開發。

默認情況下,kAudioUnitProperty_MaximumFramesPerSlice屬性設置為1024,當屏幕鎖定并且顯示器休眠時,這是不夠的。如果您的應用在屏幕鎖定時播放音頻,則必須增加此屬性的值,除非音頻輸入處于活動狀態。做如下:

  • 如果音頻輸入處于活動狀態,則無需為kAudioUnitProperty_MaximumFramesPerSlice屬性設置值。
  • 如果音頻輸入未激活,請將此屬性設置為值4096。

2.1.Multichannel Mixer Unit

Multichannel Mixer unit(子類型kAudioUnitSubType_MultiChannelMixer)可接收任意數量的單聲道或立體聲流,并將它們組合成單個立體聲輸出。它控制每個輸入和輸出的音頻增益,并允許您分別打開或關閉每個輸入。從iOS 4.0開始,多聲道混音器支持每個輸入的立體聲聲像。

Audio unit feature Details
Elements 一個或多個(單聲道或多聲道)input element,一個多聲道output element
建議使用屬性 kAudioFormatLinearPCM,kAudioFormatFlags,AudioUnitSampleType,AudioUnitCanonical
注意點 如果輸入由audio unit連接則從該連接獲取其流格式。如果輸入由回調函數提供在bus上設置完整的流格式,使用與回調提供的數據相同的流格式。在output scope,僅需要設置采樣率。
屬性 kAudioUnitProperty_MeteringMode

注意點:
iPod EQ單元提供一組預定義的色調均衡曲線作為出廠預設。通過訪問音頻單元的kAudioUnitProperty_FactoryPresets屬性獲取可用EQ設置數組。使用它作為kAudioUnitProperty_PresentPreset屬性的值來應用設置。

2.2. 3D Mixer Unit

3D Mixer unit: 控制每個輸入的立體聲聲像,播放速度和增益,并控制其他特征,例如與收聽者的視距,輸出具有音頻增益控制。通常,如果需要3D Mixer unit的功能,最佳選擇是使用OpenAL.

Audio unit feature Details
Elements 一個或多個單聲道input element,一個多聲道output element
建議使用屬性 UInt16,kAudioFormatFlagsAudioUnitCanonical
注意點 如果輸入由audio unit連接則從該連接獲取其流格式。如果輸入由回調函數提供在bus上設置完整的流格式,使用與回調提供的數據相同的流格式。在output scope,僅需要設置采樣率。

注意點:
iPod EQ單元提供一組預定義的色調均衡曲線作為出廠預設。通過訪問音頻單元的kAudioUnitProperty_FactoryPresets屬性獲取可用EQ設置數組。使用它作為kAudioUnitProperty_PresentPreset屬性的值來應用設置。

3.Using Effect Units

iPod EQ unit (子類型kAudioUnitSubType_AUiPodEQ)是iOS 4中提供的唯一效果單元。這與內置iPod應用程序使用的均衡器相同。要查看該音頻設備的iPod應用程序用戶界面,請轉至設置> iPod> EQ。該音頻單元提供一組預設均衡曲線,如低音增強器,流行音樂和口語。

Audio unit feature Details
Elements 一個input element,一個output element (該element可以是單聲道或雙聲道)
建議使用屬性 kAudioFormatLinearPCM,AudioUnitSampleType,kAudioFormatFlagsAudioUnitCanonical
注意點 如果輸入由audio unit連接則從該連接獲取其流格式。如果輸入由回調函數提供在bus上設置完整的流格式,使用與回調提供的數據相同的流格式。在output scope,設置用于輸入的相同完整流格式。
Properties kAudioUnitProperty_FactoryPresets,kAudioUnitProperty_PresentPreset

注意點:
iPod EQ單元提供一組預定義的色調均衡曲線作為出廠預設。通過訪問音頻單元的kAudioUnitProperty_FactoryPresets屬性獲取可用EQ設置數組。使用它作為kAudioUnitProperty_PresentPreset屬性的值來應用設置。

默認情況下,kAudioUnitProperty_MaximumFramesPerSlice屬性設置為1024,當屏幕鎖定并且顯示器休眠時,這是不夠的。如果您的應用在屏幕鎖定時播放音頻,則必須增加此屬性的值,除非音頻輸入處于活動狀態。做如下:

  • 如果音頻輸入處于活動狀態,則無需為kAudioUnitProperty_MaximumFramesPerSlice屬性設置值。
  • 如果音頻輸入未激活,請將此屬性設置為值4096。

4.Audio Units 標識符鍵

此表提供了訪問每個iOS audio unit的動態鏈接庫所需的標識符鍵以及其簡要說明。

名稱與描述 鍵名稱 相應編解碼器
Converter unit (提供音頻格式轉換在線性PCM與其他壓縮格式) kAudioUnitType_FormatConverter,kAudioUnitSubType_AUConverter,kAudioUnitManufacturer_Apple aufc,conv,appl
iPod Equalizer unit(提供iPod均衡器的功能) kAudioUnitType_Effect kAudioUnitSubType_AUiPodEQ kAudioUnitManufacturer_Apple aufx,ipeq,appl
3D Mixer unit (支持混合多個音頻流,輸出平移,采樣率轉換等) kAudioUnitType_Mixer,kAudioUnitSubType_AU3DMixerEmbedded,kAudioUnitManufacturer_Apple aumx,3dem,appl
Multichannel Mixer unit (支持將多個音頻流混合到單個流中) kAudioUnitType_Mixer,kAudioUnitSubType_MultiChannelMixer,kAudioUnitManufacturer_Apple aumx,mcmx,appl
Generic Output unit (支持轉換為線性PCM格式和從線性PCM格式轉換;可用于啟動和停止graph) kAudioUnitType_Output,kAudioUnitSubType_GenericOutput,kAudioUnitManufacturer_Apple auou,genr,appl
Remote I/O unit(連接到設備硬件以進行輸入,輸出或同時輸入和輸出) kAudioUnitType_Output,kAudioUnitSubType_RemoteIO,kAudioUnitManufacturer_Apple auou,rioc,appl
Voice Processing I/O unit(具有I / O單元的特性,并為雙向通信增加了回聲抑制功能) kAudioUnitType_Output,kAudioUnitSubType_VoiceProcessingIO,kAudioUnitManufacturer_Apple auou,vpio,appl

Apple官方文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。