如图所示,所有 iOS 音频技术都是基于 audio units。此处显示的更高级别的技术,如 Media Player,AV Foundation,OpenAL,AudioToolbox,是对 audio units 的封装,为特定的任务提供专用且简化的 API。

如在可控性、性能、灵活性有非常高的需求,或者需要实现特定的功能(例如回音消除),直接使用 audio unit 是一个正确的选择。

Audio Units 提供高效,模块化音频处理方案

当你需要实现以下需求时,不使用高级 API,直接使用 audio units

  • 低延时同步音频输入输出,例如 VoIP 应用
  • 响应回放合成声音,例如音乐游戏或合成乐器
  • 使用特定的 audio unit 特征,例如回声消除,混音,色调均衡
  • 处理链结构让你可以将音频处理模块组装到灵活的网络中。这是 iOS 中唯一提供此功能的音频 API

iOS 中的 Audio Units

根据不同功能分类,iOS 提供了七种 audio units

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

注意:iOS 动态插件结构不支持第三方 audio units,也就是说,动态加载的 audio units 仅能通过操作系统提供。

Effect Unit

iOS 4 提供了一个效果单元,iPod Equalizer,与 iPod 内置应用使用相同的均衡器。查看这个 audio unit 的 iPod 应用用户界面,进入设置 -> iPod -> EQ。当使用此 audio unit,必须提供自己的用户界面。此 audio unit 提供了一组预设的均衡曲线,例如低音增强,Pop 和 Spoken Word。

Mixer Units

iOS 提供两个 mixer units。3D Mixer unit 是 OpenAL 的基础,如果需要实现 3D Mixer unit 的特征,可以优先使用 OpenAL,它提供了高级 API,并且非常适合游戏应用程序。关于如何使用 OpenAL,见示例代码: oalTouch。

Multichannel Mixer unit 为任意数量的单声道或立体声提供混音,立体声输出。可以打开和关闭每一个输入,设置输入增益,并设置立体声平移位置。见示例代码:MixerHost。

I/O Units

iOS 提供了三个 I/O units,其中 Remote I/O unit 是最常用的。连接输入输出音频硬件,对传入和传出的样本值低延迟访问,提供硬件音频格式和应用音频格式之间的格式转化。见示例代码:aurioTouch。

Voice-Processing I/O unit 是对 Remote I/O unit 的拓展,添加了语音聊天中的回声消除,还提供了自动增益矫正,语音质量调整,静音等特性。

Generic Output unit 不连接音频硬件,而是提供了一种将处理链的输出发送到应用程序的机制。通常会使用做离线音频处理

Format Converter Unit

iOS 4 提供了 Format Converter Unit,通常通过 I/O unit 间接使用。

两个 Audio Unit API

iOS 中两个和 audio units 相关的 API,一个 API 直接处理 audio units ,另一个处理 audio processing graphs,在应用中可以同时使用两个 API。

这两个 API 之间有一些功能重叠,可以根据自己编程风格自由组合和搭配,提供的功能如下:

  • 获取对定义音频单元动态链接库的引用
  • 实例化 audio unit
  • audio units 互联和附件回调函数
  • 开始和停止音频流

Audio Unit 获取

首先需要在音频组件描述数据结构中确定其类型、子类型和制造商密匙。下面指定了一个特定的 audio Unit,Remote I/O unit,对 componentManufacturer 字段,所有的 iOS audio units 使用 kAudioUnitManufacturer_Apple。如需创建一个通用描述,可以将类型或者子类型设置为0,例如为了匹配所有的 I/O unit,可以将 componentSubType 设置为0。

    AudioComponentDescription ioUnitDescription;
ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
ioUnitDescription.componentFlags = 0;
ioUnitDescription.componentFlagsMask = 0;

使用 audio unit API 获取 audio unit 实例,对于 AudioComponentFindNext ,如果第一个参数传空,按照系统定义的排序,找到第一个符合的 audio unit ,如果该参数为先前找到的音频单元,则该功能找到与描述匹配的下一个 audio unit,例如此用法可以通过重复调用 AudioComponentFindNext 来获取所有 I/O 单元的引用。

    AudioComponent foundIoUnitReference = AudioComponentFindNext(NULL, &ioUnitDescription);
AudioUnit ioUnitInstance;
AudioComponentInstanceNew(foundIoUnitReference, &ioUnitInstance);

使用 audio processing graph API 获取 audio unit

    AUGraph processingGraph;
