Audio Unit: iOS中最底层最强大音频控制API

阅读前提:

  • Audio Session基础(Audio Session)
  • Core Audio基本数据结构(Core Audio)
  • 音视频基础知识
  • C/C++ 简单数据结构,函数使用

如下概念是文中经常使用的词语,由于其含义通常直接用英文表达, 通常不需中文翻译,可将其理解为固定名词词组.html

  • audio unit: 主要介绍的技术名称
  • audio processing graph: 另外一种处理audio unit的技术
  • node: 承载audio unit的容器
  • input scope /output scope : 可理解为音频流动的位置(好比从input scope流向output scope)
  • input element : 链接输入端硬件(如麦克风)的一个组件.
  • output element : 链接输出端硬件(如扬声器)的一个组件.
  • bus: 与element概念相同,在文中强调信号流时使用“bus”,在强调音频单元的特定功能方面时使用“element”,\
  • I/O Units: 输入输出经常使用的audio unit类型,其中包括Remote I/O unit, Voice-Processing I/O, Generic Output unit三种类型.

注意每一个element还可能具备input scope与output scope.node

Overview

Audio Unit : iOS提供音频处理插件,支持混合,均衡,格式转换和实时输入/输出,用于录制,播放,离线渲染和实时对话,例如VoIP(互联网协议语音).能够从iOS应用程序动态加载和使用它.编程

Audio Unit一般在称为audio processing graph的封闭对象的上下文中工做,如图所示。在此示例中,您的应用程序经过一个或多个回调函数将音频发送到graph中的第一个audio unit,并对每一个audio unit进行单独控制。 最终生成的I / O unit直接输出给链接的硬件.设计模式

1.ex

audio unit是iOS音频层面中最底层的编码层,若是要充分利用它须要对audio unit有更深刻的了解.除非你须要实时播放同步的声音,低延迟的输入输出或是一些音频优化的其余特性,不然请使用Media Player, AV Foundation, OpenAL, or Audio Toolbox等上层框架.数组

优势

Audio Unit提供了更快,模块化的音频处理,同时提供了强大的个性化功能,如立体声声像,混音,音量控制和音频电平测量。若是想充分使用它的功能,必须深刻了解包括音频数据流格式,回调函数和音频单元架构等基础知识。安全

  • 出色的响应能力: 能够经过回调函数访问实时的音频数据.能够直接使用audio unit合成乐器音,实时同步语音.
  • 动态的从新配置: 围绕AUGraph opaque类型构建的 audio processing graph API容许以线程安全的方式动态组装,从新配置和从新排列复杂的音频处理链,同时处理音频。这是iOS中惟一提供此功能的音频API。

生命周期

  • 运行时,获取对动态可连接库的引用,该库定义您要使用的audio unit
  • 新建一个audio unit实例
  • 根据需求配置audio unit
  • 初始化audio unit以准备处理音频
  • 开启audio unit
  • 控制audio unit
  • 用完后释放audio unit

选择设计模式

  • 配置audio session负责app与硬件间的交互,配置I/O unit: I/O units有两个独立元素(elements)组成,一个从输入端硬件接收音频数据,一个将音频数据送给输出端硬件.能够根据需求选择咱们要开启的元素.
  • 构造audio processing graph: 在audio processing graph中,必须指定音频数据流格式.
  • 控制audio units的生命周期:创建audio unit的链接并注册回调函数.

一.工做原理

2

如图所示,audio unit是iOS中音频最底层的API,audio unit仅在高性能,专业处理声音的需求下使用才有意义.bash

1. audio unit提供了快速的,模块化的音频处理

使用场景网络

  • 以最低延迟的方式同步音频的输入输出,如VoIP.
  • 手动同步音视频,如游戏,直播类软件
  • 使用特定的audio unit:如回声消除,混音,音调均衡
  • 一种处理链架构:将音频处理模块组装成灵活的网络。这是iOS中惟一提供此功能的音频API。

1.1. iOS中的audio unit

功能 Audio units
Effect iPod Equalizer
Mixing 3D Mixer,Multichannel Mixer
I/O Remote I/O, Voice-Processing I/O, Generic Output
Format conversion Format Converter
  • Effect Unit

提供一组预设均衡曲线,如重低音,流行音等等。session

  • Mixer Units数据结构

    • 3D Mixer unit: OpenAL构建的基础,若是须要3D Mixer unit特性,建议直接使用OpenAL,由于它提供了不少封装好的功能强大的API.
    • Multichannel Mixer unit: 为一个或多个声道的声音提供混音功能,以立体声输出.你能够单独打开或关闭其中一个声道的声音,调节音量,快进快退等.
  • I/O Units

    • Remote I/O unit: 直接链接输入,输出的音频硬件,以低延迟的方式访问单个接收或发出的音频采样点.提供了格式转换功能,
    • Voice-Processing I/O: 经过声学的回声消除拓展了Remote I/O unit,经常使用于VoIP或语音通讯的应用.它还提供了自动增益校订,语音处理质量调整和静音等功能.
    • Generic Output unit: 不链接音频硬件而是提供了一种机制:将处理链的输出传递给应用程序.一般用来作离线音频处理.
  • Format Converter Unit

    一般经过I/O unit间接使用.

1.2. 同时使用两个Audio Unit APIs

