iOS音頻(2)——Audio Unit

一其障、Audio Unit綜述
  1.1镰吆、Audio Unit 概念點(diǎn)
  1.2芹助、 AuidoUnit類型
二、構(gòu)建Audio Unit的流程
  2.1 挟纱、配置AudioSession
  2.2羞酗、指定 Audio Units類型
  2.3、創(chuàng)建AudioUnit
  2.4紊服、設(shè)置AudioUnit的屬性
三檀轨、數(shù)據(jù)處理
  3.1、 AURenderCallbackStruct
  3.2欺嗤、串連的Audio node
  3.3参萄、數(shù)據(jù)的轉(zhuǎn)換
四、附錄 
 〖灞4.1讹挎、Audio Unit 示例

一、Audio Unit綜述

相對(duì)于MacOS,Audio Unit在iOS上使用到的幾率很小吆玖,AV Foundation 和Audio Toolbox提供的API已經(jīng)滿足我們平常開(kāi)發(fā)中音視頻的錄制播放的需求點(diǎn)筒溃。Audio Unit幾乎可以認(rèn)為是對(duì)硬件驅(qū)動(dòng)層的封裝,通過(guò)它獲取麥克風(fēng)采集的音頻數(shù)據(jù)或者將音頻數(shù)據(jù)傳輸給揚(yáng)聲器播放沾乘。但是隨著直播熱對(duì)音視頻的傳輸速度高要求怜奖,將PCM音頻轉(zhuǎn)換成AAC主要用到就是Audio Unit。



  與AV Foundation 和Audio Toolbox相比較翅阵,Audio Unit主要有兩大優(yōu)勢(shì):
(1)時(shí)效性高歪玲,Audio Unit是接近硬件層導(dǎo)致對(duì)音頻流的采集回調(diào)更加迅速。
(2)動(dòng)態(tài)的配置怎顾,AUGraph可以動(dòng)態(tài)的對(duì)音頻數(shù)據(jù)的組合配置读慎,改變音效。

1.1Audio Unit 概念點(diǎn):

Audio Unit 主要涉及到三個(gè)常用的概念知識(shí):
(1)AUGraph:包含和管理Audio Unit 的組織者槐雾;
(2)AUNode /AudioComponent:是AUGraph音頻處理環(huán)節(jié)中的一個(gè)節(jié)點(diǎn)夭委。
(3)AudioUnit: 音頻處理組件,是對(duì)音頻處理節(jié)點(diǎn)的實(shí)例描述者和操控者募强。
  我們不妨想像演唱會(huì)的舞臺(tái)上株灸,有錄制歌聲與樂(lè)器的麥克風(fēng),而從麥克風(fēng)到輸出到音響之間擎值,還串接了大大小小的效果器慌烧,在這個(gè)過(guò)程中,無(wú)論是麥克風(fēng)鸠儿、音響或是效果器屹蚊,都是不同的AUNode厕氨。AUNode 是這些器材的實(shí)體,而我們要操控這些器材汹粤、改變這些器材的效果屬性命斧,就會(huì)需要透過(guò)每個(gè)器材各自的操控界面,這些介面便是AudioUnit嘱兼,最后構(gòu)成整個(gè)舞臺(tái)国葬,便是AUGraph。AUNode 與AudioComponent 的差別在于芹壕,其實(shí)像上面講到的各種器材汇四,除了可以放在AUGraph 使用之外,也可以單獨(dú)使用踢涌,比方說(shuō)我們有臺(tái)音響通孽,我們除了把音響放在舞臺(tái)上使用外,也可以單獨(dú)拿這臺(tái)音響輸出音樂(lè)睁壁。當(dāng)我們要在AUGraph 中使用某個(gè)器材利虫,我們就要使用AUNode 這種形態(tài),單獨(dú)使用時(shí)堡僻,就使用AudioComponent。但無(wú)論是操作AUNode 或AudioComponent疫剃,都還是得透過(guò)AudioUnit 這一層操作界面钉疫。(上述文字摘自KKBOX iOS/Mac OS X 基礎(chǔ)開(kāi)發(fā)教材)

下圖所示兩路音頻數(shù)據(jù)首先經(jīng)過(guò)均衡器單元,然后再經(jīng)過(guò)混音單元組合在一起巢价, 最后經(jīng)由輸入輸出單元傳輸?shù)降綋P(yáng)聲器牲阁。


1.2 AuidoUnit類型

