前言
在iOS中有很多方法可以進行音視頻采集。如 AVCaptureDevice, AudioQueue以及Audio Unit。其中 Audio Unit
是最底層的接口
,它的優點是功能強大,延遲低; 而缺點是學習成本高,難度大。
對於一般的iOS應用程序,AVCaptureDevice和AudioQueue完全夠用了。但對於音視頻直播,最好還是使用 Audio Unit 進行處理,這樣可以達到最佳的效果,
著名的 WebRTC 就使用的 Audio Unit 做的音頻采集與播放
。今天我們就重點介紹一下Audio Unit的基本知識和使用。
Audio Unit在 iOS架構中所處的位置:

基本概念
- Audio Unit的種類
共可分為四大類,並可細分為七種:

- Audo Unit 的內部結構
Audio Unit 內部結構分為兩大部分,Scope 與Element。其中 scope 又分三種,分別是 input scope, output scope, global scope。而 element 則是 input scope 或 output scope 內的一部分。

- Audio Unit 的輸入與輸出
下圖是一個 I/O type 的 Audio Unit,其輸入為麥克風,其輸出為喇叭。這是一個最簡單的Audio Unit使用范例。

ioUnit.png
The input element is element 1 (mnemonic device: the letter “I” of the word “Input” has an appearance similar to the number 1)
The output element is element 0 (mnemonic device: the letter “O” of the word “Output” has an appearance similar to the number 0)
使用流程概要
- 描述音頻元件(kAudioUnitType_Output/kAudioUnitSubType_RemoteIO /kAudioUnitManufacturerApple)
- 使用 AudioComponentFindNext(NULL, &descriptionOfAudioComponent) 獲得 AudioComponent。AudioComponent有點像生產 Audio Unit 的工廠。
- 使用 AudioComponentInstanceNew(ourComponent, &audioUnit) 獲得 Audio Unit 實例。
- 使用 AudioUnitSetProperty函數為錄制和回放開啟IO。
- 使用 AudioStreamBasicDescription 結構體描述音頻格式,並使用AudioUnitSetProperty進行設置。
- 使用 AudioUnitSetProperty 設置音頻錄制與放播的回調函數。
- 分配緩沖區。
- 初始化 Audio Unit。
- 啟動 Audio Unit。
初始化
初始化看起來像下面這樣。我們有一個 AudioComponentInstance 類型的成員變量,它用於存儲 Audio Unit。
下面的音頻格式用16位表式一個采樣。
#define kOutputBus 0#define kInputBus 1
// ...
OSStatus status;AudioComponentInstance audioUnit;
// 描述音頻元件
AudioComponentDescription desc;desc.componentType = kAudioUnitType_Output;desc.componentSubType = kAudioUnitSubType_RemoteIO;desc.componentFlags =
0
;desc.componentFlagsMask =
0
;desc.componentManufacturer = kAudioUnitManufacturer_Apple;
// 獲得一個元件
AudioComponent inputComponent = AudioComponentFindNext(
NULL
, &desc);
// 獲得 Audio Unit
status = AudioComponentInstanceNew(inputComponent, &audioUnit);checkStatus(status);
// 為錄制打開 IO
UInt32
flag =
1
;status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag,
sizeof
(flag));checkStatus(status);
// 為播放打開 IO
status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag,
sizeof
(flag));checkStatus(status);
// 描述格式
audioFormat.mSampleRate =
44100.00
;audioFormat.mFormatID = kAudioFormatLinearPCM;audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;audioFormat.mFramesPerPacket =
1
;audioFormat.mChannelsPerFrame =
1
;audioFormat.mBitsPerChannel =
16
;audioFormat.mBytesPerPacket =
2
;audioFormat.mBytesPerFrame =
2
;
// 設置格式
status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat,
sizeof
(audioFormat));checkStatus(status);status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat,
sizeof
(audioFormat));checkStatus(status);
// 設置數據采集回調函數
AURenderCallbackStruct callbackStruct;callbackStruct.inputProc = recordingCallback;callbackStruct.inputProcRefCon =
self
;status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct,
sizeof
(callbackStruct));checkStatus(status);
// 設置聲音輸出回調函數。當speaker需要數據時就會調用回調函數去獲取數據。它是 "拉" 數據的概念。
callbackStruct.inputProc = playbackCallback;callbackStruct.inputProcRefCon =
self
;status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct,
sizeof
(callbackStruct));checkStatus(status);
// 關閉為錄制分配的緩沖區(我們想使用我們自己分配的)
flag =
0
;status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ShouldAllocateBuffer, kAudioUnitScope_Output, kInputBus, &flag,
sizeof
(flag));
// 初始化
status = AudioUnitInitialize(audioUnit);checkStatus(status);
開啟 Audio Unit
OSStatus status = AudioOutputUnitStart(audioUnit);checkStatus(status);
關閉 Audio Unit
OSStatus status = AudioOutputUnitStop(audioUnit);checkStatus(status);
結束 Audio Unit
AudioComponentInstanceDispose(audioUnit);
錄制回調
static
OSStatus recordingCallback(
void
*inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const
AudioTimeStamp *inTimeStamp,
UInt32
inBusNumber,
UInt32
inNumberFrames, AudioBufferList *ioData) {
//
TODO:
// 使用 inNumberFrames 計算有多少數據是有效的// 在 AudioBufferList 里存放着更多的有效空間
AudioBufferList *bufferList;
//bufferList里存放着一堆 buffers, buffers的長度是動態的。 // 獲得錄制的采樣數據
OSStatus status; status = AudioUnitRender([audioInterface audioUnit], ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, bufferList); checkStatus(status);
// 現在,我們想要的采樣數據已經在bufferList中的buffers中了。
DoStuffWithTheRecordedAudio(bufferList);
return
noErr;}
播放回調
static
OSStatus playbackCallback(
void
*inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const
AudioTimeStamp *inTimeStamp,
UInt32
inBusNumber,
UInt32
inNumberFrames, AudioBufferList *ioData) {
// Notes: ioData 包括了一堆 buffers // 盡可能多的向ioData中填充數據,記得設置每個buffer的大小要與buffer匹配好。
return
noErr;}
結束
Audio Unit可以做很多非常棒的的工作。如
混音
,音頻特效,錄制等等。它處於 iOS 開發架構的底層,特別合適於音視頻直播這種場景中使用。
“知識無窮盡,只取我所需”。
ios音視頻演示app:
https://github.com/starrtc/ios-demo