適用于ios的音頻單元指南(1)

概述

概覽

  • 音頻單元提供快速,模塊化的音頻處理
  • 選擇一個設計模式和構建app

音頻單元托管基礎

音頻單元提供快速,模塊化的音頻處理

  • 在ios中的音頻組件
  • 使用標識符指定和獲取音頻單元
  • 使用scoped 和 elements 指定音頻單元的部件
  • 使用屬性 配置 音頻單元
  • 使用參數(shù)和UIKit 為用戶提供控制
  • I/O 單元的基本特征

音頻處理graph管理音頻單元

  • 音頻處理graph具有一個完整的 I / O單元
  • 音頻處理graph提供線程安全
  • 使用graph 的“pull”獲取音頻流

渲染回調(diào)功能將音頻輸入到音頻單元

  • 理解音頻單元的渲染回調(diào)函數(shù)

音頻流格式啟用數(shù)據(jù)流

  • 使用AudioStreamBasicDescription結(jié)構
  • 了解設置流格式的位置和方式

去蘋果官網(wǎng)找關于 ios的音頻指南,搜出來的始終都是macOS的。沒有ios方向,看起來很費勁,所以,跑去了Google搜了下,果然搜到了在蘋果官網(wǎng)沒有搜到的適用于ios的音頻單元指南

概述

ios 提供音頻處理插件,支持混合,均衡,格式轉(zhuǎn)換和實時輸入/輸出,用于錄制,回放,離線渲染和實時對話,流入VoIP(互聯(lián)網(wǎng)協(xié)議語言)。我們可以從iosapp 動態(tài)加載和使用。這些功能很強大并且很靈活,稱為音頻單元。

音頻單元通常在稱為音頻處理圖形上下文工作,如下圖。


上圖實例中,app通過一個或者多個回調(diào)函數(shù)將音頻發(fā)送到圖中的第一個音頻單元,并對每個音頻單元進行控制。I/O 單元的輸出(最后的一個音頻單元或者其他的音頻處理graph)直接連接到輸出硬件

概覽

由于音頻單元構成ios音頻堆棧中編程的最底層,因此有效使用它們需要比其他ios音頻技術需要更深入的了解。除非我們需要實時播放合成聲音,低延遲I/O,或者特定音頻單元功能,否則請下查看 Media Player, AV Foundation, OpenAL, or Audio Toolbox frameworks。

音頻單元提供快速,模塊化的音頻處理

直接使用音頻單元的兩大優(yōu)勢:

  • 出色的響應能力。音頻單元渲染的回調(diào)函數(shù)中使用的是實時優(yōu)先級線程,因此我們寫的代碼是非??拷布?。合成樂器和實時同步語音I/O 直接使用音頻單元收益最多。

  • 動態(tài)重新配置。音頻的graph api 允許我們以線程安全的方式動態(tài)組裝,重新配置和重新排列復雜的音頻處理鏈,同時處理音頻。這是ios中唯一提供此功能的音頻API.

音頻單元的生命周期如下:

  • 1.運行時,獲取對動態(tài)可鏈接庫的引用,該庫定義了我們可以使用的音頻單元。
  • 2.實例化音頻單元
  • 3.根據(jù)類型的需要配置音頻單元,以使用app的意圖
  • 4.初始化音頻單元準備處理音頻
  • 5.控制音頻單元
  • 6.完成后,取消分配音頻單元

音頻單元提供非常有用的個性化功能,如立體聲聲像,混音,音量控制和音頻電平測量。托管音頻設備可以讓我們?yōu)閍pp添加此類功能。但是,要獲取這些好處,必須獲得一系列基本概念,包括音頻數(shù)據(jù)流格式,渲染回調(diào)函數(shù)和音頻單元架構。(這些知識后面講解)

選擇一個設計模式和構建app

托管設計模式的音頻單元提供了一個非常靈活的藍圖,可根據(jù)app的具體情況進行自定義。每種模式表明:

  • 如何配置I/O。I/O 單元有兩個獨立的元件,一個接受來自輸入硬件的音頻,一個將音頻發(fā)送到輸出硬件。每個設計模式都指示應啟用哪個或哪些元素。

  • 在音頻處理grahp中,必須指定音頻數(shù)據(jù)流格式。 我們必須正確的指定格式以支持音頻流。

  • 建立音頻單元連接的位置以及附加渲染回調(diào)函數(shù)的位置。 音頻單元連接可以當做橋梁,將音頻流格式從一個音頻單元的輸出傳播到另一個音頻單元的輸出。渲染回調(diào)可讓我們處理在graph的音頻或者操作在但一個樣本級別的音頻。

無論選擇哪種設計模式,構建音頻單元托管app的步驟基本相同:

  • 配合app音頻會話確保app在系統(tǒng)和設備硬件的上下文正常工作
  • 構建音頻處理graph(如何構建往下看)
  • 提供用于控制圖形音頻單元的用戶界面。

音頻單元托管基礎

ios中所有的音頻技術都簡歷在音頻單元之上,如下圖。


只有當需要最高程度的控制,性能或者靈活性,或者只需要直接使用音頻單元就可以獲得特定功能(如聲學回音消除)時,直接使用音頻但是是正確的選擇。

音頻單元提供快速,模塊化的音頻處理