iOS有一个用于直接处理audio units的API,另外一个用于处理audio processing graphs,能够同时使用这两种API. 然而这两种API中有一部分功能是相同的,以下:

  • 获取audio units的动态可连接库的引用
  • 实例化audio units
  • 链接audio units并注册回调函数
  • 启动和中止音频流

1.3. 指定Audio Unit属性以获取其引用对象

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


AudioComponent foundIoUnitReference = AudioComponentFindNext (
                                          NULL,
                                          &ioUnitDescription
                                      );
AudioUnit ioUnitInstance;
AudioComponentInstanceNew (
    foundIoUnitReference,
    &ioUnitInstance
);
复制代码

AudioComponentFindNext:第一个参数设置为NULL表示使用系统定义的顺序查找第一个匹配的audio unit.若是你将上一个使用的audio unit引用传给该参数,则该函数将继续寻找下一个与之描述匹配的audio unit.

还可使用audio processing graph API初始化audio unit

// Declare and instantiate an audio processing graph
AUGraph processingGraph;
NewAUGraph (&processingGraph);
 
// Add an audio unit node to the graph, then instantiate the audio unit
AUNode ioNode;
AUGraphAddNode (
    processingGraph,
    &ioUnitDescription,
    &ioNode
);
AUGraphOpen (processingGraph); // indirectly performs audio unit instantiation
 
// Obtain a reference to the newly-instantiated I/O unit
AudioUnit ioUnit;
AUGraphNodeInfo (
    processingGraph,
    ioNode,
    NULL,
    &ioUnit
);
复制代码

1.4. Audio Units的Scopes,Elements.

以下图,Audio Units由Scopes,Elements组成.

3

  • scope: audio unit内部的编程上下文,scope概念有一点抽象,能够这样理解,好比input scope表示里面全部的element都须要一个输入。output scope 表示里面全部的element都会输出到某个地方。至于global scope应该是用来配置一些和输入输出概念无关的属性。

  • element: 当element是input/output scope的一部分时,它相似于物理音频设备中的信号总线.所以这两个术语"element, bus"在audio unit中是一个含义.本文档在强调信号流时使用“bus”,在强调音频单元的特定功能方面时使用“element”,例如I / O unit的输入和输出element.

上图展现了audio unit的一种常见架构,其中输入和输出上的element数量相同。然而,不一样的audio unit使用不一样架构。例如,mixer unit可能具备多个输入element可是具备单个输出element。

  • global scope:做为总体应用于audio unit而且不与任何特定音频流相关联.它只有一个element。该范围仅适用于个别属性,好比每一个片的最大帧数(kAudioUnitProperty_MaximumFramesPerSlice)

input , output scopes直接参与经过audio unit移动一个或多个音频流.audio进入input scope并从output scope离开 。属性或参数能够做为总体应用于input或output scope,例如kAudioUnitProperty_ElementCount.其余属性和参数(如enable I / O属性(kAudioOutputUnitProperty_EnableIO)或volume参数(kMultiChannelMixerParam_Volume))适用于特定scope的element.

注意: 能够这样理解scope,scope就是音频流动的方位,好比从input scope流动到 output scope, 而element是与硬件挂钩的,好比input element是跟麦克风链接的,音频从input element的input scope流入,从它的output scope流出.

1.5. 使用属性配置Audio Units

UInt32 busCount = 2;
 
OSStatus result = AudioUnitSetProperty (
    mixerUnit,
    kAudioUnitProperty_ElementCount,   // the property key
    kAudioUnitScope_Input,             // the scope to set the property on
    0,                                 // the element to set the property on
    &busCount,                         // the property value
    sizeof (busCount)
);
复制代码
  • kAudioOutputUnitProperty_EnableIO

用于在I / O unit上启用或禁用输入或输出。默认状况下,输出已启用但输入已禁用。

  • kAudioUnitProperty_ElementCount

配置mixer unit上的输入elements的数量

  • kAudioUnitProperty_MaximumFramesPerSlice

为了指定音频数据的最大帧数,audio unit应该准备好响应于回调函数调用而产生。对于大多数音频设备,在大多数状况下,您必须按照参考文档中的说明设置此属性。若是不这样作,屏幕锁定时您的音频将中止。

  • kAudioUnitProperty_StreamFormat

指定特定audio unit输入或输出总线的音频流数据格式。

大多数属性只能在audio unit没有初始化时指定,可是某些特定属性能够在audio unit运行时设置,如kAUVoiceIOProperty_MuteOutput静音功能.

要测试属性的可用性,访问其值以及监视其值的更改,请使用如下函数:

  • AudioUnitGetPropertyInfo: 测试属性是否可用;若是是,则为其值提供数据大小.
  • AudioUnitGetProperty,AudioUnitSetProperty: 获取,设置一个属性值
  • AudioUnitAddPropertyListener,AudioUnitRemovePropertyListenerWithUserData: 监听,移除监听对于特定属性.

1.6. 用户交互

audio unit parameter是用户能够在audio unit运行时提交的参数,经过下面的函数实现.

  • AudioUnitGetParameter
  • AudioUnitSetParameter

1.7. I/O Units基本特征

4.

尽管这两个elements是audio unit的一部分,但你的app应该把它们当作两个独立的实体.例如,你能够根据需求使用kAudioOutputUnitProperty_EnableIO属性独立启用或禁用每一个element.如上图,Element 1直接与音频输入硬件(麦克风)链接,在蓝色区域输入端的链接范围对于开发者是不透明的,开发者能够在黄色区域,即Element 1输出端获取麦克风采集的音频数据.一样地,Element 0的输出端直接与音频硬件(扬声器)链接,开发者能够将音频数据交给Element 0的输入端,输出端是不透明的.

