前言
在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 Unitstatus = AudioComponentInstanceNew(inputComponent, &audioUnit);checkStatus(status); // 為錄製開啟 IOUInt32 flag = 1;status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag));checkStatus(status); // 為播放開啟 IOstatus = 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:github.com/starrtc/ios…