美文网首页程序员半栈工程师iOS Developer
AUGraph结合RemoteI/O Unit与Mixer Un

AUGraph结合RemoteI/O Unit与Mixer Un

作者: 落影loyinglin | 来源:发表于2017-11-01 20:52 被阅读2257次

    前言

    相关文章:
    使用VideoToolbox硬编码H.264
    使用VideoToolbox硬解码H.264
    使用AudioToolbox编码AAC
    使用AudioToolbox播放AAC
    HLS点播实现(H.264和AAC码流)
    HLS推流的实现(iOS和OS X系统)
    iOS在线音频流播放
    Audio Unit播放PCM文件
    Audio Unit录音(播放伴奏+耳返)
    Audio Unit播放aac/m4a/mp3等文件
    Audio Unit和ExtendedAudioFile播放音频
    前文介绍了AudioUnit的录音/播放、AudioConvert进行音频转换、ExtendedAudioFile进行音频文件的读/写,其中AudioUnit的初始化都是通过AudioComponentInstanceNew实现,实际工程中更多使用的是AUGraph的方式进行AudioUnit的初始化。
    本文尝试用AUGraph来管理RemoteI/O Unit和Mixer Unit,实现录音、伴奏播放、人声和伴奏混合的功能。

    基础结构图

    正文

    1、概念介绍

    AUGraph连接一组 audio unit 之间的输入和输出,构成一张图,同时也为audio unit 的输入提供了回调。AUGraph抽象了音频流的处理过程,子结构可以作为一个AUNode嵌入到更大的结构里面进行处理。AUGraph可以遍历整个图的信息,每个节点都是一个或者多个AUNode,音频数据在点与点之间流通,并且每个图都有一个输出节点。输出节点可以用来启动、停止整个处理过程。

    每个AudioUnit都有Input, Output 和 Global 三个域。
    input输入域是音频流进入unit的入口,output输出域是音频流离开unit的出口,global全局域则代表整个unit。
    输入域和输出域都有若干个bus/element,比如说mixer unit有多个输入bus,只有一个输出bus;而splitter unit则有一个输入bus,有多个输出的bus。

    注意的是,bus和channel不是一个东西,一个是音频流,一个是音频流的格式。
    比如说Remote I/O Unit的输入域的inputBus是来自麦克风的音频流,其音频格式是双声道。

    2、具体流程

    • 1、初始化文件流和AVAudioSession,分配buffer;
    • 2、新建AUGraph,并添加两个AUNode,一个是RemoteI/O Unit的节点,一个是Mixer Unit的节点。
      添加AUNode的节点有两个步骤,先通过AUGraphAddNode添加节点,再通过AUGraphNodeInfo获取节点对应的AudioUnit。
    • 3、建立两个AUNode的联系,AUGraphConnectNodeInput通过把Mixer Unit的outputBus的输出作为RemoteI/O Unit的outputBus的输入;
      (这里需要注意,不是RemoteI/O的inputBus 的输入,因为RemoteI/O Unit的inputBus的输入是麦克风)
      同时设置好RemoteI/O Unit的输入和输出格式、Record的回调函数;
    • 4、调用AUGraphInitialize初始化AUGraph,然后通过AUGraphStart开始整个AUGraph;
      在AUGraph开启后,麦克风收到录制数据后调用kAudioOutputUnitProperty_SetInputCallback的回调,把麦克风的数据回调给APP;
      Mixer Unit还会通过之前kAudioUnitProperty_SetRenderCallback设置好的回调,要求APP填充两个inputBus的输入;
      在Mixer Unit处理好数据之后,会按照之前AUGraphConnectNodeInput设置的,把数据发送给Remote I/O Unit;
      Remote I/O Unit再把数据发送给扬声器。

    3、音频流解析

    如下,是整个demo的音频流向:



    伴奏文件被读取到内存,再被送到MixUnit的inputBus0;
    麦克风录取到音频数据,送到Remote I/O Unit的inputBus,存到内存中,再被送到MixUnit的inputBus1;
    MixUnit混合两个inputBus的数据,通过outputBus输出到Remote I/O Unit的outputBus中;
    Remote I/O Unit再把outputBus的数据发送个扬声器。

    遇到的问题

    1、AUGraphNodeInfo无法初始化AudioUnit

    实际运行时,报错是AudioUnitSetProperty方法,返回了-50的错误码。
    检查错误码,是AudioUnitSetProperty的audio unit参数为空。
    往上回溯,定位到AUGraphNodeInfo没正确初始化传入的audio unit参数,导致audio unit为空,并且当时没有报错,直到AudioUnitSetProperty时才报错。

    经过仔细检查,发现是AUGraphOpen方法被遗漏。
    必须先打开AUGraph,才进行获取AudioUnit的操作。

    2、AUGraphSetNodeInputCallback给RemoteI/O Unit设置回调无效

    如下,给RemoteI/O Unit设置回调可以用AudioUnitSetProperty方法修改kAudioOutputUnitProperty_SetInputCallback设置回调,但尝试用AUGraphSetNodeInputCallback对RemoteI/O Unit节点添加回调的时候,发现没法正常调用回调函数。

        AURenderCallbackStruct recordCallback;
        recordCallback.inputProc = RecordCallback;
        recordCallback.inputProcRefCon = (__bridge void *)self;
        
    //    CheckError(AUGraphSetNodeInputCallback(auGraph, outputNode, INPUT_BUS, &recordCallback), "record callback set fail");  // 这个不行,因为scope不一致
        CheckError(AudioUnitSetProperty(outputUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, INPUT_BUS, &recordCallback, sizeof(recordCallback)), "set property fail");
    

    AUGraphSetNodeInputCallback 默认是inputScope,如果在input bus的inputScope修改属性,会造成异常现象;

    3、kAudioOutputUnitProperty_SetInputCallback 和 kAudioUnitProperty_SetRenderCallback 混淆

    • kAudioUnitProperty_SetRenderCallback 是audio unit需要数据,向Host请求数据;
    • kAudioOutputUnitProperty_SetInputCallback是audio unit通知Host数据已经就绪,可以通过AudioUnitRender拉取数据;

    AudioUnitRender的解释是:Initiates a rendering cycle for an audio unit.
    下图阐释了AudioUnit是如何通过AudioUnitRender去Pull音频流数据

    4、AUGraphConnectNodeInput的BUS参数设置错误

    AUGraphConnectNodeInput(auGraph, mixNode, OUTPUT_BUS, outputNode, OUTPUT_BUS),从字面看是把mixNode的输出作为outputNode的输入。
    但是在bus的参数设置上,为什么Remote I/O Unit的bus不是inputBus?
    因为Remote I/O Unit有输入域有两个Bus,inputBus对应的是麦克风的输入,outputBus对应的是app发送给Remote I/O Unit的数据。
    这里Mixer Unit是把人声和伴奏混合后,输出给Remote I/O Unit,相当于app发送数据给Remote I/O Unit,所以这里应该填outputBus。

    总结

    demo中仍然存在问题,因为两个unit结构混乱:
    麦克风=>I/O Unit=>APP=>MixUnit
    文件=>APP=>MixUnit
    然后再是MixUnit=>I/O Unit=>扬声器
    其中,I/O Unit既指向MixUnit,同时MixUnit又指向I/O Unit。
    更好的实现方案,用一个Unit来实现录音,再用另外一个Unit进行播放,形成 RecordUnit=>MixUnit=>PlayUnit这样的结构会更加漂亮。
    这个设想就交由你去实现了!
    demo的代码点击这里书写不易,不如来个喜欢支持下↓↓

    附录

    Core Audio Tips
    Audio Unit Properties Reference PDF
    Audio Unit Hosting Guide for iOS

    相关文章

      网友评论

        本文标题:AUGraph结合RemoteI/O Unit与Mixer Un

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