當我們需要以下功能時候,請直接使用音頻單元,而不是更高級api:

  • 具有低延遲的同時音頻I/O ,例如用于VoIP應用
  • 響應回放合成聲音,例如用于音樂游戲或合成樂器
  • 使用特定的音頻單元功能,例如聲學回音消除,混合或者音調(diào)均衡
  • 一種處理鏈架構,可以讓音頻處理ok組合合成靈活的網(wǎng)絡。這是ios中唯一的提供此功能的音頻API
在ios中的音頻組件

ios 提供七種音頻單元,按照目的可分為四類,如表所示

Purpose 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

以上音頻單元的標識符如下

Name and description Identifier keys Corresponding four-char codes
Converter unit
支持線性PCM的音頻格式轉(zhuǎn)換
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
支持混合多個音頻流,輸出聲像,采樣率轉(zhuǎn)換,等等。
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的音頻格式轉(zhuǎn)換 可用于啟動和停止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

注意:ios動態(tài)插件架構不支持第三方音頻設備。

effect Unit
ios4 提供一個效果單元-ipod均衡器(內(nèi)置的ipod應用就是用的這個均衡器)。想查看均衡器有哪些,我們可以點擊設置->音樂->均衡器設置。是用改設備單元時候,我們需要提供UI界面。改音頻單元提供一組預設的均衡器曲線,如低音增強器,流行音樂和口語。

Mixer Units
os 提供了兩個混音器單元。3D混音器單元是OpenAL 構建的基礎。在大多數(shù)情況下,如果需要3D混音器單元的功能,最佳選擇是使用OpenAL,它提供了更適合游戲應用的更高級API。實例代碼查看oalTouch

多聲道混音器單元可以任意說了的單聲道或者立體聲流提供混音,并帶有立體聲輸出。我們可以打開或關閉每個輸入,設置輸入增強,或者設置其立體聲平移位置。代碼可查看mixerHost

I/O Units
ios 提供三個I/O 單元。遠程I/O單元是最常用的 。他連接到輸入和輸出音頻硬件,并為我們提供對各個傳入和傳出音頻樣本值的低延遲訪問。它也提供硬件音頻格式和app音頻格式之間的格式轉(zhuǎn)換,這是通過包含格式轉(zhuǎn)換單元完成的。有關代碼可以查看aurioTouch

語音處理I / O單元通過添加聲學回聲消除來擴展遠程I / O單元,以用于VoIP或語音聊天應用程序。 它還提供自動增益校正,語音處理質(zhì)量調(diào)整和靜音。

通用輸出單元不連接到音頻硬件,而是提供一種機制,用于將處理鏈的輸出發(fā)送到您的應用程序。 您通常會使用通用輸出單元進行離線音頻處理。

Format Converter Unit
iOS 4提供了一個格式轉(zhuǎn)換器單元,通常通過I / O單元間接使用。

在合奏中使用兩個音頻單元API

ios 中有一個用于直接處理音頻單元的API,另一個用于處理音頻處理graph的api。我們可以在app 中同時使用這兩個api

這兩個API 之間存在一些重疊,我們可以根據(jù)自己的編程風格自由混合搭配。音頻單元API 和音頻處理Graph 均提供一下功能:

  • 獲取對定義音頻單元的動態(tài)可鏈接庫的引用
  • 實例化音頻單元
  • 互聯(lián)音頻單元并附加渲染回到函數(shù)
  • 啟動和停止音頻流
使用標識符指定和獲取音頻單元

要在運行時查找音頻單元,首先在音頻組件中描述數(shù)據(jù)結(jié)構中指定類型,子類型和制造商key。無論是使用音頻單元還是音頻處理graph的API,都執(zhí)行以下操作

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

上述代碼只是指定一個音頻單元-遠程I/O 單元。
要創(chuàng)建通配符描述,需要將一個活多個類型/子類型字段設置為0。例如,要是想匹配所有的I/O 單元,將上述代碼的componentSubType設置為0 就可以了。

這里羅列下每個 音頻組件 ios中對應的subType

type subType 制造商
kAudioUnitType_Output kAudioUnitSubType_RemoteIO kAudioUnitManufacturer_Apple
kAudioUnitType_MusicDevice kAudioUnitSubType_Sampler
kAudioUnitSubType_MIDISynth
kAudioUnitManufacturer_Apple
kAudioUnitType_MusicEffect kAudioUnitManufacturer_Apple
kAudioUnitType_FormatConverter kAudioUnitSubType_AUConverter
kAudioUnitSubType_Varispeed
kAudioUnitSubType_DeferredRenderer
kAudioUnitSubType_Splitter
kAudioUnitSubType_MultiSplitter
kAudioUnitSubType_Merger
kAudioUnitSubType_NewTimePitch
kAudioUnitSubType_AUiPodTimeOther
kAudioUnitSubType_RoundTripAAC
kAudioUnitSubType_AUiPodTime
kAudioUnitManufacturer_Apple
kAudioUnitType_Effect kAudioUnitSubType_PeakLimiter
kAudioUnitSubType_DynamicsProcessor
kAudioUnitSubType_LowPassFilter
kAudioUnitSubType_HighPassFilter
kAudioUnitSubType_BandPassFilter
kAudioUnitSubType_HighShelfFilter
kAudioUnitSubType_LowShelfFilter
kAudioUnitSubType_ParametricEQ
kAudioUnitSubType_Distortion
kAudioUnitSubType_Delay
kAudioUnitSubType_SampleDelay
kAudioUnitSubType_NBandEQ
kAudioUnitSubType_Reverb2
kAudioUnitSubType_AUiPodEQ'
kAudioUnitManufacturer_Apple
kAudioUnitType_Mixer kAudioUnitSubType_MultiChannelMixer
kAudioUnitSubType_MatrixMixer
kAudioUnitSubType_SpatialMixer
kAudioUnitSubType_SpatialMixer
kAudioUnitManufacturer_Apple
kAudioUnitType_Panner ios不支持 kAudioUnitManufacturer_Apple
kAudioUnitType_Generator kAudioUnitSubType_ScheduledSound
kAudioUnitSubType_AudioFilePlayer
kAudioUnitManufacturer_Apple
kAudioUnitType_OfflineEffect kAudioUnitManufacturer_Apple
kAudioUnitType_MIDIProcessor kAudioUnitManufacturer_Apple