NewAUGraph(&processingGraph); AUNode ioNode;
AUGraphAddNode(processingGraph, &ioUnitDescription, &ioNode);
AUGraphOpen(processingGraph); AudioUnit ioUnit;
AUGraphNodeInfo(processingGraph, ioNode, NULL, &ioUnit);

Audio Units 结构

如图所示,Audio Unit 中有一个 input element,一个 output element,这种结构比较常见,但这并不适合所有状况。例如在 mixer unit 中,会存在多个 input element,一个 output element的情况。

element 1 可以理解为 input element(bus),element 0 理解为output element。Input scope 和 Output scope,直接参与音频流的流程,音频从 Input scope 输入,从 Output scope 输出,一些参数或属性适用于 Input scope 或 Output scope。Global scope,应用于整个 audio unit,不与音频流关联,它有一个 element,命名为 element 0,一些属性,像 kAudioUnitProperty_MaximumFramesPerSlice 应用于 Global scope。

Audio Units 属性

设置属性,可以使用函数 AudioUnitSetProperty

    UInt32 busCount = 2;
OSStatus result = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &busCount, sizeof(busCount));

下面是一些常用的属性:

  • kAudioOutputUnitProperty_EnableIO 启用或禁止 I/O,默认输出开启,输入禁止。
  • kAudioUnitProperty_ElementCount 配置元素个数
  • kAudioUnitProperty_MaximumFramesPerSlice 设置 audio unit 的最大帧数
  • kAudioUnitProperty_StreamFormat 指定输入 audio unit 输入输出元素的数据格式

大部分属性可以在 audio unit 未初始化的时候设置,因为这些属性一般不会发生改变,有些属性像 iPod EQ unit 中的 kAudioUnitProperty_PresentPreset和 Voice-Processing I/O unit 中的 kAUVoiceIOProperty_MuteOutput 这些属性会在播放音频的时候也会发生改变

查找属性是否可得,访问属性值,监听改变,可以使用一下函数:

  • AudioUnitGetPropertyInfo 查看属性是否可得,如果可以,会得到值大小和值是否可以改变
  • AudioUnitGetProperty, AudioUnitSetProperty 获取或设置属性
  • AudioUnitAddPropertyListener, AudioUnitRemovePropertyListenerWithUserData 安装或者移除监听属性变化回调函数

Audio Units 参数

audio unit 参数是用户可以在音频生成的过程中更改,事实上,大部分参数可以在 audio unit 正在执行时实时调整的,例如音量。

  • AudioUnitGetParameter
  • AudioUnitSetParameter

I/O Units 的基本特性

  • Input element 和 Output element 都是 I/O unit 的一部分,可以将它们视为一个独立的个体,单独启动或禁止每一个 Element,默认情况下,Element 1 禁用,Element 0 开启。
  • 音频输入硬件麦克风直接连着 Element 1, Element 1 的 Input scope 对你是不可见的,你首次访问硬件输入的音频数据是位于 Element 1 的 Output scope。
  • 音频输出硬件扬声器直接连着 Element 0,Element 0 的 Output scope 对你是不可见的,数据从 Element 1 的 Output scope 传递到 Element 0 的 Input scope。

每一个 Element都有自己的 input scope 和 output scope,当描述 I/O unit 的时候可能会有困惑,相当于这样描述,你收到收据来自 input element 的 output scope,发送数据到 output element 的 input scope。

I/O unit 是唯一能够在 audio processing graph 中启动和停止音频流的 audio unit。I/O unit 负责在音频单元APP中的音频流。

Audio Processing Graphs 管理 Audio Units

AUGraph 用于构建和管理 audio units 处理链,可以利用多个 audio units 和多个回调函数功能,创建几乎任何你可以想象的音频处理解决方案。

AUGraph 增加了线程安全,让你可以即时重新配置处理链,例如你可以安全插入一个均衡器,甚至在音频播放时可以交换混音器输入的其它回调函数。实际上,AUGraph 提供了 iOS 中唯一可以在音频应用程序中执行这种动态重新配置的 API 。

Audio Processing Graph 使用了 AUNode 表示上 graph 中 一个单独的 audio unit ,当使用 Audio Processing Graphs,通常与包含 audio units 的代理 AUNode 交互,而不是直接使用 audio unit。

当将 graph 组合时,需要配置每一个 audio unit ,并且必须通过 audio unit API 直接与 audio unit 交互,节点单元本身是不可以配置的,通过这种方式,需要使用这两种 API。

