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 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM