iOS 實時音頻採集與播放Audio Unit使用
前言
在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:https://github.com/starrtc/ios-demo
推薦閱讀:
※體積減半畫質翻倍,他用TensorFlow實現了這個圖像極度壓縮模型
※[計算機視覺論文速遞] ECCV 2018專場1-2
※教你如何製作自己的人臉識別系統
※[計算機視覺論文速遞] 2018-06-22 目標檢測專場
※100kfps多目標追蹤器-iou-tracker