构造Audio Unit应用

选择设计模式

有6种设计模式可以用于iOS应用audio unit。首先选择一种最接近你的app处理音频的模式。
其共同特性:

  • 仅有一个I/O unit
  • 在整个audio processing graph中使用单一音频流格式,尽管格式可能会有变化
  • 要求在特定位置设置流的格式或者部分流的格式

正确设置流格式对于建立音频数据流至关重要

I/O传递

将传入的音频直接发送到输出硬件,不需要处理audio data。

音频硬件强制Remote I/O unit输入元素的朝外一侧的流格式,而可以在朝内侧指定期望的格式。audio unit执行需要的格式转换。为避免不必要的采样率转换,请定义采样率为硬件设备采样率

利用两个remote I/O元素间的audio unit connection,不需要设置output元素的input scope格式。connection会为传播在输入元素指定的格式

在output 元素的外侧采用硬件流格式,其会自动执行需要的格式转换

input 元素默认是禁用的,因此在使用时 需要确保启用

使用这个模式 无需配置任何音频数据缓冲区

没有渲染回调的I/O

remote I/O元素间 添加一个或多个audio unit。
这种模式 仍然没有回调函数,就说明了,无法直接操作音频

无论何时,使用I/O unit外的任何unit,都必须要指定kAudioUnitProperty_MaximumFramesPerSlice属性

要设置Multichannel Mixer unit就必须在混音器的输出上设置流格式的采样率

使用这个模式 类似直通模式 也无需配置任何音频数据缓冲区

具有渲染回调的I/O

这种模式,在Remote I/O的输入和输出元素间放置渲染回调函数,可以在音频达到输出硬件前对其进行操作。简单可以使用render回调调整音量,复杂的话,可以通过Accelerate frameworkAccelerate中的Fourier transforms和卷积函数对声音做处理

将回调函数附加到输出元素的输入范围,回调函数通过调用Remote I/O的input元素的渲染回调函数获取新的音频数据

此时,当使用渲染回调函数建立从一个audio unit到另一个unit的路径,回调函数将代替audio unit connection

仅有渲染回调函数的 output

常为音乐游戏和合成器选择这种模式,此时app需要生成声音并最大程度的响应。

简单来说,这个模式将渲染回调函数直接连接到远程I/O unit的输出元素的输入范围

可以使用相同模式构造更复杂结构的app

iPod EQ要求您在输入和输出上都设置完整的流格式

多通道混频器只需要在其输出上设置正确的采样率即可

Multichannel Mixer unit inputs通常需要单独考虑每个音频单元的流格式

其它设计模式

还有两种其他设计模式:

  • 录制或分析音频,创建具有渲染回调功能的仅input app
    一般,更好的选择是使用AudioQueue对象(AudioQueueRef),此时将会有更大灵活性,因为其渲染回调并不在实时线程上

  • 执行离线音频处理,使用Generic Output unit,此unit并未连接到音频硬件。当使用它向应用程序发送音频时,取决于您app来调用其render方法

构建你的app

构建步骤:

  1. 配置audio session
  2. 指定 audio unit
  3. 创建一个audio processing graph 然后获取audio unit
  4. 配置audio unit
  5. 连接audio unit node
  6. 提供用户界面
  7. 初始化 并启动音频处理图

配置audio session

与配置所有iOS音频相同,第一步就是配置audio session

首先指定在app中采用的采样率:self.graphSampleRate = 44100.0; // Hertz
接着使用audio session对象 请求系统将您的首选采样率用作设备硬件采样率,来避免app间的采样率转换,这样可以最大化CPU性能和声音质量,并最大程度地减少电池消耗。

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

还有另外一项硬件特性:音频硬件I/O缓冲区持续时间。在44.1kHZ采样率下,默认持续时间为23ms,相当于1024个采样的切片大小。如果I/O延迟在应用中很重要,可以请求更短的持续时间,大约0.005ms(相当于256个样本)

self.ioBufferDuration = 0.005;
[mySession setPreferredIOBufferDuration: ioBufferDuration
                                  error: &audioSessionError];

参阅Audio Session Programming Guide

