iOS錄音
-
根據(jù)開發(fā)文檔的圖可知,iOS音頻相關(guān)的用的比較多的自頂向下的又 AVFoundation -> AudioToolBox -> Audio Unit
技術(shù)選擇上如果是錄音然后獲取內(nèi)存中音頻的數(shù)據(jù),然后進(jìn)行網(wǎng)絡(luò)傳輸或者存本地,那么AudioQueue或者AudioUnit都能做到。因?yàn)橹罂赡軙?huì)用到一些混響之類的,所以以下說明都是根據(jù)AudioUnit來講述的。
文章會(huì)根據(jù)代碼來引出每個(gè)參數(shù)涉及到的知識(shí)點(diǎn),然后進(jìn)行說明,個(gè)人比較喜歡參考官方文檔,其實(shí)很多東西在官方文檔里面都能找到答案。

-
iOS上提供了多重audio units,主要的功能大概可以分為,effect mixer io 和 format converter ,這次我們用到的是io模塊文檔地址
在iOS當(dāng)中,涉及到硬件的一般都會(huì)初始化一個(gè)session,然后軟件方面的一般都是一個(gè)description
錄音的時(shí)候我們用到audio unit,第一部是先初始化一個(gè)session
AVAudioSession *auSession = [AVAudioSession sharedInstance];
[auSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
[auSession setActive:YES error:nil];
audio unit 提供了 6個(gè)常用的session類別對(duì)應(yīng)著不同的功能
| AVAudioSessionCategory | 描述 |
|---|---|
| AVAudioSessionCategoryAmbient | 常用于播放背景音樂 |
| AVAudioSessionCategorySoloAmbient | 這個(gè)也是用于播放背景音樂但是會(huì)打斷別的音樂的播放 |
| AVAudioSessionCategoryPlayback | 用于播放音樂 |
| AVAudioSessionCategoryRecord | 提供同時(shí)錄音的功能 |
| AVAudioSessionCategoryPlayAndRecord | 可以同時(shí)錄音和播放,適合于通話等場(chǎng)合 |
| AVAudioSessionCategoryAudioProcessing | 硬編碼音頻不能播放和錄制 |
- 設(shè)置buffer的duration,這個(gè)值決定音頻的延遲。越小的話延遲越低
[auSession setPreferredIOBufferDuration:0.05 error:&error];
- 既然要用到audio unit 那么肯定要設(shè)置一些參數(shù) AudioComponentDescription
CF_ENUM(UInt32) {
kAudioUnitType_Output = 'auou',
kAudioUnitType_MusicDevice = 'aumu',
kAudioUnitType_MusicEffect = 'aumf',
kAudioUnitType_FormatConverter = 'aufc',
kAudioUnitType_Effect = 'aufx',
kAudioUnitType_Mixer = 'aumx',
kAudioUnitType_Panner = 'aupn',
kAudioUnitType_Generator = 'augn',
kAudioUnitType_OfflineEffect = 'auol',
kAudioUnitType_MIDIProcessor = 'aumi'
};

其實(shí)官方文檔已經(jīng)寫得很清楚不同的unit對(duì)應(yīng)的type和sub type 選取要用的設(shè)置。audio units guides
- componentManufacturer 廠家的這個(gè)參數(shù)一般固定位apple
- componentFlags和componentFlagsMask如果沒有具體要設(shè)置的值得話就填寫0,其他事根據(jù)具體用到的component來設(shè)置
- AudioComponentFindNext 獲取系統(tǒng)中的componet
- AudioComponentInstanceNew 初始化 返回值是 OSStatus類型,具體代表什么可以通過OSStatus 查詢
AudioComponentDescription inputDesc;
inputDesc.componentType = kAudioUnitType_Output;
inputDesc.componentSubType = kAudioUnitSubType_RemoteIO;
inputDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
inputDesc.componentFlags = 0;
inputDesc.componentFlagsMask = 0;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &inputDesc);
status = AudioComponentInstanceNew(inputComponent, &audioUnit);
if (status != noErr) {
NSLog(@"AudioComponentInstanceNew: %d", status);
}
- 設(shè)置AudioStreamBasicDescription(下文簡(jiǎn)稱ASBD)

- 先設(shè)置圖中紅色部分的聲音要素
- kAudioFormatFlagIsNonInterleaved 這個(gè)參數(shù)其實(shí)是指定pcm中的左右聲道排布,是lrlrlr形式還是說llllrrrr這樣
- mBytesPerFrame 一般可以通過bits depth * channels / 8計(jì)算的到
-
關(guān)于audio bus的概念
image.png
- 這個(gè)是IO unit的模型,具體可以看出有兩個(gè)element,一般稱為bus,然后 1 是負(fù)責(zé)輸入的,0 是負(fù)責(zé)輸出的。輸入的io一般是關(guān)閉狀態(tài)所以要打開
- 每個(gè)bus都一個(gè)input scope和output scope 這些在設(shè)置參數(shù)的時(shí)候會(huì)用到
- 這次錄音之后都是存本本地,所以不會(huì)用到 bus0
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &inputFlag, sizeof(inputFlag));
AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat,kAudioUnitScope_Output , 1, &inputASBD, sizeof(inputASBD));

- 具體輸入有了,那么要設(shè)置輸出根據(jù)上面的思路就是用到element 1 的 output scope
AURenderCallbackStruct recordCallBackStruct;
recordCallBackStruct.inputProc = RecordProc;
recordCallBackStruct.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, 1, &recordCallBackStruct, sizeof(recordCallBackStruct));
inputProc 是AURenderCallback類型的函數(shù),錄音的時(shí)候回在iodata里面拿到具體的數(shù)據(jù),然后調(diào)用AudioUnitRender來進(jìn)行渲染保存到自己的buffers里面
{
AudioBufferList *buffers;
}
//這里我先設(shè)置為1
buffers = (AudioBufferList *)malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer));
buffers->mNumberBuffers = 1;
buffers->mBuffers[0].mNumberChannels = 1;
buffers->mBuffers[0].mDataByteSize = RH_BFFER_SIZE;
buffers->mBuffers[0].mData = malloc(RH_BFFER_SIZE);
static OSStatus RecordProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
RHRecorder *objSelf = (__bridge RHRecorder *)inRefCon;
objSelf->buffers->mNumberBuffers = 1;
OSStatus status = noErr;
status = AudioUnitRender(objSelf->audioUnit,
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberFrames,
objSelf->buffers);
if (status != noErr) {
NSLog(@"render error :%d", status);
}
//save pcm steam
[objSelf saveRecordDataToLocalPCM:objSelf->buffers->mBuffers[0].mData
size:objSelf->buffers->mBuffers[0].mDataByteSize];
return 0;
}
