[Ray's音視頻01]Audio Units錄制音頻

iOS錄音

  1. 根據(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í)很多東西在官方文檔里面都能找到答案。

image.png
  1. 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 硬編碼音頻不能播放和錄制

官網(wǎng)地址

  1. 設(shè)置buffer的duration,這個(gè)值決定音頻的延遲。越小的話延遲越低
[auSession setPreferredIOBufferDuration:0.05 error:&error];
  1. 既然要用到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'
};

image.png

其實(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);
    }
  1. 設(shè)置AudioStreamBasicDescription(下文簡(jiǎn)稱ASBD)
image.png
  • 先設(shè)置圖中紅色部分的聲音要素
  • kAudioFormatFlagIsNonInterleaved 這個(gè)參數(shù)其實(shí)是指定pcm中的左右聲道排布,是lrlrlr形式還是說llllrrrr這樣
  • mBytesPerFrame 一般可以通過bits depth * channels / 8計(jì)算的到
  1. 關(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));    
image.png
  1. 具體輸入有了,那么要設(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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容