iOS AudioUnit 總結(jié)

iOS AudioUnit 總結(jié)

iOS 的 AudioUnit 功能十分強大,使用圖的形式連接各個節(jié)點,來實現(xiàn)我們不通的需求,AUGraph 就是我們的圖,,然后將 AudioUnit 連接到我們的圖上面,就可以實現(xiàn)例如混音功能的實現(xiàn)。

AudioUnit 提供了低延遲的音頻處理,可以實現(xiàn),混音,回聲消除,均衡器,壓縮器,混響,濾波器等各個強大的功能。

這里有一篇官方文檔 ,圖畫的很 nice

以下綜合幾個不錯的博客,混在一起,當做個記錄,以后會附上我自己的demo

每個 AudioUnit 都有 Element1 和 Element0,也就對應 input 和 output,因為i和o對應1和0很想,所以蘋果的連個節(jié)點的定義就是這么來的,element0 控制輸出,Element1 控制輸入,也叫做 Bus,音頻流從 input 輸入,然后從 output 輸出。

如果想使用揚聲器播放,就必須將 AudioUnit 的 Element0的 outputScope 和 揚聲器鏈接,如果想使用麥克風錄音,就必須將 AudioUnit 的 Element1的 inputScope 和 麥克風鏈接。

下面選取我覺得比較好的博客,參考,參考,參考,參考,參考,參考

其中如果你讀懂這篇,你就掌握 AudioUnit 的精髓了。

AudioUnit 初始化

播放使用AudioUnit,首先由3個相關(guān)的東西:AudioComponentDescription、AudioComponent和AudioComponentInstance。AudioUnit和AudioComponentInstance是一個東西,typedef定義的別名而已。

AudioComponentDescription是描述,用來做組件的篩選條件,類似于SQL語句where之后的東西。

AudioComponent是組件的抽象,就像類的概念,使用AudioComponentFindNext來尋找一個匹配條件的組件。

AudioComponentInstance是組件,就像對象的概念,使用AudioComponentInstanceNew構(gòu)建。

k


 //首先構(gòu)造出要用到創(chuàng)建Unit的結(jié)構(gòu)體
 AudioComponentDescription ioUnitDescription;
 ioUnitDescription.componentType = kAudioUnitType_Output;
 ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
 ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
 ioUnitDescription.componentFlags = 0;
 ioUnitDescription.componentFlagsMask = 0;
 
 AudioComponent ioUnitRef = AudioComponentFindNext(NULL, &ioUnitDescription);
 //創(chuàng)建AudioUnit實例
 AudioComponentInstanceNew(ioUnitRef, &ioUnitInstance);

//首先構(gòu)造出要用到創(chuàng)建Unit的結(jié)構(gòu)體
 AudioComponentDescription ioUnitDescription;
 ioUnitDescription.componentType = kAudioUnitType_Output;
 ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
 ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
 ioUnitDescription.componentFlags = 0;
 ioUnitDescription.componentFlagsMask = 0;
 
 //1 new
 NewAUGraph(&processingGraph);
 AUGraphAddNode(processingGraph, &ioUnitDescription, &ioNode);
 
 //2 open
 AUGraphOpen(processingGraph);
 
 //3 從相應的Node中獲得AudioUnit
 AUGraphNodeInfo(processingGraph, ioNode, NULL, &ioUnit);

推薦使用第二種,擴展性更高,注意 AUNode 和 AudioUnit 必須成對出現(xiàn)

當我們控制Remote IO Unit的時候想告訴麥克風 各種input的參數(shù) 可以通過 一個叫ASBD 格式的結(jié)構(gòu)體數(shù)據(jù)描述來設置給相應的Unit

UInt32 bytePerSample = sizeof(Float32);
AudioStreamBasicDescription asbd;
bzero(&asbd, sizeof(asbd));
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mSampleRate = 44100;
asbd.mChannelsPerFrame = channels;
asbd.mFramesPerPacket = 1;
asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
asbd.mBitsPerChannel = 8 * bytePerSample;
asbd.mBytesPerFrame = bytePerSample;
asbd.mBytesPerPacket = bytePerSample;


mFormatID 可用來指定編碼格式 eg:PCM
mSampleRate 采樣率
mChannelsPerFrame 每個Frame有幾個channel
mFramesPerPacket 每個Packet有幾Frame
mFormatFlags 這個是用來描述聲音格式表示格式的參數(shù),上面代碼我們指定的是每個sample的表示格式為Float格式,有點類似SInt16,如果后邊是NonInterleaved代表非交錯的,對于這個音頻來講就是左右聲道的是非交錯存放的,實際的音頻數(shù)據(jù)會存儲在一個AudioBufferList結(jié)構(gòu)中的變量mBuffers中,如果mFormatFlags指定的是NonInterleaved,那么左聲道就在會在mBuffers[0]里面,右聲道就在mBuffers[1]里面.
mBitsPerChannel 表示一個聲道的音頻數(shù)據(jù)用多少位來表示,上面我們用的是Float來表示, 所以這里使用的是 8 乘以 每個采樣的字節(jié)數(shù)來賦值.
mBytesPerFrame 和 mBytesPerPacket 這兩個的賦值需要根據(jù)mFormatFlags 的值來進行分配,如果是NonInterleaved非交錯的情況下, 就賦值bytePerSample(因為左右聲道是分開的).但如果是Interleaved的話,那就應該是 bytePerSample * channels (因為左右聲道是交錯存放),這樣才能表示一個Frame里面到底有多少byte.

如下代碼 設置ASBD給相應的Audio Unit


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


//設置ASBD
AudioStreamBasicDescription inputFormat;
inputFormat.mSampleRate = 44100;
inputFormat.mFormatID = kAudioFormatLinearPCM;
inputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsNonInterleaved;
inputFormat.mFramesPerPacket = 1;
inputFormat.mChannelsPerFrame = 1;
inputFormat.mBytesPerPacket = 2;
inputFormat.mBytesPerFrame = 2;
inputFormat.mBitsPerChannel = 16;
//設置給輸入端 配置麥克風輸出的數(shù)據(jù)是什么格式
OSStatus status = noErr;
status = AudioUnitSetProperty(audioUnit,
 kAudioUnitProperty_StreamFormat,
 kAudioUnitScope_Output,
 InputBus,
 &inputFormat,
 sizeof(inputFormat));
CheckStatus(status, @"AudioUnitGetProperty bus1 output ASBD error", YES);

構(gòu)建AUGraph

NewAUGraph(&processingGraph);
...
status = AUGraphAddNode(processingGraph, &playDesc, &recordPlayNode);
...
status = AUGraphAddNode(processingGraph, &mixerDesc, &mixerNode);
status = AUGraphOpen(processingGraph);

NewAUGraph新建,然后不斷通過AUGraphAddNode添加節(jié)點,也就是一個處理組件。最后AUGraphOpen打開。

AUGraphAddNode的3個參數(shù)分別是:要添加的AUGraph、節(jié)點性質(zhì)描述和節(jié)點變量。

屬性描述使用AudioComponentDescription對象,對于錄音和播放都使用:

 playDesc.componentType = kAudioUnitType_Output;
    playDesc.componentSubType = kAudioUnitSubType_RemoteIO;

而混音組件是:

 mixerDesc.componentType = kAudioUnitType_Mixer;
    mixerDesc.componentSubType = kAudioUnitSubType_MultiChannelMixer;

開啟之后,使用status = AUGraphNodeInfo(processingGraph, recordPlayNode, NULL, &recordPlayUnit);獲取node對應的AudioUnit??梢允褂胊udioUnit的大量功能函數(shù)來做復雜的處理。

在node之間建立連接

status = AUGraphConnectNodeInput(processingGraph, mixerNode, 0, recordPlayNode, 0);

參數(shù)分別是:AUGraph變量、前一個node、前一個node的element索引、后一個node、后一個node的element索引。

每個node都可能有多個輸入輸出流,每個對應一個element,可以理解為機器的連接線之類的。上面的這段代碼就是:把mixerNode的element0輸出連接到recordPlayNode的element0。

