iOS AudioUnit學(xué)習(xí)

AudioUnit是iOS底層音頻框架,相比于AudioQueue和AVAudioRecorder,能夠?qū)σ纛l數(shù)據(jù)進(jìn)行更多的控制,可以用來進(jìn)行混音、均衡、格式轉(zhuǎn)換、實(shí)時(shí)IO錄制、回放、離線渲染等音頻處理。

1、AudioUnit錄音:

先看下蘋果官方的原理圖(此處以I/O單元為例):


image.png

1、一個(gè)AudioUnit包含2個(gè)element。
2、每個(gè)element包含輸入輸入部分(scope)。
3、硬件到element的部分(即圖中淡藍(lán)色部分)我們無法介入,我們能控的就element與我們APP關(guān)聯(lián)的部分(即圖中淡黃色部分)。
4、實(shí)現(xiàn)錄音就是主要關(guān)注element1到APP的過程,播放則是關(guān)注APP到element0的過程。

代碼實(shí)現(xiàn):
代碼只實(shí)現(xiàn)錄音功能,所以只使用了element1:

設(shè)置音頻格式:

-  (void)initRecordFormat {
    _recordFormat.mSampleRate =  32000;  //采樣率
    _recordFormat.mChannelsPerFrame = 1; //聲道數(shù)量
    //編碼格式
    _recordFormat.mFormatID = kAudioFormatLinearPCM;
    _recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
    //每采樣點(diǎn)占用位數(shù)
    _recordFormat.mBitsPerChannel = 16;
    //每幀的字節(jié)數(shù)
    _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
    //每包的字節(jié)數(shù)
    _recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame;
    //每幀的字節(jié)數(shù)
    _recordFormat.mFramesPerPacket = 1;
}

接著實(shí)例化AudioUnit,配置相關(guān)屬性和方法

- (void)initConfig {
    //配置描述
    AudioComponentDescription acd;
    acd.componentType = kAudioUnitType_Output;
    acd.componentManufacturer = kAudioUnitManufacturer_Apple;
    //remoteIO對(duì)應(yīng)的就是(I/O)Unit
    acd.componentSubType = kAudioUnitSubType_RemoteIO;
    acd.componentFlags = 0;
    acd.componentFlagsMask = 0;
    
    //AudioComponent類似組件工廠,用于實(shí)例化audioUnit
    AudioComponent comp = AudioComponentFindNext(nil, &acd);
    OSStatus status =  AudioComponentInstanceNew(comp, &_audioUnit);
    if(status!= kAudioSessionNoError) {
        NSLog(@"InstanceError");
        return;
    }
    
    //開啟麥克風(fēng)到Element1的inputScope部分,參數(shù)1代表element1。
    UInt32 flag=1;
    OSStatus statusPerpotyIO =  AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag));  
    if(statusPerpotyIO!= kAudioSessionNoError) {
        NSLog(@"SetPropertyIOError");
        return;
    }
  
  
    //設(shè)置Element1到APP的outScope部分的流格式(即我們需要得到的音頻數(shù)據(jù)格式)
    OSStatus statusPerpotyFR =  AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &_recordFormat, sizeof(_recordFormat));
    if(statusPerpotyFR!= kAudioSessionNoError) {
        NSLog(@"SetPropertyFRError");
        return;
    }
    
    //設(shè)置錄制過程的回調(diào)函數(shù)
    AURenderCallbackStruct callBackSt;
    callBackSt.inputProc = inputCallBack;
    //將對(duì)象指針傳入回調(diào)函數(shù)內(nèi)部,方便內(nèi)部使用
    callBackSt.inputProcRefCon = (__bridge void * _Nullable)(self);
    OSStatus statusPerpotyCall =  AudioUnitSetProperty(_audioUnit,
                                                        kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &callBackSt, sizeof(callBackSt));
    if(statusPerpotyCall!= kAudioSessionNoError) {
        NSLog(@"SetPropertyCallError");
        return;
    }
}

設(shè)置AudioSession模式,開啟

- (void)setAudioSessionEnable:(BOOL)YesOrNo {
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    [[AVAudioSession sharedInstance] setActive:YesOrNo error:nil];
}

啟動(dòng)audioUnit,開始錄音

