AUGraph结合RemoteI/O Unit与Mixer Unit

前言

相关文章: 使用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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏点滴积累

geotrellis使用(十)缓冲区分析以及多种类型要素栅格化

目录 前言 缓冲区分析 多种类型要素栅格化 总结 参考链接 一、前言        上两篇文章介绍了如何使用Geotrellis进行矢量数据栅格化以及栅格渲染,...

38180
来自专栏Pythonista

Python之路,Day1 - Python基础1

python的创始人为吉多·范罗苏姆(Guido van Rossum)。1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解...

18220
来自专栏AI科技大本营的专栏

精选26个Python实用技巧,想秀技能先Get这份技术列表!

【导读】Python 虽然是脚本语言,但是因为其易学,迅速成为科学家的工具,从而积累了大量的工具库、架构,人工智能涉及大量的数据科学,用 Python 是很自然...

14950
来自专栏海说

深入理解计算机系统(3.1)---走进汇编的世界

  本系列拖了蛮久了,主要是因为LZ写的时候其实刚看到第二章,因此这一段时间快速看了下第三章,并花了点时间沉淀了一下,这才耽误了下来。

9730
来自专栏我的小碗汤

使用pprof优化golang性能

Donald E.Knuth说过一句非常著名的话,过早的优化是万恶之源。原文如下:

20540
来自专栏小樱的经验随笔

CTF---Web入门第十题 Once More

Once More分值:10 来源: iFurySt 难度:易 参与人数:4782人 Get Flag:2123人 答题人数:2166人 解题通过率:98%...

32260
来自专栏CDA数据分析师

精选26个Python实用技巧,想秀技能先Get这份技术列表!

【导读】Python 虽然是脚本语言,但是因为其易学,迅速成为科学家的工具,从而积累了大量的工具库、架构,人工智能涉及大量的数据科学,用 Python 是很自然...

12020
来自专栏海说

深入理解计算机系统(3.1)---走进汇编的世界

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer13.html

10630
来自专栏ThoughtWorks

在项目中透明地引入特性开关

之前曾经推荐过崔立强的《使用功能开关更好地实现持续部署》,介绍Feature Toggle的实践。北京办公室的孟宇现在对这个问题有了新的思考,当我们抛却Spri...

42960
来自专栏Flutter入门到实战

那些年遇到的后台返回的奇葩json数据

然而:错误数据返回null不说,错误信息居然返回一个一个url?就这么一点错误信息,还要我再去请求一次服务器获取这个错误信息吗。。 服务器流量不要钱的吧。。。...

72230

扫码关注云+社区

领取腾讯云代金券