iOS 實時音頻採集與播放Audio Unit使用

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)

使用流程概要

  1. 描述音頻元件(kAudioUnitType_Output/kAudioUnitSubType_RemoteIO /kAudioUnitManufacturerApple
  2. 使用 AudioComponentFindNext(NULL, &descriptionOfAudioComponent) 獲得 AudioComponent。AudioComponent有點像生產 Audio Unit 的工廠。
  3. 使用 AudioComponentInstanceNew(ourComponent, &audioUnit) 獲得 Audio Unit 實例。
  4. 使用 AudioUnitSetProperty函數為錄製和回放開啟IO。
  5. 使用 AudioStreamBasicDescription 結構體描述音頻格式,並使用AudioUnitSetProperty進行設置。
  6. 使用 AudioUnitSetProperty 設置音頻錄製與放播的回調函數。
  7. 分配緩衝區。
  8. 初始化 Audio Unit。
  9. 啟動 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-


推薦閱讀:

體積減半畫質翻倍,他用TensorFlow實現了這個圖像極度壓縮模型
[計算機視覺論文速遞] ECCV 2018專場1-2
教你如何製作自己的人臉識別系統
[計算機視覺論文速遞] 2018-06-22 目標檢測專場
100kfps多目標追蹤器-iou-tracker

TAG:科技 | 計算機視覺 |