使用AUGraphConnect的好處是不需要我們編程處理數(shù)據(jù)了,兩個node之間連接好之后,系統(tǒng)會處理它們之間的數(shù)據(jù)傳輸。mixerNode是負責混音的節(jié)點,recordPlayNode即負責播放也負責錄音(remoteIO的audioUnit固定兩個element,一個錄音一個播放),它的element0負責播放,所以最后一個參數(shù)傳了0。而對于kAudioUnitSubType_MultiChannelMixer類型混音節(jié)點,輸入可能有多個,但輸出是一個,即element0。

所以上面這段代碼的實際作用是:把混音結(jié)束后的音頻流輸出給播放組件。

我們可以通過設置 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ù)需要自己選擇位置了。

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


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


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 具體怎么連接,我們是不需要關(guān)心的。我們第一次獲取的數(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。

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

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

音頻處理graph具有一個完整的 I / O單元

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

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

設置音頻格式

AUGraphConnect可以建立連接后讓系統(tǒng)處理,但對于更復雜的需求,還需要自己來手動處理音頻數(shù)據(jù)。

在這之前要先設定音頻格式,為了簡便,固定3個輸入源:索引0是第一個音頻文件,1是錄音數(shù)據(jù),2是第二個音頻文件。

for (int i = 0; i<MixerInputSourceCount; i++) {
        if ([[self.audioChannelTypes objectForKey:@(i)] integerValue] == AUGraphMixerChannelTypeStereo) {
            sourceStreamFmts[i] = *([[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                                         sampleRate:44100
                                                                           channels:2
                                                                        interleaved:NO].streamDescription);
        }else{
            sourceStreamFmts[i] = *([[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                                         sampleRate:44100
                                                                           channels:1
                                                                        interleaved:YES].streamDescription);
        }
    }


MixerInputSourceCount是輸入源數(shù)量,根據(jù)設置的聲道類型,來確定音頻格式。兩種格式的區(qū)別只是聲道和interleaved這個屬性。

在雙聲道時設為2,左邊或右邊單聲道設為1。interleaved這個單詞是"交錯,交叉存取"的意思,這個在設為NO的時候,AudioBufferList包含兩個AudioBuffer,每個負責一個聲道的數(shù)據(jù),而設為YES時,是一個AudioBuffer,兩個聲道的數(shù)據(jù)混在一起的。跟視頻數(shù)據(jù)如YUV里面的plane的概念類似。

左右聲道分開的好處是,可以單獨的填充左邊或右邊的聲音,比如把音頻文件1的數(shù)據(jù)都只填充到第一個AudioBuffer里,那只有左邊有聲音。

給每個輸入源設置回調(diào)和輸入格式:


for (int i = 0; i<inputCount; ++i) {
        AURenderCallbackStruct mixerInputCallback;
        mixerInputCallback.inputProc = &mixerDataInput;
        mixerInputCallback.inputProcRefCon = (__bridge void*)self;
        
        status = AUGraphSetNodeInputCallback(processingGraph, mixerNode, i, &mixerInputCallback);
        
        status = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, i, &mixStreamFmt, sizeof(AudioStreamBasicDescription));
    }

這里變量i代表著輸入源的索引,也是element的索引。在文檔里,element和bus是同一個東西,都是指一個完整的數(shù)據(jù)流處理環(huán)境(context),和輸入輸出流是對應的。

讀取文件

文件讀取使用ExtAudioFile,這個據(jù)我了解,有兩點很重要:1.自帶轉(zhuǎn)碼 2.只處理pcm。

不僅是ExtAudioFile,包括其他audioUnit,其實應該是流數(shù)據(jù)處理的性質(zhì),這些組件都是“輸入+輸出”的這種工作模式,這種模式?jīng)Q定了你要設置輸出格式、輸出格式等。

ExtAudioFileOpenURL使用文件地址構(gòu)建一個ExtAudioFile
文件里的音頻格式是保存在文件里的,不用設置,反而可以讀取出來,比如得到采樣率用作后續(xù)的處理。

設置輸出格式

AudioStreamBasicDescription clientDesc;
   clientDesc.mSampleRate = fileDesc.mSampleRate;
   clientDesc.mFormatID = kAudioFormatLinearPCM;
   clientDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
   clientDesc.mReserved = 0;
   clientDesc.mChannelsPerFrame = 1; //2
   clientDesc.mBitsPerChannel = 16;
   clientDesc.mFramesPerPacket = 1;
   clientDesc.mBytesPerFrame = clientDesc.mChannelsPerFrame * clientDesc.mBitsPerChannel / 8;
   clientDesc.mBytesPerPacket = clientDesc.mBytesPerFrame;

設置格式

   size = sizeof(clientDesc);
   status = ExtAudioFileSetProperty(audioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientDesc);

在APP這一端的是client,在文件那一端的是file,帶client代表設置APP端的屬性。測試mp3文件的讀取,是可以改變采樣率的,即mp3文件采樣率是11025,可以直接讀取輸出44100的采樣率數(shù)據(jù)。

讀取數(shù)據(jù)

ExtAudioFileRead(audioFile, framesNum, bufferList)
framesNum輸入時是想要讀取的frame數(shù),輸出時是實際讀取的個數(shù),數(shù)據(jù)輸出到bufferList里。bufferList里面的AudioBuffer的mData需要分配內(nèi)存。


AudioOutputUnitProperty_EnableIO,打開IO。默認情況element0,也就是從APP到揚聲器的IO時打開的,而element1,即從麥克風到APP的IO是關(guān)閉的。使用AudioUnitSetProperty函數(shù)設置屬性,它的幾個參數(shù)分別作用是:1.要設置的audioUnit 2.屬性名稱 3.element, element0和element1選一個,看你是接收音頻還是播放 4.scope也就是范圍,這里是播放,我們要打開的是輸出到系統(tǒng)的通道,使用kAudioUnitScope_Output 5.要設置的值 6.值的大小。
比較難搞的就是element和scope,需要理解audioUnit的工作模式,也就是最開始的兩張圖。

設置輸入格式AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, renderAudioElement, &audioDesc, sizeof(audioDesc));,格式就用AudioStreamBasicDescription結(jié)構(gòu)體數(shù)據(jù)。輸出部分是系統(tǒng)控制,所以不用管。

