iOS AudioUnit学习

作者: yitez | 来源:发表于2019-07-03 11:25 被阅读0次

    AudioUnit是iOS底层音频框架,相比于AudioQueue和AVAudioRecorder,能够对音频数据进行更多的控制,可以用来进行混音、均衡、格式转换、实时IO录制、回放、离线渲染等音频处理。

    1、AudioUnit录音:

    先看下苹果官方的原理图(此处以I/O单元为例):


    image.png

    1、一个AudioUnit包含2个element。
    2、每个element包含输入输入部分(scope)。
    3、硬件到element的部分(即图中淡蓝色部分)我们无法介入,我们能控的就element与我们APP关联的部分(即图中淡黄色部分)。
    4、实现录音就是主要关注element1到APP的过程,播放则是关注APP到element0的过程。

    代码实现:
    代码只实现录音功能,所以只使用了element1:

    设置音频格式:

    -  (void)initRecordFormat {
        _recordFormat.mSampleRate =  32000;  //采样率
        _recordFormat.mChannelsPerFrame = 1; //声道数量
        //编码格式
        _recordFormat.mFormatID = kAudioFormatLinearPCM;
        _recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
        //每采样点占用位数
        _recordFormat.mBitsPerChannel = 16;
        //每帧的字节数
        _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
        //每包的字节数
        _recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame;
        //每帧的字节数
        _recordFormat.mFramesPerPacket = 1;
    }
    

    接着实例化AudioUnit,配置相关属性和方法

    - (void)initConfig {
        //配置描述
        AudioComponentDescription acd;
        acd.componentType = kAudioUnitType_Output;
        acd.componentManufacturer = kAudioUnitManufacturer_Apple;
        //remoteIO对应的就是(I/O)Unit
        acd.componentSubType = kAudioUnitSubType_RemoteIO;
        acd.componentFlags = 0;
        acd.componentFlagsMask = 0;
        
        //AudioComponent类似组件工厂,用于实例化audioUnit
        AudioComponent comp = AudioComponentFindNext(nil, &acd);
        OSStatus status =  AudioComponentInstanceNew(comp, &_audioUnit);
        if(status!= kAudioSessionNoError) {
            NSLog(@"InstanceError");
            return;
        }
        
        //开启麦克风到Element1的inputScope部分,参数1代表element1。
        UInt32 flag=1;
        OSStatus statusPerpotyIO =  AudioUnitSetProperty(_audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag));  
        if(statusPerpotyIO!= kAudioSessionNoError) {
            NSLog(@"SetPropertyIOError");
            return;
        }
      
      
        //设置Element1到APP的outScope部分的流格式(即我们需要得到的音频数据格式)
        OSStatus statusPerpotyFR =  AudioUnitSetProperty(_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &_recordFormat, sizeof(_recordFormat));
        if(statusPerpotyFR!= kAudioSessionNoError) {
            NSLog(@"SetPropertyFRError");
            return;
        }
        
        //设置录制过程的回调函数
        AURenderCallbackStruct callBackSt;
        callBackSt.inputProc = inputCallBack;
        //将对象指针传入回调函数内部,方便内部使用
        callBackSt.inputProcRefCon = (__bridge void * _Nullable)(self);
        OSStatus statusPerpotyCall =  AudioUnitSetProperty(_audioUnit,
                                                            kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &callBackSt, sizeof(callBackSt));
        if(statusPerpotyCall!= kAudioSessionNoError) {
            NSLog(@"SetPropertyCallError");
            return;
        }
    }
    

    设置AudioSession模式,开启

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

    启动audioUnit,开始录音

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

    回调函数实现,获取音频数据:

    OSStatus   inputCallBack    (void *                            inRefCon,
                                  AudioUnitRenderActionFlags *      ioActionFlags,
                                  const AudioTimeStamp *            inTimeStamp,
                                  UInt32                            inBusNumber,
                                  UInt32                            inNumberFrames,
                                  AudioBufferList * __nullable      ioData)
    {
        
        YTAudioUnitManager *audioManager = [YTAudioUnitManager sharedManager];
        //创建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的数据渲染到bufferList
        OSStatus status =  AudioUnitRender(audioManager.audioUnit,
                        ioActionFlags,
                        inTimeStamp,
                        1,
                        inNumberFrames,
                        &bufferList);
        
        //这里是将数据写入文件
       // 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实现播放和录音功能,未免太大材小用,我们来看看混音是如何实现的。
    混音是把多种来源的声音,整合至一个立体音轨(Stereo)或单音音轨(Mono)中,此处是读取两段音频,由左右声道同时进行播放。

    官方也很贴心的给出了demo:Apple官方Demo地址

    原理:
    首先我们看下官方的使用原理图:

    image.png
    1、整体是AudioGraph,用来管理多个AudioUnit,此处涉及到是I/O单元和Mix单元。
    2、两个音频数据源,提供数据给Mix Unit,Mix Unit具有多个input通道,但是只有一个输出通道。
    3、Mix Unit将处理完的数据传输给I/O单元,I/O单元负责播放。

    具体实现:

    变量定义:

    //定义结构体用来保存音频数据的信息
    typedef struct {
        AudioStreamBasicDescription asbd;
        Float32 *data;
        UInt32 numFrames;
        UInt32 startFrameNum;
    } SoundBuffer;
    
    @interface YTAudioMixManager() {
        SoundBuffer mSoundBuffer[2];  //两个音频文件
    }
    @property (nonatomic,assign)AUGraph auGraph;
    //针对音频文件的格式
    @property (nonatomic,strong)AVAudioFormat *fileFormat;
    //针对unit的格式
    @property (nonatomic,strong)AVAudioFormat *unitFormat;
    @end
    

    初始化Format:

    -  (void)initRecordFormat {
         //Audiounit的描述 ,
         //声道为2,
        //interleaved为NO,使左右声道的数据分别存储在AudioBufferList的两个AudioBuffer中。
         _unitFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                        sampleRate:44100
                                                          channels:2
                                                       interleaved:NO];
        //文件音数据的描述 ,
        //声道为1,
        //interleaved为YES
        _fileFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
                                                       sampleRate:44100
                                                         channels:1
                                                      interleaved:YES];
    }
    
    

    读取两个音频文件数据:

    //读取音频文件数据
    - (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);
            //设置我们想要要获取的音频文件格式,ExtAudioFile是自带转码功能的。
            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;
            }
            //读取文件帧数 
            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;
            
            //将文件数据读取传入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数据复制给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);
        }
    }
    

    设置AudioUnit相关对象

    - (void)initAudioUnit {
        
        //IO单元描述
        AudioComponentDescription IOacd;
        IOacd.componentType = kAudioUnitType_Output;
        IOacd.componentManufacturer = kAudioUnitManufacturer_Apple;
        //remoteIO对应的就是(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);
        
        //建立两个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对应的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;
        }
        //设置输入数量
        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的两个element设置回调
            status = AUGraphSetNodeInputCallback(_auGraph, mixNode, i, &rcbs);
           if (status) { printf("AUGraphSetNodeInputCallback result %ld %08lX %4.4s\n", (long)status, (long)status, (char*)&status); return; }
            //设置输入格式
            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;
        }
    }
    

    实现回调的方法,填充数据供输出:

    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;  // 总的帧数
        Float32 *bufferData = sndbuf[inBusNumber].data; // audio data buffer
        
        Float32 *outA = (Float32 *)ioData->mBuffers[0].mData; //  第一声道数据
        Float32 *outB = (Float32 *)ioData->mBuffers[1].mData; //  第二声道数据
        
    
        
        for (UInt32 i = 0; i < inNumberFrames; ++i) {
            if (inBusNumber == 0) {
                outA[i] = bufferData[startFrame++];   //填充第一声道数据(用的是第一个SoundBuffer)
            } else {
                outB[i] = bufferData[startFrame++];   //填充第二声道数据(用的是第二个SoundBuffer)
            }
        
            if (startFrame > numFrames) {
                // 结束了,再从0开始循环
                printf("looping data for bus %d after %ld source frames rendered\n", (unsigned int)inBusNumber, (long)startFrame-1);
                startFrame = 0;
            }
        }
        sndbuf[inBusNumber].startFrameNum = startFrame; // 记录帧数
        return noErr;
    }
    

    总结:AudioUnit更像是个看图编程的过程ㄟ( ▔, ▔ )ㄏ。

    相关文章

      网友评论

        本文标题:iOS AudioUnit学习

        本文链接:https://www.haomeiwen.com/subject/auzfhctx.html