iOS提供了四大類別7種不同的AuidoUnit
AudioComponentDescription對(duì)象來(lái)描述一個(gè)具體的AudioUnit:

typedef struct AudioComponentDescription {
   OSType              componentType;
   OSType              componentSubType;
   OSType              componentManufacturer;
   UInt32              componentFlags;
   UInt32              componentFlagsMask;
} AudioComponentDescription;
  • componentType AuidoUnit主要有四種大類型:均衡器/混音/輸入輸出/格式轉(zhuǎn)換;
  • componentSubType 指的四大類型對(duì)應(yīng)的子類型 可以對(duì)照下面的表壤躲;
  • componentManufacturer 目前iOS開(kāi)發(fā)中只有: kAudioUnitManufacturer_Apple城菊;
  • componentFlags和“componentFlagsMask一般設(shè)置為0。
類型 componentType kAudioUnitType_Effect
均衡器 kAudioUnitType_Effect kAudioUnitSubType_AUiPodEQ
混音 kAudioUnitType_Mixer kAudioUnitSubType_AU3DMixerEmbedded kAudioUnitSubType_MultiChannelMixer多路 混音
輸入輸出 kAudioUnitType_Output kAudioUnitSubType_RemoteIO遠(yuǎn)端kAudioUnitSubType_VoiceProcessingIO kAudioUnitSubType_GenericOutput
格式轉(zhuǎn)換 kAudioUnitType_FormatConverter kAudioUnitSubType_AUConverter

二碉克、構(gòu)建Audio Unit的流程

2.1 配置AudioSession

AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setPreferredSampleRate:44100 error:&error];
[audioSession setPreferredInputNumberOfChannels:1 error:&error];
[audioSession setPreferredIOBufferDuration:0.05 error:&error];
[audioSession setActive: YES error: nil];

2.2凌唬、指定 Audio Units類型

  // multichannel mixer unit
    AudioComponentDescription mixer_desc;
    mixer_desc.componentType = kAudioUnitType_Mixer;
    mixer_desc.componentSubType = kAudioUnitSubType_MultiChannelMixer;
    mixer_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    mixer_desc.componentFlags = 0;
    mixer_desc.componentFlagsMask = 0;
    // multichannel mixer unit
    AudioComponentDescription eq_desc;
    eq_desc.componentType = kAudioUnitType_Effect;
    eq_desc.componentSubType = kAudioUnitSubType_AUiPodEQ;
    eq_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    eq_desc.componentFlags = 0;
    eq_desc.componentFlagsMask = 0;
    // output unit
    AudioComponentDescription output_desc;
    output_desc.componentType = kAudioUnitType_Output;
    output_desc.componentSubType = kAudioUnitSubType_RemoteIO;
    output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    output_desc.componentFlags = 0;
    output_desc.componentFlagsMask = 0;

2.3、創(chuàng)建AudioUnit

(1)通過(guò)AudioComponent創(chuàng)建

    AudioComponent outComponent = AudioComponentFindNext(NULL,&output_desc);
    AudioUnit outUnit;
    AudioComponentInstanceNew(outComponent, &outUnit);

AudioComponentFindNext參數(shù)inComponent一般設(shè)置為NULL漏麦,從系統(tǒng)中找到第一個(gè)符合inDesc描述的Component客税,如果為其賦值,則從其之后進(jìn)行尋找撕贞。
函數(shù)AudioComponentInstanceNew的第二個(gè)參數(shù)類型是AudioComponentInstance更耻,AudioUnit實(shí)際上就是 AudioComponentInstance,在AudioComponent類中有定義:

typedef AudioComponentInstance AudioUnit;

(2)通過(guò)AUNode創(chuàng)建AudioUnit
AUGraph是由AUNode的串聯(lián)而成,首先需要先創(chuàng)建一個(gè) AUGraph:

  OSStatus status = NewAUGraph(&audioGraph);

獲取一個(gè) AUNode,第一個(gè)參數(shù)是創(chuàng)建的AUGraph捏膨。第二個(gè)參數(shù)是描述信息AudioComponentDescription秧均,輸出AUNode食侮。

   AUNode mixerNode;
    status = AUGraphAddNode(audioGraph, &mixerUnitDescription, &mixerNode);

獲取一個(gè) AudioUnit,第一個(gè)和第三個(gè)參數(shù)是還是之前的AUGraph,AudioComponentDescription目胡。第二個(gè)參數(shù)是我們剛才的AUNode锯七,最終輸出的AudioUnit。

 status = AUGraphNodeInfo(audioGraph, mixerNode, &mixerUnitDescription, &mixerUnit);

