Audio Unit
推荐先阅读Audio Unit Hosting Guide for iOS,部分翻译的文章可参考Audio Unit 基础
Audio Unit在框架中位置
Audio Unit提供了音频快速的模块化处理,在以下的场景中更适合使用AudioUnit,而不是更高层次的音频框架:
- Simultaneous audio I/O (input and output) with low latency, such as for a VoIP (Voice over Internet Protocol) application
想使用低延迟的音频I/O,比如说VoIP的应用场景下 - Responsive playback of synthesized sounds, such as for musical games or synthesized musical instruments
多路声音的合成并且播放,比如游戏或者音乐合成器的应用 - Use of a specific audio unit feature such as acoustic echo cancelation, mixing, or tonal equalization
使用AudioUnit里面提供的特有功能,比如:回声消除、Mix两轨音频,以及均衡器、压缩器、混响器等效果器 - A processing-chain architecture that lets you assemble audio processing modules into flexible networks. This is the only audio API in iOS offering this capability.
Audio units通常在名为Audio Processing Graph的context中使用,如下所示:
Audio units usually do their work in the context of an enclosing object called an audio processing graph, as shown in the figure. In this example, your app sends audio to the first audio units in the graph by way of one or more callback functions and exercises individual control over each audio unit. The output of the I/O unit—the last audio unit in this or any audio processing graph—connects directly to the output hardware.
APP持有的Audio Processing Graph容器中包含两个EQ Unit、一个Mixer Unit、一个I/O Unit,APP将磁盘或者网络中的两路流数据分别通过EQ Unit进行均衡处理,然后在Mixer Unit经过混音处理为一路,进入I/O Unit将此路数据送往硬件去播放。在这整个流程中,APP随时可以调整设置AU Graph及其中每个Unit的工作状态及参数,动态性的接入或者移出指定的Unit,并且保证线程安全。
iOS Audio Unit(一)
在iOS 與 Mac OS X 的 Audio API 概觀一文中,对Audio Unit Processing Graph
有这样的描述:
Audio Unit Processing Graph 是一個可以處理播放與錄音的 API,這層把 audio 播放的處理過程,抽象化變成一個個的組件,我們可以透過組合這些組件創造我們想要的錄製與播放效果。Audio Unit Processing Graph API 裡頭大概有三個主要的角色:
- AUNode 或 AudioComponent
- AudioUnit
- AUGraph
我們不妨想像我們現在身處在演唱會的舞台上,有錄製歌聲與樂器的麥克風,而從麥克風到輸出到音響之間,還串接了大大小小的效果器,在這個過程中,無論是麥克風、音響或是效果器,都是不同的 AUNode。AUNode 是這些器材的實體,而我們要操控這些器材、改變這些器材的效果屬性,就會需要透過每個器材各自的操控介面,這些介面便是AudioUnit,最後構成整個舞台,便是 AUGraph。
AUNode 與 AudioComponent 的差別在於,其實像上面講到的各種器材,除了可以放在 AUGraph 使用之外,也可以單獨使用,比方說我們有台音響,我們除了把音響放在舞台上使用外,也可以單獨拿這台音響輸出音樂。當我們要在 AUGraph 中使用某個器材,我們就要使用 AUNode 這種形態,單獨使用時,就使用 AudioComponent。但無論是操作 AUNode 或 AudioComponent,都還是得透過 AudioUnit 這一層操作介面。
AUNode 與 AudioComponent 分成好幾類,包括輸入、輸出、混音、效果處理、格式轉換等等,彼此之間可以互相串接。輸入裝置包括像麥克風輸入或 MIDI 樂器,效果處理則包括像EQ 等化器、殘響(reverb)、改變音調(pitch)等;至於這邊所謂的格式轉換,是指在不同的 LPCM 格式之間轉換,在這一層 API 中只支援 LPCM 格式,但LPCM 之間又有很多種,不見得每個 node 都支援所有的 LPCM 格式,像 reverb效果的 effect node 就只支援浮點數,所以要讓音訊資料通過這個 node 之前,就需要先轉換成浮點數格式的 LPCM 資料。
每個 AudioUnit 都有各自的輸入與輸出,在串接的時候,就是從某個 AudioUnit 的輸出,串接到另外一個 AudioUnit 的輸入,這種輸入輸出的端子叫做 bus,而每個 AudioUnit 最少會有一個輸入與輸出的 bus,也可能會有多個 bus。以 mixer 來說,就會有多個 bus,當我們從兩個輸入 bus 將資料送到同一個 mixer 上時,就可以產生混音效果。在 Mac OS X 上,我們通常會使用 default output 作為音訊播放的最終輸出的 AudioUnit,以 default input 作為錄音的起點。在 iOS 上則有一個特別的 AudioUnit,叫做 Remote IO,這個 AudioUnit 同時代表 iOS 的輸出與輸入裝置。Remote IO 有兩個 bus,bus 0 就是 iOS 的預設輸出,bus 1 則是輸入,所以我們在 iOS 上播放音樂,就是往 Remote IO 的 bus 0 傳送資料。
Remote IO 的 bus 1 預設是關閉的,當我們要錄音的時候,我們必須先告訴 Remote IO 把 bus 1 變成 enable,但我們要做這件事情的時候,我們不但要獲得使用者給予我們使用麥克風的授權,還要設定正確的 Audio Session。我們會在後面說明 Audio Session。在使用 Audio Unit Processing Graph API 的時候,我們經常需要設定 render callback function。以錄音來說,當我們從 Remote IO 的 bus 1 收到資料後,想要儲存檔案,我們並沒有一種叫做「存檔」的 AudioUnit,而是我們要對某個 AudioUnit 設定 callback function,綁定某個 bus,在這個 function 中撰寫存檔的程式。以播放來說,當我們告訴 AUGraph 或 Remote IO 開始播放,我們也要設定 render callback function,提供用來播放用的資料。
Audio Units in iOS
iOS提供了4中类型的共7个的audio units,如下:
Purpose | 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 |
具体的identifiers可参考Identifier Keys for Audio Units
在《音视频开发进阶指南》一书中对AudioUnit分类的具体介绍如下:
1.Effect Units
类型是kAudioUnitType_Effect
,主要提供声音特效处理的功能,其子类型Effect Audio Unit Subtypes
及其用途说明如下:
- 均衡效果器:子类型是
kAudioUnitSubType_NBandEQ
,主要作用是未声音的某些频带增强或者减弱能量 - 压缩效果器:子类型是
kAudioUnitSubType_DynamicsProcessor
,主要作用是当声音较小的时候,可以提供声音的能量,当声音的能量超过设置的阈值时,可以降低声音的能量… - 混响效果器:子类型是
kAudioUnitSubType_Reverb2
2.Mixer Units
类型是kAudioUnitType_Mixer
,主要提供Mix多路声音的功能,其子类型Mixer Audio Unit Subtypes
及用途如下:
- MultiChannelMixer: 子类型是
kAudioUnitSubType_MultiChannelMixer
,是多路声音混音的效果器,可以接受多路音频的输入,还可以分别调整每一路音频的增益与开关,并将多路音频合并成一路,该效果器在处理音频的图状结构中非常有用
3.I/O Units
类型是kAudioUnitType_Output
,主要提供的是I/O的功能,其子类型说明如下:
- RemoteIO: 子类型是
kAudioUnitSubType_RemoteIO
,用来采集音频与播放音频的,其实当开发者的应用场景中要使用麦克风及扬声器的时候回用到该AudioUnit - Generic Output:子类型是
kAudioUnitSubType_GenericOutput
,当开发者需要进行离线处理,或者说在AUGraph
中不使用Speaker(扬声器)来驱动整个数据流,而是希望使用一个输出(可以放入内存队列或者进行磁盘I/O操作)来驱动数据流时,就使用该子类型
4.Format Convert Units
类型是kAudioUnitType_FormatConverter
,主要用于提供格式转换功能,比如:采样格式由Float到SInt16的转换、交错和平铺的格式转换、单双声道的转换等,其子类型及用途说明如下:
-
AUConverter: 子类型是
kAudioUnitSubType_AUConverter
, 它将是本书要重点介绍的格式转换效果器, 当某些效果器对输入的音频格式有明确的要求时(比如3D Mixer Unit就必须使用UInt16格式的Sample), 或者开发者将音频数据输入给一些其他的编码器进行编码,又或者开发者想使用SInt16格式的PCM裸数据在其他CPU上进行音频算法计算等的场景下,就需要使用到这个ConvertNode
了。一个典型例子,由FFmpeg解码出来的PCM数据是SInt16
格式的, 因此不能直接输送给RemoteIO Unit进行播放, 所以需要构建一个ConvertNode
将SInt16
格式表示的数据转换为Float32
格式表示的数据, 然后再输送给RemoteIO Unit , 最终才能正常播放出来 -
Time Pitch: 子类型是
kAudioUnitSubType_NewTimePitch
即变速变调效果器, 这是一个很有意思的效果器, 可以对声音的音高, 速度进行调整, 像会说话的Tom猫
类似的应用场景就可以使用这个效果器来实现
5.Generator Units
类型是kAudioUnitType_Generator
, 在开发中我们经常使用它来提供播放器的功能. 其子类型及用途说明如下.
AudioUnitFilePlayer
: 子类型kAudioUnitSubType_AudioFilePlayer
, 在AudioUnit 里面, 如果我们的输入不是麦克风, 而希望其是一个媒体文件, 当然, 也可以类似于代码仓库中的AudioPlayer
项目自行解码, 转换之后将数据输送给RemoteIO Unit播放出来, 但是其实还有一种更简单, 方便的方式. 那就是使用AudioFilePlayer这个 AudioUnit, 可以参考代码仓库中的AUPlayer项目,该项目就是使用AudioUnitFilePlayer
作为输入数据源来提供数据的。需要注意的是, 必须在初始化AUGraph
之后, 再去配置AudioFilerPlayer
的数据源以及播放范围等属性, 否者就会出现错误,其实数据源还是会调用AudioFile的解码功能, 将媒体文件中的压缩数据解压成为PCM裸数据, 最终再交给AudioFilePlayer Unit进行后续处理.
构建AudioUnit
文档中介绍有2套API来创建AudioUnit
- To work with audio units directly—configuring and controlling them—use the functions described in Audio Unit Component Services Reference
- To create and configure an audio processing graph (a processing chain of audio units) use the functions described in Audio Unit Processing Graph Services Reference
为了在运行时找到一个audio unit,需要指定audio component description数据结构中type、subtype、manufacturer的key对应的值
- type - AudioUnit的四大类型的type
- subtype - 该大类型对应的子类型
- manufacturer - 厂商,一般写成
kAudioUnitManufacturer_Apple
如下的例子:
AudioComponentDescription ioUnitDescription;ioUnitDescription.componentType = kAudioUnitType_Output;
ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
ioUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
ioUnitDescription.componentFlags = 0;
ioUnitDescription.componentFlagsMask = 0;
上述代码创建了RemoteIO类型的AudioUnit描述的结构体,那么如何使用这个描述来创建真正的AudioUnit呢?
1.使用audio unit API获取一个audio unit实例
AudioComponent foundIoUnitReference = AudioComponentFindNext (NULL,&ioUnitDescription);
AudioUnit ioUnitInstance;
AudioComponentInstanceNew (foundIoUnitReference,&ioUnitInstance
);
2.使用 audio processing graph API获取一个audio unit实例
// 声明并初始化一个AUGraph
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
);
Audio Units中的Scopes和Elements
When invoking a function to configure or control an audio unit, you specify the scope and element to identify the specific target of the function.
当调用方法来配置or控制audio unit时,其实是指定通过指定scope和element来表示方法的target
- scope - A scope is a programmatic context within an audio unit
- element - An element is a programmatic context nested within an audio unit scope。当一个element是一个input或者output scope的组成部分时,它类似于音频设备中的 signal bus——由于这个原因,有时它也被称为bus。
You specify an element (or bus) by its zero-indexed integer value. If setting a property or parameter that applies to a scope as a whole, specify an element value of 0.
可以通过零索引整数值指定element(或bus)。如果要设置适用于scope的属性或参数,指定element值为0
上图显示的是audio unit的通用架构,它显示的是在input和output中的elements数量是相同的。然而,不同的audio unit其架构是不同的。例如,mixer unit,可能会有多个input element,但只有一个output element。
使用属性配置Audio Units
An audio unit property is a key-value pair you can use to configure an audio unit.
使用AudioUnitSetProperty
方法来在任何audio unit设置属性:
UInt32 busCount = 2;OSStatus result = AudioUnitSetProperty (mixerUnit,kAudioUnitProperty_ElementCount, // the property key 属性的keykAudioUnitScope_Input, // the scope to set the property on 设置属性所在的scope0, // the element to set the property on 设置属性所在的element&busCount, // the property value 属性的值sizeof (busCount)
);
一些常用的属性:
kAudioOutputUnitProperty_EnableIO
- 启用or禁用I/O unit的input or output 。默认情况下,output是启用的,input是禁用的kAudioUnitProperty_ElementCount
- 例如,配置mixer unit中的 input elements 的数量kAudioUnitProperty_MaximumFramesPerSlice
- for specifying the maximum number of frames of audio data an audio unit should be prepared to produce in response to a render call. For most audio units, in most scenarios, you must set this property as described in the reference documentation. If you don’t, your audio will stop when the screen locks.kAudioUnitProperty_StreamFormat
- for specifying the audio stream data format for a particular audio unit input or output bus.
其它的一些方法:
AudioUnitGetPropertyInfo
- To discover whether a property is available; if it is, you are given the data size for its value and whether or not you can change the valueAudioUnitGetProperty
,AudioUnitSetProperty
- To get or set the value of a propertyAudioUnitAddPropertyListener
,AudioUnitRemovePropertyListenerWithUserData
- To install or remove a callback function for monitoring changes to a property’s value
Audio Units 参数
audio unit 参数是用户可以在音频生成的过程中更改,事实上,大部分参数可以在 audio unit 正在执行时实时调整的,例如音量。
AudioUnitGetParameter
AudioUnitSetParameter
I/O Units的特点
I/O unit包含2个elements,如下图所示:
RemoteIO是与硬件IO相关的一个Unit,它分为输入端和输出端(I代表Input,O代表Output)。输入端一般是指麦克风,输出端一般是指扬声器(Speaker)或者耳机。如果需要同时使用输入输出功能,即耳返(在唱歌或者说话时,耳机会将麦克风收录的声音播放处理,让用户能够听到自己的声音),则需要开发者做一些设置将它们连接起来
RemoteIO Unit分为Element0 和 Element1,其中Element0控制输出端,Element1控制输入端,同时每个Element又分为Input Scope和Output Scope。如果开发者想使用扬声器的声音播放功能,那么必须将个Unit的Output Scope和Speaker进行连接。而如果开发者要使用麦克风的录音功能,那么必须将这个Unit的 Element1 的Input Scope和麦克风进行连接。
使用Audio Processing Graphs来管理Audio Units
一个audio processing graph是一个Core Foundation风格的不透明类型, AUGraph
,可使用它来构建和管理一个audio unit processing链。一个graph能够利用多个audio units和多个render callback functions
audio processing graph API使用另一个不透明类型AUNode
,来表示graph上下文中的单个audio unit
总的来说,构建audio processing graph需要三个任务:
- Adding nodes to a graph 添加node到graph
- Directly configuring the audio units represented by the nodes 直接配置nodes表示的audio units
- Interconnecting the nodes 互连node
一个 Audio Processing Graph 只有一个I/O Unit
Every audio processing graph has one I/O unit, whether you are doing recording, playback, or simultaneous I/O.
Graphs let you start and stop the flow of audio by way of the AUGraphStart and AUGraphStop functions. These functions, in turn, convey the start or stop message to the I/O unit by invoking its AudioOutputUnitStart or AudioOutputUnitStop function. In this way, a graph’s I/O unit is in charge of the audio flow in the graph.
Graphs允许你通过
AUGraphStart
和AUGraphStop
函数启动和停止音频流。反过来,这些函数通过调用其AudioOutputUnitStart
或AudioOutputUnitStop
函数将开始或停止消息传递给I/O unit
Audio Processing Graphs Provide Thread Safety
以下是audio processing graph API支持的一些常见重新配置及其相关方法:
- Adding or removing audio unit nodes (
AUGraphAddNode
,AUGraphRemoveNode
) - 添加or移除audio unit node - Adding or removing connections between nodes (
AUGraphConnectNodeInput
,AUGraphDisconnectNodeInput
) - 添加or移除node之间的连接 - Connecting a render callback function to an input bus of an audio unit (
AUGraphSetNodeInputCallback
)
Audio Flows Through a Graph Using “Pull”
在audio processing graph中,consumer调用provider来提供更多的audio data。
每次请求data的过程叫做render call,或者叫做pull。上图中灰色的箭头表示的就是render call,render call请求的data被称为audio sample frames
In turn, a set of audio sample frames provided in response to a render call is known as a slice.
Render Callback Functions Feed Audio to Audio Units
Understanding the Audio Unit Render Callback Function
static OSStatus MyAURenderCallback (void *inRefCon,AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp,UInt32 inBusNumber,UInt32 inNumberFrames,AudioBufferList *ioData
) { /* callback body */ }
- inRefCon,参数指向回调附加到 audio unit 输入时指定的编程上下文
- ioActionFlags,参数允许提供提示当没有音频数据处理时,例如当你的应用程序是合成吉他,用户当面没有播放,请执行此操作。当你想要输出静音,可以在回调主体中使用如下语句:*ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence,并且你必须明确地将 ioData 参数指向的缓冲区设置为0。
- inTimeStamp,回调函数被触发时间,包含一个 AudioTimeStamp 结构体。
- inBusNumber,参数指示调用回调的音频单元总线
- inNumberFrames,当前调用的音频采样数
- ioData,指向音频数据缓存区
Audio Stream Formats Enable Data Flow
AudioStreamBasicDescription的结构
struct AudioStreamBasicDescription {Float64 mSampleRate;UInt32 mFormatID;UInt32 mFormatFlags;UInt32 mBytesPerPacket;UInt32 mFramesPerPacket;UInt32 mBytesPerFrame;UInt32 mChannelsPerFrame;UInt32 mBitsPerChannel;UInt32 mReserved;
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
AudioStreamBasicDescription,可配置audio stream basic description (ASBD)来指定 linear PCM 格式和constant bit rate (CBR)
In Core Audio, the following definitions apply:
-
An audio stream is a continuous series of data that represents a sound, such as a song.
audio stream是连续的一系列data,表示一个sound -
A channel is a discrete track of monophonic audio. A monophonic stream has one channel; a stereo stream has two channels.
chanel是单声道音频的离散轨道。单声道流有一个频道;立体声流有两个通道。 -
A sample is single numerical value for a single audio channel in an audio stream.
-
A frame is a collection of time-coincident samples. For instance, a linear PCM stereo sound file has two samples per frame, one for the left channel and one for the right channel.
frame是时间重合samples的集合。例如,线性PCM立体声声音文件每帧有两个sample,一个用于左声道,一个用于右声道。 -
A packet is a collection of one or more contiguous frames. A packet defines the smallest meaningful set of frames for a given audio data format, and is the smallest data unit for which time can be measured. In linear PCM audio, a packet holds a single frame. In compressed formats, it typically holds more; in some formats, the number of frames per packet varies.
packet是frame的集合 -
The sample rate for a stream is the number of frames per second of uncompressed (or, for compressed formats, the equivalent in decompressed) audio.
sample rate是每秒多个frame
参考:iOS音频开发之AudioStreamBasicDescription
首先,音频文件的产生是模拟信号->PCM以后的数字信号->压缩、编码以后的音频文件。
PCM时采样频率叫做sample rate。
每一次采样可以得到若干采样数据,对应多个channel。
每一个采样点得到的若干采样数据组合起来,叫做一个frame。
若干frame组合起来叫做一个packet。
mSampleRate
,就是采用频率
mBitsPerChannel
,就是每个采样数据的位数
mChannelsPerFrame
,可以理解为声道数,也就是一个采样时刻产生几个采样数据。
mFramesPerPacket
,就是每个packet的中frame的个数,等于这个packet中经历了几次采样间隔。
mBytesPerPacket
,每个packet中数据的字节数。
mBytesPerFrame
,每个frame中数据的字节数
计算公式
1.计算每个packet的持续时间
duration = (1 / mSampleRate) * mFramesPerPacket