然后是設置怎么提供數(shù)據(jù)。這里的工作原理是:audioUnit開啟后,系統(tǒng)播放一段音頻數(shù)據(jù),一個audioBuffer,播完了,通過回調(diào)來跟APP索要下一段數(shù)據(jù),這樣循環(huán),知道你關(guān)閉這個audioUnit。重點就是:1. 是系統(tǒng)主動來跟你索要,不是我們的程序去推送數(shù)據(jù) 2.通過回調(diào)函數(shù)。就像APP這邊是工廠,而系統(tǒng)是商店,他們斷貨了或者要斷貨了,就來跟我們進貨,直到你工廠倒閉了、不賣了等等

比較難搞的就是element和scope,需要理解audioUnit的工作模式,也就是最開始的兩張圖。

設置輸入格式AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, renderAudioElement, &audioDesc, sizeof(audioDesc));,格式就用AudioStreamBasicDescription結(jié)構(gòu)體數(shù)據(jù)。輸出部分是系統(tǒng)控制,所以不用管。

然后是設置怎么提供數(shù)據(jù)。這里的工作原理是:audioUnit開啟后,系統(tǒng)播放一段音頻數(shù)據(jù),一個audioBuffer,播完了,通過回調(diào)來跟APP索要下一段數(shù)據(jù),這樣循環(huán),知道你關(guān)閉這個audioUnit。重點就是:1. 是系統(tǒng)主動來跟你索要,不是我們的程序去推送數(shù)據(jù) 2.通過回調(diào)函數(shù)。就像APP這邊是工廠,而系統(tǒng)是商店,他們斷貨了或者要斷貨了,就來跟我們進貨,直到你工廠倒閉了、不賣了等等

調(diào)整音量


AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, (UInt32)index, volume, 0);



把輸入源封裝成一個類,可以是文件、是錄音、是網(wǎng)絡數(shù)據(jù)流等,然后混音可以自由的組合和拆解各個輸入源。不僅調(diào)節(jié)音量,或者還可以加上變調(diào)等,就跟使用濾鏡處理圖像一樣。

混音輸出可以加一個實時輸出到文件,給mixer組件加一個renderCallback就可以拿到數(shù)據(jù),然后可以輸出到文件或者推送到服務器都沒問題。