指定所需音频单元

使用AudioComponentDescription结构指定所需的每个audio unit.

构建Audio Processing Graph

这一步将创建前面所说的设计模式框架。

//如何对包含Remote I/O单元和Multichannel Mixer unit的图形执行这些步骤
AUGraph processingGraph;
NewAUGraph (&processingGraph);

AUNode ioNode;
AUNode mixerNode;
//
AUGraphAddNode (processingGraph, &ioUnitDesc, &ioNode);
AUGraphAddNode (processingGraph, &mixerDesc, &mixerNode);

此时该graph已实例化,并拥有您将在应用程序中使用的nodes。
然后open graph并初始化audio unit

AUGraphOpen (processingGraph);

然后获取audio unit实例的引用

AudioUnit ioUnit;
AudioUnit mixerUnit;
 
AUGraphNodeInfo (processingGraph, ioNode, NULL, &ioUnit);
AUGraphNodeInfo (processingGraph, mixerNode, NULL, &mixerUnit);

获取了unit对象的引用,就可以配置或者互连这些audio unit

配置audio unit

每个unit都有自己的配置,参阅Using Specific Audio Units

默认 Remote I/O已启用输出并禁用输入。

除了Remote I/OVoice-processing I/O单元外,所有iOS音频单元都需要配置kAudioUnitProperty_MaximumFramesPerSlice属性。此属性可确保audio unit准备好响应渲染调用而产生足够数量的音频数据帧。参阅Audio Unit Properties

所有的audio unit都需要在输入或者输出上定义其频流格式。

编写和渲染回调函数

有关示例,请在iOS库中查看各种音频单元示例项目包括MixerHost、aurioTouch、SynthHost

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

//使用AUGraph 以线程安全的方式
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc        = &renderCallback;
callbackStruct.inputProcRefCon  = soundStructArray;
 
AUGraphSetNodeInputCallback (
    processingGraph,
    myIONode,
    0,                 // output element
    &callbackStruct
);
// ... some time later
Boolean graphUpdated;
AUGraphUpdate (processingGraph, &graphUpdated);

连接 Audio Unit Nodes

大多数情况下,使用AUdio processing Graph中的API AUGraphConnectNodeInput、AUGraphDisconnectNodeInput函数在audio unit间建立和断开连接,这些函数是线程安全的,而且避免显式定义连接的编码开销

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

不推荐:可以直接使用音频单元属性机制在音频单元之间建立和断开连接。为此,请结合使用AudioUnitSetProperty函数和kAudioUnitProperty_MakeConnection属性。此方法要求您为每个连接定义一个AudioUnitConnection结构,以用作其属性值

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)
);

提供用户界面

参阅Use Parameters and UIKit to Give Users Control

初始化并且开始Audio Processing Graph

开始音频流之前,必须通过调用AUGraphInitialize函数来初始化音频处理图。
这一步执行的步骤:

  • 通过自动为每个unit单独调用AudioUnitInitialize函数初始化所有audio unit。
  • 验证图表的连接和音频数据流格式。
  • 在音频单元连接之间传播流格式。
OSStatus result = AUGraphInitialize (processingGraph);
// Check for error. On successful initialization, start the graph...
AUGraphStart (processingGraph);
 
// Some time later
AUGraphStop (processingGraph)

如果graph 初始化失败,可以使用CAShow函数,将graph的状态输出到Xcode控制台

调试

打印ASBD信息 查看问题

- (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);
}

Using Specific Audio Units

Using Specific Audio Units

Using I/O Units

Remote I/O Unit

参见示例代码项目aurioTouch

Using Mixer Units

Multichannel Mixer Unit

示例项目Audio Mixer (MixerHost)

默认情况下,kAudioUnitProperty_MaximumFramesPerSlice属性设置为值1024,这在屏幕锁定和显示器休眠时是不够的。如果您的应用在锁定屏幕的情况下播放音频,则除非音频输入处于活动状态,否则您必须增加此属性的值。进行如下操作:

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

Using Effect Units

Mixer iPodEQ AUGraph Test示例代码项目

各种音频单元标识符