設置描述符后,我們可以使用兩個API 中的任意一個獲得對指定音頻單元(或者一組音頻單元)的庫的引用。如下

///獲取音頻單元的引用
    AudioComponent foundIoUnitReference = AudioComponentFindNext (NULL,&ioUnitDescription);
    ///實例化音頻單元
    AudioUnit ioUnitInstance;
    AudioComponentInstanceNew (foundIoUnitReference,
                               &ioUnitInstance);

傳入NULL 給函數(shù)AudioComponentFindNext是告訴此函數(shù)使用系統(tǒng)定義的順序查找與描述匹配的第一個系統(tǒng)音頻單元。如果我們在這里傳入先前找到的音頻單元,則改函數(shù)將好到與描述匹配的下一個音頻單元。例如,通過重復調(diào)用該函數(shù),可以獲取對所有I/O單元的引用。
AudioComponentFindNext 函數(shù)的結(jié)果是對定義音頻單元的動態(tài)可鏈接庫的引用。將引用傳遞給AudioComponentInstanceNew函數(shù)以實例化音頻單元。

我們可以改為使用音頻處理grahp api來實例化音頻單元。

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

這里出現(xiàn)了AUNode,一種opaque類型,代表在音頻處理graph上下文的對音頻單元。在AUGraphNodeInfo的輸出中,我們將在ioUnit參數(shù)中接受對新的音頻單元實例的引用。

我們現(xiàn)在有兩個音頻單元實例了,接下來我們需要學習如何對其配置了。因此,我們需要了解兩個音頻單元的特征,scopes和elements。

使用scoped 和 elements 指定音頻單元的部件

音頻單元 是由scopes和elements 組成的,見下圖。當我們調(diào)用函數(shù)來配置和控制音頻單元時,我們需要指定scopes 和element的標識符。


scope 是音頻單元內(nèi)的編程上下文。這些上下文是不可以互相嵌套的。我們可以使用AudioUnitScope(<AudioUnitPropertyies.h>)枚舉中的常量指定要定位的范圍。

elements是嵌套在音頻單元scope內(nèi)的編程上下文。當元素是輸入或者輸出scope的一部分時,它相當于物理音頻設置中的信號bus(總線)-因此有時稱為bus(總線)。這兩個術語-element和bus-在音頻單元編程中完全相同。本文檔在強調(diào)信號流時使用bus,強調(diào)音頻單元的特定功能方面使用element。

這里搞清楚點,element 是scope 一部分,但是相同的 element 存在多個scope中的。

我們可以通過設置 0來作為索引的值來指定element和bus。如果設置全局的scope的屬性和參數(shù)的時候,我們需要設置element 的值是0

因為Global scope 只有一個element 0(element output)。

剛開始看到上面的這句話不是很理解,反復看翻譯文檔。算是理解大概了解了這句話的意思了
其實可以這么理解,scope 可以包含多個element元素,有時候我們需要設置scope 的屬性和參數(shù),有時候需要設置element 的屬性和參數(shù),而設置屬性函數(shù)AudioUnitSetProperty每次需要指定scope 和element ,但是設置scope的屬性和參數(shù)的值的時候是不需要element的,因此我們就把element 所在的位置設置成0就行了。
例如 kAudioUnitProperty_ElementCount 是用來配置混音器單元的element 的數(shù)量的,這就算是scope的屬性了,因此,設置element 參數(shù)所在位置是0
而kAudioOutputUnitProperty_EnableIO 是用來開啟I/O 的,是針對元素的,因此element 就要根據(jù)需要自己選擇位置了。

上圖說明了音頻單元的基本架構,其中輸入和輸出上的elements數(shù)量相同。然而,各種音頻單元的架構有所不同。例如,混音器單元可能有多個輸入元件但是具有一個輸出元件。盡管體系結(jié)構有這些變化,但是我們還是可以把這里學到的知識擴展到其他音頻單元上。
Global scope 使用于整個音頻單元,不與任何特定的音頻流相關聯(lián)。他只有一個element,命名element 0.有些屬性(例如每個切片的最大幀數(shù)kAudioUnitProperty_MaximumFramesPerSlice)只能在
Global scope 使用。

