前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AUGraph结合RemoteI/O Unit与Mixer Unit

AUGraph结合RemoteI/O Unit与Mixer Unit

作者头像
落影
发布2018-04-27 18:09:15
1.9K0
发布2018-04-27 18:09:15
举报
文章被收录于专栏:落影的专栏落影的专栏

前言

相关文章:

使用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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.11.01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • 1、概念介绍
      • 2、具体流程
      • 3、音频流解析
      • 遇到的问题
        • 1、AUGraphNodeInfo无法初始化AudioUnit
          • 2、AUGraphSetNodeInputCallback给RemoteI/O Unit设置回调无效
            • 3、kAudioOutputUnitProperty_SetInputCallback 和 kAudioUnitProperty_SetRenderCallback 混淆
              • 4、AUGraphConnectNodeInput的BUS参数设置错误
              • 总结
              • 附录
              相关产品与服务
              云点播
              面向音视频、图片等媒体,提供制作上传、存储、转码、媒体处理、媒体 AI、加速分发播放、版权保护等一体化的高品质媒体服务。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档