2.4讶隐、設(shè)置AudioUnit的屬性

image.png

AudioUnit實(shí)際上就是一個(gè)AudioComponentInstance實(shí)例對(duì)象起胰,一個(gè)AudioUnit由scope(范圍)和element(元素)組成,實(shí)際上開(kāi)發(fā)中主要涉及到輸入輸出的問(wèn)題

CF_ENUM(AudioUnitScope) {
    kAudioUnitScope_Global      = 0,
    kAudioUnitScope_Input         = 1,
    kAudioUnitScope_Output      = 2,
    kAudioUnitScope_Group       = 3,
    kAudioUnitScope_Part        = 4,
    kAudioUnitScope_Note        = 5,
    kAudioUnitScope_Layer       = 6,
    kAudioUnitScope_LayerItem   = 7
};

scope主要使用到的輸入kAudioUnitScope_Input和輸出kAudioUnitScope_Output巫延,而在element效五, Input用“1”(和I很像)表示,Output用“0”(和O很像)表示炉峰,

image.png
AudioUnitSetProperty(               AudioUnit               inUnit,
                                    AudioUnitPropertyID     inID,
                                    AudioUnitScope          inScope,
                                    AudioUnitElement        inElement,
                                    const void * __nullable inData,
                                    UInt32                  inDataSize) 
  • AudioUnitPropertyID 設(shè)置屬性名稱
  • AudioUnitScope AudioUnit的Scope 主要用于輸入輸出范圍
  • AudioUnitElement AudioUnit的Element 主要用1 輸入總線(bus),0輸出總線(bus);
  • inData 輸入值
  • inDataSize 輸入值的長(zhǎng)度

AudioUnit 的Remote IO有2個(gè)element畏妖,大部分代碼和文獻(xiàn)都用bus代替element,兩者同義疼阔,bus0就是輸出bus 1代表輸入轴合,播放音頻文件就是在bus 0傳送數(shù)據(jù),bus 1輸入在Remote IO 默認(rèn)是關(guān)閉的客情,在錄音的狀態(tài)下 需要把bus 1設(shè)置成開(kāi)啟狀態(tài)溺森。

   UInt32 one = 1;
    AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof(one));

三、數(shù)據(jù)處理

3.1淘邻、AURenderCallbackStruct

在Audio Unit存儲(chǔ)輸入的數(shù)據(jù)和提供播放輸出數(shù)據(jù)都是通過(guò)RenderCallback函數(shù)茵典,通過(guò)AudioUnitSetProperty與輸入輸出回調(diào)相關(guān)聯(lián)。

    AURenderCallbackStruct callBackStruct;
    callBackStruct.inputProc = BXAURenderCallback;
    callBackStruct.inputProcRefCon = (__bridge void * _Nullable)(self);
    AudioUnitSetProperty(_outAudioUinit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callBackStruct, sizeof(AURenderCallbackStruct));

AURenderCallbackStruct是結(jié)構(gòu)體宾舅,inputProc 就是我們注冊(cè)回調(diào)函數(shù)统阿。inputProcRefCon注冊(cè)者。

typedef struct AURenderCallbackStruct {
    AURenderCallback __nullable inputProc;
    void * __nullable           inputProcRefCon;
} AURenderCallbackStruct;

typedef OSStatus(*AURenderCallback)(void *                          inRefCon,
                        AudioUnitRenderActionFlags *    ioActionFlags,
                        const AudioTimeStamp *          inTimeStamp,
                        UInt32                          inBusNumber,
                        UInt32                          inNumberFrames,
                        AudioBufferList * __nullable    ioData);

  • ioData 需要填充的緩存數(shù)據(jù)
  • inNumberFrames 需要填充的數(shù)據(jù)幀數(shù)筹我,根據(jù)這個(gè)幀數(shù) 從原始音頻數(shù)據(jù)格式中輸出多少frame的LPCM.
  • ioActionFlags 數(shù)據(jù)回調(diào)發(fā)送錯(cuò)誤或者其他情況 上下文傳遞數(shù)據(jù)扶平。
    *inBusNumber 是輸出或者輸出的哪個(gè)bus.

3.2、串連的Audio node

image.png