- (void)startRecord {
    OSStatus statusInit = AudioUnitInitialize(_audioUnit);
    if(statusInit!= kAudioSessionNoError) {
        NSLog(@"statusInitError");
        return;
    }
    
    AudioOutputUnitStart(_audioUnit);
    self.isRecording = YES;
}

回調(diào)函數(shù)實(shí)現(xiàn),獲取音頻數(shù)據(jù):

OSStatus   inputCallBack    (void *                            inRefCon,
                              AudioUnitRenderActionFlags *      ioActionFlags,
                              const AudioTimeStamp *            inTimeStamp,
                              UInt32                            inBusNumber,
                              UInt32                            inNumberFrames,
                              AudioBufferList * __nullable      ioData)
{
    
    YTAudioUnitManager *audioManager = [YTAudioUnitManager sharedManager];
    //創(chuàng)建bufferlist
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mDataByteSize = sizeof(SInt16)*inNumberFrames;
    bufferList.mBuffers[0].mNumberChannels = 1;
    bufferList.mBuffers[0].mData = (SInt16*) malloc(sizeof(SInt16)*inNumberFrames);
    
    //將unit的數(shù)據(jù)渲染到bufferList
    OSStatus status =  AudioUnitRender(audioManager.audioUnit,
                    ioActionFlags,
                    inTimeStamp,
                    1,
                    inNumberFrames,
                    &bufferList);
    
    //這里是將數(shù)據(jù)寫入文件
   // ExtAudioFileWrite(audioManager.fileInfo->extAudioFileRef, inNumberFrames, &bufferList);
    
    return status;
}

停止錄音:

- (void)stopRecord {
    [self setAudioSessionEnable:NO];
    AudioOutputUnitStop(_audioUnit);
    AudioUnitUninitialize(_audioUnit);
    ExtAudioFileDispose(_fileInfo->extAudioFileRef);
    self.isRecording = NO;
}

2、AudioUnit混音:

如果只是使用AudioUnit實(shí)現(xiàn)播放和錄音功能,未免太大材小用,我們來看看混音是如何實(shí)現(xiàn)的。
混音是把多種來源的聲音,整合至一個(gè)立體音軌(Stereo)或單音音軌(Mono)中,此處是讀取兩段音頻,由左右聲道同時(shí)進(jìn)行播放。

官方也很貼心的給出了demo:Apple官方Demo地址。

原理:
首先我們看下官方的使用原理圖:

image.png

1、整體是AudioGraph,用來管理多個(gè)AudioUnit,此處涉及到是I/O單元和Mix單元。
2、兩個(gè)音頻數(shù)據(jù)源,提供數(shù)據(jù)給Mix Unit,Mix Unit具有多個(gè)input通道,但是只有一個(gè)輸出通道。
3、Mix Unit將處理完的數(shù)據(jù)傳輸給I/O單元,I/O單元負(fù)責(zé)播放。

具體實(shí)現(xiàn):

變量定義:

//定義結(jié)構(gòu)體用來保存音頻數(shù)據(jù)的信息
typedef struct {
    AudioStreamBasicDescription asbd;
    Float32 *data;
    UInt32 numFrames;
    UInt32 startFrameNum;
} SoundBuffer;

@interface YTAudioMixManager() {
    SoundBuffer mSoundBuffer[2];  //兩個(gè)音頻文件
}
@property (nonatomic,assign)AUGraph auGraph;
//針對(duì)音頻文件的格式
@property (nonatomic,strong)AVAudioFormat *fileFormat;
//針對(duì)unit的格式
@property (nonatomic,strong)AVAudioFormat *unitFormat;
@end

初始化Format:

-  (void)initRecordFormat {
     //Audiounit的描述 ,
     //聲道為2,
    //interleaved為NO,使左右聲道的數(shù)據(jù)分別存儲(chǔ)在AudioBufferList的兩個(gè)AudioBuffer中。
     _unitFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                    sampleRate:44100
                                                      channels:2
                                                   interleaved:NO];
    //文件音數(shù)據(jù)的描述 ,
    //聲道為1,
    //interleaved為YES
    _fileFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                   sampleRate:44100
                                                     channels:1
                                                  interleaved:YES];
}

讀取兩個(gè)音頻文件數(shù)據(jù):