通常情况下,Audio Processing Graphs 通常需要三个任务,将节点添加到 Graph 中,直接配置由节点表示的 audio unit,互连节点。

#Audio Processing Graph 有一个 I/O Unit

每一个 audio processing graph 有一个 I/O unit,无论你是录音,播放,同步 I/O。Graphs 通过 AUGraphStart 和 AUGraphStop 启动和停止音频流,通过函数 AudioOutputUnitStart 和 AudioOutputUnitStop 传递开始和停止消息到 I/O unit。

Audio Processing Graphs 提供线程安全

audio processing graph 使用“待办事项列表”提供线程安全,API 的一些函数添加工作单元到稍后执行的更改列表中,在你指定完整的更改好,让 graph 实现他们。

这是一些 audio processing graph 支持的重配置函数

  • 添加或删除音频单元节点(AUGraphAddNode,AUGraphRemoveNode)
  • 添加或删除节点间的连接(AUGraphConnectNodeInput,AUGraphDisconnectNodeInput)
  • 渲染回调函数连接到 aduio unit 的输入总线(AUGraphSetNodeInputCallback)

下面看一个重配置 audio processing graph 的例子,构建一个 graph 包含 Multichannel Mixer unit 和 Remote I/O unit,将声音输入到混频器的两个输入总线上。从混合器输出数据到 I/O unit 的 Output element 上。

现在用户想插入均衡器到其中一个音频流上,如何完成动态配置

  • 使用 AUGraphDisconnectNodeInput 断掉 input 1 到 mixer unit 的回调
  • 添加一个 iPod EQ unit 到 graph 中,需要使用 AudioComponentDescription 指定 iPod EQ unit 的结构,接着调用 AUGraphAddNode,至此,iPod EQ unit 被安装但是没有初始化,被 graph 拥有但是没有参与到音频流中
  • 重配置和初始化 iPod EQ unit,详情如下:

调用 AudioUnitGetProperty 函数得到 mixer input 的流格式(kAudioUnitProperty_StreamForamt)

调用 AudioUnitSetProperty 函数两次,一次设置 iPod EQ unit 的输入格式,一次设置它的输出格式

调用 AudioUnitInitialize 函数,给 iPod EQ 分配资源和准备处理音频,这个函数调用时线程安全的

调用 AUGraphSetNodeInputCallback 函数,设置鼓的回调函数到 iPod EQ unit 的输入

回调函数提供数据给 Audio Units

为了给 audio unit 的输入总线提供数据,使用遵从 AURenderCallback 原型的回调函数,音频输入单元需要一帧数据的时候触发回调。在处理 audio unit 应用中,写回调函数可能是最具有创意的工作,你能根据你的意愿产生和改变声音。

回调函数有严格的性能要求,回调存在于实时线程上,随后回调异步到达,回调函数内部所有的工作发生在时间有限的环境中,当下一帧数据到达,你仍在处理之前的回调产生的帧,声音则会产生间隙,出于这个原因,不得在回调函数主体中执行耗时操作,例如锁定,分配内存,访问文件系统,网络连接等。

理解音频单元的回调函数

回调函数头部

static OSStatus MyAURenderCallBack(void                                    *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData);
  • inRefCon,参数指向回调附加到 audio unit 输入时指定的编程上下文

  • ioActionFlags,参数允许提供提示当没有音频数据处理时,例如当你的应用程序是合成吉他,用户当面没有播放,请执行此操作。当你想要输出静音,可以在回调主体中使用如下语句:*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence,并且你必须明确地将 ioData 参数指向的缓冲区设置为0。

  • inTimeStamp,回调函数被触发时间,包含一个 AudioTimeStamp 结构体。

  • inBusNumber,参数指示调用回调的音频单元总线

  • inNumberFrames,当前调用的音频采样数

  • ioData,指向音频数据缓存区