kAudioUnitProperty_MaximumFramesPerSlice 該屬性只能設置在global Scope 上。

input和output scopes 直接參與通過音頻單元移動一個或多個音頻流。正如我們期望的一樣,音頻input的scope 并從output scope。
屬性和參數(shù)可以應用到整個input 和output的scope上,例如element 數(shù)量屬性(kAudioUnitProperty_ElementCount),我們可以通過該屬性設置input 和output scope 上的element數(shù)量。
還有一些其他屬性和參數(shù),例如 開啟關閉I/O 屬性(kAudioOutputUnitProperty_EnableIO)和音量屬性(kMultiChannelMixerParam_Volume),可以應用于scope中的特定element上。

這里想說的是屬性和參數(shù),一部分可以修飾scope, 另一份也可以用來修飾scope中的element。

使用屬性 配置 音頻單元

音頻單元屬性是一個鍵值對,可以用來配置音頻單元。屬性的key是具有關聯(lián)標識符的唯一整數(shù),例如kAudioUnitProperty_MaximumFramesPerSlice= 14.Apple 保留0到63999之間的屬性鍵。

每個屬性的值都是指定的數(shù)據(jù)類型,并具有指定的讀寫訪問權限,可以參考 Audio Unit Properties Reference.設置音頻單元的屬性,可以使用函數(shù)AudioUnitSetProperty。下列代碼顯示該函數(shù)的用法。

 OSStatus result = AudioUnitSetProperty (
                                            ioUnit,
                                            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)
                                            );
    

下面是我們在音頻單元開發(fā)中經(jīng)常使用的一些屬性,可以看mixerHost代碼來熟悉下列屬性。

  • kAudioOutputUnitProperty_EnableIO 用于在I/O 單元上啟動和禁止輸入和輸出。默認啟用輸出,關閉輸入。
  • kAudioUnitProperty_ElementCount,用于配置混音器單元上的輸入元素的數(shù)量
  • kAudioUnitProperty_MaximumFramesPerSlice 用于指定音頻單元應準備響應于渲染調(diào)用而產(chǎn)生的音頻數(shù)據(jù)的最大幀數(shù)。對應大多數(shù)音頻設備,在大多數(shù)情況下,必須按照參考文檔的說明來設置此屬性。如果不這樣設置,屏幕鎖定時候音頻將停止。
  • kAudioUnitProperty_StreamFormat 用于指定特定音頻單元輸入或者輸出總線的音頻流數(shù)據(jù)格式

只有音頻單元未初始化時,才能設置大多數(shù)屬性值。這些屬性不應該由用戶更改。但是,有些屬性( iPod EQ unit上的kAudioUnitProperty_PresentPreset屬性)和語音處理I/O單元的kAUVoiceIOProperty_MuteOutput屬性,在播放的音頻時會被更改。

檢查屬性是否可用或者訪問值以及觀察值的變化更改,使用下列函數(shù)

  • AudioUnitGetPropertyInfo 檢查屬性是否可用;如果返回yes,那么會返回知道數(shù)據(jù)大小以及是否可以更改該值
  • AudioUnitGetProperty ,AudioUnitSetProperty 獲取和設置屬性的值
  • AudioUnitAddPropertyListener, AudioUnitRemovePropertyListenerWithUserData 安裝或者刪除回調(diào)函數(shù)以監(jiān)視屬性值的更改
使用參數(shù)和UIKit 為用戶提供控制

音頻單元參數(shù)是用戶可調(diào)節(jié)的設置,可在音頻單元產(chǎn)生音頻時改變。實際上,大多數(shù)參數(shù)(例如音量或者立體聲平移位置)的意圖是音頻單元正在執(zhí)行的時候進行的調(diào)整。

與音頻單元屬性類似,音頻單元參數(shù)是鍵值對。key是被適用的音頻單元定義。它始終是個枚舉常量,例如kMultiChannelMixerParam_Pan=2,對于音頻單元是唯一的,但是全局不一定唯一。

與屬性值不同,每個參數(shù)值都是相同的類型:32位 float。值的取值范圍及其表示的度量單位由音頻單元的參數(shù)實現(xiàn)決定。

設置和獲取參數(shù)值使用下列函數(shù)

I/O 單元的基本特征

I/O 單元是app應用中常用的音頻單元,而且在幾個方面也是很特殊的。因此,我們需要了解I/O 單元的基本特性才能獲取更好的音頻單元編程。

I/O 音頻單元有兩個元素


雖然這兩個elements是音頻單元的組成部分,但是我們的app還是需要將他們視為獨立的實體。 打個比方,我們可以根據(jù)需要使用kAudioOutputUnitProperty_EnableIO 屬性來獨立啟用或者禁用每個element。

可以這么想,把I/O 音頻單元相當于,輸入音頻單元和輸出音頻單元封裝在一起了。

在 I/O 單元中的Element 1 直接連接到設備上的音頻輸入硬件,在圖中是麥克風表示。element 1 和麥克風通過input scope 具體怎么連接,我們是不需要關心的。我們第一次獲取的數(shù)據(jù)是來自output scope 的element 1 。

