音視頻學(xué)習(xí)筆記(一)Audio Units
Audio Unit Hosting Guide for iOS 官方文檔
最近在學(xué)習(xí)iOS音視頻開發(fā)锹杈,在此寫一個學(xué)習(xí)筆記野舶。
前言
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)用程序的步驟基本相同:
- 配置 audio session尖啡;
- 指定audio units;
- 創(chuàng)建audio processing graph剩膘,然后獲取audio units;
- 配置 audio units;
- 連接audio units節(jié)點(diǎn);
- 提供用戶界面;
- 初始化然后啟動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)建科阎。
- 使用 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);
- 使用 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è)置票唆,幾種常用的如下圖:
3. AudioUnit的通用參數(shù)設(shè)置
以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));
參考文檔:
- 《音視頻開發(fā)進(jìn)階指南》 - 展曉凱
- Audio Unit 基礎(chǔ)