1、前言
在iOS中有很多方法可以進行音視訊采集。如 AVCaptureDevice, AudioQueue以及Audio Unit。其中 Audio Unit是最底層的接口,它的優點是功能強大,延遲低; 而缺點是學習成本高,難度大。對于一般的iOS應用程式,AVCaptureDevice和AudioQueue完全夠用了。但對于音視訊直播,最好還是使用 Audio Unit 進行處理,這樣可以達到最佳的效果,著名的 WebRTC 就使用的 Audio Unit 做的音頻采集與播放。今天我們就重點介紹一下Audio Unit的基本知識和使用。
下圖是 Audio Unit在 iOS架構中所處的位置:
2、基本概念
在介紹 Audio Unit 如何使用之前,先要介紹一下Audio Unit的基本概念,這樣更有利于我們了解對它的使用。
- Audio Unit的種類
- Audio Units共可分為四大類,並可細分為七種,可參考下表:
- 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使用範例。
- 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)
3、使用流程概要
- 描述音頻元件
- 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。
C++音視訊學習資料免費擷取方法:關注音視訊開發T哥,點選「連結」即可免費擷取2023年最新C++音視訊開發進階獨家免費學習大禮包!
4、初始化
初始化看起來像下面這樣。我們有一個 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);
5、錄制回調
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;
}
6、播放回調
static OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
// Notes: ioData 包括了一堆 buffers
// 盡可能多的向ioData中填充資料,記得設定每個buffer的大小要與buffer比對好。
return noErr;
}
7、結束
Audio Unit可以做很多非常棒的的工作。如混音,音頻特效,錄制等等。它處于 iOS 開發架構的底層,特别合适于音視訊直播這種場景中使用。
我們今天介紹的隻是 Audio Unit衆多功能中的一小點知識,但這一點點知識對于我來說已經夠用了。對于那些想了解更多Audio Unit的人,隻好自行去google了。
“知識無窮盡,隻取我所需”。這就是我的思想,哈!
原文連結:iOS 實時音頻采集與播放