//讀取音頻文件數(shù)據(jù)
- (void)loadDataFromURLS:(NSArray *)urlNames {
    for (int i =0;i<urlNames.count;i++) {
        
        NSString *urlName = urlNames[i];
        CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)urlName, NULL);
        ExtAudioFileRef audioFileRef;
        ExtAudioFileOpenURL(url, &audioFileRef);
        
        
        OSStatus status;
        UInt32 propSize = sizeof(AudioStreamBasicDescription);
        //設(shè)置我們想要要獲取的音頻文件格式,ExtAudioFile是自帶轉(zhuǎn)碼功能的。
        status = ExtAudioFileSetProperty(audioFileRef, kExtAudioFileProperty_ClientDataFormat, propSize, _fileFormat.streamDescription);
        if(status!=kAudioSessionNoError) {
            NSLog(@"FileGetProperty Error");
            return;
        }
        
        AudioStreamBasicDescription fileFormat;
        UInt32 formSize = sizeof(fileFormat);
     //讀取文件格式屬性  
        status =  ExtAudioFileGetProperty(audioFileRef, kAudioFileStreamProperty_FileFormat, &formSize, &fileFormat);
        if(status!=kAudioSessionNoError) {
            NSLog(@"FileGetProperty Error");
            return;
        }
        //讀取文件幀數(shù) 
        UInt64 numFrames;
        UInt32 numFramesSize = sizeof(numFrames);
        status = ExtAudioFileGetProperty(audioFileRef, kExtAudioFileProperty_FileLengthFrames, &numFramesSize, &numFrames);
        if(status!=kAudioSessionNoError) {
            NSLog(@"FileGetProperty Error");
            return;
        }
        
        mSoundBuffer[i].numFrames = (UInt32)numFrames;
        mSoundBuffer[i].asbd = fileFormat;
        
        UInt64 samples = numFrames * mSoundBuffer[i].asbd.mChannelsPerFrame;
        mSoundBuffer[i].data = (Float32 *)calloc(samples, sizeof(Float32));
        mSoundBuffer[i].sampleNum = 0;
        
        //將文件數(shù)據(jù)讀取傳入BufferList
        AudioBufferList bufList;
        bufList.mNumberBuffers = 1;
        bufList.mBuffers[0].mNumberChannels = 1;
        bufList.mBuffers[0].mData = (Float32*)malloc(sizeof(Float32)*samples);
        bufList.mBuffers[0].mDataByteSize = (Float32)samples * sizeof(Float32);
        UInt32 numPackets = (UInt32)numFrames;
        status = ExtAudioFileRead(audioFileRef, &numPackets, &bufList);
        
        //將bufferList數(shù)據(jù)復(fù)制給mSoundBuffer
        memcpy(mSoundBuffer[i].data, bufList.mBuffers[0].mData , bufList.mBuffers[0].mDataByteSize);
        
        if(status!=kAudioSessionNoError) {
//            printf("ExtAudioFileRead Error");
            free(mSoundBuffer[i].data);
            mSoundBuffer[i].data = 0;
        }
        ExtAudioFileDispose(audioFileRef);
    }
}

設(shè)置AudioUnit相關(guān)對(duì)象