Audio Unit 基础的更多相关文章

  1. Audio Unit 介绍

    关于 Audio Unit iOS 提供了音频处理插件,支持混音,声音均衡,格式转化,以及用于录音,回放,离线渲染,实时对话的输入输出.可以动态载入和使用这些强大而灵活的插件,在 iOS 应用中这些插 ...

  2. IOS音频架构之Audio Unit

    在前面的章节部分我们已经对IOS音频结构有了一个清晰的认识,知道Audio Unit是位于整个音频结构的最底层,这一层非常多API已经開始和硬件打交道了.所以比較复杂,有了前面的基础再来看这个部分就比 ...

  3. iOS 实时音频采集与播放Audio Unit使用

    前言 在iOS中有很多方法可以进行音视频采集.如 AVCaptureDevice, AudioQueue以及Audio Unit.其中 Audio Unit是最底层的接口,它的优点是功能强大,延迟低; ...

  4. 音频单元组件服务参考(Audio Unit Component Services Reference)

    目录 了解Audio Unit体系结构 文档结构预览 结构单元介绍 本文主要介绍AudioUnit的组成 本文由自己理解而成,如有错误,请欢迎网友们指出校正. 了解Audio Unit体系结构 开始前 ...

  5. 最新 iOS 框架整体梳理(一)

    前言 这段话其实是我差不多写完文章之后再回过头来写的,原本在写文章之前想写一下写的初衷的,但当我写完之后感觉初衷没有收获更真切一些.其实到这篇为止总结出来的也就三十多个,有些是比较新的框架,有些是我们 ...

  6. 使用Core Audio实现VoIP通用音频模块

    最近一直在做iOS音频技术相关的项目,由于单项直播SDK,互动直播SDK(iOS/Mac),短视频SDK,都会用到音频技术,因此在这里收集三个SDK的音频技术需求,开发一个通用的音频模块用于三个SDK ...

  7. IOS Audio session

    iOS实现长时间后台的两种方法:Audio session和VOIP socket 十二月 04 我们知道 iOS 开启后台任务后可以获得最多 600 秒的执行时间,而一些需要在后台下载或者与服务器保 ...

  8. 一篇对iOS音频比较完善的文章

    转自:http://www.cnblogs.com/iOS-mt/p/4268532.html 感谢作者:梦想通 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也 ...

  9. IOS 音频播放

    iOS音频播放 (一):概述 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究.写这个系列的博客目的一方面希望能够抛砖 ...

随机推荐

  1. ----------- Rootkit 核心技术之绕过 IopParseDevice() 调用源检测逻辑 ---------------

    ---------------------------------------------------------------- 在上一篇文章中,我们已经看到 IopParseDevice() 如何对 ...

  2. 把玩爬虫框架Gecco

    如果你现在接到一个任务,获取某某行业下的分类. 作为一个非该领域专家,没有深厚的运营经验功底,要提供一套摆的上台面且让人信服的行业分类,恐怕不那么简单. 找不到专家没有关系,我们可以爬虫.把那些专家的 ...

  3. [bzoj4240] 有趣的家庭菜园

    还是膜网上题解QAQ 从低到高考虑,这样就不会影响后挪的草了. 每次把草贪心地挪到代价较小的一边.位置为i的草,花费为min( 1..i-1中更高的草的数目,i+1..n中更高的草的数目 ) 因为更小 ...

  4. noip2015 提高组 解题报告

    完美退役...说好的不卡常呢QAQ day1: T1:模拟题?..考察选手将题目描述翻译成代码的能力233 //其实真相是考验rp..论代码雷同的危害233 T2:简单图论,每个点出度为1所以是基环内 ...

  5. PL/SQL 一个数据对象一个事务(rollback,submit)

    /*********************************************** 一个数据对象一个事务(且记录错误信息到处理对象) ************************** ...

  6. Eclipse安装JD-Eclipse反编译插件成功看源码

    Eclipse安装JD-Eclipse反编译插件 转载 2017年12月24日 15:19:27   http://heavengate.blog.163.com/blog/static/202381 ...

  7. python基础2 day3

    一.上节回顾 1,while else2,格式化输出name = input('>>>')s1 = '我叫%s,今年%d岁'%(name,18)dic1name = input('& ...

  8. python网络编程(进程与多线程)

    multiprocessing模块 由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程. multiproce ...

  9. Button重写onClick两种方式

    实现接口和匿名内部类 下午没课,自己又继续安卓的学习,照着书上做了一个left碎片Button点击后动态加载right碎片布局的Test,准备自己再继续做一个单击左碎片的button1 加载右碎片布局 ...

  10. JVM核心之JVM运行和类加载全过程

    为什么研究类加载全过程? 有助于连接JVM运行过程 更深入了解java动态性(解热部署,动态加载),提高程序的灵活性 类加载机制 JVM把class文件加载到内存,并对数据进行校验.解析和初始化,最终 ...