实际使用过程当中,咱们常常须要选择使用哪一个element,使用时咱们常使用它们的编号而不是名称,input element编号为1,output elemnet编号为0.(能够将input首字母I当作1,ouput首字母O当作0来记忆)

如上图所示,每一个element都有本身的输入,输入范围.这是为了更加清楚的描述.例如,在一个同时具有输入输出音频的app中,你能够收到音频从input element的 output scope, 发送音频给output element的input scope.

2. Audio Processing Graphs管理Audio Units

audio processing graph(AUGraph):基于Core Foundation风格的数据结构,经常使用来管理audio unit处理链. graph能够利用多个audio unit与回调函数,以用来解决任意音频处理方法。

  • AUGraph类型保证了线程安全.例如播放音频时,容许你添加一个均衡器或者在mixer输入端更换回调函数.AUGraph提供了音频动态配置在iOS平台.
  • AUNode: 表明graph上下文中单个的audio unit.使用graph时,咱们经常使用它做为代理与audio unit交互,而不是直接使用audio unit.

当咱们将graph放在一块儿时,必须使用audio unit的API配置每一个audio unit. 而nodes则不能直接配置audio unit.所以,使用graph必须同时使用这两套API.

使用AUNode实例对象(使用node表明一个完整的audio processing subgraph)做为一个复杂graph中的element.在这种状况下, I/O unit结尾的subgraph必须是Generic Output unit(不能链接音频硬件的I/O unit).

步骤

  • 向graph中添加nodes
  • 经过nodes直接配置audio units
  • 互相链接nodes
2.1. Audio Processing Graph拥有精确的I/O Unit.

不管你正在录制,播放或是同步,每一个audio processing graph都有一个I/0 unit.经过AUGraphStartAUGraphStop能够开启或中止音频流.经过AudioOutputUnitStartAudioOutputUnitStop能够开启或中止I/O unit.经过这种方式,graph的I / O单元负责graph中的音频流。

2.2. 线程安全

audio processing graph API保证了线程安全.此API中的某些功能会将一个audio unit添加到稍后要执行的更改列表中.指定完整的更改集后,而后要求graph去实现它们。

如下是audio processing graph API支持的一些常见从新配置及其相关功能:

  • 添加,移除audio unit nodes (AUGraphAddNode, AUGraphRemoveNode)
  • 添加移除nodes间的链接(AUGraphConnectNodeInput, AUGraphDisconnectNodeInput)
  • 链接audio unit input bus的回调函数.(AUGraphSetNodeInputCallback)
    a1

如下是一个从新配置运行中的audio processing graph.例如,构建一个graph包含Multichannel Mixer unit与Remote I/O unit.用于播放合成两种输入源的混音效果.将两个输入源的数据送给Mix的input buses.mixer的输出端链接I/OUnit的output element最终将声音传给硬件.

a2

在上面这种状况下,用户若是想在任一一路流前插入一个均衡器.能够添加一个iPod EQ unit在从硬件输入端到mixer输入端以前.如图,如下是从新配置的步骤

  • 1.经过调用AUGraphDisconnectNodeInput断开mixer unit input 1 的“beats sound”回调。
  • 2.添加一个包含iPod EQ unit的audio unit node到graph中.能够经过配置ASBD以生成iPod EQ unit,而后调用AUGraphAddNode将其加入到graph.此时,iPod EQ unit已具备实例化对象但未初始化,已经存在于graph中但未参与音频流.
  • 3.配置,初始化iPod EQ unit.
    • 调用AudioUnitGetProperty从mixer的输入端检索kAudioUnitProperty_StreamFormat流格式.
    • 调用两次AudioUnitSetProperty,一次设置iPod EQ unit’s input流格式,一次设置输出流格式.
    • 调用AudioUnitInitialize以分配内存准备使用.这个函数是线程不安全的.可是,当iPod EQ unit还没有主动参与audio processing graph时,必须在序列时执行它,由于此时没有调用AUGraphUpdate函数。
  • 4.经过调用AUGraphSetNodeInputCallback将“beats sound”回调函数添加到iPod EQ input端。

上面1,2,4步使用AUGraph*开头的函数,都会被添加到graph的任务执行列表中.经过调用AUGraphUpdate执行这些未开始任务.若是成功返回,则graph已经被动态从新配置而且iPod EQ也已经就位正在处理音频数据.

2.3. 经过graph "pull" 音频流

在audio processing graph可使用相似生产者消费者模式,消费者在须要更多音频数据时通知生产者。请求音频数据流的方向与音频流提供的方向正好相反.

2.