同理 element 0 直接連接在音頻的輸出硬件,例如圖中的揚聲器。數(shù)據(jù)傳輸也是首先經(jīng)過input scope 中的 element 0 到達output scope 的 element0 ,在傳遞給揚聲器的。

使用音頻單元時,我們經(jīng)常聽到I/O單元的兩個element ,而不是他們的編號是名稱:

  • input element是 element 1(助記符設備:但是input的字母i具有類似數(shù)字1的外觀)
  • output element 是 element 0(助記符設備:output的字母o具有類似于0 的外觀)

上圖,每個element 都有個一個input scope 和 output scope。因此,描述I/O 單元的這些部分可能有點混亂。例如,我們可說在一個同步的I/O APP中,我們從 element 1 通過output scope接受音頻,并將音頻通過input scope 傳遞給element 0。

最后,I/O 單元是唯一能夠在音頻處理graph中啟動和停止音頻流的音頻單元。通過這種方式,I/O 音頻單元負責音頻單元應用中的音頻流。

音頻處理graph管理音頻單元

音頻處理graph是coreFoundation風格的opaque類型 AUGraph,用于構建和管理音頻單元處理鏈。graph可以利用多個音頻單元和多個渲染回調(diào)函數(shù)的功能,允許我們創(chuàng)建幾乎任何我們能想象的音頻處理解決方案。

AUGraph類型是線程安全的;他使我們能夠動態(tài)的重新配置處理鏈。例如,我們可以安全的插入均衡器,甚至可以在播放音頻時交換混音器輸入的不同渲染的回調(diào)函數(shù)。事實上,AUGraph類型僅在ios中使用,用于在音頻應用程序中執(zhí)行此類動態(tài)重新配置。

音頻處理graph API使用另一個不透明類型AUNode來表示圖形上下文的單個音頻單元。當使用graph的時候,我們通常與AUnode 交互,這里AUnode 是作為音頻單元的代理。

當我們將grahp放在一起的時候,我們必須配置每個音頻單元。因此,我們必須通過音頻單元api直接與音頻單元進行交互。AUNode本身是不可以配置的。

我們還可以把AUNode作為element增加到 grahp中。

總的來說,構建音頻處理graph 需要三個任務

  • 將node 增加到 grahp中
  • 直接配置由node表示的音頻單元
  • 互聯(lián)node
音頻處理graph具有一個完整的 I / O單元

無論您是在進行錄制,回放還是同步I / O,每個音頻處理grahp都有一個I / O單元。I / O單元可以是iOS中的任何一個,具體取決于您的應用程序的需求。

graph 可以通過AUGraphStart 和AUGraphStop 函數(shù)啟動和停止音頻流。反過來,這些函數(shù)可以通過調(diào)用AudioOutputUnitStart 和AudioOutputUnitStop 函數(shù)將開始或者停止消息傳遞給I/O單元。

音頻處理graph提供線程安全

音頻處理graph API 使用“to do list”來提供線程安全。此api中的某些函數(shù)將工作音頻單元添加到稍后要執(zhí)行的更改列表中。當我們指定完整的更改集后,我們可以請求graph實現(xiàn)他們。

以下是音頻處理 graph API支持的一些常見的重新配置以及相關功能:

讓我們看一個重新配置正在運行的音頻處理grahp 示例
假設我們構建了一個包含多聲道混音器單元和遠程 I/O單元的graph,用于混合播放兩個兩個合成的聲音。我們將聲音發(fā)送到混頻單元的兩個input bus。讓混頻單元的輸出連接到I/O單元的輸出元件,然后出入到輸出音頻硬件。



現(xiàn)在,假設用戶想要將均衡器插入兩個音頻流之中一中。因此,在其中一個聲音的輸入和他進入的混音器輸入之間添加一個iPod EQ 單元。如下圖


完成此時時重新配置的步驟如下:

  • 1 通過調(diào)用AUGraphDisconnectNodeInput斷開混音器單元 input 1 架子鼓的回調(diào)
  • 2.將包含iPod EQ單元的音頻單元節(jié)點添加到graph中。通過AudioComponentDescription結(jié)構指定iPod EQ單元,然后調(diào)用AUGraphAddNode函數(shù)執(zhí)行此操作。此時,iPod EQ單元已實例化但未初始化。他被graph所有,但是尚未參加音頻流程
  • 3.配置和初始化iPod的EQ單元。在這個例子中,這需要做一些事情:

1 ·調(diào)用AudioUnitGetProperty函數(shù)從混音器的input 中檢索流格式(kAudioUnitProperty_StreamFormat)
2.調(diào)用AudioUnitSetProperty 函數(shù)兩次,一次在iPod EQ單元的input中設置該流格式,第二次在output上設置它。(意思是輸入通過該單元不要改變流的格式)
3 調(diào)用AudioUnitInitialize函數(shù)為 iPod EQ單元分配資源并準備處理音頻。這個函數(shù)調(diào)用不是線程安全的,但是當 iPod EQ單元沒有主動參加音頻處理graph時,我們可以(必須)在序列中的這個時候執(zhí)行它,這個時候還沒有調(diào)用AUGraphUpdate。

  • 4 通過調(diào)用AUGraphSetNodeInputCallback 函數(shù)將架子鼓回調(diào)函數(shù)附加到iPod EQ 的input上。

