AudioUnit框架详细解析(二十二) —— 构建您的应用程序(一)

版本记录

版本号 时间
V1.0 2018.07.05

前言

AudioUnit框架作为您的应用程序添加复杂的音频操作和处理功能。 创建在主机应用程序中生成或修改音频的音频单元扩展。接下来几篇我们就一起看一下这个框架,感兴趣的看上面几篇文章。
1. AudioUnit框架详细解析(一) —— 基本概览
2. AudioUnit框架详细解析(二) —— 关于Audio Unit Hosting之概览(一)
3. AudioUnit框架详细解析(三) —— 关于Audio Unit Hosting之如何使用本文档和参考资料(二)
4. AudioUnit框架详细解析(四) —— 音频单元提供快速的模块化音频处理之iOS中的Audio Units(一)
5. AudioUnit框架详细解析(五) —— 音频单元提供快速的模块化音频处理之在Concert中使用两个音频单元API(二)
6. AudioUnit框架详细解析(六) —— 音频单元提供快速的模块化音频处理之使用标识符来指定和获取音频单元(三)
7. AudioUnit框架详细解析(七) —— 音频单元提供快速的模块化音频处理之使用范围和元素来指定音频单元的部分(四)
8. AudioUnit框架详细解析(八) —— 音频单元提供快速的模块化音频处理之使用属性来配置音频单元(五)
9. AudioUnit框架详细解析(九) —— 音频单元提供快速的模块化音频处理之使用参数和UIKit为用户提供控制(六)
10. AudioUnit框架详细解析(十) —— 音频单元提供快速的模块化音频处理之I / O单元的基本特性(七)
11. AudioUnit框架详细解析(十一) —— 音频处理图管理音频单元之音频处理图具有一个I / O单元(一)
12. AudioUnit框架详细解析(十二) —— 音频处理图管理音频单元之音频处理图提供线程安全性(二)
13. AudioUnit框架详细解析(十三) —— 音频处理图管理音频单元之音频使用Pull通过图表(三)
14. AudioUnit框架详细解析(十四) —— 呈现回调函数将音频馈送到音频单元之了解音频单元呈现回调函数(一)
15. AudioUnit框架详细解析(十五) —— 音频流格式启用数据流之使用AudioStreamBasicDescription结构(一)
16. AudioUnit框架详细解析(十六) —— 音频流格式启用数据流之了解何处以及如何设置流格式(二)
17. AudioUnit框架详细解析(十七) —— 从选择设计模式开始之I / O传递(一)
18. AudioUnit框架详细解析(十八) —— 从选择设计模式开始之没有渲染回调函数的I / O(二)
19. AudioUnit框架详细解析(十九) —— 从选择设计模式开始之具有渲染回调函数的I / O(三)
20. AudioUnit框架详细解析(二十) —— 从选择设计模式开始之仅具有渲染回调函数的输出(四)
21. AudioUnit框架详细解析(二十一) —— 从选择设计模式开始之其他音频单元Hosting设计模式(五)

Constructing Your App - 构建您的应用程序

无论您选择哪种设计模式,构建音频单元hosting应用程序的步骤基本相同:

    1. 配置音频会话。
    1. 指定音频单元。
    1. 创建音频处理图,然后获取音频单元。
    1. 配置音频单元。
    1. 连接音频单元节点。
    1. 提供用户界面。
    1. 初始化然后启动音频处理图。

Configure Your Audio Session - 配置音频会话

接下来,使用音频会话对象请求系统使用您的首选采样率作为设备硬件采样率,如Listing 2-1所示。 这里的目的是避免硬件和您的应用程序之间的采样率转换。 这可以最大限度地提高CPU性能和音质,并最大限度地减少电池消耗。

Listing 2-1 Configuring an audio session
    self.graphSampleRate = 44100.0; // Hertz
 
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. 获取应用程序的单例音频会话对象的引用。
    1. 请求硬件采样率。 系统可能会或可能不会授予请求,具体取决于设备上的其他音频活动。
    1. 请求所需的音频会话类别。 此处指定的play and record类别支持音频输入和输出。
    1. 请求激活您的音频会话。
    1. 激活音频会话后,根据系统提供的实际采样率更新您自己的采样率变量。

您可能需要配置另一个硬件特性:音频硬件I / O缓冲区持续时间。 在44.1 kHz采样率下,默认持续时间约为23 ms,相当于1,024个样本的切片大小。 如果您的应用中的I / O延迟至关重要,您可以请求较小的持续时间,最低约0.005毫秒(相当于256个样本),如下所示:

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

有关如何配置和使用音频会话对象的完整说明,请参阅Audio Session Programming Guide


Specify the Audio Units You Want - 指定您想要的音频单元

在运行时,运行音频会话配置代码后,您的应用尚未获取音频单元。 您可以使用AudioComponentDescription结构指定所需的每个。 有关如何执行此操作,请参阅Use Identifiers to Specify and Obtain Audio Units。 每个iOS音频单元的标识符键列在Identifier Keys for Audio Units中。

获取指定的音频单元说明符,然后根据您选择的模式构建音频处理图。


Build an Audio Processing Graph - 构建音频处理图

在此步骤中,您将创建本章第一部分中介绍的其中一种设计模式的框架。 具体来说,你:

    1. 实例化AUGraph opaque类型。 该实例表示音频处理图。
    1. 实例化一个或多个AUNode不透明类型,每个类型代表图中的一个音频单元。
    1. 将节点添加到图表中。
    1. 打开图形并实例化音频单元。
    1. 获取音频单元的引用。

Listing 2-2显示了如何为包含远程I / O单元和多通道混合器单元的图形执行这些步骤。 它假设您已经为每个音频单元定义了AudioComponentDescription结构。