对一组音频数据的每一个请求称为渲染调用(render call),也称为拉流(pull)。该图表示拉流为灰色“控制流”箭头。拉流请求的数据更恰当地称为一组音频样本帧(audio sample frames)。反过来,响应拉流而提供的一组音频样本帧被称为slice.提供slice的代码称为渲染回调函数( render callback function).

  • 调用AUGraphStart函数.虚拟输出设备调用Remote I/O unit output element的回调函数.该调用请求一片处理过的音频数据帧。
  • Remote I/O unit的回调函数在其输入缓冲区中查找要处理的音频数据。若是有数据等待处理,Remote I/O unit将使用它.不然,如图所示,它将调用应用程序链接到其输入的任何内容的回调函数。Remote I/O unit的输入端链接一个effect unit的输出端.I/O uni从effect unit中拉流,请求一组音频数据帧.
  • effect unit的行为与Remote I/O unit同样.当它须要音频数据时,它从输入链接中获取它.上例中,effect unit从回调函数中获取音频数据
  • effect unit处理回调函数中获取的音频数据. effect unit而后将先前请求的(在步骤2中)处理的数据提供给Remote / O unit
  • Remote I/O unit经过effect unit提供的音频片。而后,Remote I/O unit 将最初请求的处理过的片(在步骤1中)提供给虚拟输出设备。这完成了一个拉动周期。

3. 回调函数中将音频传给audio unit

为了从本地文件或内存中将音频传给audio unit的input bus.使用符合AURenderCallback回调函数完成.audio unit input须要一些音频数据帧将调用回调函数.

回调函数是惟一能够对音频帧作处理的地方,同时,回调函数必须遵照严格的性能要求.以录制为例,回调函数是按照固定时间间隔进行唤醒调用,若是咱们在间隔时间内尚未处理完上一帧数据,那么下一帧数据到达时将产生一个间隙的效果.所以回调函数内应尽可能避免加锁,分配内存,访问文件系统或网络链接,或以其余方式在回调函数的主体中执行耗时的任务。