上面 第一步,第二步,第四步中所有的AUGraph*開頭的函數(shù)都是被增加到了“to-do”列表中了。調(diào)用AUGraphUpdate來執(zhí)行這些掛起的任務,AUGraphUpdate函數(shù)成功返回后,就代表grahp以動態(tài)重新配置了。

使用graph 的“pull”獲取音頻流

在音頻處理graph中,消費者在需要更多的音頻數(shù)據(jù)時調(diào)用提供者。有一些請求音頻數(shù)據(jù)流,該流程是與音頻流相反的方向進行。


對一些列數(shù)據(jù)的請求稱作渲染調(diào)用,或者非正式的成為 pull。該表表示渲染調(diào)用的是灰色控制流箭頭。渲染調(diào)用請求的數(shù)據(jù)更恰當?shù)姆Q為一組音頻樣本幀(frame)

反過來,相應于渲染調(diào)用而提供的一組音頻樣本幀被稱為片(slice)。提供切片的代碼稱為渲染回調(diào)函數(shù)。
上圖的具體過程:

  • 1當調(diào)用AUGraphStart函數(shù)之后,虛擬輸出設備將調(diào)用Remote I/O 單元的output element。該調(diào)用請求一個處理過的音頻數(shù)據(jù)幀的一個slice。
  • 2.遠程I / O單元的渲染回調(diào)函數(shù)在其輸入緩沖區(qū)中查找要處理的音頻數(shù)據(jù),以滿足渲染調(diào)用。如果有數(shù)據(jù)等待處理,那么遠程I / O單元使用它。否則,如圖所示,他會調(diào)用app連接到其輸入的渲染回調(diào)。在該實例中,遠程I / O單元的的input(element 0的 input scope)連接到其effect 音頻單元的output(element 0的output scope)。因此遠程I / O單元 pull effect 音頻單元,要求音頻幀的slice。
  • 3.effect 單元與遠程I / O單元的行為一致。當他需要音頻數(shù)據(jù)時,他從input 連接獲取它。在上述實例中,effect 單元將啟動app的渲染回調(diào)函數(shù)。
  • 4app的渲染回調(diào)函數(shù)是pull的最終接受者。它將pull的幀提供給effect 單元。
  • 5.effect單元處理程序渲染回調(diào)的提供的slice。effcet單元然后將先前請求的處理數(shù)據(jù)提供給(在步驟二中)提供給遠程I/O單元
  • 6 遠程I / O單元處理effect單元提供的slice。然后,遠程I / O單元將最初請求的處理過的slice(在步驟一中)提供給虛擬輸出設備完成一個周期。

渲染回調(diào)功能將音頻輸入到音頻單元

從磁盤或者內(nèi)存向音頻單元 input bus 提供音頻,需要使用AURenderCallback原型的渲染回調(diào)函數(shù)。音頻單元 input 在需要另一樣本幀的slice的時候,調(diào)用該回調(diào)。

編寫渲染回調(diào)函數(shù)的過程可能是設計和構建音頻單元應用程序最具創(chuàng)造性的方面了。這里可以以任何可想象和編碼的方式生成或者改變聲音的機會。

這里,渲染回調(diào)必須遵守嚴格的性能要求。渲染回調(diào)存在于實時優(yōu)先級的線程上,后續(xù)渲染調(diào)用將異步到達。(相當于定時觸發(fā)該函數(shù))我們在渲染回調(diào)函數(shù)體重所做的工作是在這個受限時間的環(huán)境中進行的。如果在下一個渲染調(diào)用到達時,如果回調(diào)扔在渲染上一個渲染調(diào)用,那么聲音就會產(chǎn)生間隙。因此,在改函數(shù)中,我們不能采用鎖,分配內(nèi)存,訪問文件系統(tǒng)或者網(wǎng)絡連接,或者以其他方式在渲染回調(diào)函數(shù)的主題中執(zhí)行耗時間的任務。

回調(diào)函數(shù)不能執(zhí)行耗時任務,否則會產(chǎn)生聲音間隙。就是斷續(xù)聲音

理解音頻單元的渲染回調(diào)函數(shù)

下列代碼顯示符合AURenderCallback原型函數(shù)

static OSStatus MyAURenderCallback (
    void                        *inRefCon,
    AudioUnitRenderActionFlags  *ioActionFlags,
    const AudioTimeStamp        *inTimeStamp,
    UInt32                      inBusNumber,
    UInt32                      inNumberFrames,
    AudioBufferList             *ioData
) { /* callback body */ }

當我們將渲染回調(diào)函數(shù)綁定到input上的時候, inRefCon 參數(shù)指向的是我們指定的編程上下文。上下文的目的可以是一下兩個中的一個,第一個是提供給回調(diào)函數(shù)音頻輸入數(shù)據(jù),第二個是提供給回調(diào)函數(shù)需要計算給定渲染調(diào)用的輸出音頻的狀態(tài)信息

ioActionFlags 參數(shù)允許回調(diào)向調(diào)用音頻單元提供沒有要處理的音頻的提示。例如,如果app是合成吉他并且用戶當前沒有播放音符,那么執(zhí)行此操作。在要為其輸出靜默的回調(diào)調(diào)用期間,執(zhí)行下列語句:

*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;

當想要產(chǎn)生靜音時,還必須將ioData參數(shù)指向的緩沖區(qū)設置為0。
inTimeStamp參數(shù)表示調(diào)用回調(diào)的時間。他包含一個AudioTimeStamp 結(jié)構,mSampleTime 字段是一個樣本幀計數(shù)器。在每次調(diào)用回調(diào)時候,mSampleTime字段的值將增加inNumberFrames參數(shù)匯總的數(shù)字。
inBusNumber 參數(shù)指示調(diào)用回調(diào)的音頻單元的bus,允許我們根據(jù)該值在回調(diào)內(nèi)進行分支。此外,在講回調(diào)附加到音頻單元時,我們可以為每個bus 指定不同的上下文。
inNumberFrames參數(shù)指示要求回調(diào)在當前調(diào)用中提供的音頻樣本幀的數(shù)量。我們可以將這些幀提供給ioData參數(shù)中的緩沖區(qū)
ioData參數(shù)指向回調(diào)在調(diào)用時必須填充的音頻數(shù)據(jù)緩沖區(qū)。放入這些緩沖區(qū)的音頻必須符合調(diào)用回調(diào)的bus的音頻流格式。

如果正在特定的回調(diào)調(diào)用播放靜音,請將這些緩沖區(qū)顯示設置設置為0,使用memset函數(shù)

下圖描繪了ioData參數(shù)中的一對非交織立體聲緩沖區(qū)。使用圖中的element可視化回調(diào)需要填充的ioData緩沖區(qū)的詳細信息。


image.png

音頻流格式啟用數(shù)據(jù)流

在單個樣本級別處理音頻數(shù)據(jù)時,與使用音頻單元一樣,僅指定正確的數(shù)據(jù)類型來表示音頻是不夠的。單個音頻的采樣率的布局具有重要意義,因此像Float32 或者Uint16這樣的數(shù)據(jù)類型不夠表達。在本小節(jié)中,我們將了解下Core Audio對此問題的解決方案。

使用AudioStreamBasicDescription結(jié)構

在應用中或者應該與硬件之間移動音頻值的貨比是AudioStreamBasicDescription結(jié)構。

struct AudioStreamBasicDescription {
    Float64 mSampleRate;
    UInt32  mFormatID;
    UInt32  mFormatFlags;
    UInt32  mBytesPerPacket;
    UInt32  mFramesPerPacket;
    UInt32  mBytesPerFrame;
    UInt32  mChannelsPerFrame;
    UInt32  mBitsPerChannel;
    UInt32  mReserved;
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;

因為AudioStreamBasicDescription很長,因此通常在會話和文檔中縮寫為ASBD 。

下列是編寫ASBD的代碼

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;

首先,確定表示一個音頻抽樣值的數(shù)據(jù)類型。上述實例使用AudioUnitSampleType類型,這是大多數(shù)音頻單元推薦的數(shù)據(jù)類型。在iOS中,AudioUnitSampleType被定義為8.24定點類型。上述實例的第一行計算了類型中的字節(jié)數(shù);在定義ASBD的某些字段值時需要該數(shù)字。
接下來,聲明一個AudioStreamBasicDescription類型變量,字段初始化為0,確保沒有字段包含垃圾數(shù)據(jù)。不要跳過這個歸零操作;否則以后肯定會遇到麻煩。
現(xiàn)在定義ASBD字段值。為mFormatID字段指定kAudioFormatLinearPCM。音頻單元使用未壓縮的音頻數(shù)據(jù)。這是格式標識符。
接下來,對于大多數(shù)音頻單元,為mFormatFlags字段指定kAudioFormatFlagsAudioUnitCanonical元標志。此標志在CoreAudio.framework / CoreAudioTypes.h定義如下

kAudioFormatFlagsAudioUnitCanonical = kAudioFormatFlagIsFloat |
                                kAudioFormatFlagsNativeEndian |
                                     kAudioFormatFlagIsPacked |
                             kAudioFormatFlagIsNonInterleaved

此標記負責指定AudioUnitSampleType類型的線型PCM樣本值中位的所有布局詳細信息。
某些音頻單元采用非典型音頻數(shù)據(jù)格式,需要不同的樣本數(shù)據(jù)類型和mFormatFlags字段的不同標志集。例如3D混音器單元需要UInt16數(shù)據(jù)類型作為其音頻樣本值,并要求將ASBD的mFormatFlags字段設置為kAudioFormatFlagsCanonical。使用特定音頻設備時,請小心使用正確的數(shù)據(jù)格式和正確的格式標志??梢詤⒖?a target="_blank" rel="nofollow">Using Specific Audio Units.
繼續(xù)看上面代碼,接下來的四個字段進一步制定了樣本幀中位的組織和含義。根據(jù)您正在使用的音頻流的性質(zhì),設置這些字段-mBytesPerPacket, mBytesPerFrame, mFramesPerPacket和 mBitsPerChannel 。每個字段的具體含義可以看AudioStreamBasicDescription。
設置ASBD的mChannelsPerFrame字段來設置流的聲道數(shù)量,1是單聲道 2是立體聲。
最后,跟著您在整個app使用的采樣率設置mSampleRate字段。Understanding Where and How to Set Stream Formats解釋了避免采樣率轉(zhuǎn)換的重要性。 Configure Your Audio Session確保應用程序的采樣率與音頻硬件采樣率相匹配。
我們也可以使用CAStreamBasicDescription.h ((/Developer/Extras/CoreAudio/PublicUtility/))中提供的C++ 實用程序方法而不是這里指定的ASBD字段。特別是 SetAUCanonical 和 SetCanonical 方法。下列指明數(shù)學ASBD字段的正確方法

