Audio Unit 基础

时间:2021-03-29 16:30:45

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

Audio Unit 基础

如在可控性、性能、灵活性有非常高的需求,或者需要实现特定的功能(例如回音消除),直接使用 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 基础

如图所示,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 的基本特性

Audio Unit 基础

  • 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 上。

Audio Unit 基础

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

  • 使用 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 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 基础