AUGraph 中的各個(gè)Audio unit 是傳連接的 需要把各個(gè)unit 通過(guò)AUGraphConnectNodeInput 連接起來(lái) 一般主要用于混音 或者音效改變的時(shí)候 需要用到蔬蕊。

AUGraphConnectNodeInput(_audioGraph, EQNode, 0, outNode, 0);

3.3结澄、數(shù)據(jù)的轉(zhuǎn)換

AudioConverterRef 第一個(gè)參數(shù)是輸入的格式 第二個(gè)是需要輸出的轉(zhuǎn)換格式。

extern OSStatus
AudioConverterNew(      const AudioStreamBasicDescription * inSourceFormat,
                        const AudioStreamBasicDescription * inDestinationFormat,
                        AudioConverterRef __nullable * __nonnull outAudioConverter) 

需要把我們轉(zhuǎn)換的LPCM格式回調(diào)輸入AudioConverterFillComplexBuffer

extern OSStatus
AudioConverterFillComplexBuffer(    AudioConverterRef                   inAudioConverter,
                                    AudioConverterComplexInputDataProc  inInputDataProc,
                                    void * __nullable                   inInputDataProcUserData,
                                    UInt32 *                            ioOutputDataPacketSize,
                                    AudioBufferList *                   outOutputData,
                                    AudioStreamPacketDescription * __nullable outPacketDescription)

AudioConverterComplexInputDataProc回調(diào)函數(shù)就是讀取原有數(shù)據(jù)的幀數(shù)據(jù) 放置于ioData中

static OSStatus BXPlayerConverterFiller(AudioConverterRef inAudioConverter, UInt32 * ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription** outDataPacketDescription, void * inUserData)
{
   
    BXAudioUnitEQPlayer *self = (__bridge BXAudioUnitEQPlayer *)(inUserData);
    
    if (self->_readPacketIndex >= self->_packetArray.count) {
        *ioNumberDataPackets = 0;
        return 'bxnd';
    }
    NSData *packet = self->_packetArray[self->_readPacketIndex];
    ioData->mNumberBuffers = 1;
    ioData->mBuffers[0].mData = (void *)packet.bytes;
    ioData->mBuffers[0].mDataByteSize = (UInt32)packet.length;
    
    static AudioStreamPacketDescription aspdesc;
    aspdesc.mDataByteSize = (UInt32)packet.length;
    aspdesc.mStartOffset = 0;
    aspdesc.mVariableFramesInPacket = 1;
    *outDataPacketDescription = &aspdesc;
    self->_readPacketIndex++;
     *ioNumberDataPackets = 1;
    return noErr;
}

四岸夯、附錄

AudioUnit掌握需要同學(xué)們切合實(shí)際的敲代碼運(yùn)用AudioUnit 使用的簡(jiǎn)單示例

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末概而,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子囱修,更是在濱河造成了極大的恐慌赎瑰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件破镰,死亡現(xiàn)場(chǎng)離奇詭異餐曼,居然都是意外死亡压储,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)源譬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)集惋,“玉大人,你說(shuō)我怎么就攤上這事踩娘」涡蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵养渴,是天一觀的道長(zhǎng)雷绢。 經(jīng)常有香客問(wèn)我,道長(zhǎng)理卑,這世上最難降的妖魔是什么翘紊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮藐唠,結(jié)果婚禮上帆疟,老公的妹妹穿的比我還像新娘。我一直安慰自己宇立,他們只是感情好踪宠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著妈嘹,像睡著了一般殴蓬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟋滴,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音痘绎,去河邊找鬼津函。 笑死,一個(gè)胖子當(dāng)著我的面吹牛孤页,可吹牛的內(nèi)容都是我干的尔苦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼行施,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼允坚!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蛾号,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤稠项,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鲜结,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體展运,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡活逆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拗胜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔗候。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖埂软,靈堂內(nèi)的尸體忽然破棺而出锈遥,到底是詐尸還是另有隱情,我是刑警寧澤勘畔,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布所灸,位于F島的核電站,受9級(jí)特大地震影響咖杂,放射性物質(zhì)發(fā)生泄漏庆寺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一诉字、第九天 我趴在偏房一處隱蔽的房頂上張望懦尝。 院中可真熱鬧,春花似錦壤圃、人聲如沸陵霉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)踊挠。三九已至,卻和暖如春冲杀,著一層夾襖步出監(jiān)牢的瞬間效床,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工权谁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剩檀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓旺芽,卻偏偏與公主長(zhǎng)得像沪猴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子采章,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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