音視頻學(xué)習(xí)筆記(一)Audio Units

音視頻學(xué)習(xí)筆記(一)Audio Units

Audio Unit Hosting Guide for iOS 官方文檔

最近在學(xué)習(xí)iOS音視頻開發(fā)锹杈,在此寫一個學(xué)習(xí)筆記野舶。

Programming layer in the iOS audio stack.png


前言

Audio Unit提供高效滤蝠、模塊化的音頻處理方案。當(dāng)你需要實(shí)現(xiàn)以下需求時,推薦直接使用 Audio Units:

  • 想使用低延遲的音頻I/O(input或者output)尝艘,比如說在VoIP的應(yīng)用場景下;
  • 多路聲音的合成并且回放,比如游戲或者音樂合成器的應(yīng)用;
  • 使用AudioUnit里面提供的特有功能姿染,比如:回聲消除背亥、Mix兩軌音頻,以及均衡器悬赏、壓縮器狡汉、混響器等效果器;
  • 鏈?zhǔn)教幚斫Y(jié)構(gòu)闽颇,你可以將音頻處理模塊集成到靈活的網(wǎng)絡(luò)中(這是 iOS 中唯一提供此功能的音頻 API)盾戴。

否則可以優(yōu)先考慮Media Player, AV Foundation, OpenAL,或者Audio Toolbox frameworks,移步Multimedia Programming Guide 官方文檔

Audio Units 提供四大類,共七種音頻處理單元:(Identifier Keys for Audio Units.)

Purpose 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

構(gòu)建你的APP

無論您選擇哪種設(shè)計(jì)模式兵多,構(gòu)建音頻單元托管應(yīng)用程序的步驟基本相同:

  1. 配置 audio session尖啡;
  2. 指定audio units;
  3. 創(chuàng)建audio processing graph剩膘,然后獲取audio units;
  4. 配置 audio units;
  5. 連接audio units節(jié)點(diǎn);
  6. 提供用戶界面;
  7. 初始化然后啟動audio processing graph

1. AudioSession

音頻會話(AudioSession)衅斩,其用于管理與獲取iOS設(shè)備音頻的硬件信息,并且是以單例的形式存在援雇∶剩可以使用如下代碼來獲取AudioSession的實(shí)例:

AVAudioSession *audioSession = [AVAudioSession sharedInstance];

獲得AudioSession的實(shí)例之后,就可以設(shè)置以何種方式使用音頻硬件做哪些處理了惫搏,基本的設(shè)置具體如下所示:

// 1. 根據(jù)我們需要硬件設(shè)備提供的能力來設(shè)置類別:(AVAudioSessionCategory)
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];

//2. 設(shè)置I/O的Buffer具温,Buffer越小則說明延遲越低:
NSTimeInterval bufferDuration = 0.002;
[audioSession setPreferredIOBufferDuration:bufferDuration error:&error];

//3. 設(shè)置采樣頻率,讓硬件設(shè)備按照設(shè)置的采樣頻率來采集或者播放音頻:
double hwSampleRate = 44100.0;
[audioSession setPreferredSampleRate:hwSampleRate error:&error];

4. 當(dāng)設(shè)置完畢所有的參數(shù)之后就可以激活A(yù)udioSession了筐赔,代碼如下:
[audioSession setActive:YES error:&error];

2. 構(gòu)建AudioUnit

構(gòu)建AudioUnit的時候需要指定類型(Type)铣猩、子類型(subtype)以及廠商(Manufacture)。 如需創(chuàng)建一個通用描述茴丰,可以將類型或者子類型設(shè)置為0达皿,例如為了匹配所有的 I/O unit天吓,可以將 componentSubType 設(shè)置為0。

如果要輸出音頻峦椰,那么就要如下設(shè)置:

AudioComponentDescription ioUnitDescription;
/*componentType類型是相對應(yīng)的龄寞,什么樣的功能設(shè)置什么樣的類型,componentSubType是根據(jù)componentType設(shè)置的汤功。*/
ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
/*廠商的身份驗(yàn)證*/
ioUnitDescription.componentManufacturer=kAudioUnitManufacturer_Apple;
/*如果沒有一個明確指定的值物邑,那么它必須被設(shè)置為0*/
ioUnitDescription.componentFlags = 0;
/*如果沒有一個明確指定的值,那么它必須被設(shè)置為0*/
ioUnitDescription.componentFlagsMask = 0;

上述代碼構(gòu)造了RemoteIO類型的AudioUnit描述的結(jié)構(gòu)體滔金,那么如何使用這個描述來構(gòu)造真正的AudioUnit呢色解?有兩種方式:第一種方式是直接使用AudioUnit裸的創(chuàng)建方式;第二種方式是使用AUGraph和AUNode(其實(shí)一個AUNode就是對AudioUnit的封裝餐茵,可以理解為一個AudioUnit的Wrapper)來構(gòu)建科阎。

  1. 使用 audio unit API 獲取 audio unit 實(shí)例:
