美文网首页iOS DeveloperiOS 你不知道的新鲜事iOS 开发成长中心
iOS 使用AUGraph录音同时播放(并转码成Mp3)

iOS 使用AUGraph录音同时播放(并转码成Mp3)

作者: Salad可乐 | 来源:发表于2016-11-21 18:26 被阅读3218次

    如果只需要完成简单的录音功能,苹果有更高级、方便的接口供开发者使用:AVAudioRecorder,面向对象,不关心细节实现。但是如果开发者想要拿到实时数据并对其进行处理,就要用到Audio Unit Services和Audio Processing Graph Services。下面我会介绍如何使用它们来完成一个最简单的录音DEMO。

    AudioSession

    首先,我们需要了解下AudioSession这个类。先看下苹果对它的介绍:

    iOS handles audio behavior at the app, inter-app, and device levels through audio sessions

    iOS通过AudioSession来控制APP中的音频表现,跨应用和硬件设备。按我的理解,它就是用来设定最基础的音频配置的,比如:
    1、当耳机被拔出,是否停止音频的播放?
    2、本APP的音频播放是否和其他APP的音频实现混音?还是让其他APP的音频暂停?
    3、是否允许APP获取麦克风数据?

    在本文中,我们需要用到麦克风录音并且播放,调用以下代码,APP就会弹出窗口询问是否允许APP访问麦克风:

    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
    

    Audio Processing Graph

    先用一张表格(来自苹果文档)介绍下Audio Unit的类型:

    Purpose Audio units
    Effect iPod Equalizer
    Mixing 3D Mixer
    Multichannel Mixer
    I/O Remote I/O
    Voice-Processing I/O
    Generic Output
    Format conversion Format Converter

    7种类型,4个作用:均衡器、混合、输入/输出和格式转换。
    在本DEMO中只使用了Remote I/O完成简单的录音和播放功能。
    而Audio Unit并不能独立完成工作,需要配合AUGraph来使用。AUGraph是管理者,不同的Unit作为Node添加到AUGraph中去发挥作用,如下图AUGraph管理着Mixer Unit和Remote I/O Unit:


    AudioProcessingGraphBeforeEQ_2x.png

    声明一个Remote I/O类型的Node,并添加到AUGraph中:

    AUNode remoteIONode;
    AudioComponentDescription componentDesc; //关于Node的描述
    componentDesc.componentType = kAudioUnitType_Output;
    componentDesc.componentSubType = kAudioUnitSubType_RemoteIO;
    componentDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
    componentDesc.componentFlags = 0;
    componentDesc.componentFlagsMask = 0;
    
    CheckError(NewAUGraph(&auGraph),"couldn't NewAUGraph"); //创建AUGraph
    CheckError(AUGraphOpen(auGraph),"couldn't AUGraphOpen"); //打开AUGraph
    
    CheckError(AUGraphAddNode(auGraph,&componentDesc,&remoteIONode),"couldn't add remote io node");
    CheckError(AUGraphNodeInfo(auGraph,remoteIONode,NULL,&remoteIOUnit),"couldn't get remote io unit from node");
    

    Remote I/O Unit

    Remote I/O Unit 属于Audio Unit其中之一,是一个与硬件设备相关的Unit,它分为输入端和输出端,如扬声器、麦克风和耳机等。在这里我们需要在录音的同时播放,所以我们要让输入端和输出端的Unit相连,如下图所示:


    IO_unit_2x.png

    其中Element0代表着输出端,Element1代表输入端;而每个Element又分为Input scope和Output scope。我们具体要做的就是将Element0的Output scope和喇叭接上,Element1的Intput和麦克风接上。代码如下:

     UInt32 oneFlag = 1; 
     CheckError(AudioUnitSetProperty(remoteIOUnit,
                                        kAudioOutputUnitProperty_EnableIO,
                                        kAudioUnitScope_Output,
                                        kOutputBus,
                                        &oneFlag,
                                        sizeof(oneFlag)),"couldn't kAudioOutputUnitProperty_EnableIO with kAudioUnitScope_Output");
        
     CheckError(AudioUnitSetProperty(remoteIOUnit,
                                        kAudioOutputUnitProperty_EnableIO,
                                        kAudioUnitScope_Input,
                                        kInputBus,
                                        &oneFlag,
                                        sizeof(oneFlag)),"couldn't kAudioOutputUnitProperty_EnableIO with kAudioUnitScope_Input");
    

    然后设置一下输入输出的音频格式:

    AudioStreamBasicDescription mAudioFormat;
    mAudioFormat.mSampleRate         = 44100.0;//采样率
    mAudioFormat.mFormatID           = kAudioFormatLinearPCM;//PCM采样
    mAudioFormat.mFormatFlags        = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    mAudioFormat.mFramesPerPacket    = 1;//每个数据包多少帧 mAudioFormat.mChannelsPerFrame   = 1;//1单声道,2立体声
    mAudioFormat.mBitsPerChannel     = 16;//语音每采样点占用位数
    mAudioFormat.mBytesPerFrame      = mAudioFormat.mBitsPerChannel*mAudioFormat.mChannelsPerFrame/8;//每帧的bytes数
    mAudioFormat.mBytesPerPacket     = mAudioFormat.mBytesPerFrame*mAudioFormat.mFramesPerPacket;//每个数据包的bytes总数,每帧的bytes数*每个数据包的帧数
    mAudioFormat.mReserved           = 0;
    UInt32 size = sizeof(mAudioFormat);
        
    CheckError(AudioUnitSetProperty(remoteIOUnit,
                                        kAudioUnitProperty_StreamFormat,
                                        kAudioUnitScope_Output,
                                        1,
                                        &mAudioFormat,
                                        size),"couldn't set kAudioUnitProperty_StreamFormat with kAudioUnitScope_Output");
        
    CheckError(AudioUnitSetProperty(remoteIOUnit,
                                        kAudioUnitProperty_StreamFormat,
                                        kAudioUnitScope_Input,
                                        0,
                                        &mAudioFormat,
                                        size),"couldn't set kAudioUnitProperty_StreamFormat with kAudioUnitScope_Input");
    

    至此我们就差不多完成了,只差最后一个步骤:设置CallBack,每次音频从麦克风进来转化为数字信号时就会调用此CallBack函数,将数字信号作你所想要的处理,完了再送到输出端去进行播放。回调函数是一个C语言的静态方法,代码如下:

    static OSStatus CallBack(
                                void      *inRefCon,
                                AudioUnitRenderActionFlags  *ioActionFlags,
                                const AudioTimeStamp   *inTimeStamp,
                                UInt32       inBusNumber,
                                UInt32       inNumberFrames,
                                AudioBufferList    *ioData)
    {
        RecordTool *THIS=(__bridge RecordTool*)inRefCon;
        
        OSStatus renderErr = AudioUnitRender(THIS->remoteIOUnit, ioActionFlag,  inTimeStamp, 1, inNumberFrames, ioData);
    
    //--------------------------------------------//
    //              在这里处理音频数据               //
    //--------------------------------------------//
        
    //转Mp3,后面有时间再详细介绍下    
    //     [THIS->cover convertPcmToMp3:ioData->mBuffers[0] toPath:THIS->outPath];
    
        
        return renderErr;
    }
    
    ioData->mBuffers[n] //n=0~1,单声道n=0,双声道n=1
    ioData->mBuffers[0].mData //PCM数据
    ioData->mBuffers[0].mDataByteSize //PCM数据的长度
    

    定义好CallBack函数后,将其与AUGraph关联起来:

    AURenderCallbackStruct inputProc;
    inputProc.inputProc = CallBack;
    inputProc.inputProcRefCon = (__bridge void *)(self);
    CheckError(AUGraphSetNodeInputCallback(auGraph, remoteIONode, 0, &inputProc),"Error setting io output callback");
     
    CheckError(AUGraphInitialize(auGraph),"couldn't AUGraphInitialize" );
    CheckError(AUGraphUpdate(auGraph, NULL),"couldn't AUGraphUpdate" );
    
    //最后再调用以下代码即可开始录音
    CheckError(AUGraphStart(auGraph),"couldn't AUGraphStart");
    CAShow(auGraph);
    

    最后

    因为时间的关系,未能细说PCM转MP3(使用LAME)的内容,我已经把DEMO上传到GitHub,有需要的朋友可以下载来看看,往后有时间了我再补全关于转码MP3的内容。
    如果觉得我的DEMO对您有帮助,请Star,非常感谢!

    更新

    使用LAME转码请看iOS-使用Lame转码:PCM->MP3

    相关文章

      网友评论

        本文标题:iOS 使用AUGraph录音同时播放(并转码成Mp3)

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