前言
相關文章:
使用VideoToolbox硬編碼H.264
使用VideoToolbox硬解碼H.264
使用AudioToolbox編碼AAC
使用AudioToolbox播放AAC
HLS點播實現(xiàn)(H.264和AAC碼流)
HLS推流的實現(xiàn)(iOS和OS X系統(tǒng))
iOS在線音頻流播放
Audio Unit播放PCM文件
Audio Unit錄音(播放伴奏+耳返)
Audio Unit播放aac/m4a/mp3等文件
Audio Unit和ExtendedAudioFile播放音頻
前文介紹了AudioUnit的錄音/播放、AudioConvert進行音頻轉換、ExtendedAudioFile進行音頻文件的讀/寫,其中AudioUnit的初始化都是通過AudioComponentInstanceNew
實現(xiàn),實際工程中更多使用的是AUGraph的方式進行AudioUnit的初始化。
本文嘗試用AUGraph來管理RemoteI/O Unit和Mixer Unit,實現(xiàn)錄音、伴奏播放、人聲和伴奏混合的功能。
正文
1、概念介紹
AUGraph連接一組 audio unit 之間的輸入和輸出,構成一張圖,同時也為audio unit 的輸入提供了回調。AUGraph抽象了音頻流的處理過程,子結構可以作為一個AUNode嵌入到更大的結構里面進行處理。AUGraph可以遍歷整個圖的信息,每個節(jié)點都是一個或者多個AUNode,音頻數(shù)據(jù)在點與點之間流通,并且每個圖都有一個輸出節(jié)點。輸出節(jié)點可以用來啟動、停止整個處理過程。
每個AudioUnit都有Input, Output 和 Global 三個域。
input輸入域是音頻流進入unit的入口,output輸出域是音頻流離開unit的出口,global全局域則代表整個unit。
輸入域和輸出域都有若干個bus/element,比如說mixer unit有多個輸入bus,只有一個輸出bus;而splitter unit則有一個輸入bus,有多個輸出的bus。
注意的是,bus和channel不是一個東西,一個是音頻流,一個是音頻流的格式。
比如說Remote I/O Unit的輸入域的inputBus是來自麥克風的音頻流,其音頻格式是雙聲道。
2、具體流程
- 1、初始化文件流和AVAudioSession,分配buffer;
- 2、新建AUGraph,并添加兩個AUNode,一個是RemoteI/O Unit的節(jié)點,一個是Mixer Unit的節(jié)點。
添加AUNode的節(jié)點有兩個步驟,先通過AUGraphAddNode
添加節(jié)點,再通過AUGraphNodeInfo
獲取節(jié)點對應的AudioUnit。 - 3、建立兩個AUNode的聯(lián)系,
AUGraphConnectNodeInput
通過把Mixer Unit的outputBus的輸出作為RemoteI/O Unit的outputBus的輸入;
(這里需要注意,不是RemoteI/O的inputBus 的輸入,因為RemoteI/O Unit的inputBus的輸入是麥克風)
同時設置好RemoteI/O Unit的輸入和輸出格式、Record的回調函數(shù); - 4、調用
AUGraphInitialize
初始化AUGraph,然后通過AUGraphStart
開始整個AUGraph;
在AUGraph開啟后,麥克風收到錄制數(shù)據(jù)后調用kAudioOutputUnitProperty_SetInputCallback
的回調,把麥克風的數(shù)據(jù)回調給APP;
Mixer Unit還會通過之前kAudioUnitProperty_SetRenderCallback
設置好的回調,要求APP填充兩個inputBus的輸入;
在Mixer Unit處理好數(shù)據(jù)之后,會按照之前AUGraphConnectNodeInput
設置的,把數(shù)據(jù)發(fā)送給Remote I/O Unit;
Remote I/O Unit再把數(shù)據(jù)發(fā)送給揚聲器。
3、音頻流解析
如下,是整個demo的音頻流向:
伴奏文件被讀取到內存,再被送到MixUnit的inputBus0;
麥克風錄取到音頻數(shù)據(jù),送到Remote I/O Unit的inputBus,存到內存中,再被送到MixUnit的inputBus1;
MixUnit混合兩個inputBus的數(shù)據(jù),通過outputBus輸出到Remote I/O Unit的outputBus中;
Remote I/O Unit再把outputBus的數(shù)據(jù)發(fā)送個揚聲器。
遇到的問題
1、AUGraphNodeInfo
無法初始化AudioUnit
實際運行時,報錯是AudioUnitSetProperty
方法,返回了-50的錯誤碼。
檢查錯誤碼,是AudioUnitSetProperty
的audio unit參數(shù)為空。
往上回溯,定位到AUGraphNodeInfo
沒正確初始化傳入的audio unit參數(shù),導致audio unit為空,并且當時沒有報錯,直到AudioUnitSetProperty
時才報錯。
經(jīng)過仔細檢查,發(fā)現(xiàn)是
AUGraphOpen
方法被遺漏。
必須先打開AUGraph,才進行獲取AudioUnit的操作。
2、AUGraphSetNodeInputCallback
給RemoteI/O Unit設置回調無效
如下,給RemoteI/O Unit設置回調可以用AudioUnitSetProperty
方法修改kAudioOutputUnitProperty_SetInputCallback設置回調,但嘗試用AUGraphSetNodeInputCallback
對RemoteI/O Unit節(jié)點添加回調的時候,發(fā)現(xiàn)沒法正常調用回調函數(shù)。
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = RecordCallback;
recordCallback.inputProcRefCon = (__bridge void *)self;
// CheckError(AUGraphSetNodeInputCallback(auGraph, outputNode, INPUT_BUS, &recordCallback), "record callback set fail"); // 這個不行,因為scope不一致
CheckError(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, INPUT_BUS, &recordCallback, sizeof(recordCallback)), "set property fail");
AUGraphSetNodeInputCallback 默認是inputScope,如果在input bus的inputScope修改屬性,會造成異?,F(xiàn)象;
3、kAudioOutputUnitProperty_SetInputCallback 和 kAudioUnitProperty_SetRenderCallback 混淆
- kAudioUnitProperty_SetRenderCallback 是audio unit需要數(shù)據(jù),向Host請求數(shù)據(jù);
- kAudioOutputUnitProperty_SetInputCallback是audio unit通知Host數(shù)據(jù)已經(jīng)就緒,可以通過
AudioUnitRender
拉取數(shù)據(jù);
AudioUnitRender
的解釋是:Initiates a rendering cycle for an audio unit.
下圖闡釋了AudioUnit是如何通過AudioUnitRender
去Pull音頻流數(shù)據(jù)
4、AUGraphConnectNodeInput的BUS參數(shù)設置錯誤
AUGraphConnectNodeInput(auGraph, mixNode, OUTPUT_BUS, outputNode, OUTPUT_BUS)
,從字面看是把mixNode的輸出作為outputNode的輸入。
但是在bus的參數(shù)設置上,為什么Remote I/O Unit的bus不是inputBus?
因為Remote I/O Unit有輸入域有兩個Bus,inputBus對應的是麥克風的輸入,outputBus對應的是app發(fā)送給Remote I/O Unit的數(shù)據(jù)。
這里Mixer Unit是把人聲和伴奏混合后,輸出給Remote I/O Unit,相當于app發(fā)送數(shù)據(jù)給Remote I/O Unit,所以這里應該填outputBus。
總結
demo中仍然存在問題,因為兩個unit結構混亂:
麥克風=>I/O Unit=>APP=>MixUnit
文件=>APP=>MixUnit
然后再是MixUnit=>I/O Unit=>揚聲器
其中,I/O Unit既指向MixUnit,同時MixUnit又指向I/O Unit。
更好的實現(xiàn)方案,用一個Unit來實現(xiàn)錄音,再用另外一個Unit進行播放,形成 RecordUnit=>MixUnit=>PlayUnit這樣的結構會更加漂亮。
這個設想就交由你去實現(xiàn)了!
demo的代碼點擊這里,書寫不易,不如來個喜歡支持下↓↓
附錄
Core Audio Tips
Audio Unit Properties Reference PDF
Audio Unit Hosting Guide for iOS