static OSStatus MyAURenderCallback (
    void                        *inRefCon,
    AudioUnitRenderActionFlags  *ioActionFlags,
    const AudioTimeStamp        *inTimeStamp,
    UInt32                      inBusNumber,
    UInt32                      inNumberFrames,
    AudioBufferList             *ioData
) { /* callback body *
复制代码
  • inRefCon: 注册回调函数时传递的指针,通常可传本类对象实例,由于回调函数是C语言形式,没法直接访问本类中属性与方法,因此将本例实例化对象传入能够间接调用本类中属性与方法.
  • ioActionFlags: 让回调函数为audio unit提供没有处理音频的提示.例如,若是应用程序是合成吉他而且用户当前没有播放音符,请执行此操做。在要为其输出静默的回调调用期间,在回调体中使用以下语句:*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;当您但愿产生静默时,还必须显式地将ioData参数指向的缓冲区设置为0。
  • inTimeStamp: 表示调用回调函数的时间,能够用做音频同步的时间戳.每次调用回调时, mSampleTime 字段的值都会由 inNumberFrames参数中的数字递增。例如, 若是你的应用是音序器或鼓机, 则可使用 mSampleTime 值来调度声音。
  • inBusNumber: 调用回调函数的audio unit bus.容许你经过该值在回调函数中进行分支.另外,当audio unit注册回调函数时,能够指定不一样的inRefCon为每一个bus.
  • inNumberFrames: 回调函数中提供的音频帧数.这些帧的数据保存在ioData参数中.
  • ioData: 真正的音频数据,若是设置静音,须要将buffer中内容设置为0.

4. 音频流格式启用数据流

  • AudioStreamBasicDescription结构 (简称ASBD)
struct AudioStreamBasicDescription {
    Float64 mSampleRate;
    UInt32  mFormatID;
    UInt32  mFormatFlags;
    UInt32  mBytesPerPacket;
    UInt32  mFramesPerPacket;
    UInt32  mBytesPerFrame;
    UInt32  mChannelsPerFrame;
    UInt32  mBitsPerChannel;
    UInt32  mReserved;
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;


size_t bytesPerSample = sizeof (AudioUnitSampleType);
AudioStreamBasicDescription stereoStreamFormat = {0};
 
stereoStreamFormat.mFormatID          = kAudioFormatLinearPCM;
stereoStreamFormat.mFormatFlags       = kAudioFormatFlagsAudioUnitCanonical;
stereoStreamFormat.mBytesPerPacket    = bytesPerSample;
stereoStreamFormat.mBytesPerFrame     = bytesPerSample;
stereoStreamFormat.mFramesPerPacket   = 1;
stereoStreamFormat.mBitsPerChannel    = 8 * bytesPerSample;
stereoStreamFormat.mChannelsPerFrame  = 2;           // 2 indicates stereo
stereoStreamFormat.mSampleRate        = graphSampleRate;
复制代码

首先,应该肯定采样值的数据类型,上面使用的是AudioUnitSampleType,这是大部分audio units推荐的类型,在iOS平台上,被定义为8.24位固定的整型数据.接下来定义ASBD类型变量,初始化为0表明不包含任何数据.(注意,不能跳过该步,不然可能产生一些想不到的问题.). 而后就是对ASBD赋值,设备PCM类型表示未压缩的音频数据,

  • mFormatFlags(metaflag): 某些audio unit使用不规则的音频数据格式即不一样的音频数据类型,则mFormatFlags字段须要不一样的标志集。 例如:3D Mixer unit须要UInt16类型数据做为采样值且mFormatFlags须要设置为kAudioFormatFlagsCanonical.

  • mBytesPerPacket: 每一个音频包中有多少字节数

  • mBytesPerFrame: 每一帧中有多少字节

  • mFramesPerPacket: 每一个包中有多少帧

  • mBitsPerChannel: 每一个声道有多少位

在audio processing graph中你必须在关键点设置音频数据格式.其余点,系统将会设置这个格式.IOS 设备上的音频输入和输出硬件具备系统肯定的音频流格式,而且格式始终是未压缩的, 采用交错的线性 PCM 格式.

5

如上图, 麦克风表示输入的音频硬件。系统肯定输入硬件的音频流格式, 并将其施加到 Remote I/O unit’s 的input element的output scope内。一样,扬声器表示输出音频硬件。系统肯定输出硬件的流格式,并将其施加到Remote I/O unit’s output element的output scope。

开发者则负责在两个Remote I/O unit’s elements之间创建音频流格式.I/O unit则自动在硬件与自身链接的一端作一个必要的格式转换.

audio unit链接的一个关键功能 (如图1-8 所示) 是, 该链接将音频数据流格式从其源音频单元的输出传播到目标音频单元的输入。这是一个关键点, 所以值得强调的是: 流格式传播是经过音频单元链接的方式进行的, 仅在一个方向上进行--从源音频单元的输出到目标音频单元的输入。

虽然咱们经过ASBD灵活的设置音频数据流的属性(如采样率),可是建议仍是使用当前设备硬件默认使用值.由于若是保持一致,系统不须要作采样率转换,这能够下降能耗同时提升音频质量.

二.Audio Unit使用步骤

1.选择设计模式

  • 仅仅只有一个 I/O unit.
  • audio processing graph中使用单一音频流格式,
  • 要求你去设置流的格式,或则仅仅设置其中一部分.

正确的设置流格式在创建音频流中显得相当重要.这些模式大多依赖于音频流格式从源到目的地的自动传播,它们之间经过audio unit链接.合理利用传播的特性能够减小代码书写量,同时,你必须保证清楚了解每种模式须要如何进行设置.能够在不使用audio processing graph的状况下实现任一一种模式,可是使用它能够减小代码书写量并支持动态从新配置.

1.1. I/O Pass Through

I/O Pass Through传递模式在不处理音频的状况下将传入的音频直接发送到输出硬件.

6

如上图所示,输入端硬件将它的格式告诉Remote I/O unit的input element.开发者则在I/O unit的输出端指定了流格式,audio unit内部将根据需求自动进行转换,为了不采样率转换,咱们在定义ASBD时最好使用硬件使用的采样率.在上图,咱们没必要指定I/O unit的输出element,由于数据格式会从输入的element传给输出.同理,传给硬件的流将会根据硬件须要完成一次自动转换.

1.2. I/O不带有回调函数

app能够添加一个或多个audio unit在Remote I/O unit’s elements之间.例如使用多通道Mixer unit将传入的麦克风音频定位到立体声域中,或提供输出音量控制,在这中模式下,仍然没有用到回调函数.它简化了模式,但限制了其实用性。若是没有呈现回调函数,就没法直接操做音频。

7

在这种模式下,和Pass Through模式相同,你能够配置这两个Remote I/O unit.为了设置多通道Mixer unit,你必须把你的音频流采样率设置给mixer output.mixer的输入端音频流格式会自动设置经过接收从I/O unit输出端传递来的音频流.同理,Output element输入端的格式也将自动设置经过流传递.

使用此模式必须设置kAudioUnitProperty_MaximumFramesPerSlice属性.

1.3. I/O带有回调函数

经过注册回调函数在Remote I/O unit的input,output elements之间,开发者能够在音频数据送到输出硬件以前操控它.好比,经过回调函数调节输出音频的音量,还能够添加颤音、环调制、回波或其余效果(Accelerate framework中有相关API).

8

如图所示,这个模式使用两个Remote I/O unit, 回调函数被附加在output element的input scope.当output element须要音频数据时,系统会触发回调,紧接着,回调完成后系统将数据传给output element的input scope.

必须显式启动input在Remote I/O unit.由于它默认是禁止的.

1.4. 仅输出的回调函数

该模式一般用于游戏,专业音频app使用.简单的说,该模式在直接链接在Remote I/O unit的output element的input scope.能够利用此模式完成复杂的音频结构,如将几种不一样的声音混合在一块儿,而后经过输出硬件播放他们,以下图.

9

如上图所示,注意iPod EQ须要开发者设置Input,output二者的流格式.而 Multichannel Mixer仅仅只须要设置它输出的采样率便可.正如前面说到过,完整的音频流格式信息会在传递的过程当中自动赋值.

1.5. 其余

audio unit还有两种主要的设计模式.

  • 录制与分析音频: 建立一个带有回调的仅输入的app.回调函数会首先被唤醒,随后将数据传给Remote I/O unit’s input element.可是大多数状况下直接使用audio queue更为简单方便. audio queue使用起来更加灵活,由于它的回调函数不在一个实时的线程上.
  • Generic Output unit: 离线音频处理.不像Remote I/O unit,这个audio unit不链接设备的音频硬件.当你使用它发送音频到app时,它仅仅取决于你的应用程序调用它的渲染方法.

2.构建App

  • 配置audio session
  • 指定audio unit
  • 建立audio processing graph,而后获取audio unit
  • 配置audio unit
  • 链接audio unit nodes
  • 提供用户界面
  • 初始化而后开启audio processing graph.

2.1. 配置Audio Session

audio session是app与硬件交互的中介。设置采样率,

NSError *audioSessionError = nil;
AVAudioSession *mySession = [AVAudioSession sharedInstance];     // 1
[mySession setPreferredHardwareSampleRate: graphSampleRate       // 2
                                    error: &audioSessionError];
[mySession setCategory: AVAudioSessionCategoryPlayAndRecord      // 3
                                    error: &audioSessionError];
[mySession setActive: YES                                        // 4
               error: &audioSessionError];
self.graphSampleRate = [mySession currentHardwareSampleRate];    // 5
复制代码
  • 1.获取audio session单例对象
  • 2.请求当前设备硬件使用的采样率.
  • 3.设置音频分类。AVAudioSessionCategoryPlayAndRecord指的是支持音频输入与输出。
  • 4.激活audio session
  • 5.激活audio session后更新采样率。

你还能够配置一些其余功能,如采样率为44.1 kHz默认的duration是大概23ms,至关于每次采集1024个采样点。若是你的app要求延迟很低,你能够最低设置0.005ms(至关于256个采样点)

self.ioBufferDuration = 0.005;
[mySession setPreferredIOBufferDuration: ioBufferDuration
                                  error: &audioSessionError];
复制代码

2.2. 指定audio unit

配置完audio session后还没法直接得到audio unit,经过AudioComponentDescription赋值能够获得一个指定的audio unit. (1.3中讲到如何赋值)

2.3. 构建Audio Processing Graph

  • 实例化AUGraph对象(表明 audio processing graph)。
  • 实例化一个或多个AUNode对象(每个表明一个 audio unit in the graph.)。
  • 添加nodes到graphgraph而且实例化
  • 打开graph而且实例化 audio units
  • 得到audio unit引用
AUGraph processingGraph;
NewAUGraph (&processingGraph);
 
AUNode ioNode;
AUNode mixerNode;
 
AUGraphAddNode (processingGraph, &ioUnitDesc, &ioNode);
AUGraphAddNode (processingGraph, &mixerDesc, &mixerNode);
复制代码

调用AUGraphAddNode函数后,graph被实例化而且拥有nodes.为了打开graph而且实例化audio unit须要调用AUGraphOpen AUGraphOpen (processingGraph);

经过AUGraphNodeInfo函数获取对audio unit实例的引用

AudioUnit ioUnit;
AudioUnit mixerUnit;
 
AUGraphNodeInfo (processingGraph, ioNode, NULL, &ioUnit);
AUGraphNodeInfo (processingGraph, mixerNode, NULL, &mixerUnit);
复制代码

2.4.配置audio unit.

iOS中每种audio unit都有其配置的方法,咱们应该熟悉一些经常使用的audio unit配置.

Remote I/O unit, 默认输入禁用,输出可用.若是仅仅使用输入,你必须相应地从新配置 I/O unit.除了Remote I/O与Voice-Processing I/O unit以外全部的audio unit都必须配置kAudioUnitProperty_MaximumFramesPerSlice属性.该属性确保audio unit 准备好响应于回调函数产生足够数量的音频数据帧。

2.5. 注册并实现回调函数

对于须要使用回调函数的设计模式,咱们必须注册并实现相应的回调函数.此外,还能够经过回调函数拉取音频数据流.

AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc        = &renderCallback;
callbackStruct.inputProcRefCon  = soundStructArray;
 
AudioUnitSetProperty (
    myIOUnit,
    kAudioUnitProperty_SetRenderCallback,
    kAudioUnitScope_Input,
    0,                 // output element
    &callbackStruct,
    sizeof (callbackStruct)
);


AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc        = &renderCallback;
callbackStruct.inputProcRefCon  = soundStructArray;
 
AUGraphSetNodeInputCallback (
    processingGraph,
    myIONode,
    0,                 // output element
    &callbackStruct
);
// ... some time later
Boolean graphUpdated;
AUGraphUpdate (processingGraph, &graphUpdated);

复制代码

2.6. 链接Audio Unit Nodes

使用AUGraphConnectNodeInputAUGraphDisconnectNodeInput函数能够创建,断开Node.它们是线程安全的而且下降代码的开销,由于若是不适用graph咱们将必须手动实现.

AudioUnitElement mixerUnitOutputBus  = 0;
AudioUnitElement ioUnitOutputElement = 0;
 
AUGraphConnectNodeInput (
    processingGraph,
    mixerNode,           // source node
    mixerUnitOutputBus,  // source node bus
    iONode,              // destination node
    ioUnitOutputElement  // desinatation node element
);
复制代码

固然,咱们也能够直接使用audio unit的属性创建链接,以下.

AudioUnitElement mixerUnitOutputBus  = 0;
AudioUnitElement ioUnitOutputElement = 0;
 
AudioUnitConnection mixerOutToIoUnitIn;
mixerOutToIoUnitIn.sourceAudioUnit    = mixerUnitInstance;
mixerOutToIoUnitIn.sourceOutputNumber = mixerUnitOutputBus;
mixerOutToIoUnitIn.destInputNumber    = ioUnitOutputElement;
 
AudioUnitSetProperty (
    ioUnitInstance,                     // connection destination
    kAudioUnitProperty_MakeConnection,  // property key
    kAudioUnitScope_Input,              // destination scope
    ioUnitOutputElement,                // destination element
    &mixerOutToIoUnitIn,                // connection definition
    sizeof (mixerOutToIoUnitIn)
);
复制代码

2.7. 提供用户界面

通常而言,咱们须要提供一个用户界面,让用户微调音频行为。能够定制用户界面以容许用户调整特定的音频单元参数,并在某些特殊状况下调整音频单元属性。

2.8. 初始化并开启Audio Processing Graph

调用AUGraphInitialize初始化audio processing graph.

  • 为每一个audio units单独自动调用AudioUnitInitialize函数来初始化graph所拥有的audio units。 (若是要在不使用graph的状况下构建处理链,则必须依次显式初始化每一个audio unit)
  • 验证graph的链接与音频数据流格式
  • 经过不一样audio unit的链接传播指定格式的音频流数据。
OSStatus result = AUGraphInitialize (processingGraph);
// Check for error. On successful initialization, start the graph...
AUGraphStart (processingGraph);
 
// Some time later
AUGraphStop (processingGraph);
复制代码

3.故障排除提示

经过函数返回值能够检查调用是否成功.

请留意函数调用之间的依赖,例如,仅仅只能在audio processing graph初始化成功后再开启它.其次,利用CAShow函数能够打印当前graph的状态.

确保ASBD初始化赋值为0. AudioStreamBasicDescription stereoStreamFormat = {0};.将ASBD的字段初始化为0可确保没有字段包含垃圾数据。(注意:做为类声明中的实例变量 - 其字段会自动初始化为0,无需本身初始化它们)

- (void) printASBD: (AudioStreamBasicDescription) asbd {
 
    char formatIDString[5];
    UInt32 formatID = CFSwapInt32HostToBig (asbd.mFormatID);
    bcopy (&formatID, formatIDString, 4);
    formatIDString[4] = '\0';
 
    NSLog (@" Sample Rate: %10.0f",  asbd.mSampleRate);
    NSLog (@" Format ID: %10s",    formatIDString);
    NSLog (@" Format Flags: %10X",    asbd.mFormatFlags);
    NSLog (@" Bytes per Packet: %10d",    asbd.mBytesPerPacket);
    NSLog (@" Frames per Packet: %10d",    asbd.mFramesPerPacket);
    NSLog (@" Bytes per Frame: %10d",    asbd.mBytesPerFrame);
    NSLog (@" Channels per Frame: %10d",    asbd.mChannelsPerFrame);
    NSLog (@" Bits per Channel: %10d",    asbd.mBitsPerChannel);
}
复制代码

三.类型对比

1. 使用I/O Unit

iOS提供了三种I/O unit.大部分应用使用Remote I/O unit,它链接到输入和输出音频硬件,并提供对各个传入和传出音频样本值的低延迟访问.对于VoIP应用,Voice-Processing I/O unit经过添加声学回声消除和其余功能来扩展远程I / O单元。要将音频发送回应用程序而不是输出音频硬件,请使用通用输出单元。

1.1. Remote I/O Unit

Remote I/O unit(子类型kAudioUnitSubType_RemoteIO)链接到设备硬件,用于输入,输出或同时输入和输出。用于播放,录制或低延迟同时输入和输出,不须要回声消除。

设备的音频硬件将其音频流格式强制放置在 Remote I/O unit的外侧。audio unit提供硬件音频格式和应用音频格式之间的格式转换,经过附带的格式转换器audio unit进行格式转换.

input element input scope 从输入硬件获取音频格式, output element output scope从输出硬件获取音频格式,

在input元素的输出范围上设置应用程序格式。 input元素根据须要在其输入和输出范围之间执行格式转换。使用应用程序流格式的硬件采样率。若是输出元素的输入范围由音频单元链接提供,则它从该链接获取其流格式。可是,若是它由渲染回调函数提供,请在其上设置应用程序格式。

Audio unit feature Details
Elements 一个input element,一个output element
建议使用属性 kAudioFormatLinearPCM,kAudioFormatFlags,AudioUnitSampleType,AudioUnitCanonical
注意点 Remote I/O unit朝外侧从音频硬件获取其格式,input element从输入端硬件获取,output element 从输出端硬件获取.
属性注意 不须要设置kAudioUnitProperty_MaximumFramesPerSlice

1.2.Voice-Processing I/O Unit

Voice-Processing I/O unit(子类型kAudioUnitSubType_VoiceProcessingIO)具备 Remote I/O unit 的特性,而且添加了回声抑制。它还增长了自动增益校订,语音处理质量调整和静音功能。这是用于VoIP(互联网协议语音)应用程序的正确I/O unit。

1.3. Generic Output Unit

在将audio processing graph的输出发送到应用程序而不是输出音频硬件时,请使用此类型为kAudioUnitSubType_GenericOutput的音频单元。一般使用Generic Output Unit进行离线音频处理。与其余I / O units同样,Generic Output unit包含格式转换器。所以能够在audio processing graph中使用的流格式与所需格式之间执行格式转换。

2. Using Mixer Units

iOS提供两种mixer units。在大多数状况下,您应该使用Multichannel Mixer unit,它能够为任意数量的单声道或立体声流提供混音。若是您须要3D Mixer unit的功能,请使用OpenAL。 OpenAL创建在3D混音器单元之上,提供与简单API相同的性能,很是适合游戏应用程序开发。

默认状况下,kAudioUnitProperty_MaximumFramesPerSlice属性设置为1024,当屏幕锁定而且显示器休眠时,这是不够的。若是您的应用在屏幕锁定时播放音频,则必须增长此属性的值,除非音频输入处于活动状态。作以下:

  • 若是音频输入处于活动状态,则无需为kAudioUnitProperty_MaximumFramesPerSlice属性设置值。
  • 若是音频输入未激活,请将此属性设置为值4096。

2.1.Multichannel Mixer Unit

Multichannel Mixer unit(子类型kAudioUnitSubType_MultiChannelMixer)可接收任意数量的单声道或立体声流,并将它们组合成单个立体声输出。它控制每一个输入和输出的音频增益,并容许您分别打开或关闭每一个输入。从iOS 4.0开始,多声道混音器支持每一个输入的立体声声像。

Audio unit feature Details
Elements 一个或多个(单声道或多声道)input element,一个多声道output element
建议使用属性 kAudioFormatLinearPCM,kAudioFormatFlags,AudioUnitSampleType,AudioUnitCanonical
注意点 若是输入由audio unit链接则从该链接获取其流格式。若是输入由回调函数提供在bus上设置完整的流格式,使用与回调提供的数据相同的流格式。在output scope,仅须要设置采样率。
属性 kAudioUnitProperty_MeteringMode

注意点: iPod EQ单元提供一组预约义的色调均衡曲线做为出厂预设。经过访问音频单元的kAudioUnitProperty_FactoryPresets属性获取可用EQ设置数组。使用它做为kAudioUnitProperty_PresentPreset属性的值来应用设置。

2.2. 3D Mixer Unit

3D Mixer unit: 控制每一个输入的立体声声像,播放速度和增益,并控制其余特征,例如与收听者的视距,输出具备音频增益控制。一般,若是须要3D Mixer unit的功能,最佳选择是使用OpenAL.

Audio unit feature Details
Elements 一个或多个单声道input element,一个多声道output element
建议使用属性 UInt16,kAudioFormatFlagsAudioUnitCanonical
注意点 若是输入由audio unit链接则从该链接获取其流格式。若是输入由回调函数提供在bus上设置完整的流格式,使用与回调提供的数据相同的流格式。在output scope,仅须要设置采样率。

注意点: iPod EQ单元提供一组预约义的色调均衡曲线做为出厂预设。经过访问音频单元的kAudioUnitProperty_FactoryPresets属性获取可用EQ设置数组。使用它做为kAudioUnitProperty_PresentPreset属性的值来应用设置。

3.Using Effect Units

iPod EQ unit (子类型kAudioUnitSubType_AUiPodEQ)是iOS 4中提供的惟一效果单元。这与内置iPod应用程序使用的均衡器相同。要查看该音频设备的iPod应用程序用户界面,请转至设置> iPod> EQ。该音频单元提供一组预设均衡曲线,如低音加强器,流行音乐和口语。

Audio unit feature Details
Elements 一个input element,一个output element (该element能够是单声道或双声道)
建议使用属性 kAudioFormatLinearPCM,AudioUnitSampleType,kAudioFormatFlagsAudioUnitCanonical
注意点 若是输入由audio unit链接则从该链接获取其流格式。若是输入由回调函数提供在bus上设置完整的流格式,使用与回调提供的数据相同的流格式。在output scope,设置用于输入的相同完整流格式。
Properties kAudioUnitProperty_FactoryPresets,kAudioUnitProperty_PresentPreset

注意点: iPod EQ单元提供一组预约义的色调均衡曲线做为出厂预设。经过访问音频单元的kAudioUnitProperty_FactoryPresets属性获取可用EQ设置数组。使用它做为kAudioUnitProperty_PresentPreset属性的值来应用设置。

默认状况下,kAudioUnitProperty_MaximumFramesPerSlice属性设置为1024,当屏幕锁定而且显示器休眠时,这是不够的。若是您的应用在屏幕锁定时播放音频,则必须增长此属性的值,除非音频输入处于活动状态。作以下:

  • 若是音频输入处于活动状态,则无需为kAudioUnitProperty_MaximumFramesPerSlice属性设置值。
  • 若是音频输入未激活,请将此属性设置为值4096。

4.Audio Units 标识符键

此表提供了访问每一个iOS audio unit的动态连接库所需的标识符键以及其简要说明。

名称与描述 键名称 相应编解码器
Converter unit (提供音频格式转换在线性PCM与其余压缩格式) kAudioUnitType_FormatConverter,kAudioUnitSubType_AUConverter,kAudioUnitManufacturer_Apple aufc,conv,appl
iPod Equalizer unit(提供iPod均衡器的功能) kAudioUnitType_Effect kAudioUnitSubType_AUiPodEQ
3D Mixer unit (支持混合多个音频流,输出平移,采样率转换等) kAudioUnitType_Mixer,kAudioUnitSubType_AU3DMixerEmbedded,kAudioUnitManufacturer_Apple aumx,3dem,appl
Multichannel Mixer unit (支持将多个音频流混合到单个流中) kAudioUnitType_Mixer,kAudioUnitSubType_MultiChannelMixer,kAudioUnitManufacturer_Apple aumx,mcmx,appl
Generic Output unit (支持转换为线性PCM格式和从线性PCM格式转换;可用于启动和中止graph) kAudioUnitType_Output,kAudioUnitSubType_GenericOutput,kAudioUnitManufacturer_Apple auou,genr,appl
Remote I/O unit(链接到设备硬件以进行输入,输出或同时输入和输出) kAudioUnitType_Output,kAudioUnitSubType_RemoteIO,kAudioUnitManufacturer_Apple auou,rioc,appl
Voice Processing I/O unit(具备I / O单元的特性,并为双向通讯增长了回声抑制功能) kAudioUnitType_Output,kAudioUnitSubType_VoiceProcessingIO,kAudioUnitManufacturer_Apple auou,vpio,appl

Apple官方文档