- (void)initAudioUnit {
    
    //IO單元描述
    AudioComponentDescription IOacd;
    IOacd.componentType = kAudioUnitType_Output;
    IOacd.componentManufacturer = kAudioUnitManufacturer_Apple;
    //remoteIO對(duì)應(yīng)的就是(I/O)Unit
    IOacd.componentSubType = kAudioUnitSubType_RemoteIO;
    IOacd.componentFlags = 0;
    IOacd.componentFlagsMask = 0;
    
    //mix單元描述
    AudioComponentDescription mixacd;
    mixacd.componentType = kAudioUnitType_Mixer;
    mixacd.componentManufacturer = kAudioUnitManufacturer_Apple;
    mixacd.componentSubType = kAudioUnitSubType_MultiChannelMixer;
    mixacd.componentFlags = 0;
    mixacd.componentFlagsMask = 0;
    
    OSStatus status;
    status=  NewAUGraph (&_auGraph);
    if(status!=kAudioSessionNoError) {
        NSLog(@"NewAUGraph Error");
         return;
    }
    
    
    AUNode ioNode;
    AUNode mixNode;
    
    AudioUnit ioUnit;
    AudioUnit mixUnit;
    
    //添加node
    AUGraphAddNode (_auGraph,&IOacd,&ioNode);
    AUGraphAddNode (_auGraph,&mixacd, &mixNode);
    
    //建立兩個(gè)node的輸入和輸出連接
    status = AUGraphConnectNodeInput(_auGraph, mixNode, 0, ioNode, 0);
    if (status!=kAudioSessionNoError) {
        printf("AUGraphConnect Error");
        return;
    }

    AUGraphOpen (_auGraph);
    if(status!=kAudioSessionNoError) {
        NSLog(@"AUGraphOpen Error");
         return;
    }
    
    //獲取node對(duì)應(yīng)的Aunit
    status = AUGraphNodeInfo(_auGraph, mixNode, NULL, &mixUnit);
    if(status!=kAudioSessionNoError) {
        NSLog(@"AUGraphNodeMixInfo Error");
        return;
    }
    status = AUGraphNodeInfo(_auGraph, ioNode, NULL, &ioUnit);
    if(status!=kAudioSessionNoError) {
        NSLog(@"AUGraphOpen Error");
        return;
    }
    //設(shè)置輸入數(shù)量
    int elementCount = 2;
    status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &elementCount, sizeof(elementCount));
    if(status!=kAudioSessionNoError) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
    

    
    for (int i = 0; i < elementCount;i++) {
        // setup render callback struct
        AURenderCallbackStruct rcbs;
        rcbs.inputProc = &mixInputCallBack;
        rcbs.inputProcRefCon = mSoundBuffer;
        
        //給mix的兩個(gè)element設(shè)置回調(diào)
        status = AUGraphSetNodeInputCallback(_auGraph, mixNode, i, &rcbs);
       if (status) { printf("AUGraphSetNodeInputCallback result %ld %08lX %4.4s\n", (long)status, (long)status, (char*)&status); return; }
        //設(shè)置輸入格式
        status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, i,_unitFormat.streamDescription , sizeof(AudioStreamBasicDescription));
        
        if(status!=kAudioSessionNoError) {
            NSLog(@"AudioUnitSetProperty Error");
            return;
        }
    }
    
    
    status = AudioUnitSetProperty(mixUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, _unitFormat.streamDescription, sizeof(AudioStreamBasicDescription));
    if (status) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
    
    status = AudioUnitSetProperty(ioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, _unitFormat.streamDescription, sizeof(AudioStreamBasicDescription));
    if (status) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
    
    
    status = AUGraphInitialize(_auGraph);
    if (status) {
        NSLog(@"AudioUnitSetProperty Error");
        return;
    }
}

實(shí)現(xiàn)回調(diào)的方法,填充數(shù)據(jù)供輸出:

OSStatus mixInputCallBack (void *                            inRefCon,
                           AudioUnitRenderActionFlags *      ioActionFlags,
                           const AudioTimeStamp *            inTimeStamp,
                           UInt32                            inBusNumber,
                           UInt32                            inNumberFrames,
                           AudioBufferList * __nullable      ioData) {
    
    SoundBuffer *sndbuf = (SoundBuffer *)inRefCon;
    
    UInt32 startFrame = sndbuf[inBusNumber].startFrameNum;      // 從哪一幀開始
    UInt32 numFrames = sndbuf[inBusNumber].numFrames;  // 總的幀數(shù)
    Float32 *bufferData = sndbuf[inBusNumber].data; // audio data buffer
    
    Float32 *outA = (Float32 *)ioData->mBuffers[0].mData; //  第一聲道數(shù)據(jù)
    Float32 *outB = (Float32 *)ioData->mBuffers[1].mData; //  第二聲道數(shù)據(jù)
    

    
    for (UInt32 i = 0; i < inNumberFrames; ++i) {
        if (inBusNumber == 0) {
            outA[i] = bufferData[startFrame++];   //填充第一聲道數(shù)據(jù)(用的是第一個(gè)SoundBuffer)
        } else {
            outB[i] = bufferData[startFrame++];   //填充第二聲道數(shù)據(jù)(用的是第二個(gè)SoundBuffer)
        }
    
        if (startFrame > numFrames) {
            // 結(jié)束了,再從0開始循環(huán)
            printf("looping data for bus %d after %ld source frames rendered\n", (unsigned int)inBusNumber, (long)startFrame-1);
            startFrame = 0;
        }
    }
    sndbuf[inBusNumber].startFrameNum = startFrame; // 記錄幀數(shù)
    return noErr;
}

總結(jié):AudioUnit更像是個(gè)看圖編程的過程ㄟ( ▔, ▔ )ㄏ。

最后編輯于
?著作權(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)容