錄音和混音之間加一個緩沖區(qū),為了簡便,是在mixer需要數(shù)據(jù)的時候調(diào)用AudioUnitRender,但混音需求數(shù)據(jù)的頻率和錄音輸出數(shù)據(jù)的頻率不一定一致,會導致某些數(shù)據(jù)丟失。

在其他iphone或mac試一下雙聲道錄音是否可以得到兩個聲道數(shù)據(jù)不同。否則雙聲道沒有意義了。



Effect Unit




|子類型|   用途說明|   子枚舉類型|
均衡效果器   為聲音的某些 頻帶 增強或衰減能量,效果器需要指定多個頻帶,然后為各頻帶設置增益最終改變聲音在音域上的能量分布 kAudioUnitSubType_NBandEQ
壓縮效果器   當聲音較小或較大通過設置閥值來提高或降低聲音能量 eg:作用時間、釋放時間、以及觸發(fā)值從而最終控制聲音在時域上的能量范圍    kAudioUnitSubType_DynamicsProcessor
混響效果器   通過聲音反射的延遲控制聲音效果 kAudioUnitSubType_Reverb2

Mixer Units


子類型 用途說明    子枚舉類型
3D Mixer    僅支持 macOS   
MultiChannelMixer   多路聲音混音效果器,可以接受多路音頻輸入,還可以分別調(diào)整每一路的音頻增益和開關(guān),并將多路音頻合成一路  kAudioUnitSubType_MultiChannelMixer



子類型 用途說明    子枚舉類型
Remote I/O  采集音頻與播放音頻,在Audio Unit中使用麥克風和揚聲器的時候會用到這個Unit kAudioUnitType_Output
Generic Output  進行離線處理,或者說AUGraph中不使用揚聲器來驅(qū)動整個數(shù)據(jù)流,而希望使用一個輸出(可以放入內(nèi)存隊列或者磁盤I/O操作)來驅(qū)動數(shù)據(jù)流時



子類型 用途說明    子枚舉類型
AUConverter 格式轉(zhuǎn)換,當某些效果器對輸入的音頻格式有明確要求時,或者我們將音頻數(shù)據(jù)輸入給一些其它的編碼器進行編碼。。。   kAudioUnitSubType_AUConverter
Time Pitch  變速變調(diào)效果器,調(diào)整聲音音高. eg:會說話的Tom貓 kAudioUnitSubType_NewTimePitch
注意: AUConverter 如果由FFMpeg解碼出來的PCM 是SInt16格式 如果要用格式轉(zhuǎn)換效果器unit必須轉(zhuǎn)成Float32格式表示的數(shù)據(jù).

耳返

直播中的 耳返 就是用的這個把麥克風采集的數(shù)據(jù)直接扔給揚聲器 這樣就能做到 低延遲的實時聽到麥克風的聲音.

直播中一般使用 Remote I/O unit來進行采集工作

  1. 使用AudioUnit連接揚聲器

OSStatus status = noErr;
UInt32 onFlag = 1;
UInt32 busZero = 0; //Element0 就是bus0
status = AudioUnitSetProperty(remoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, busZero, &onFlag, sizeof(onFlag));
CheckStatus(status, @"不能連接揚聲器", YES);

kAudioUnitScope_Output 就是連接揚聲器的key.

  1. 連接麥克風

OSStatus status = noErr;
UInt32 busOne = 1; //Element1 就是bus1 接麥克風輸入
UInt32 oneFlag = 1;
status = AudioUnitSetProperty(remoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, busOne, &oneFlag, sizeof(oneFlag));
CheckStatus(status, @"不能連接麥克風", YES);
  1. debug
可以使用如下代碼檢查每一步執(zhí)行出錯debug

static void CheckStatus(OSStatus status, NSString *message, BOOL fatal) {
 if (status != noErr) {
 char fourCC[16];
 *(UInt32 *)fourCC = CFSwapInt32HostToBig(status);
 fourCC[4] = '';
 if (isprint(fourCC[0]) && isprint(fourCC[1]) &&
 isprint(fourCC[2]) && isprint(fourCC[4])) {
 NSLog(@"%@:%s",message, fourCC);
 } else {
 NSLog(@"%@:%d",message, (int)status);
 }
 
 if (fatal) {
 exit(-1);
 }
 }
}

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

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

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