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

小東邪發表於2019-05-06

閱讀前提:

  • 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
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官方文件

相關文章