// 1. 首先根據(jù)AudioUnit的描述,找出實(shí)際的AudioUnit類型:
AudioComponent ioUnitRef = AudioComponentFindNext(NULL, &ioUnitDescription)
// 2. 然后聲明一個AudioUnit引用:
AudioUnit ioUnitInstance;
// 3. 最后根據(jù)類型創(chuàng)建出這個AudioUnit實(shí)例:
AudioComponentInstanceNew(ioUnitRef, &ioUnitInstance);
  1. 使用 audio processing graph API 獲取 audio unit
// 1. 首先聲明并且實(shí)例化一個AUGraph:
AUGraph processingGraph;
NewAUGraph (&processingGraph);

// 2. 然后按照AudioUnit的描述在AUGraph中增加一個AUNode:
AUNode ioNode;
AUGraphAddNode (processingGraph, &ioUnitDescription, &ioNode);

// 3. 接下來打開AUGraph忿族,其實(shí)打開AUGraph的過程也是間接實(shí)例化AUGraph中所有的AUNode锣笨。注意,必須在獲取AudioUnit之前打開整個AUGraph道批,否則我們將不能從對應(yīng)的AUNode中獲取正確的AudioUnit:
AUGraphOpen (processingGraph);

// 4. 最后在AUGraph中的某個Node里獲得AudioUnit的引用:
AudioUnit ioUnit;
AUGraphNodeInfo (processingGraph, ioNode, NULL, &ioUnit);

附:如下圖所示: componentType 和 componentSubType 根據(jù)不同的音頻單元功能來設(shè)置票唆,幾種常用的如下圖:

AudioComponentDescription.png

3. AudioUnit的通用參數(shù)設(shè)置

AudioUnit Struct.png

以Remote I/O為例,RemoteIO Unit分為Element0和Element1屹徘,其中Element0控制輸出端走趋,Element1控制輸入端,同時每個Element又分為Input Scope和Output Scope噪伊。如果開發(fā)者想要使用揚(yáng)聲器的聲音播放功能簿煌,那么必須將這個Unit的Element0的OutputScope和Speaker進(jìn)行連接。而如果開發(fā)者想要使用麥克風(fēng)的錄音功能鉴吹,那么必須將這個Unit的Element1的InputScope和麥克風(fēng)進(jìn)行連接姨伟。
注:這種結(jié)構(gòu)比較常見,但這并不適合所有情況豆励。例如在 mixer unit 中夺荒,會存在多個 input element,一個 output element的情況良蒸。

  • 使用揚(yáng)聲器:
OSStatus status = noErr;
UInt32 oneFlag = 1;
UInt32 busZero = 0;// Element 0
status = AudioUnitSetProperty(remoteIOUnit,
        kAudioOutputUnitProperty_EnableIO,
        kAudioUnitScope_Output,
        busZero,
        &oneFlag,
        sizeof(oneFlag));
CheckStatus(status, @"Could not Connect To Speaker", YES);
  • 啟用麥克風(fēng):
UInt32 busOne = 1; // Element 1
AudioUnitSetProperty(remoteIOUnit,
        kAudioOutputUnitProperty_EnableIO,
        kAudioUnitScope_Input,
        busOne,
        &oneFlag,
        sizeof(oneFlag);

AudioUnitSetProperty 方法的說明技扼,很簡單,就不翻譯了

/*!
    @function       AudioUnitSetProperty
    @abstract       sets the value of a specified property
    @discussion     The API can is used to set the value of the property. Property values for 
                    audio units are always passed by reference
                    
    @param          inUnit
                    the audio unit
    @param          inID
                    the property identifier
    @param          inScope
                    the scope of the property
    @param          inElement
                    the element of the scope
    @param          inData
                    if not null, then is the new value for the property that will be set. If null, 
                    then inDataSize should be zero, and the call is then used to remove a 
                    previously set value for a property. This removal is only valid for
                    some properties, as most properties will always have a default value if not 
                    set.
    @param          inDataSize  
                    the size of the data being provided in inData

    @result         noErr, or various audio unit errors related to properties
*/
};

下面是一些常用的屬性:

  • kAudioOutputUnitProperty_EnableIO 啟用或禁止 I/O嫩痰,默認(rèn)輸出開啟剿吻,輸入禁止。
  • kAudioUnitProperty_ElementCount 配置元素個數(shù)
  • kAudioUnitProperty_MaximumFramesPerSlice 設(shè)置 audio unit 的最大幀數(shù)
  • kAudioUnitProperty_StreamFormat 指定輸入 audio unit 輸入輸出元素的數(shù)據(jù)格式