  • 流是用于I/O (SetCanonical)還是用于音頻處理 (SetAUCanonical)
  • 希望流格式有多少通道
  • 希望流格式是交錯還是非交錯

無論是否再項目中包含CAStreamBasicDescription.h 文件以直接使用其方法,Apple 建議我們研究該文件以了解使用AudioStreamBasicDescription的正確方法。

了解設置流格式的位置和方式

我們必須在音頻處理graph的關鍵點設置音頻數(shù)據(jù)流格式。在其他非關鍵點,系統(tǒng)幫助我們設置流格式。在其他的非關鍵點,音頻單元連接將流數(shù)據(jù)從一個單元傳遞到另一個單元。

ios設置上的音頻輸入和輸出硬件具有系統(tǒng)確定的音頻流格式。這些格式始終是未壓縮的,采用線型PCM格式,并且是交錯的。系統(tǒng)在音頻處理graph中將這些格式強制放在I/O 單元的朝外側(cè)。

這里還是需要強調(diào)下 音頻輸入和輸出硬件的音頻流格式是確定好的,格式是始終是未壓縮的,采用線型PCM格式,并且是交錯的

這里有個朝外側(cè)什么意思呢?我們知道麥克風始終連接的是I/O 音頻單元的input element 上,所以可以理解為input element 所在的input scope 是朝外側(cè)。同理 output elment 所在的output Scope也是朝外側(cè)。

在圖中,麥克風代表輸入音頻硬件。系統(tǒng)確定輸入硬件的音頻流格式,并且經(jīng)過 遠程I/O 單元的input scope 連接到 input element上

類似的,圖中的揚聲器代表輸出音頻硬件。系統(tǒng)確定輸出硬件的流格式,并經(jīng)過遠程I/O單元的 output scope 的output element 發(fā)送給揚聲器。

app負責在I/O單元 element 的向內(nèi)側(cè)建立音頻流格式。I/O單元在我們的app格式和硬件格式之間執(zhí)行必要的轉(zhuǎn)換。我們的app還負責在上圖中的任何其他位置設置流格式。在某些情況下,例如,上圖中的多通道混音器單元的輸出 ,我們只需要設置格式的一部分-特別是采樣率。Start by Choosing a Design Pattern可以顯示為各種類型的音頻單元應用設置流格式的位置。 Using Specific Audio Units列出每個ios音頻單元的流格式要求。

如圖,音頻單元連接的一個關鍵特性是連接音頻數(shù)據(jù)流格式從其源音頻單元的output 傳播到目標音頻單元的input 。這是一個關鍵點,因此需要強調(diào)的是:流格式傳播通過音頻單元連接,傳播方向只能從源音頻單元的輸出到目標音頻單元的輸入進行。

output 可以理解為output Scope 中的 element 0 和element 1
input 可以理解為input scope 中的 element 0 和 element 1
這里也需要注意,不是所有音頻單元都可以連接到 element 0 和element 1的。 例如,混音器單元有多個input element 和一個輸出 element ?;煲羝鞯?input scope 的output 是不可以連接其他音頻單元的output

利用格式傳播。這可以顯著的減少我們需要編寫的代碼量。例如,將多聲道混音器單元的輸出連接到遠程I/O單元進行播放時,無需為I/O單元設置流格式。它根據(jù)混音器的輸出流格式通過音頻單元之間的連接進行適當設置。

流格式的傳播發(fā)生在音頻處理grahp的生命周期的特定點-即初始化的時候??梢詤⒖?a target="_blank" rel="nofollow">Initialize and Start the Audio Processing Graph.

我們可以非常靈活的定義應用程序音頻流格式。但是,只要有可能,請使用硬件使用的采樣率。執(zhí)行此操作時候,I/O單元無需執(zhí)行采樣率轉(zhuǎn)換。這最大限度的減少了能源使用-這是移動設備中的一個重要考慮因素-并且最大化了音頻質(zhì)量。了解如何使用硬件采樣率,可以參考Configure Your Audio Session.


mixerHost
audio unit ios
參考文章
音頻單元
Audio Unit Component Services

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,203評論 3 119
  • 材料有七張手工紙,固體膠,粗細雙面膠,剪刀。
    宋燕茹閱讀 390評論 0 0
  • 參考資料: slides 擬合的缺陷 最小二乘法無法很好地區(qū)分開錯誤的那些點,導致對結(jié)果的影響很大 多種結(jié)構 算法...
    抬頭挺胸才算活著閱讀 2,213評論 0 1
  • 作為哈爾濱球迷,我還是親眼看到過踢假球的事情發(fā)生的,也是我不愿提起的 記憶,所以分享一個同胞的回答,很觸目驚心。。...
    一銘_YiMi閱讀 410評論 0 0

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