Listing 2-2 Building an audio processing graph

AUGraph processingGraph;
NewAUGraph (&processingGraph);

AUNode ioNode;
AUNode mixerNode;

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

AUGraphAddNode函数调用使用音频单元说明符ioUnitDescmixerDesc。 此时,图形将被实例化,并拥有您将在应用程序中使用的节点。 要打开图形并实例化音频单元,请调用AUGraphOpen:

AUGraphOpen (processingGraph);

然后,通过AUGraphNodeInfo函数获取对音频单元实例的引用,如下所示:

AudioUnit ioUnit;
AudioUnit mixerUnit;

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

ioUnitmixerUnit变量现在包含对图形中音频单元实例的引用,允许您配置并互连音频单元。


Configure the Audio Units - 配置音频单元

每个iOS音频设备都需要自己的配置,如Using Specific Audio Units中所述。 但是,有些配置很常见,所有iOS音频开发人员都应该熟悉它们。

默认情况下,远程I / O单元已启用输出并禁用输入。 如果您的应用程序同时执行I / O或仅使用输入,则必须相应地重新配置I / O单元。 有关详细信息,请参阅Audio Unit Properties Reference中的kAudioOutputUnitProperty_EnableIO属性。

除了远程I/O和语音处理I/O单元之外,所有iOS音频单元都需要配置它们的kAudioUnitProperty_MaximumFramesPerSlice属性。此属性确保音频单元准备生成足够数量的音频数据帧以响应呈现调用。有关详细信息,请参见Audio Unit Properties Reference中的kAudioUnitProperty_MaximumFramesPerSlice

所有音频单元都需要在输入、输出或两者上定义它们的音频流格式。有关音频流格式的解释,请参阅Audio Stream Formats Enable Data Flow。对于各种iOS音频单元的特定流格式要求,请参见Using Specific Audio Units


Write and Attach Render Callback Functions - 编写并附加呈现回调函数

对于采用渲染回调函数的设计模式,您必须编写这些函数,然后将它们附加到正确的点。Render Callback Functions Feed Audio to Audio Units介绍了这些回调的作用并说明了它们的工作原理。 有关工作回调的示例,请查看iOS参考库中的各种音频单元示例代码项目,包括音频混合器(MixerHost)aurioTouch以及SynthHost

当音频不流动时,您可以使用音频单元API立即附加渲染回调,如Listing 2-3所示。

Listing 2-3 Attaching a render callback immediately

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

通过使用音频处理图API,您可以以线程安全的方式附加渲染回调,即使在音频流动时也是如此。 Listing 2-4显示了如何做。

Listing 2-4 Attaching a render callback in a thread-safe manner
 
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc        = &renderCallback;
callbackStruct.inputProcRefCon  = soundStructArray;
AUGraphSetNodeInputCallback (
    processingGraph,
    myIONode,
    0,                 // output element
    &callbackStruct
);
// ... some time later
Boolean graphUpdated;
AUGraphUpdate (processingGraph, &graphUpdated);

Connect the Audio Unit Nodes - 连接音频单元节点

在大多数情况下,使用音频处理图API中的AUGraphConnectNodeInputAUGraphDisconnectNodeInput函数建立或断开音频单元之间的连接是最好和最容易的。 这些函数是线程安全的,可以避免显式定义连接的编码开销,因为在不使用图形时必须这样做。

Listing 2-5显示了如何使用音频处理图API将混合器节点的输出连接到I / O单元输出element的输入。

Listing 2-5 Connecting two audio unit nodes using the audio processing graph API

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属性,如Listing 2-6所示。 此方法要求您为每个连接定义AudioUnitConnection结构以用作其属性值。

Listing 2-6 Connecting two audio units directly
 
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,
    ioUnitOutputElement,
    &mixerOutToIoUnitIn,
    sizeof (mixerOutToIoUnitIn)
);
// destination scope
// destination element
// connection definition

Provide a User Interface - 提供一个用户界面

此时,在构建应用程序时,音频单元(通常是音频处理图形)已完全构建和配置。在许多情况下,您需要提供一个用户界面,让您的用户微调音频行为。您可以定制用户界面以允许用户调整特定的音频单元参数,并在某些特殊情况下调整音频单元属性。在任何一种情况下,用户界面还应提供有关当前设置的视觉反馈。

Use Parameters and UIKit to Give Users Control介绍了构建用户界面以让用户控制参数值的基础知识。有关工作示例,请查看示例代码项目Audio Mixer(MixerHost)

iPod EQ单元是一种不寻常的情况,为了更改其活动均衡曲线,您可以更改kAudioUnitProperty_PresentPreset属性的值。无论音频是否正在运行,您都可以执行此操作。有关工作示例,请查看示例代码项目iPhoneMixerEQGraphTest


Initialize and Start the Audio Processing Graph - 初始化并启动音频处理图

在开始音频流之前,必须通过调用AUGraphInitialize函数来初始化音频处理图。 这个关键步骤:

  • 通过为每个音频单独自动调用AudioUnitInitialize函数来初始化图形所拥有的音频单元。 (如果要在不使用图形的情况下构建处理链,则必须依次显式初始化每个音频单元。)
  • 验证图形的连接和音频数据流格式。
  • 在音频单元连接上传播流格式。

Listing 2-7显示了如何使用AUGraphInitialize。

Listing 2-7 Initializing and starting an audio processing graph

OSStatus result = AUGraphInitialize (processingGraph);
// Check for error. On successful initialization, start the graph...
AUGraphStart (processingGraph);
// Some time later
AUGraphStop (processingGraph);

后记

本篇主要讲述了构建您的应用程序,感兴趣的给个赞或者关注~~~~

推荐阅读更多精彩内容