再來看一下kAudioUnitProperty_StreamFormat串纺;在iOS平臺不論音頻還是視頻的API都會接觸到很多StreamBasic Description丽旅,它是用來描述音視頻具體格式的椰棘。下面就來具體分析一下上述代碼是如何指定格式的。

UInt32 bytesPerSample = sizeof(Float32);
AudioStreamBasicDescription asbd;
bzero(&asbd, sizeof(asbd));
// mFormatID參數(shù)可用來指定音頻的編碼格式榄笙,此處指定音頻的編碼格式為PCM格式邪狞。
asbd.mFormatID = kAudioFormatLinearPCM;
// 聲音的采樣率
asbd.mSampleRate = _sampleRate;
// 聲道數(shù)
asbd.mChannelsPerFrame = channels;
// 每個Packet有幾個Frame
asbd.mFramesPerPacket = 1;

asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked |
                kAudioFormatFlagIsNonInterleaved;

// 一個聲道的音頻數(shù)據(jù)用多少位來表示
asbd.mBitsPerChannel = 8 * bytesPerSample;

/* 
最終是參數(shù)mBytesPerFrame和mBytesPerPacket的賦值,這里需要根據(jù)mFormatFlags的值來進(jìn)行分配茅撞,
如果在NonInterleaved的情況下外恕,就賦值為bytesPerSample(因?yàn)樽笥衣暤朗欠珠_存放的);
但如果是Interleaved的話乡翅,那么就應(yīng)該是bytesPerSample*channels(因?yàn)樽笥衣暤朗墙诲e存放的),
這樣才能表示一個Frame里面到底有多少個byte罪郊。
*/
asbd.mBytesPerFrame = (asbd.mBitsPerChannel / 8)
asbd.mBytesPerPacket =(asbd.mBitsPerChannel / 8);

其中蠕蚜,mFormatFlags是用來描述聲音表示格式的參數(shù),代碼中的第一個參數(shù)指定每個sample的表示格式是Float格式悔橄,這點(diǎn)類似于之前講解的每個sample都是使用兩個字節(jié)(SInt16)來表示靶累;然后是后面的參數(shù)kAudioFormatFlagIsNonInterleaved,字面理解這個單詞的意思是非交錯的癣疟,其實(shí)對于音頻來講就是左右聲道是非交錯存放的挣柬,實(shí)際的音頻數(shù)據(jù)會存儲在一個AudioBufferList結(jié)構(gòu)中的變量mBuffers中,如果mFormatFlags指定的是NonInterleaved睛挚,那么左聲道就會在mBuffers[0]里面邪蛔,右聲道就會在mBuffers[1]里面;而如果mFormatFlags指定的是Interleaved的話扎狱,那么左右聲道就會交錯排列在mBuffers[0]里面侧到,理解這一點(diǎn)對于后續(xù)的開發(fā)將是十分重要的。

最后淤击,調(diào)用AudioUnitSetProperty來設(shè)置給對應(yīng)的AudioUnit:

AudioUnitSetProperty( remoteIOUnit, kAudioUnitProperty_StreamFormat,
        kAudioUnitScope_Output, 1, &asbd, sizeof(asbd));

參考文檔:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匠抗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子污抬,更是在濱河造成了極大的恐慌汞贸,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件印机,死亡現(xiàn)場離奇詭異矢腻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)射赛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門踏堡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咒劲,你說我怎么就攤上這事顷蟆〗胗纾” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵帐偎,是天一觀的道長逐纬。 經(jīng)常有香客問我,道長削樊,這世上最難降的妖魔是什么豁生? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮漫贞,結(jié)果婚禮上甸箱,老公的妹妹穿的比我還像新娘。我一直安慰自己迅脐,他們只是感情好芍殖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谴蔑,像睡著了一般豌骏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隐锭,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天窃躲,我揣著相機(jī)與錄音,去河邊找鬼钦睡。 笑死蒂窒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荞怒。 我是一名探鬼主播刘绣,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挣输!你這毒婦竟也來了纬凤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤撩嚼,失蹤者是張志新(化名)和其女友劉穎停士,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體完丽,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恋技,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逻族。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜻底。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖聘鳞,靈堂內(nèi)的尸體忽然破棺而出薄辅,到底是詐尸還是另有隱情要拂,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布站楚,位于F島的核電站脱惰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏窿春。R本人自食惡果不足惜拉一,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旧乞。 院中可真熱鬧蔚润,春花似錦、人聲如沸尺栖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽决瞳。三九已至,卻和暖如春左权,著一層夾襖步出監(jiān)牢的瞬間皮胡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工赏迟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屡贺,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓锌杀,卻偏偏與公主長得像甩栈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子糕再,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容