Apple - Audio Unit Programming Guide

本文翻译自:Audio Unit Programming Guide(更新日期:2014-07-15
https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/Introduction/Introduction.html


文章目录


一、介绍

本文档介绍了音频单元及其创建方法。
音频单元 是基于 Apple 世界一流的 OS X Core Audio 技术的数字音频插件。

重要提示: 本文档介绍了如何设计和构建版本 2(V2)音频单元,该版本将在未来的操作系统版本中被弃用。

所有新的音频单元开发都应采用音频单元 V3 API。
有关音频单元 V3 API 的最新信息,请参阅*音频单元框架参考应用扩展编程指南中的*音频单元章节。


作为业余爱好者或计算机科学专业的学生,您可以设计和构建自己的音频单元,让 GarageBand 等应用程序通过声音实现新的作用。

作为商业开发者,您可以创建专业品质的软件组件,提供滤波、混响、动态处理和基于样本的循环等功能。
您还可以创建简单或复杂的基于 MIDI 的音乐合成器,以及更具技术性的音频单元,例如时间和音调变换器以及数据格式转换器。

作为 Core Audio 的一部分并成为 OS X 不可或缺的一部分,音频单元提供了一种音频插件开发方法,在性能、稳定性和部署简易性方面均表现优异。
借助音频单元,您还可以为最终用户提供一致、简单的体验。

您的目标市场很广泛,包括表演者、DJ、录音和母带工程师以及任何喜欢在 iMac 上播放声音的人。

注意: 音频单元编程指南* 的第一个版本没有深入探讨一些对商业音频单元开发人员来说很重要的主题,例如版权保护、参数自动化和自定义视图(音频单元的图形用户界面)。
除了最常见的类型(效果单元)之外,此版本也没有提供开发其他类型音频单元的说明。


谁应该阅读本文档?

要使用本文档,您应该已经熟悉 C 编程语言。
您应该能够熟练使用 Xcode 创建 OS X 插件,如 Xcode 概述 中所述。
例如,您应该了解 Xcode 的各种构建选项(如 ZeroLink)以及何时使用它们。
您还应该知道如何以及为何在 Xcode 构建的链接阶段包含框架和文件。

在使用本文档时,熟悉 C++ 编程语言以阅读头文件和实现文件会非常有帮助。
对 OS X 组件管理器(如 QuickTime 的组件管理器 中所述)有基本的了解,以及对数字音频编码和音频 DSP(数字信号处理)有一定了解也会很有帮助。

本文档不满足音频单元主机应用程序开发人员的需求,他们的代码打开、连接和使用音频单元。
本文档也不是音频单元手册。
它用很少的篇幅来介绍 DSP 或音乐合成技术,这些技术在音频单元中的工作方式与其他音频软件技术基本相同


本文档的组织

根据您的需求和学习风格,您可以通过以下方式使用本文档。


本文档包含以下章节:

  • 音频单元开发基础知识,音频单元开发的鸟瞰图,涵盖 Xcode、Core Audio SDK、设计、开发、测试和部署
  • 音频单元,针对音频单元中执行音频工作的部分进行设计和编程考虑
  • 音频单元视图,两种音频单元视图类型(通用和自定义)的描述以及参数自动化的解释
  • Core Audio SDK 快速导览:利用 Core Audio SDK 中的代码是音频单元开发的最快途径
  • 教程:使用通用视图构建简单的效果单元,本教程将带你从零开始构建一个功能齐全的效果单元
  • 附录:音频单元类层次结构,Core Audio SDK 提供的音频单元类层次结构的概览

音频单元开发取得新进展

要根据您在这里学到的知识继续开发自己的音频单元,您将需要:

  • 使用 C++ 编程语言开发插件的能力,因为 Core Audio SDK 中的音频单元类层次结构使用 C++。
  • 音频 DSP 基础知识,包括必备的数学知识。
    或者,您可以与能够为您的音频单元提供 DSP 代码的人以及能够跨越音频单元和数学领域的人合作。
    为了获得最佳的音频单元质量和性能,DSP 代码需要正确地整合到音频单元框架中。
  • 如果您正在开发仪器单元,则需要具备 MIDI 基础知识。

音频单元开发所需的工具

当您完整安装当前版本的 OS X(包括 Xcode Tools)时,您的系统上将拥有音频单元开发所需的一切。
这些项目也可以从 Apple 的开发者网站http://developer.apple.com免费获取:

  • Xcode 的最新版本。 本文档中的示例使用 Xcode 版本 2.4。
  • 最新的 OS X 头文件。 本文档中的示例使用随 Apple 的 Xcode Tools 安装的 10.4.0 OS X SDK 中的头文件。
  • 最新的 Core Audio 开发工具包。 本文档中的示例使用 Core Audio SDK v1.4.3,与 Apple 的 Xcode Tools 一起安装在您系统的以下位置:/Developer/Examples/CoreAudio
  • 至少一个音频单元托管应用程序。 Apple建议使用 AU Lab 应用程序,该应用程序与 Apple 的 Xcode Tools 一起安装在系统的以下位置:/Developer/Applications/Audio/AU Lab
  • 音频单元验证命令行工具auval,Apple 的音频单元验证工具,随 OS X 提供。

也可以看看

当您了解如何开发音频单元时,您可能会发现以下信息和工具很有帮助:


二、音频单元开发基础知识

当您着手创建音频单元时,Core Audio 的 Audio Unit 框架 的强大功能和灵活性可让您随时随地使用声音。
然而,这种强大功能和灵活性 也意味着需要学习很多东西 才能顺利起步。
在本章中,您将全面了解这项前沿技术,帮助您迈出成为音频单元开发人员的第一步。

您首先会快速了解音频单元开发周期。
然后,您将重点了解音频单元是什么,并发现 Core Audio SDK 在音频单元开发中的重要作用。
您将了解音频单元如何在 OS X 中作为插件发挥作用,并与使用它们的应用程序协同工作。
最后,您将了解音频单元规范,以及它如何定义音频单元开发人员和应用程序开发人员都编写的插件 API 。

阅读完本章后,您将可以深入了解 音频单元 (Audio Unit) 中介绍的架构和开发细节。

如果您想立即开始构建音频单元,可以暂时跳过本章,直接转到 教程:使用通用视图构建简单效果单元
在构建音频单元时,您可以参考本章和本文档中的其他部分,了解与您正在做的事情相关的概念信息。


1、音频单元开发周期

音频单元开发通常遵循以下步骤:

  1. 设计音频单元:指定音频单元的动作、编程和用户界面以及捆绑配置信息。
  2. 创建并配置适当的 Xcode 项目。
  3. 实现音频单元,包括参数、工厂预设和属性(所有这些都将在下一章中描述);如果需要,实现复制保护;实现合成、DSP 或数据格式转换代码。
  4. 如果需要,实现图形用户界面(称为自定义视图)。
    如果需要,实现参数自动化支持。
  5. 验证并测试音频单元。
  6. 通过将音频单元包打包在安装程序中或提供安装说明来部署该音频单元包。

与任何软件开发一样,每个步骤通常都需要迭代。

本文档后面的教程 “教程:使用通用视图构建简单的效果单元” 将引导您完成大部分步骤。


2、什么是音频单元?

音频单元(audio unit)(在头文件和其他地方通常缩写为AU)是 OS X 插件,可增强 Logic Pro 和 GarageBand 等数字音频应用程序。
您还可以使用音频单元,将音频功能构建到您自己的应用程序中。
从编程上讲,音频单元被打包为一个,并配置为OS X 组件管理器定义的组件。

从更深层次来看,取决于您的观点,音频单元是两种截然不同的东西之一。

从内部来看(在音频单元开发人员看来),音频单元是标准插件 API 内的可执行实现代码。
API 是标准的,因此任何设计用于与音频单元配合使用的应用程序都会知道如何使用您的应用程序。
API 由音频单元规范 定义。

音频单元开发人员可以通过音频单元参数机制为用户或应用程序添加实时控制音频单元的功能。
参数是自描述的;它们的值和功能对于使用音频单元的应用程序是可见的。

从外部来看(从使用音频单元的应用程序的角度来看),音频单元只是其插件 API。
此插件 API 允许应用程序查询音频单元的特定功能,这些功能由音频单元开发人员定义为参数和属性。

由于这种封装,如何实现音频单元完全取决于您。
最快捷的方法(Apple 认可的方法,也是本文档中描述的方法)是将可免费下载的 Core Audio SDK 中的相应 C++ 超类子类化。


音频单元程序结构和生命周期

下图显示了使用 SDK 构建的正在运行的音频单元。
该图显示了音频单元及其视图以及正在使用该音频单元的应用程序(称为主机)的上下文:

图 1-1 使用 Core Audio SDK 构建的正在运行的音频单元

在这里插入图片描述


该图显示了音频单元包的两个不同内部部分:左侧是音频单元本身,右侧是音频单元视图。
音频单元执行音频工作。
视图为音频单元提供图形用户界面,如果您提供,还支持参数自动化。(请参阅支持参数自动化。)
创建音频单元时,通常会将两个部分 打包在同一个包中(稍后您将学习这样做),但它们在逻辑上是独立的代码片段。

音频单元、其视图和主机应用程序通过主机应用程序设置的通知中心相互通信。
这允许所有三个实体保持同步。
通知中心的功能是 Core Audio 音频单元事件 API 的一部分。

当用户首次启动主机应用程序时,音频单元及其视图都未实例化。
在此状态下,除主机应用程序外,图 1-1所示的任何部分均不存在。

音频单元及其视图通过以下两种方式之一存在并发挥作用:

  • 通常,用户会向主机应用程序表明他们想要使用音频单元。
    例如,用户可以要求主机将混响效果应用于音频通道。
  • 对于您提供的用于向自己的应用程序添加功能的音频单元,应用程序会直接打开音频单元,可能是在应用程序启动时。

当主机打开音频单元时,它会将音频单元挂接到主机的音频数据链上——图中用浅黄色(音频数据)箭头表示。
此连接分为两部分:向音频单元提供新的音频数据,以及从音频单元检索已处理的音频数据。

  • 为了向音频单元提供最新的音频数据,主机定义了一个回调函数(由音频单元调用),该函数一次提供一片音频数据。
    一片是若干音频数据帧。
    是所有通道上的一个音频数据样本。
  • 为了从音频单元检索已处理的音频数据,主机需要调用音频单元的渲染方法。

以下是主机应用程序和音频单元之间的音频数据流的流程:

  1. 主机调用音频单元的渲染方法,有效地向音频单元请求一段处理过的音频数据
  2. 音频单元通过调用主机的回调函数来获取一部分音频数据样本进行处理
  3. 音频单元处理音频数据样本,并将结果放在输出缓冲区中,供主机检索
  4. 主机检索处理后的数据,然后再次调用音频单元的渲染方法

图 1-1 的音频单元描述中,外部立方体代表插件 API。
Apple 提供了音频单元规范,该规范定义了各种音频单元类型的插件 API。
当您根据此规范开发音频单元时,它将与同样遵循该规范的任何主机应用程序兼容。

音频单元内部包含编程脚手架,用于将插件 API 连接到您的自定义代码。
当您使用 Core Audio SDK 构建音频单元时,此脚手架以组件管理器的粘合代码形式提供,同时提供 C++ 类层次结构。
图 1-1(相当形象地)将您的自定义代码表示为音频单元内的内部立方体,并将 SDK 的类和粘合代码表示为将内部立方体连接到外部立方体的支柱。

您可以在不使用 Core Audio SDK 的情况下构建音频单元,但这样做需要做大量工作。
Apple 建议您在除最专业的音频单元开发之外的所有情况下使用 Core Audio SDK。

要了解音频单元的内部架构,请阅读音频单元中的音频单元架构


音频单元文件结构

OS X 文件系统中的音频单元如下所示:

图 1-2 OS X 文件系统中的音频单元
在这里插入图片描述


当您使用 Xcode 和提供的音频单元模板构建音频单元时,您的 Xcode 项目会负责适当地打包所有这些部分。

作为一个组件,音频单元具有以下文件系统特征:

  • 它是一个带有 .component 文件扩展名的 bundle。
  • 它是一个package;当用户在 Finder 中查看它时,它看起来是不透明的。

软件包顶级 Contents 文件夹中的信息属性列表 (Info.plist ) 文件 为系统和想要使用音频单元的主机应用程序提供了关键信息。
例如,此文件提供:

  • 以反向域名(或统一类型标识符)形式表示的唯一捆绑标识符字符串。
    例如,对于 Core Audio SDK 中提供的 FilterDemo 音频单元,此标识符为com.apple.demo.audiounit.FilterDemo
  • 软件包中音频单元的正确文件名。
    此文件位于MacOS软件包中的文件夹中。

音频单元包可以包含自定义用户界面,称为视图。
视图的标准位置是音频单元包的文件夹中。
图 1-2Resources所示的音频单元包含这样一个视图,它本身被打包为一个不透明的包。
查看音频单元视图包内部,可以看到视图包文件结构:

图 1-3 OS X 文件系统中的音频单元视图
在这里插入图片描述


当主机应用程序打开音频单元时,它可以询问音频单元是否有自定义视图。
如果有,音频单元可以通过提供视图包的路径来响应。
您可以将视图包放在任何地方,包括网络位置。
但是,通常情况下,视图的打包方式如下图所示。

音频单元包通常包含一个音频单元,如本节所述。
但单个音频单元包可以包含任意数量的音频单元。
例如,Apple 将其所有音频单元打包在一个包中, System/Library/Components/CoreAudio.component
CoreAudio.componentbundle 包括一个 包含所有 Apple 音频单元的可执行代码文件,以及另一个包含所有提供的自定义视图的文件:


图 1-4 OS X 文件系统中的 Apple 音频单元

在这里插入图片描述


一些基本术语

要理解本文档,重要的是要理解术语“音频单元”、“音频单元视图”和“音频单元包”以及它们之间的关系。

  • “音频单元”通常指 音频单元包中 MacOS 文件夹内 的可执行代码,如图 1-2 所示。这是执行音频工作的部分。
    有时,如本文标题所示,“音频单元”在上下文中指整个音频单元包及其内容。
    在这种情况下,“音频单元”一词 对应于用户对 OS X 文件系统中插件 的看法。
  • “音频单元视图(Audio unit view)” 是指音频单元的图形用户界面,如音频单元视图 中所述。
    如图1-2所示,自定义视图的代码 通常位于Resources音频单元包内的文件夹中的自己的包中。
    视图是可选的,因为AudioUnit框架允许主机应用程序根据音频单元中的参数和属性代码创建通用视图。
  • “音频单元包”是指包含音频单元和(可选)自定义视图的文件系统封装。
    当本文档使用“音频单元包”时,封装的特征(例如文件扩展名和 Info.plist 文件)非常重要。
    有时,例如在音频单元的安装位置描述中,“音频单元包”既指内容也指封装。
    在这种情况下,这类似于在谈论文件夹时同时指文件夹及其内容。

3、音频单元作为插件

在本部分中,您将从外到内了解音频单元。
首先,您将了解如何在 Apple 的 AU Lab 主机应用程序中使用音频单元。
然后,您将了解音频单元如何在 OS X 中扮演组件的角色。


插件的本质

插件是具有某些特殊特征的可执行代码。
作为库而非程序,插件无法独立运行。
相反,插件的存在是为了向主机应用程序提供功能。
例如,音频单元可以为 GarageBand 提供向音频信号添加电子管放大器失真的功能。

OS X 提供两种插件技术:Core Foundation 的 CFPlugin 架构和 Component Manager。
音频单元是基于 Component Manager 的插件。
在本节后面,您将了解如何在音频单元中支持 Component Manager。

主机应用程序可以附带插件,在这种情况下,插件的使用对用户来说是透明的。
在其他情况下,用户可以获取插件并将其明确添加到正在运行的应用程序中。

与其他代码库相比,插件的特殊之处在于它们能够动态地为正在运行的主机应用程序提供功能。
您可以在 AU Lab 应用程序中看到这一点,它是 Xcode Tools 安装的一部分。


教程:在主机应用程序中使用音频单元

这个迷你教程通过以下方式说明了插件的动态特性:

  • 将音频单元添加到正在运行的主机应用程序
  • 使用音频单元
  • 从正在运行的主机应用程序中删除音频单元

在此过程中,本教程将向您展示 如何开始使用 非常有用的 AU Lab 应用程序。

  1. 启动 AU Lab 音频单元主机应用程序(在 /Developer/Applications/Audio/中),并创建一个新的 AU Lab 文档。
    除非您已将 AU Lab 配置为使用默认文档样式,否则将打开“创建新文档”窗口。
    如果 AU Lab 已在运行,请选择 文件 > 新建 以显示此窗口。

在这里插入图片描述


确保配置与图中所示的设置相匹配:音频设备为内置音频、输入源为线路输入、输出通道为立体声。
不要配置窗口的“输入”选项卡;稍后您将指定输入。
单击“确定”。

将打开一个新的 AU Lab 窗口,显示您指定的输出通道。

在这里插入图片描述


此时,AU Lab 已经实例化了您计算机上所有可用的音频单元,对它们进行了查询以找出每个单元如何与其他音频单元结合使用等内容,然后再次将它们全部关闭。

(更准确地说,OS X 组件管理器已代表 AU Lab 调用了音频单元的实例化和关闭。
下面的 “音频单元的组件管理器要求”对此进行了解释。)


  1. 在 AU Lab 中,选择“编辑”>“添加音频单元生成器”。
    AU Lab 窗口会打开一个对话框,让您指定要用作音频源的生成器单元。

在这里插入图片描述


在对话框中,确保在 Generator 弹出窗口中选择了 AUAudioFilePlayer 生成器单元。
按照此示例,将 Group Name 更改为 Player。单击 OK。

您可以随时通过在 AU Lab 窗口中双击组名来更改组名。

AU Lab 窗口现在显示立体声输入轨道。
此外,发电机单元的检查器窗口已打开。
如果您关闭检查器,可以通过单击播放器轨道顶部附近的矩形“AU”按钮重新打开它。


在这里插入图片描述


  1. 将一个或多个音频文件添加到播放器检查器窗口中的音频文件列表中。
    通过从 Finder 中拖动音频文件来执行此操作,如图所示。
    将一些音频文件放入播放器检查器窗口中可让您通过 AU Lab 应用程序以及您添加到播放器轨道的音频单元发送音频。
    几乎任何音频文件都可以。对于此示例,音乐文件效果很好。

在这里插入图片描述


现在 AU Lab 已配置完毕,可以添加音频单元了。


  1. 要动态添加音频单元到AU Lab主机应用程序,请点击AU Lab中播放器轨道中效果部分第一行的三角形菜单按钮,如图所示。

在这里插入图片描述


打开菜单,列出系统上所有可用的音频单元,按类别和制造商排列。
AU Lab 从组件管理器获取此列表,组件管理器维护已安装音频单元的注册表。


在这里插入图片描述


从弹出窗口中选择一个音频单元。
按照此示例,从 Apple 子菜单中选择 AUParametricEQ 音频单元。
(此音频单元作为 OS X 的一部分提供,是一个单波段均衡器,可控制中心频率、增益和 Q。)

AU Lab 要求组件管理器实例化您选择的音频单元。
然后 AU Lab 初始化音频单元。
AU Lab 还打开音频单元的 Cocoa 通用视图,该视图显示为实用程序窗口:


在这里插入图片描述

您现在已将 AUParametricEQ 音频单元动态添加到正在运行的 AU Lab 主机应用程序中。


  1. 为了演示 AU Lab 中音频单元的功能,请单击 AUAudioFilePlayer 检查器中的“播放”按钮,通过音频单元发送音频。
    改变通用视图中的滑块以收听音频单元的工作情况。
  2. 要从主机应用程序中移除音频单元,再次单击播放器轨道中效果部分第一行的三角形菜单按钮,如图所示。

在这里插入图片描述


从弹出菜单中,选择“删除 AUParametricEQ”。


在这里插入图片描述


组件管理器代表 AU Lab 关闭音频单元。
现在,您已从正在运行的 AU Lab 主机应用程序中动态删除了音频单元及其功能。


核心音频 SDK 的作用

使用 Core Audio SDK 构建音频单元时,您可以免费获得 Component Manager 脚手架。
您还可以获得对大多数Audio Unit 规范的全面支持。
这让您可以专注于音频单元开发中更有趣的方面:音频处理和用户界面。

通过在 SDK 的音频单元 C++ 类层次结构中对相应类进行子类化,可以创建基于 SDK 的音频单元。
附录:音频单元类层次结构显示了此层次结构。


主机应用程序通过其插件 API 和组件管理器与音频单元通信。
总共有六部分代码相互协作以支持正在运行的音频单元:

  • 音频单元包。
    该包包装了音频单元及其视图(如果您提供了自定义视图),并为音频单元提供了标识,以便 OS X 和组件管理器可以使用音频单元。
  • 音频单元本身。
    当您使用 Core Audio SDK 构建音频单元时(建议如此),音频单元会从 SDK 的类层次结构继承。
  • 音频单元视图。
  • 核心音频 API 框架。
  • 组件管理器。
  • 主机应用程序。

如果您想了解 SDK 的其余部分,请参阅Core Audio SDK 快速浏览。


音频单元的组件管理器要求

组件管理器充当主机应用程序与其使用的音频单元之间的沟通桥梁——代表主机查找、打开、实例化和关闭音频单元。

为了使 OS X 识别您的音频单元,它们必须满足某些要求。
它们必须:

  • 按照组件管理器的定义,打包为组件
  • 具有组件管理器可识别的单一入口点
  • 拥有一个资源(.rsrc)文件,用于指定系统范围内的唯一标识符和版本字符串
  • 响应组件管理器调用

从头开始满足这些要求需要付出巨大努力,并且需要熟练掌握组件管理器 API。
但是,Core Audio SDK 可以帮您免除这些麻烦。
教程:使用通用视图构建简单效果单元 一章中所示,使用 SDK 时,满足组件管理器要求的工作量非常小。


音频单元的安装和注册

OS X 组件管理器在某些特定位置查找音频单元,其中一个位置是保留给 Apple 使用。

在开发或部署期间安装音频单元时,通常将它们放在以下两个位置之一:

  • ~/Library/Audio/Plug-Ins/Components/ : 此处安装的音频单元仅供主文件夹的所有者使用
  • /Library/Audio/Plug-Ins/Components/ : 此处安装的音频单元可供计算机上的所有用户使用

您可以自行决定使用哪个位置或者向用户推荐哪个位置。

OS X 预装的音频单元放置在 Apple 保留的位置:

/System/Library/Components/

组件管理器维护这些位置的音频单元的缓存注册表(以及它在其他标准位置找到的任何其他插件)。
只有注册的音频单元才可用于主机应用程序。
组件管理器会在系统启动时、用户登录时以及三个 Components 文件夹之一的修改时间戳发生变化时刷新注册表。

主机应用程序 可以使用组件管理器的 RegisterComponentRegisterComponentResourceRegisterComponentResourceFile函数显式注册安装在任意位置的音频单元。
以这种方式注册的音频单元 仅供调用注册的主机应用程序使用。
这样,您可以使用音频单元 向正在开发的主机应用程序 添加功能,而无需让其他主机可以使用您的音频单元。


音频单元识别

系统上的每个音频单元都必须具有唯一的签名。
音频单元规范利用这一点,让主机应用程序根据其签名了解任何音频单元的插件 API。
本节介绍其工作原理。

组件管理器通过三个四字符代码来识别音频单元:

  • “类型”指定音频单元提供的功能的一般类型。
    这样,类型还标识了音频单元的插件 API。
    这样,类型代码具有编程意义。
    例如,主机应用程序知道任何 aufx 类型的音频单元(用于“音频单元效果”)都提供 DSP 功能。
    音频单元规范 指定了音频单元的可用类型代码,以及每种音频单元类型的插件 API。
  • “子类型(subtype)”更准确地描述了音频单元的功能,但对于音频单元而言在编程上并不重要。
    例如,OS X 包含一个子类型为lpas 的效果单元,其命名表明它提供低通滤波。
    如果您的音频单元使用音频单元框架头文件 AUComponent.h 中列出的子类型之一(例如'lpas'),则表明音频单元的用户其行为与命名的子类型相同。
    但是,主机应用程序不会根据您的音频单元的子类型对其做出任何假设。
    您可以自由使用任何子类型代码,包括仅以小写字母命名的子类型。
  • “制造商代码(manufacturer code)”标识了音频单元的开发商。
    Apple 希望每位开发者在数据类型注册页面上注册一个制造商代码,即“创建者代码” 。
    制造商代码必须至少包含一个大写字符。
    注册后,您可以为所有音频设备使用相同的制造商代码。

除了这四个字符的代码之外,每个音频单元还必须指定正确格式的版本号。
当组件管理器注册音频单元时,如果系统中存在多个音频单元,它会选择最新版本。

作为组件,音频单元在其资源 (.rsrc ) 文件中将其版本标识为八位十六进制数。
正如您将在 教程:使用通用视图构建简单效果单元 中看到的那样,您可以使用 Xcode 指定此信息。

以下是如何构造版本号的示例。
它使用人为设定的大数字来明确说明格式。
对于十进制版本号29.33.40,其十六进制等价值为0x001d2128
此数字的格式如下:

图 1-5 构建音频单元版本号

在这里插入图片描述


最高四位十六进制数字表示主版本号。
接下来两位表示次版本号。
最低两位数字表示点号。

当您发布音频单元的新版本时,您必须确保其版本号的值高于上一个版本(不等于上一个版本,也不低于上一个版本)。
否则,安装了旧版本音频单元的用户将无法使用新版本。


音频单元的插件 API 要求

音频单元有类型,如上文 音频单元识别 中所述。
当主机应用程序看到音频单元的类型时,它就知道如何与其通信。

从头开始为任何给定类型的音频单元实现插件 API 是一项艰巨的工作。
它需要对音频单元和音频工具箱框架 API 以及音频单元规范有深入的了解。
但是,核心音频 SDK 也为您免去了其中的大部分麻烦。
使用 SDK,您只需实现与您的音频单元相关的方法和属性。
您将后面音频单元 中了解音频单元属性机制。


音频单元规范

音频单元规范定义了音频单元开发人员和主机应用程序开发人员必须支持的通用接口。

注意: 音频单元规范* 文档目前正在开发中。
以下头文件包含与音频单元规范相关的信息AUComponent.hAudioUnitProperties.hMusicDevice.hOutputUnit.h

此外,以下教程文件包含与音频单元规范相关的信息:AUPannerUnits.textOfflineRendering.rtfOfflineAPIAdditions.text


音频单元规范描述:

  • AUComponent.h为音频单元定义的各种 Apple 类型,如Audio Unit 框架头文件中的“AudioUnit 组件类型和子类型”枚举所列
  • 每种音频单元的功能和行为要求
  • 每种音频单元的插件 API,包括必需和可选属性

您可以开发符合音频单元规范的 音频单元。
然后使用下一节中描述的 auval 命令行工具测试此一致性。

音频单元规范为以下音频单元类型定义了插件 API:

  • 效果单元('aufx'),例如音量控制、均衡器和混响,用于修改音频数据流
  • 音乐效果单元('aumf'),例如循环器,它将乐器单元的功能(例如启动和停止样本)与效果单元的功能相结合
  • 离线效果单元('auol'),可让您对音频执行不实时的操作,例如时间反转或前瞻级别标准化
  • 乐器单元('aumu'),以 MIDI 和音库数据作为输入,并提供音频数据作为输出,让用户演奏虚拟乐器
  • 生成器单元('augn'),以编程方式生成音频数据流或播放文件中的音频
  • 数据格式转换器单元('aufc'),用于改变音频数据流的特性,例如位深度、采样率或播放速度
  • 混音器单元('aumx'),用于组合音频数据流
  • 声像单元 ( 'aupn'),使用空间化算法将一组输入通道分配到一组输出通道

4、音频单元作为模型-视图-控制器设计模式的实例

Apple 的核心音频团队围绕一种更流行的软件设计模式“模型-视图-控制器”或 MVC 设计了音频单元技术。
有关此模式的更多信息,请参阅“模型-视图-控制器” 。


构建音频单元时请牢记 MVC 模式:

  • 音频单元作为模型,封装了执行音频工作所需的所有知识
  • 音频单元的视图自然地充当视图,显示音频单元的当前设置并允许用户更改它们
  • 音频单元事件 API 以及音频单元中的代码和调用此 API 的视图对应于控制器,支持音频单元、其视图和主机应用程序之间的通信

5、音频单元实际运行


打开和关闭音频单元

主机应用程序在组件管理器的帮助下负责查找、打开和关闭音频单元。
反过来,音频单元也需要可查找、可打开和可关闭。
当您从 Core Audio SDK 构建音频单元并使用 Xcode 音频单元模板时,您的音频单元会获得这些属性。

音频单元在主机中可供使用需要两个步骤。
这两个步骤是打开和初始化。
打开音频单元相当于实例化音频单元主类的对象。
初始化相当于分配资源,以便音频单元准备好工作。

要成为性能良好、主机友好的插件,音频单元的实例化必须快速且轻量。
音频单元的资源密集型启动工作进入初始化步骤。
例如,使用大量样本数据的乐器单元应在初始化时加载它,而不是实例化时。

有关从音频单元开发人员的角度查找、打开和关闭的更多信息,请参阅 音频单元 中的 音频单元初始化和取消初始化关闭


添加复制保护

如果您选择为音频单元添加版权保护,则考虑音频单元的打开顺序尤为重要。
版权保护的时间是在音频单元初始化期间,而不是实例化期间。
因此,您将版权保护代码 放入 SDK 的AUBase超类方法的 Initialize的重写中。
您不会将 版权保护代码 放入 音频单元的构造函数 中。

这是一个很重要的场景。
假设用户没有您的(受版权保护的)音频单元所需的硬件加密狗。
也许他们在带着笔记本电脑去表演时把它忘在家里了。
如果您的音频单元在实例化时调用其版权保护,这可能会阻止主机应用程序打开。
如果您的音频单元在初始化时调用其版权保护(如建议的那样),表演者至少可以使用主机应用程序。


多重实例

音频单元可以由主机应用程序和任意数量的主机实例化任意次数。
更准确地说,组件管理器代表主机应用程序调用音频单元实例化。
组件管理器基础结构确保每个音频单元实例都存在且独立运行。

您可以在 AU Lab 中演示多个实例。
首先将 AUParametricEQ 效果单元的一个实例添加到 AU Lab 文档中,如上文教程:在主机应用程序中使用音频单元中所述。
然后调用播放器轨道中效果部分其他行中的弹出菜单。
您可以根据需要向轨道添加任意数量的单频段参数均衡器。
音频单元的每个实例都独立运行,如图中不同的设置所示:

图1-6 AU Lab中音频单元的多个实例
在这里插入图片描述


音频处理图和拉模型

主机应用程序可以将音频单元相互连接,以便一个音频单元的输出为下一个音频单元的输入提供信息。
这种相互连接的音频单元系列称为音频处理图
在图中,每个连接的音频单元称为节点

当您完成本章前面的教程:在主机应用程序中使用音频单元部分时,AU Lab 应用程序为您构建了一个音频处理图。
该图由 AUAudioFilePlayer 生成器单元、AUParametricEQ 效果单元以及最后(未在 AU Lab 的用户界面中显示)Apple 提供的与扬声器等外部硬件接口的 AUHAL I/O 单元组成。

音频处理图连接提供了这些连接如何工作的详细信息。

Audio Processing Graph API 在 Audio Toolbox 框架中声明,它提供了用于帮助主机应用程序创建和管理音频处理图的接口。
当主机应用程序使用此 API 时,它会使用一种称为图对象(AUGraph 类型)的不透明数据类型。

某些应用程序(例如 AU Lab)在连接音频单元时始终使用图形对象。
其他应用程序(例如 Logic)则直接将音频单元相互连接。
但是,单个音频单元并不知道其连接是由图形对象代表主机应用程序管理,还是由主机直接管理。

正如您所期望的那样,图中的音频数据流从第一个(输入)节点流向最后一个(输出)节点。
但是,控制权从最后一个节点流回到第一个节点。
在 Core Audio 中,这称为拉动模型
主机应用程序负责调用拉动。

您可以将拉模型想象成一杯水中的吸管。
杯子里的水代表等待处理的新鲜音频数据。
吸管代表音频处理图,甚至是一个音频单元。
作为主机应用程序,您可以通过“拉”(啜饮)吸管末端来开始音频数据流。
具体来说,主机应用程序通过调用图中最终节点的渲染方法来启动音频数据流。
您通过吸管喝的每一口都相当于再次拉动一片音频数据帧 - 再次调用最终节点的渲染方法。

在音频或控制流开始之前,主机应用程序会执行将音频单元相互连接的工作。
在如图所示的情况下,主机还会与音频处理图建立连接。
但主机不一定会将音频数据提供给它们使用的图。
如果图中的第一个音频单元是生成器单元,则没有输入连接;生成器通过算法或播放文件来提供音频数据。

图 1-7显示了主机应用程序按顺序使用两个效果单元的特定情况下的拉模型。

图 1-7 具有两个效应单元的拉动模型

在这里插入图片描述


图 1-7中拉动过程如下:

  1. 主机应用程序调用图中最后一个节点(效果单元 B)的 render 方法,请求一个切片的已处理音频数据帧。
  2. 效果单元 B 的渲染方法在其输入缓冲区中查找要处理的音频数据,以满足渲染调用。
    如果有音频数据等待处理,效果单元 B 将使用它。
    否则,如图所示,效果单元 B(使用 SDK 音频单元类层次结构中的超类)将调用主机已连接到效果单元 B 的输入的渲染方法。
    在此示例中,效果单元 A 已连接到 B 的输入 — 因此效果单元 B 拉动效果单元 A,请求切片音频数据帧。
  3. 效果单元 A 的行为与效果单元 B 的行为相同。
    当它需要音频数据时,它会从其输入连接中获取数据,该连接也是由主机建立的。
    主机将效果单元 A 的输入连接到主机中的渲染回调。
    效果单元 A 会拉取主机的渲染回调。
  4. 主机的渲染回调将请求的音频数据帧提供给效果单元 A。
  5. 效果单元 A 处理主机提供的数据片段。
    然后,效果单元 A 将之前请求的已处理音频数据帧(在步骤 2 中)提供给效果单元 B。
  6. 效果单元 B 处理效果单元 A 提供的数据片段。
    然后,效果单元 B 将最初请求的已处理音频数据帧(在步骤 1 中)提供给主机应用程序。
    这样就完成了一个拉取周期。

音频单元通常不知道其输入和输出是否连接到其他音频单元、主机应用程序或其他东西。
音频单元只响应渲染调用。
主机负责建立连接,超类(对于使用 Core Audio SDK 构建的音频单元)负责实现拉取。

作为音频单元开发人员,您无需直接使用音频处理图,只需确保您的音频单元能够很好地与它们配合使用即可。
为此,您可以通过确保您的音频单元通过 Apple 的验证测试(如使用auval 工具进行音频单元验证中所述)来完成部分工作。
您还应该通过使用主机应用程序将您的音频单元连接到各种处理图中来执行测试,如音频单元测试和主机应用程序中所述。


处理:问题的核心

当然,音频单元会处理音频数据。
它们还需要知道如何正常停止处理,以及如何根据用户的调整修改其处理。
本节简要讨论这些内容。
音频单元更详细地描述了处理。


加工

处理音频数据的音频单元(例如效果单元)以渲染周期的方式工作。
在每个渲染周期中,音频单元:

  • 获取要处理的新音频数据帧片段。
    它通过调用已在音频单元中注册的渲染回调函数来实现此目的。
  • 处理音频数据帧。
  • 将生成的音频数据帧放入音频单元的输出缓冲区。

音频单元在主机应用程序的命令下完成这项工作。
主机应用程序还设置每个切片的音频数据帧数。
例如,AU Lab 默认使用每个切片 512 帧,您可以将这个数字从 24 变为 4,096。
请参阅使用 AU Lab 进行测试

渲染下一帧片段的编程调用可以从以下两个地方到达:

  • 从主机应用程序本身,在主机直接使用音频单元的情况下
  • 如果音频单元是音频处理图的一部分,则从音频单元的下游邻居开始

无论调用上下文如何,音频单元的行为都完全相同 - 也就是说,无论是主机应用程序还是请求音频数据的下游音频单元。


重置

音频单元还需要能够从容地停止渲染。
例如,实现 IIR 滤波器的音频单元使用内部样本缓冲区。
它在将频率曲线应用于正在处理的样本时使用这些缓冲样本的值。
假设此类音频单元的用户停止播放音频文件,然后在文件的不同位置重新开始。
在这种情况下,音频单元必须从空的处理缓冲区开始,以避免产生伪影。

开发音频单元的 DSP 代码时,您需要实现一种Reset方法,将音频单元的 DSP 状态返回到音频单元首次初始化时的状态。
主机应用程序会Reset根据需要调用该方法。


渲染时调整

当音频单元正在渲染时,用户可以使用音频单元的视图调整渲染行为。
例如,在参数滤波器音频单元中,用户可以调整中心频率。
主机应用程序也可以使用参数自动化来改变渲染,下一节将对此进行介绍。


支持参数自动化

参数让用户调整音频单元。
例如,Apple 的低通滤波器音频单元具有截止频率和共振参数。

参数自动化让用户可以沿时间线对参数调整进行编程。
例如,用户可能希望使用低通滤波器音频单元来提供类似吉他哇音踏板的效果。
借助参数自动化,用户可以录制哇音效果并将其作为音乐作品的一部分。
主机应用程序记录手动更改以及同步信息,将更改与音轨的时间标记联系起来。
然后,主机可以播放参数更改以提供对音频单元的自动控制。

主机应用程序还可以为用户提供间接指定参数操作的功能。
例如,主机可以让用户沿着音轨的波形表示绘制增益或声像曲线。
然后主机可以将此类图形输入转换为参数自动化数据。


参数自动化依赖于三件事:

  • 音频单元根据主机应用程序的请求以编程方式更改其参数值的能力
  • 当用户更改参数值时,音频单元视图能够发布通知
  • 主机应用程序支持参数自动化数据的记录和回放的能力

一些支持音频单元参数自动化的主机是 Logic Pro、Ableton Live 和 Sagan Metro。

参数自动化使用 Audio Unit Event API,该 API 在AudioUnitUtilties.h头文件中声明为 Audio Toolbox 框架的一部分。
此线程安全 API 提供了一种通知机制,支持保持音频单元、其视图和主机同步。

要支持音频单元中的参数自动化,您必须创建自定义视图。
您可以向视图的可执行代码添加自动化支持,利用音频单元事件 API 支持以下部分或全部事件类型:

  • 参数手势,包括 kAudioUnitEvent_BeginParameterChangeGesturekAudioUnitEvent_EndParameterChangeGesture 事件类型
  • 参数值改变,通过kAudioUnitEvent_ParameterValueChange事件类型识别
  • 属性更改,由kAudioUnitEvent_PropertyChange事件类型识别

在某些特殊情况下,您可能需要为音频单元本身添加对参数自动化的支持。
例如,您可以创建一个具有可调上限和下限转角频率的带通滤波器。
然后,您的音频单元需要确保上限频率永远不会低于下限频率。
在这种情况下,当音频单元调用参数更改时,它需要发出参数更改通知。

音频单元视图定义和使用参数提供了有关参数自动化的更多信息。


6、音频单元验证和测试


使用 auval 工具进行音频单元验证

Apple 强烈建议您auval在开发过程中使用命令行工具验证音频单元。
auval工具(名为“音频单元验证”的缩写)随 OS X 一起提供。它会对以下设备执行一整套测试:

  • 音频单元的插件 API,由其编程类型定义
  • 音频单元的基本功能包括哪些音频数据通道配置可用、实例化音频单元所需的时间以及音频单元渲染音频的能力

auval工具仅测试音频单元本身。它不测试以下任何内容:

  • 音频单元视图
  • 音频单元架构,使用推荐的模型视图控制器设计模式来分离关注点
  • 正确使用 Audio Unit Event API
  • DSP 的质量、音频生成的质量或音频数据格式转换的质量

auval工具可以验证 Apple 定义的每种音频单元。
运行该工具时,它会输出测试日志,并以“通过”或“失败”指示总结结果。

有关更多信息,请参阅auval内置帮助系统。
要查看auval帮助文本,请在终端应用程序的提示符下输入以下命令:

auval -h

音频单元测试和主机应用程序

当您按照音频单元规范进行构建时,您就做了正确的事情。
这样的音频单元应该适用于所有主机。
但实际上,只有在商业应用中测试音频单元后,开发才算完成。
原因包括:

  • Core Audio 框架和 SDK 的演变
  • 不同主机应用程序版本之间的差异
  • 某些主机应用程序实现中的特质

随着识别音频单元的主机应用程序的激增,在所有潜在主机中测试音频单元的任务变得更加复杂。

这种情况有点类似于在各种浏览器中测试网站:您的代码可能完全符合相关规范,但在某些浏览器中不符合规范,则需要您进行补偿。

考虑到这一点,以下部分概述了基于主机的音频单元测试。


使用 AU Lab 进行测试

AU Lab 是您在教程:在主机应用程序中使用音频单元中使用的应用程序,它是参考音频单元主机。
它由 Apple 的 Core Audio 团队积极开发。
他们使其与 auval工具、Core Audio 框架和 SDK 以及 OS X 本身保持同步。
这使得 AU Lab 成为测试音频单元的首选。


您可以使用 AU Lab 测试什么

使用 AU Lab 测试您的音频单元可让您测试:

  • 行为,即被主机发现、显示在菜单中以及打开
  • 视图,包括通用视图和自定义视图
  • 听觉表现
  • 放置在音频处理图中时与其他音频单元的交互
  • I/O 功能,例如侧链和多输出,以及单声道和立体声操作的基本测试

在 OS X v10.4 “Tiger” 中,AU Lab 可让您测试以下类型的音频单元:

  • 转换单位
  • 效果单元
  • 发电机组
  • 仪器单位

改变主机应用程序的特征

AU Lab 允许您控制其部分托管特性,从而让您测试音频单元在不同条件下的行为。
例如,您可以更改每个渲染周期中要处理的音频数据帧数。
您可以使用“设备偏好设置”执行此操作。

在 AU Lab 中,从 AU Lab 菜单中选择“首选项”。
单击“设备”以显示“设备首选项”:


在这里插入图片描述


点击“帧”弹出菜单。
您可以选择音频单元在每个渲染周期中要处理的帧数:


在这里插入图片描述


单击显示三角形以选择“专家设置”。
您可以调整滑块以选择用于音频处理的 CPU 时间百分比。
这可让您测试音频单元在不同负载条件下的行为:


在这里插入图片描述


音频单元的自定义测试

作为音频设备开发人员,您需要了解目标市场正在使用的主机应用程序。
Apple 建议您至少使用 Apple 的专业主机应用程序套件来测试您的音频设备:

  • GarageBand
  • Logic Pro
  • Soundtrack Pro
  • Final Cut Pro

有许多第三方和开源应用程序支持音频单元,其中包括 Ableton Live、Amadeus、Audacity、Cubase、Digital Performer、DSP-Quattro、Peak、Rax 和 Metro。


三、音频单元

开发音频单元时,首先要开发执行音频工作的部分。
此部分位于MacOS音频单元包内的文件夹中,如图1-2所示。
您可以随意添加自定义用户界面或视图,如下一章“音频单元视图”中所述。

在本章中,您将了解音频单元的架构和编程元素。
您还将了解创建音频单元时要采取的步骤。


1、音频单元架构

音频单元的内部架构由范围、元素、连接和通道组成,所有这些都为音频处理代码提供服务。
图 2-1说明了这些部分在典型效果单元中的存在情况。本节依次介绍每个部分。
有关图中标记为DSP 的部分(表示效果单元中的音频处理代码)的讨论,请参阅合成、处理和数据格式转换代码


图 2-1 效果单元的音频单元架构
在这里插入图片描述


1.1 音频单元范围

音频单元范围是编程上下文。
然而,与一般的计算机科学范围概念不同,音频单元范围不能嵌套。
每个范围都是一个离散上下文。

在编写设置或检索参数和属性值的代码时,可以使用范围。
例如,清单 2-1显示了标准方法的实现GetProperty,该方法用于在教程:使用通用视图构建简单效果单元中构建的效果单元


例 2-1 在 GetProperty 方法中使用“scope”

ComponentResult TremoloUnit::GetProperty (
    AudioUnitPropertyID    inID,
    AudioUnitScope         inScope,   // the host specifies the scope
    AudioUnitElement       inElement,
    void                   *outData
) {
    return AUEffectBase::GetProperty (inID, inScope, inElement, outData);
}

当主机应用程序调用此方法来检索属性的值时,主机将指定定义该属性的范围。
GetProperty 方法的实现 反过来可以使用如下代码响应各种范围:

if (inScope == kAudioUnitScope_Global) {
    // respond to requests targeting the global scope
} else if (inScope == kAudioUnitScope_Input) {
    // respond to requests targeting the input scope
} else {
    // respond to other requests
}

AudioUnitProperties.hApple在Audio Unit框架的头文件中定义了五个作用域,如清单2-2所示:


例 2-2 音频单元范围

enum {
    kAudioUnitScope_Global   = 0,
    kAudioUnitScope_Input    = 1,
    kAudioUnitScope_Output   = 2,
    kAudioUnitScope_Group    = 3,
    kAudioUnitScope_Part     = 4
};

其中最重要的三个范围是:

  • 输入范围:音频数据进入音频单元的上下文。
    音频单元、主机应用程序或音频单元视图中的代码可以处理音频单元的输入范围,例如:
    • 定义附加输入元素的音频单元
    • 音频单元或主机设置输入音频数据流格式
    • 音频单元视图设置混频器音频单元上的各种输入级别
    • 将音频单元连接到音频处理图的主机应用程序
      主机应用程序在注册渲染回调时也会使用输入范围,如渲染回调连接中所述。
  • 输出范围:离开音频单元的音频数据的上下文。
    输出范围的用途与输入范围的用途大致相同:连接、定义其他输出元素、设置输出音频数据流格式,以及在混音器单元具有多个输出的情况下设置输出级别。
    主机应用程序或音频处理图中的下游音频单元在调用渲染时也会处理输出范围。
  • 全局范围:适用于整个音频单元的音频单元特征的上下文。
    音频单元内的代码处理其自己的全局范围,用于设置或获取以下属性的值:
    • latency,
    • 尾部时间,以及
    • 支持的频道数量。

主机应用程序还可以查询音频单元的全局范围来获取这些值。

还有两个额外的音频单元范围,用于乐器单元,定义在AudioUnitProperties.h

  • 组范围:特定于乐器单位中音符渲染的环境
  • 部分范围:用于管理多音色乐器单元的各种声音的特定环境

此版本的音频单元编程指南不讨论组范围或部分范围。


1.2 音频单元元素

音频单元元素是嵌套在范围内的编程上下文。
最常见的情况是,元素在输入和输出范围内发挥作用。
在这里,它们充当硬件音频设备中使用的信号总线的编程模拟。
由于这种类比,音频单元开发人员通常将输入或输出范围内的元素称为总线;本文档也如此。

您可能已经在清单 2-1中注意到,主机在获取或设置属性或参数时指定元素及其目标范围。
下面再次显示了该方法,其中突出显示了inElement参数:


例 2-3 在 GetProperty 方法中使用“element”

ComponentResult TremoloUnit::GetProperty (
    AudioUnitPropertyID    inID,
    AudioUnitScope         inScope,
    AudioUnitElement       inElement,  // the host specifies the element here
    void                   *outData
) {
    return AUEffectBase::GetProperty (inID, inScope, inElement, outData);
}

元素由整数标识,并以零为索引。
在输入和输出范围内,元素编号必须是连续的。
在典型情况下,输入和输出范围各有一个元素,即元素(或总线)0

音频单元中的全局范围不同寻常,因为它始终只有一个元素。
因此,全局范围的单个元素始终是 element 0

总线(即输入或输出元素)始终只有一种流格式。
流格式指定总线的各种特性,包括采样率和通道数。
流格式由音频流描述结构( AudioStreamBasicDescription) 描述,该结构在头文件 CoreAudioTypes.h 中声明,如清单 2-4所示:


例 2-4 音频流描述结构

struct AudioStreamBasicDescription {
    Float64 mSampleRate;        // sample frames per second
    UInt32  mFormatID;          // a four-char code indicating stream type
    UInt32  mFormatFlags;       // flags specific to the stream type
    UInt32  mBytesPerPacket;    // bytes per packet of audio data
    UInt32  mFramesPerPacket;   // frames per packet of audio data
    UInt32  mBytesPerFrame;     // bytes per frame of audio data
    UInt32  mChannelsPerFrame;  // number of channels per frame
    UInt32  mBitsPerChannel;    // bit depth
    UInt32  mReserved;          // padding
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;

音频单元可以让主机应用程序使用在头文件 AudioUnitProperties.h 中声明的 kAudioUnitProperty_StreamFormat 属性来获取和设置其总线的流格式。
此属性的值是音频流描述结构。

通常,音频单元中只需要一条输入总线和一条输出总线。
通过子类化 AUEffectBase 类来创建效果单元时,默认情况下会获得一条输入总线和一条输出总线。
音频单元可以通过覆盖主类的构造函数来指定其他总线。
然后,您可以使用 kAudioUnitProperty_BusCount 属性或其同义词 kAudioUnitProperty_ElementCount 来指示其他总线,两者都在头文件 AudioUnitProperties.h 中声明。

如果您正在构建交织器或去交织器音频单元,或者包含主音频数据路径以及调制数据侧链路径的音频单元,您可能会发现额外的总线很有帮助。

一个总线只能有一个连接,如下所述。


音频单元连接

连接是音频数据进入或离开音频单元的交接
当音频单元调用渲染回调时,新鲜的音频数据样本会通过连接进入音频单元。
当音频单元的渲染方法被调用时,处理过的音频数据样本会离开音频单元。
Core Audio SDK 的类层次结构与音频单元的渲染代码配合使用,实现了音频数据的交接。

主机以总线为单位建立连接,而不是以单个通道为单位。
您可以在图 2-1中看到这一点。
连接中的通道数由流格式定义,该格式是为包含连接的总线设置的。


音频处理图形连接

要将一个音频单元连接到另一个音频单元,主机应用程序需要在目标音频单元中设置一个属性。
具体来说,它会kAudioUnitProperty_MakeConnection在目标音频单元的输入范围内设置该属性。
当您使用 Core Audio SDK 构建音频单元时,系统会为您实现此属性。

在设置此属性的值时,主机使用音频单元连接结构( )指定源和目标总线编号,如清单 2-5AudioUnitConnection所示:


例 2-5 音频单元连接结构

typedef struct AudioUnitConnection {
    AudioUnit sourceAudioUnit;    // the audio unit that supplies audio
                                  //    data to the audio unit whose
                                  //    connection property is being set
    UInt32    sourceOutputNumber; // the output bus of the source unit
    UInt32    destInputNumber;    // the input bus of the destination unit
} AudioUnitConnection;

kAudioUnitProperty_MakeConnection 属性和音频单元连接结构在 Audio Unit 框架中的 AudioUnitProperties.h 文件中声明。

作为音频单元开发者,您必须确保您的音频单元可以连接,以使其有效。
您可以通过支持适当的流格式来实现这一点。
当您通过对 SDK 中的类进行子类化来创建音频单元时,您的音频单元将可连接。
音频单元的默认、必需的流格式在常用属性中进行了描述。

图 1-7说明音频单元的上游实体可以是另一个音频单元或主机应用程序。
无论是哪种情况,上游实体通常负责在建立连接之前设置音频单元的输入流格式。
如果音频单元无法支持所请求的流格式,则会返回错误并且连接失败。


渲染回调连接

主机应用程序可以直接将音频数据发送到音频单元,也可以直接从音频单元检索处理后的数据。
您无需对音频单元进行任何更改即可支持此类连接。

为了准备将数据发送到音频单元,主机定义了一个渲染回调(如图 1-7所示),并将其注册到音频单元。
回调的签名在音频单元框架的头文件 AUComponent.h 中声明,如清单 2-6 所示:


例 2-6 渲染回调

typedef OSStatus (*AURenderCallback)(
    void                          *inRefCon,
    AudioUnitRenderActionFlags    *ioActionFlags,
    const AudioTimeStamp          *inTimeStamp,
    UInt32                        inBusNumber,
    UInt32                        inNumberFrames,
    AudioBufferList               *ioData
);

主机必须明确设置音频单元输入的流格式,这是建立连接的先决条件。
当音频单元准备好接收更多音频数据时,它会调用主机中的回调。

相比之下,对于音频处理图连接,上游音频单元提供渲染回调。
在图中,上游音频单元还设置下游音频单元的输入流格式。

主机可以通过调用音频单元上的 AudioUnitRender 函数直接从音频单元检索处理后的音频数据,如清单 2-7 所示:


例 2-7 AudioUnitRender 函数

extern ComponentResult AudioUnitRender (
    AudioUnit                     ci,
    AudioUnitRenderActionFlags    *ioActionFlags,
    const AudioTimeStamp          *inTimeStamp,
    UInt32                        inOutputBusNumber,
    UInt32                        inNumberFrames,
    AudioBufferList               *ioData
);

Core Audio SDK 将此函数调用作为对音频单元Render方法的调用传递到您的音频单元中。

您可以看到渲染回调和AudioUnitRender签名之间的相似性,这反映了它们在音频处理图连接中的协调使用。
与渲染回调一样,该函数在Audio Unit 框架的头文件AudioUnitRender中声明。
AUComponent.h


音频单元通道

从概念上讲,音频单元通道是音频数据样本进出音频单元处理代码的单声道、非交错路径。
Core Audio SDK 将通道表示为缓冲区。
每个缓冲区都由音频缓冲区结构 ( AudioBuffer) 描述,如 Core Audio 框架中的头文件 CoreAudioTypes.h 中声明的那样,如清单 2-8所示:


例 2-8 音频缓冲区结构

struct AudioBuffer {
    UInt32  mNumberChannels; // number of interleaved channels in the buffer
    UInt32  mDataByteSize;   // size, in bytes, of the buffer
    void    *mData;          // pointer to the buffer
};
typedef struct AudioBuffer AudioBuffer;

音频缓冲区可以容纳单个通道或多个交错通道。
但是,大多数类型的音频单元(包括效果单元)仅使用非交错数据。
这些音频单元期望音频缓冲区结构中的 mNumberChannels字段等于1

输出单元和格式转换器单元可以接受交叉通道,由 mNumberChannels 字段设置为2或更大的音频缓冲区表示。

音频单元将总线中的通道集作为音频缓冲区列表结构(AudioBufferList)进行管理,该结构也在 CoreAudioTypes.h 中定义,如清单 2-9所示:


例 2-9 音频缓冲区列表结构

struct AudioBufferList {
    UInt32      mNumberBuffers;  // the number of buffers in the list
    AudioBuffer mBuffers[kVariableLengthArray]; // the list of buffers
};
typedef struct AudioBufferList  AudioBufferList;

在构建nn通道效果单元的常见情况下(例如您在教程:使用通用视图构建简单效果单元AUEffectBase中构建的效果单元),音频单元模板和超类会为您管理通道。
您可以通过在 SDK 中对类进行子类化来创建这种类型的效果单元。

相反,当您构建mn 个声道效果单元(例如立体声到单声道效果单元)时,您必须编写代码来管理声道。
在这种情况下,您可以通过子类化该类来创建效果单元AUBase
(与本文档的其余部分一样,此考虑适用于 Core Audio SDK 1.4.3 版,该版本在发布时为最新版本。)


2、通过子类化创建音频单元

创建音频单元的最简单且推荐的方法是将 Core Audio SDK 的 C++ 超类子类化。
只需付出很少的努力,您就可以获得音频单元与 Core Audio 的其他部分、组件管理器和音频单元主机应用程序交互所需的所有编程框架和挂钩。

例如,当您使用 SDK构建n通道到*n通道效果单元时,您将音频单元的主类定义为 *AUEffectBase 超类的子类。
当您构建基本乐器单元(也称为基于软件的音乐合成器)时,您将音频单元的主类定义为 SDKAUInstrumentBase超类的子类。
附录:音频单元类层次结构 介绍了这些类以及 SDK 中的其他类。

在实践中,子类化通常意味着以下两种情况之一:

  • 使用提供的模板创建音频单元 Xcode 项目。
    在这种情况下,创建项目会为您提供定义相应超类的自定义子类的源文件。
    您可以修改和扩展这些文件来定义音频单元的自定义功能和行为。
  • 从 SDK 中复制音频单元项目,该项目已包含自定义子类。
    在这种情况下,您可能需要删除与您的音频单元无关的代码,并更改项目中的符号以正确识别和引用您的音频单元。
    然后,您可以按照与使用 Xcode 模板相同的方式进行操作。

3、控制代码:参数、出厂预设和属性

大多数音频单元都是用户可实时调整的。
例如,混响单元可能具有初始延迟、混响密度、衰减时间和干/湿混合的用户设置。
此类可调整的设置称为参数,具有浮点值。
浮点参数通常使用音频单元视图中的滑块界面。
您可以将名称与整数值关联起来,为参数提供菜单界面,例如让用户在颤音效果单元中选择颤音类型。
音频单元中内置(开发人员定义)的参数设置组合称为出厂预设

所有音频单元还具有一些特性,这些特性通常不随时间变化且不能由用户直接设置,这些特性称为属性
属性是一个键/值对,它通过声明属性或行为来优化音频单元的插件 API。
例如,您可以使用属性机制来声明音频单元特性,如样本延迟和音频数据流格式。
每个属性都有一个关联的数据类型来保存其值。
有关属性以及延迟和流格式定义的更多信息,请参阅常用属性

主机应用程序可以查询音频单元的参数和标准属性,但不能查询其自定义属性。
自定义属性用于音频单元与与音频单元协同设计的自定义视图之间的通信。

要从音频单元获取参数信息,主机应用程序首先获取音频单元 kAudioUnitProperty_ParameterList 属性的值,该属性由 SDK 中的超类提供。
此属性的值是音频单元定义的参数 ID 列表。
然后,主机可以查询每个参数 ID 的 kAudioUnitProperty_ParameterInfo 属性。

宿主和视图还可以使用通知接收参数和属性变化信息,如参数和属性事件中所述。


定义和使用参数

指定参数意味着指定您想要提供给用户控制的设置,以及适当的单位和范围。
例如,提供颤音效果的音频单元可能会提供颤音速率的参数。
您可能会指定赫兹的单位,并可能指定从 1 到 10 的范围。

要定义音频单元的参数,您需要重写 AUBase 类 中的 GetParameterInfo 方法。
您编写此方法来告诉视图如何表示每个参数的控件,并指定每个参数的默认值。

GetParameterInfo方法可以被称为:

  • 当视图在屏幕上绘制时,通过音频单元的视图(如果提供,则为自定义视图,否则为通用视图)
  • 通过为音频单元提供通用视图的主机应用程序
  • 通过主机应用程序在硬件控制界面上显示音频单元的参数

要在渲染音频时使用参数的当前设置(由用户调整),请调用GetParameter方法。此方法继承自 AUEffectBase 类。

GetParameter方法将参数 ID 作为其一个参数并返回参数的当前值。
您通常在音频单元的Process方法中进行此调用,以便为每个渲染周期更新一次参数值。
然后您的渲染代码可以使用参数的当前值。

除了GetParameterInfo方法(用于告知视图或主机参数的当前值)和GetParameter方法(用于在渲染期间使用参数值)之外,音频单元还需要一种设置其参数值的方法。
为此,它通常使用 AUEffectBase 类中的 SetParameter 方法。

音频单元调用该方法主要有两次SetParameter

  • 在实例化期间(在其构造函数方法中)设置其默认参数值
  • 运行时(当主机或视图调用参数值更改时)更新其参数值

SetParameter方法采用两个方法参数——要更改的参数的 ID 及其新值,如清单 2-10所示:


例 2-10 SetParameter 方法

void SetParameter(
    UInt32 paramID,
    Float32 value
);

教程:使用通用视图构建简单效果单元中 构建的音频单元使用了所有以下三种方法:GetParameterInfoGetParameterSetParameter

音频单元有时需要调用某个参数的值更改。
它可能是为了响应另一个参数的更改(由视图或主机调用)而执行此操作。
当音频单元主动更改参数值时,它应该发布事件通知。

例如,在带通滤波器音频单元中,用户可能会将上角频率降低到低于当前频带下限设置的值。
音频单元可以通过适当降低下角频率来做出响应。
在这种情况下,音频单元负责发布有关自调用更改的事件通知。
通知会将下角频率参数的新值告知视图和主机。
要发布通知,音频单元在调用SetParameter方法后 会调用 AUParameterListenerNotify方法。


工厂预设和参数持久性

您可以指定工厂预设,为用户提供便利和附加值。
音频单元越复杂,其参数数量越多,用户就越欣赏工厂预设。

例如,OS X Matrix Reverb 单元包含十多个参数。
用户可能会发现将它们设置为有用的组合是一件令人望而生畏的事情。
Matrix Reverb 的开发人员考虑到了这一点,并提供了一系列出厂预设,这些预设具有高度描述性的名称,例如小房间、大厅和大教堂。

GetPresets方法对出厂预设的作用与 GetParameterInfo方法对参数的作用相同。
您可以通过重写来定义出厂预设GetPresets,音频单元的视图会调用此方法来填充视图的出厂预设菜单。

当用户选择出厂预设时,视图会调用音频单元的NewFactoryPresetSet方法。
您可以与方法并行定义此方法GetPresets
对于您在出厂预设菜单中提供的每个预设,您可以在方法中包含代码,NewFactoryPresetSet以便在用户请求时设置该预设。
对于每个出厂预设,此代码由一系列方法调用组成。
有关实现出厂预设的分步指导,SetParameter请参阅教程:使用通用视图构建简单效果单元。

参数持久性是主机应用提供的一项功能,可让用户将参数设置从一个会话保存到下一个会话。
当您使用 Core Audio SDK 开发音频单元时,您的音频单元将自动支持参数持久性。

主机应用程序开发人员利用 SDK 的kAudioUnitProperty_ClassInfo属性提供参数持久性。
此属性使用CFPropertyListRef字典来表示音频单元的当前设置。


定义和使用属性

Apple 为音频单元定义了 100 多个属性。
您可以在AudioUnitProperties.h音频单元框架的头文件中 找到它们的声明。
每种类型的音频单元都有一组必需属性,如音频单元规范中所述。

您可以作为音频单元开发人员开始工作,而无需接触甚至了解大多数这些属性。
在大多数情况下,Core Audio SDK 中的超类会为您实现所需的属性。
而且,在许多情况下,SDK 会为它们设置有用的值。

然而,您对可用的音频单元属性的丰富程度了解得越多,您就能制作出更好的音频单元。

Apple 定义的每个属性都有相应的数据类型来表示属性的值。
根据属性的不同,数据类型可以是结构体、字典、数组或浮点数。

例如,描述音频单元的音频数据流格式的 kAudioUnitProperty_StreamFormat属性, 将其值存储在 AudioStreamBasicDescription 结构中。
此结构在 CoreAudio 框架的头文件 CoreAudioTypes.h 中声明。

AUBase超类提供了通用的获取和设置方法,您可以重写这些方法来实现属性,如定义自定义属性中所述。

GetPropertyInfoGetPropertySetProperty这些方法,和 SDK 中的相关“调度”方法一起使用,你不需要直接调用。
调度方法(例如)DispatchGetPropertyInfo为 SDK 提供了大部分音频单元属性魔法。
您可以在SDK 中的 AUBase.cpp 文件中检查它们以了解它们的作用。

AUEffectBaseMusicDeviceBase类和其他子类 使用特定于一种 音频单元的属性代码 覆盖属性访问器方法。
例如,AUEffectBase类 处理特定于效果单元 的属性调用,例如kAudioUnitProperty_BypassEffect 属性。


常用属性

对于一些常用的属性,Core Audio SDK 提供了特定的访问器方法。
例如,CAStreamBasicDescriptionSDK 中的类提供了用于管理AudioStreamBasicDescription属性结构的方法kAudioUnitProperty_StreamFormat

以下是您可能需要为音频单元实现的一些属性。
当您想要自定义音频单元以不同于音频单元类型的默认行为时,您可以实现这些属性:

  • kAudioUnitProperty_StreamFormat
    声明音频单元输入或输出通道的音频数据流格式。
    主机应用可以分别设置输入和输出通道的格式。
    如果您未实现此属性来描述其他流格式,则 SDK 中的超类会声明您的音频单元支持默认流格式:非交错、32 位浮点、本机字节序、线性 PCM。
  • kAudioUnitProperty_BusCount
    声明音频单元的输入或输出范围内的总线(也称为元素)数量。
    如果您不实现此属性,则 SDK 中的超类会声明您的音频单元使用单个输入和输出总线,每个总线的 ID 为0
  • kAudioUnitProperty_Latency
    声明音频单元样本从输入到输出的最短可能时间(以秒为单位)。
    例如,基于 FFT 的过滤器必须获取一定数量的样本来填充 FFT 窗口,然后才能计算输出样本。
    延迟时间短至两三个样本的音频单元应实现此属性以报告其延迟时间。
    如果音频单元的采样延迟发生变化,请使用此属性报告最大延迟。
    或者,您可以在延迟发生变化时更新 kAudioUnitProperty_Latency 属性值,并使用 Audio Unit Event API 发出属性更改通知。
    如果您的音频单元的延迟为0秒,则无需实现此属性。
    否则,您应该让主机应用程序进行适当补偿。
  • kAudioUnitProperty_TailTime
    声明音频单元延迟之外的时长,即在输入端瞬间变为静音后,在音频单元输出端衰减为静音的标称电平信号所需的时间。
    对于执行延迟或混响等效果的音频单元而言,尾部时间非常重要。
    Apple 建议所有音频单元都实现kAudioUnitProperty_TailTime属性,即使其值为0
    如果音频单元的尾部时间会发生变化(例如,延迟时间可变),请使用此属性报告最大尾部时间。
    或者,您可以在尾部时间发生变化时更新kAudioUnitProperty_TailTime属性值,并使用 Audio Unit Event API 发出属性更改通知。
  • kAudioUnitProperty_SupportedNumChannels
    声明音频单元支持的输入和输出通道数。
    此属性的值存储在通道信息结构 ( AUChannelInfo) 中,该结构在 AudioUnitProperties.h 头文件中声明:

typedef struct AUChannelInfo {
    SInt16    inChannels;
    SInt16    outChannels;
} AUChannelInfo;

字段值例子举例来说
两个字段都是–1inChannels = –1
outChannels = –1
这是默认情况。任意数量的输入和输出通道,只要数字匹配
一个是–1,另一个场是正的inChannels = –1``outChannels = 2任意数量的输入通道,恰好两个输出通道
一个字段是–1,另一个字段是–2inChannels = –1``outChannels = –2任意数量的输入通道,任意数量的输出通道
两个字段都有 非负值inChannels = 2``outChannels = 6正好两个输入通道,正好六个输出通道
inChannels = 0``outChannels = 2没有输入通道,只有两个输出通道(例如具有立体声输出的乐器单元)
两个字段都为负值,都不是–1–2inChannels = –4``outChannels = –8最多四个输入通道 和 最多八个输出通道

如果您不实现此属性,则 SDK 中的超类会声明您的音频单元可以使用任意数量的通道,前提是输入的数量与输出的数量匹配。


  • kAudioUnitProperty_CocoaUI
    声明主机应用程序可以在哪里找到音频单元的基于 Cocoa 的视图的包和主类。
    如果您提供 Cocoa 自定义视图,请实现此属性。

kAudioUnitProperty_TailTime属性是 您需要为效果单元实现的最常见属性。
具体操作如下:

  1. 通过将以下方法语句添加到音频单元自定义类定义,来覆盖 AUBase 超类中的 SupportsTail方法:
virtual bool SupportsTail () {
		return true;
}
  1. 如果您的音频单元的尾部时间不是0秒,请从 AUBase 超类中重写 GetTailTime 方法。
    例如,如果您的音频单元产生最大衰减时间为 3000 毫秒的混响,请将以下重写添加到您的音频单元自定义类定义中:
virtual Float64 GetTailTime() {return 3;}

定义自定义属性

您可以定义自定义音频单元属性,以便将信息传递到自定义视图或从自定义视图传递信息。
例如,Core Audio SDK 中的 FilterDemo 项目使用自定义属性将音频单元的频率响应传达给其视图。
这允许视图将频率响应绘制为曲线。

要在从 SDK 构建音频单元时定义自定义属性,请重写 AUBase 类中的GetPropertyInfoGetProperty方法。
自定义视图在需要音频单元属性的当前值时会调用这些方法。

您向 GetPropertyInfo方法添加代码以返回每个自定义属性的大小以及指示其是否可写的标志。
您还可以使用此方法检查是否使用适当的范围元素调用每个自定义属性。
清单 2-11显示了此方法的签名:


例 2-11 SDK 的 AUBase 类中的 GetPropertyInfo 方法

virtual ComponentResult GetPropertyInfo (
    AudioUnitPropertyID  inID,
    AudioUnitScope       inScope,
    AudioUnitElement     inElement,
    UInt32               &outDataSize,
    Boolean              &outWritable
);

您可以向 GetProperty 方法 添加代码 来告诉视图 每个自定义属性的当前值:


例 2-12 SDK 的 AUBase 类中的 GetProperty 方法

virtual ComponentResult GetProperty (
    AudioUnitPropertyID  inID,
    AudioUnitScope       inScope,
    AudioUnitElement     inElement,
    void                 *outData
);

您通常会将GetPropertyInfoGetProperty方法构建为 switch 语句,每个自定义属性对应一个 case。
查看FilterDemo 项目中的Filter::GetPropertyInfoFilter::GetProperty方法,以查看如何使用这些方法的示例。

您可以重写 SetProperty方法来执行为每个自定义属性建立新设置所需的任何工作。

每个音频单元属性必须具有唯一的整数 ID。
Apple 保留 0 ---- 63999 之间的属性 ID 号。
如果您使用自定义属性,请指定 64000,或更大的 ID 号。


4、合成、处理和数据格式转换代码

音频单元合成、处理或转换音频数据。
您可以根据音频单元所需的功能在这里做任何您想做的事情。
执行此操作的数字音频代码正是您创建音频单元的核心。
然而,这种代码在很大程度上独立于它所处的插件架构。
您将对音频单元或其他音频插件架构使用相同或相似的算法和数据结构。
因此,本编程指南重点介绍如何创建音频单元作为音频 DSP 代码的容器和接口,而不是如何编写 DSP 代码。

同时,数字音频代码与插件的匹配方式在不同架构中也有所不同。
本节介绍使用 Core Audio SDK 构建的音频单元如何支持数字音频代码。
教程:使用通用视图构建简单效果单元 一章包含一些非平凡的 DSP 代码,以帮助说明它如何用于效果单元。


信号处理

要执行 DSP,您需要使用效果单元('aufx' 类型),通常作为 AUEffectBase 类的子类构建。
AUEffectBase 使用辅助类 AUKernelBase 来处理 DSP,并为每个音频通道实例化一个内核对象( AUKernelBase)。

内核对象特定于从 AUEffectBase 类中 子类化的nn 个通道效果单元。
它们不属于其他类型的音频单元。

AUEffectBase 类仅用于构建nn通道效果单元。
如果您要构建的效果单元未采用 输入到输出通道的直接映射,则应改为对AUBase超类进行子类化。

《处理:问题的核心》中所述,音频单元 DSP 代码有两种主要方法:ProcessReset
您可以重写Process方法来定义音频单元的 DSP。
您可以重写方法Reset来定义当用户 采取行动结束信号处理(例如在声音编辑器窗口中移动播放点)时要执行的清理操作。
例如,您可以确保Reset混响衰减不会干扰声音文件新点的播放开始。

教程:使用通用视图 构建简单效果单元 提供了实现 Process 方法的分步示例。

当音频单元正在渲染时,用户可以使用音频单元的视图进行实时调整。
处理代码通常会考虑与处理相关的参数和属性的当前值。
例如,高通滤波器效果单元的处理代码将根据音频单元视图中设置的当前转折频率执行计算。
处理代码通过读取适当的参数来获取此值,如 定义和使用参数中所述。

使用 Core Audio SDK 中的类构建的音频单元 仅适用于恒定比特率 (CBR) 音频数据。
当主机应用程序读取可变比特率 (VBR) 数据时,它会将其转换为线性 PCM 形式的 CBR 表示,然后再将其发送到音频单元。


音乐合成

乐器单元('aumu'类型)与效果单元不同,它以音符的形式呈现音频。它充当虚拟音乐合成器。
乐器单元使用一组声音并响应 MIDI 控制数据,通常由键盘发起。

您可以为大多数乐器单元创建 AUMonotimbralInstrumentBase 类的子类。
此类支持单音和复音乐器单元,它们一次可以播放一种声音(也称为音色或乐器声音)。
例如,如果用户选择钢琴声音,乐器单元就像一架虚拟钢琴,在音乐键盘上按下每个键都会调出一个钢琴音符。

Core Audio SDK 类层次结构也提供了 AUMultitimbralInstrumentBase 类。
此类支持可同时演奏多个声音的单音和复音乐器单元。
例如,您可以创建一个多音乐器单元,让用户使用单个键盘用左手演奏虚拟低音吉他,同时用右手演奏虚拟小号。


音乐效果

音乐效果单元('aumf'类型)提供 DSP,就像效果单元一样,但也响应 MIDI 数据,就像乐器单元一样。
您可以通过从 SDK 中 继承 AUMIDIEffectBase 来构建音乐效果单元。
例如,您可以这样做来创建一个音频单元,该单元提供根据键盘上按下的音符进行调整的过滤效果。


数据格式转换

音频数据转换包括采样率转换、向多个目的地发送信号以及更改时间或音调等操作。
要以这些方式转换音频数据,您需要构建一个格式转换器单元('aufc'类型),作为SDK 中 AUBase 超类的子类。

音频单元不适用于处理可变比特率 (VBR) 数据,因此音频单元通常不适合转换为或从 MP3 等有损压缩格式转换。
要处理有损压缩格式,请使用 Core Audio 的音频转换器 API,该 API 在Audio Toolbox 框架的头文件 AudioConverter.h 中声明。


5、音频单元生命周期

音频单元不仅仅是其可执行代码、视图和插件 API。
它是一个动态、响应迅速的实体,具有复杂的生命周期。
在这里,您可以了解此生命周期,以帮助您做出良好的设计和编码决策。

与本文档的其余部分一致,本节从基于 Core Audio SDK 的开发角度描述了音频单元的生命周期。
例如,本节对对象实例化和初始化的讨论涉及 SDK 子类。
如果您直接使用 Audio Unit 框架进行开发,而不是使用 SDK,则音频单元类层次结构不在图中。


概述

与任何插件一样,音频单元的生命周期包括响应请求。
您在音频单元中重写或从头编写的每个方法都由外部进程调用,其中包括:

  • OS X 组件管理器,代表主机应用程序
  • 主机应用程序本身
  • 音频处理图,特别是下游音频单元
  • 用户操作的音频单元视图

您无需预测哪个进程或上下文正在调用您的代码。
相反,您可以将音频单元设计为与调用上下文无关。

音频单元的生命周期经历一系列状态,包括:

  • 未实例化。
    在此状态下,没有音频单元的对象实例,但音频单元在系统中的类存在已由组件管理器注册。
    主机应用程序使用组件管理器注册表来查找和打开音频单元。
  • 已实例化但未初始化。
    在此状态下,主机应用程序可以查询音频单元对象的属性并配置某些属性。
    用户可以通过视图来操作已实例化的音频单元的参数。
  • 已初始化。
    主机应用程序可以将已初始化的音频单元挂接到音频处理图中。
    主机和图形可以要求已初始化的音频单元渲染音频。
    此外,一些属性可以在初始化状态下更改。
  • 未初始化。
    音频单元架构允许主机应用程序明确取消音频单元的初始化。
    取消初始化过程不一定与初始化对称。
    例如,乐器单元可以设计为在此状态下仍可访问其在初始化时分配的 MIDI 音库。

程序化活动类别

音频单元响应两大类程序化事件,本章后面将详细介绍:

  • 主机应用程序发起的日常事件
    这些事件包括查找、打开、验证、连接和关闭音频单元。
    对于这些类型的事件,从 Core Audio SDK 构建的音频单元通常依赖于其提供的超类中的代码。
  • 调用自定义代码的操作事件
    这些事件由主机或音频单元的视图发起,包括初始化、配置、渲染、重置、实时或离线更改渲染、取消初始化、重新初始化和关闭时清理。
    对于一些简单的音频单元,一些操作事件(尤其是初始化)也可以依赖于 SDK 超类中的代码。

让音频单元焕发活力

甚至在实例化之前,音频单元在 OS X 中就如同幽灵般存在。
这是因为在用户登录期间,组件管理器会构建可用音频单元的列表。
它无需打开这些单元即可完成此操作。
然后,主机应用程序可以使用组件管理器查找并打开音频单元。

当主机应用程序要求组件管理器实例化音频单元时,音频单元的生命就开始了。
这通常发生在主机应用程序启动时 - 此时主机通常会实例化每个已安装的音频单元。
例如,AU Lab 和 Logic Pro 等主机会这样做,以了解输入和输出数据流格式以及每个已安装音频单元的输入和输出数量。
主机通常会缓存此信息,然后关闭音频单元。

当用户通过从菜单中选择来告诉主机他们想要使用某个音频单元时,主机应用程序会再次实例化该特定音频单元。

实例化会导致调用音频单元的构造函数方法。
为了不影响主机应用程序的打开速度,保持构造函数方法轻量且快速非常重要。
构造函数是定义音频单元参数并设置其初始值的地方。
它不是进行资源密集型工作的地方。

n通道到n通道效果单元(从 AUEffectBase 类构建)在音频单元初始化之前不会实例化其内核对象。
出于这个原因,对于这种类型的音频单元,在内核实例化期间执行资源密集型工作(例如设置波表)是合适的。
有关更多信息,请参阅 n 到n 效果单元中的内核实例化

大多数属性也应该在构造函数中实现和配置,如下一节所述。


属性配置

如果可能,音频单元应在其构造函数方法中配置其属性。
但是,音频单元属性可以在各种时间由各种实体配置。
每个单独的属性通常采用以下方式之一进行配置:

  • 由音频单元本身,通常在实例化期间
  • 由托管音频单元的应用程序在音频单元初始化之前或之后
  • 从音频单元的角度来看,当音频单元初始化或未初始化时,由用户操纵

配置音频单元属性的这种可变性源于各种属性的要求、音频单元的类型以及主机应用程序的需求。

对于某些属性,SDK 超类定义了配置是在音频单元初始化时进行还是仅在音频单元未初始化时进行。
例如,主机应用无法更改音频单元的流格式(使用kAudioUnitProperty_StreamFormat属性),除非它确保音频单元未初始化。

对于其他属性,例如kAudioUnitProperty_SetRenderCallback属性,音频单元规范禁止主机更改已初始化音频单元上的属性,但没有针对此进行编程强制执行。

对于其他属性(例如 kAudioUnitProperty_OfflineRender属性),音频单元将决定是否在更改属性值之前要求取消初始化。
如果音频单元可以在初始化时妥善处理更改,则可以允许这样做。

音频单元规范详细说明了 Apple 定义的每个属性的配置要求。


音频单元初始化和取消初始化

耗时且耗资源的音频单元启动操作位于音频单元的初始化方法中。
其理念是尽可能推迟音频单元中的工作,直到即将使用。
例如,AU Lab 应用程序不会初始化安装在系统上的音频单元,直到用户专门将音频单元添加到 AU Lab 通道。
此策略通过最大限度地减少主机应用程序启动时的不必要延迟来改善用户体验,尤其是对于安装了大量音频单元的用户。

以下是一些适合初始化时间的工作示例:

  • 乐器单元获取 MIDI 音库,供该单元在响应 MIDI 数据时使用
  • 效果单元分配内存缓冲区以供渲染期间使用
  • 效果单元计算渲染期间使用的波表

一般来说,每个操作都应该在类Initialize的方法覆盖中执行AUBase

如果为效果单元,定义了 Initialize 方法的重写,请从调用 AUEffectBase::Initialize 开始。
这将确保为您的音频单元,处理好诸如正确的通道设置之类 的日常任务。

如果您要设置内部缓冲区以进行处理,则可以通过调用 AUBase::GetMaxFramesPerSlice 方法来了解它们的大小。
这将访问音频单元的主机应用程序在调用初始化之前定义的值。
每次渲染调用的实际帧数可能会有所不同。
它由主机应用程序使用AUBase::DoRender 方法 或 AUEffectBase::Process方法的 inFramesToProcess参数 来设置。

初始化也是调用音频单元复制保护的适当时机。
复制保护可以包括密码挑战或检查硬件加密狗是否存在等内容。

Core Audio SDK 中的音频单元类层次结构为各种类型的音频单元提供了专门的 Initialize 方法。
例如,效果单元使用 AUEffectBase 类中的 Initialize 方法。
此方法执行许多重要的 日常任务,包括:

  • 保护效果单元 免受 主机应用程序 以无效方式连接它
  • 确定效果单元支持的输入输出通道数,以及当前初始化要使用的通道配置。
    (效果单元可以设计为支持可变数量的输入和输出通道,并且所使用的数量可以从一次初始化变为下一次初始化。)
  • 设置或更新效果单元的内核对象,确保它们已准备好开始工作

在许多情况下,例如在 教程:使用通用视图构建简单效果单元中创建 的效果单元中,效果单元不需要在音频单元的类中进行额外的初始化工作。
它们可以通过继承 AUBase,直接使用Initialize方法。
您将在本教程中构建的效果单元就是这样做的。

对于基于AUEffectBase超类的效果单元,您可以将资源密集型初始化代码放入 DSP 内核对象的构造函数中。
这样做是可行的,因为内核是在效果单元初始化期间实例化的。
您稍后在本文档中构建的示例效果单元描述了效果单元生命周期的这一部分。

实例化后,主机应用程序可以根据用户需要反复初始化和取消初始化音频单元。
例如,如果用户想要更改采样率,主机应用程序无需先关闭音频单元即可更改采样率。
(其他一些音频插件技术不提供此功能。)


n 对 n 个效果单元中的内核实例化

在使用 AUEffectBase 超类构建的效果单元(例如效果单元教程中构建的颤音单元)中,处理在一个或多个所谓的内核对象中进行。
这些对象是从 AUKernelBase 类中派生出来的子类,如“让音频单元焕发生机”中所述。

此类效果单元会为所使用的每个通道实例化一个内核对象。
内核对象实例化在音频单元初始化期间进行,作为以下序列的一部分:

  1. n通道n通道效果单元被实例化
  2. 效果单元初始化
  3. 在初始化期间,效果单元实例化适当数量的内核对象

此事件序列使内核对象构造函数 成为您想要在音频单元初始化期间调用的代码的理想位置。
例如,本文档教程中的颤音单元在内核实例化期间构建其颤音波表。


音频处理图交互

音频处理图(多个音频单元的连接)是音频单元最常见的使用方式。
本节介绍图中发生的事情。

音频单元与音频处理图的交互包括:

  • 建立输入和输出连接
  • 断开输入和输出连接
  • 响应渲染请求

主机应用程序负责建立和断开音频处理图的连接。
连接和断开连接通过设置属性进行,如本章前面的音频处理图连接 中所述。
要将音频单元添加到图 或从图中删除,必须将其取消初始化。

图中音频数据流按照拉模型进行,如音频处理图和拉模型 中所述。


音频单元处理

根据其类型,音频单元会执行以下操作之一:

  • 处理音频(例如效果单元和音乐效果单元)
  • 通过 MIDI 数据(乐器单元)或其他方式(例如通过读取文件(生成器单元))生成音频
  • 通过改变采样率、位深度、编码方案或其他音频数据特征来转换音频数据(格式转换器单元)

在使用 Core Audio SDK 构建的效果单元中,处理工作在名为 Process 的 C++ 方法中进行。
此方法来自 AUKernelBase 类,在 SDK 的头文件 AUEffectBase.h 中声明。
在使用 SDK 构建的乐器单元中,音频生成工作在 AUInstrumentBase 类中定义的名为 Render 的方法中进行。

在效果单元中,当单元收到对其Process方法的调用时,处理就开始了。
此调用通常来自音频处理图中的下游音频单元。
如音频处理图 交互 中所述,此调用是来自主机应用程序的级联的结果,通过图对象,要求图中的最终节点启动。

音频单元接收的处理调用指定输入和输出缓冲区以及要处理的数据量:


例 2-13 AUKernelBase 类中的 Process 方法

virtual void Process (
    const Float32    *inSourceP,
    Float32          *inDestP,
    UInt32           inFramesToProcess,
    UInt32           inNumChannels,
    bool &           ioSilence) = 0;

有关 Process 方法的实现示例,请参阅 教程:使用通用视图构建简单的效果单元

处理是音频单元生命周期中计算量最大的部分。
在处理循环中,请避免执行以下操作:

  • 互斥(互斥)资源锁定
  • 内存或资源分配

注意: 某些 Core Foundation 调用(例如 CFRetain 和 CFRelease)使用互斥锁。
因此,最好在处理过程中避免使用 Core Foundation 调用。


结束语

当主机使用完音频单元后,应通过调用组件管理器的CloseComponent函数 来将其关闭。
此函数会调用音频单元的析构函数。
音频单元本身必须负责释放它们分配的所有资源。

如果您在音频单元中使用复制保护,则应仅在对象销毁时结束它。


四、音频单元视图

几乎每个音频单元都需要一个图形界面,以便用户调整音频单元的操作 并查看其正在做什么。
在 Core Audio 术语中,这样的图形界面称为视图。
当您了解视图的工作原理以及如何构建它们时,您可以为您创建的音频单元增加很多价值。


1、视图类型

主要有两种类型的观点:

  • 通用视图 :提供实用但不浮华的界面。
    您可以免费获得通用视图。
    它由打开音频单元的主机应用程序根据音频单元源文件中的参数和属性定义为您构建。
  • 自定义视图 :是您设计和构建的图形用户界面。
    创建出色的自定义视图可能需要音频单元一半以上的开发时间。
    通过您的努力,您可以为用户提供不仅更具吸引力而且功能更强大的东西。

您可以选择等到音频单元工作后,再开发自定义视图,或者完全放弃自定义视图选项。
如果您确实创建了自定义视图,则可以使用 Carbon 或 Cocoa。


2、关注点分离

从用户的角度来看,视图就是音频单元
从开发者的角度来看,情况就更微妙一些。

您构建的视图在逻辑上与音频单元可执行代码分离,但打包在同一包中。
为了实现这种编程分离,Apple 建议您开发 自定义视图,以便它们可以在与音频单元可执行代码不同的地址空间、不同的进程和不同的机器上运行。
例如,您只能通过值在音频单元可执行代码和其视图之间传递数据,而不能通过引用传递数据。

然而,在不影响这种正式分离的情况下,在音频单元可执行代码和其视图之间共享头文件通常很方便。
您将在 SDK 的 FilterDemo 项目中看到这一点。
例如,共享头文件可以提供音频单元的可执行代码及其视图都可以使用的自定义属性的数据类型和常量。

通用视图和自定义视图都使用通知机制与其关联的音频单元可执行代码进行通信,如参数和属性事件中所述。


3、通用观点

主机应用程序根据您的参数和属性定义为您的音频单元创建通用视图。
从 OS X v10.4 Tiger 开始,主机应用程序可以生成 Carbon 或 Cocoa 通用视图。

下面是 FilterDemo 音频单元的 Cocoa 通用视图,它是 Core Audio SDK 中的示例项目之一:


在这里插入图片描述


与此视图关联的音频单元有两个连续可变的参数,每个参数在视图中都由一个简单的滑块以及显示参数当前值的文本字段表示。
音频单元的通用视图将具有类似的外观。

表 3-1描述了通用视图中每个用户界面元素的来源。
表中的“值来源”列指的是您在使用“带有 Cocoa View 的音频单元效果”模板构建的音频单元 Xcode 项目中看到的文件。

用户界面项示例值价值来源
音频单元名称,位于视图实用程序窗口的左上角Audio Unit: Filter<className>.r文件中定义的 NAME 常量
音频单元名称,在视图实用程序窗口的标题栏和弹出窗口中Filter<className>.r文件中定义的 NAME 常量
制造商名称,位于视图实用程序窗口的右上角Apple Demo<className>.r文件中定义的 NAME常量
参数名称cutoff frequency(截止频率 )<className>.h 文件中的 kParameterOneName `CFStringRef 字符串值
参数最大值22050<className>.cpp文件中的 GetParameterInfo方法
参数最小值12.00<className>.cpp文件中的 GetParameterInfo 方法
参数默认值1000<className>.h文件中的 kDefaultValue_ParamOne 浮点值
计量单位名称Hz<className>.cpp 文件中的 GetParameterInfo方法指定的 测量单位。
滑块用户可调。
初始设置为指示参数默认值。
通用视图机制为该参数提供了一个滑块。
这是因为 GetParameterInfo 方法中定义的参数测量单位是“线性增益”。

如下所示,自定义视图可以提供比通用视图更多的价值和实用性。


4、自定义视图

当主机打开您的音频单元时,它会询问是否有可用的自定义视图。
如果有,它可以按照视图实例化和初始化中所述使用它。

下面是通用视图中描述的 相同音频单元 的自定义视图,即来自 Core Audio SDK 的 FilterDemo 音频单元:


在这里插入图片描述


此自定义视图的主要功能是 实时频率响应曲线。
这使得自定义视图(以及与之相关的音频单元)更具吸引力,也更加实用。
用户现在可以看到频率响应,包括滤波器共振和截止频率如何影响响应,而不是只看到一对数字。
无论您构建哪种音频单元,当您在音频单元中包含自定义视图时,都可以为用户提供类似的好处。

自定义视图相对于通用视图的优点包括:

  • 能够隐藏不必要的细节,或逐步披露控制措施
  • 提供参数自动化支持的能力
  • 用户界面控件的选择,例如旋钮、推子或水平滑块
  • 通过实时图表为用户提供更多信息,例如频率响应曲线
  • 为您的公司提供品牌推广机会

SDK 的 FilterDemo 音频单元项目 是创建音频单元自定义视图时 值得遵循的一个很好的示例。
有关自定义视图的更多信息,请参阅本章后面的教程:演示参数手势和音频单元事件。


5、视图实例化和初始化

当主机应用程序打开音频单元时,它可以查询音频单元是否具有自定义视图。
主机使用 SDK 中 CocoaAUHost 项目中的代码片段 执行此操作:


例 3-1 主机应用程序 从音频单元 获取 Cocoa 自定义视图

if (AudioUnitGetProperty (
        inAU,                       // the audio unit the host is checking
        kAudioUnitProperty_CocoaUI, // the property the host is querying
        kAudioUnitScope_Global,
        0,
        cocoaViewInfo,
        &dataSize) == noErr) {
    CocoaViewBundlePath =            // the host gets the path to the view bundle
        (NSURL *) cocoaViewInfo -> mCocoaAUViewBundleLocation;
    factoryClassName    =            // the host gets the view's class name
        (NSString *) cocoaViewInfo -> mCocoaAUViewClass[0];
}

如果您没有为音频单元 提供自定义视图,则主机将根据音频单元的参数 和属性 定义构建通用视图。

当主机打开音频单元时,向用户呈现视图时会发生以下情况:

  1. 主机应用程序调用GetProperty音频单元上的方法来查明它是否具有自定义视图,如清单 3-1所示。
    如果音频单元提供 Cocoa 视图,则音频单元应实现kAudioUnitProperty_CocoaUI属性。
    如果音频单元提供 Carbon 视图,则音频单元应实现 kAudioUnitProperty_GetUIComponentList属性。
    此序列的其余部分假定使用 Cocoa 自定义视图。
  2. 主机调用 kAudioUnitProperty_CocoaUI 属性的 GetPropertyInfo 方法来查明有多少个 Cocoa 自定义视图可用。
    作为一种捷径,主机可以跳过对 GetPropertyInfo 的调用。
    在这种情况下,主机将使用上面清单中 所示的代码,获取视图类数组中的第一个视图,使用数组索引0
    factoryClassName = (NSString *) cocoaViewInfo -> mCocoaAUViewClass[0]; 在这种情况下,请直接跳到步骤 4。
  3. 音频单元以整数值的形式返回 AudioUnitCocoaViewInfo 结构的大小,指示有多少个 Cocoa 自定义视图可用。
    通常,开发人员为每个音频单元创建一个视图。
  4. 主机检查cocoaViewInfo的值以找出 视图包在哪里,以及视图的主视图类是什么(如果音频单元提供多个视图,则为指定的视图)。
  5. 主机加载视图包,首先加载主视图类来实例化它。

关于如何构建 Cocoa 视图的主视图类,有一些规则:

  • 视图必须实现AUCocoaUIBase协议。
    该协议指定视图类充当视图的工厂,并使用 uiViewForAudioUnit:withSize:方法返回一个 NSView对象。
    该方法告诉视图哪个音频单元拥有它,并提供有关视图屏幕尺寸的提示(以像素为单位)(使用 NSSize 结构)。
- (NSView *) uiViewForAudioUnit: (AudioUnit) inAudioUnit
                       withSize: (NSSize) inPreferredSize;

  • 如果您使用 nib 文件来构造视图(而不是以编程方式生成视图),则 nib 文件的所有者是视图的主(工厂)类。

无论音频单元是简单实例化还是已初始化,音频单元的视图都应该有效。
如果主机取消初始化音频单元,视图应该继续工作。
也就是说,视图不应该假设 其音频单元已初始化。
这在实践中非常重要,因此 auval工具包括一项测试,用于在取消初始化和重新初始化期间保留参数值。


6、参数和属性事件

音频单元自动化(如支持参数自动化 中所述)是由主机应用程序 和自定义视图实现的功能。
它依赖于参数和属性事件,而这些事件又依赖于 下文所述的音频单元事件 API。


音频单元事件 API

主机、视图和音频单元可以利用 Core Audio 通知来确保这三个实体在参数调整方面保持同步。
无论这三个实体中的哪一个调用参数更改,通知都可以通过 Audio Unit Event API 访问。
该 API 在Audio Toolbox 框架的头文件 AudioUnitUtilities.h 中声明。

Audio Unit Event API 定义了一种 AudioUnitEvent 数据类型,如 例 3-2所示:


例 3-2 AudioUnitEvent 结构

typedef struct AudioUnitEvent {
    AudioUnitEventType mEventType;          // 1
        union {
            AudioUnitParameter mParameter;  // 2
            AudioUnitProperty mProperty;    // 3
        } mArgument;
} AudioUnitEvent;

这个结构的工作原理如下:

  1. 标识通知的类型,如AudioUnitEventType枚举中所定义。
  2. 标识通知中涉及的参数,用于开始或结束手势或参数更改的通知。
    (请参阅参数手势。)
    AudioUnitParameter数据类型由 Audio Unit Event API 使用,而不是由 Audio Unit 框架使用,即使它是在 Audio Unit 框架中定义的。
  3. 对于属性变更通知,标识通知中涉及的属性。

相应的AudioUnitEventType枚举列出了各种定义的 AudioUnitEvent 的事件类型,如清单 3-3 所示:


例 3-3 AudioUnitEventType 枚举

typedef UInt32 AudioUnitEventType;
 
enum {
    kAudioUnitEvent_ParameterValueChange        = 0,
    kAudioUnitEvent_BeginParameterChangeGesture = 1,
    kAudioUnitEvent_EndParameterChangeGesture   = 2,
    kAudioUnitEvent_PropertyChange              = 3
};

  • kAudioUnitEvent_ParameterValueChange
    表示通知描述了参数值的变化
  • kAudioUnitEvent_BeginParameterChangeGesture
    表示通知描述了参数“开始”手势;参数值即将改变
  • kAudioUnitEvent_EndParameterChangeGesture
    表示通知描述了参数“结束”手势;参数值已完成更改
  • kAudioUnitEvent_PropertyChange
    表示通知描述了 音频单元属性值的变化

参数手势

表示参数更改开始或结束的用户界面事件称为手势
这些事件可用于在主机、视图和音频单元之间传递通知,告知参数即将更改或刚刚完成更改。
与参数和属性更改一样,手势使用音频单元事件 API 进行通信。
具体来说,手势使用kAudioUnitEvent_BeginParameterChangeGesturekAudioUnitEvent_EndParameterChangeGesture 事件类型,如上面的清单 3-3 所示。


基本参数调整

对于基本参数调整,Core Audio 提供了在 Audio Unit 框架的头文件 AUComponent.h 中声明的 AudioUnitSetParameter 函数。
当主机或视图调用此函数时,它会通过调用音频单元中的 SetParameter 方法设置音频单元中的指定参数。
它不提供通知来支持自动化或视图的更新。


带通知的参数调整

为了添加参数已更改的通知,主机或自定义视图在调用 AUParameterListenerNotify 函数之后,还会从 头文件 AudioUnitUtilities.h 中的音频单元 事件 API调用 AudioUnitSetParameter 函数。

为了设置参数并一次性通知监听器,主机或自定义视图可以调用 AUParameterSet函数,也可以从音频单元事件 API 中调用。

有时音频单元本身会更改其某个参数的值。
在这种情况下,它应该发出有关值更改的通知。
有关此内容的讨论,请参阅在音频单元中 定义和使用参数


教程:演示参数手势和音频单元事件

这个迷你教程通过以下方式说明了手势和音频单元事件的工作原理:

  • 实例化音频单元的两个视图
  • 在其中一个视图中执行调整,同时在另一个视图中观察效果

在此过程中,本教程:

  • 介绍如何使用 Xcode 编译音频单元项目
  • 展示 AU Lab 如何显示同一音频单元的通用视图和自定义视图
  • 展示自定义属性如何支持音频单元和自定义视图之间的通信

在开始之前,请确保您已经安装了 Xcode 和 Core Audio SDK,它们都是 Apple 的 Xcode Tools 安装的一部分。

  1. 打开 Developer/Examples/CoreAudio/AudioUnits/FilterDemo 文件夹 中的 FilterDemo.xcodeproj Xcode项目文件。

在这里插入图片描述


  1. 单击“Build”以构建音频单元项目。
    (您可能会看到一些关于“非虚拟析构函数”的警告。请忽略这些警告。)
    Xcode 在 FilterDemo 项目文件夹内,创建一个新 build 文件夹。
  2. 打开build文件夹,并查看Development 目标文件夹内部。

在这里插入图片描述


新建的音频单元包名为FilterDemo.component,如图。


  1. FilterDemo.component包复制到~/Library/Audio/Plug-Ins/Components文件夹。
    在此位置,新建的音频单元可供主机应用程序使用。
  2. 启动 AU Lab 音频单元主机应用程序(在 /Developer/Applications/Audio/ 中)并创建一个新的 AU Lab 文档。
    除非您已将 AU Lab 配置为使用默认文档样式,否则将打开“创建新文档”窗口。
    如果 AU Lab 已在运行,请选择文件 > 新建以显示此窗口。

在这里插入图片描述


确保配置与图中所示的设置相匹配:音频设备为内置音频、输入源为线路输入、输出通道为立体声。
不要配置窗口的“输入”选项卡;稍后您将指定输入。单击“确定”。
将打开一个新的 AU Lab 窗口,显示您指定的输出通道。


在这里插入图片描述


  1. 点击AU Lab中Master Out轨道中Effects部分一行中的三角形菜单按钮,如图所示。

在这里插入图片描述


在打开的菜单中,从 Apple Demo 组中选择 Filter 音频单元:


在这里插入图片描述


打开过滤器音频单元的自定义视图。


在这里插入图片描述


自定义视图的频率响应曲线是根据音频单元的实际频率响应实时绘制的。
音频单元通过声明自定义属性将其频率响应数据提供给自定义视图。
音频单元使其自定义属性的值保持最新。
自定义视图查询音频单元的自定义属性以绘制频率响应曲线。


  1. 按住 Option 键并单击“效果”行中的音频单元名称。

在这里插入图片描述


打开过滤器音频单元视图的第二个实例。


在这里插入图片描述


  1. 点击音频单元视图的一个实例中的视图类型弹出菜单,如图:

在这里插入图片描述


在打开的菜单中,选择通用视图项:


在这里插入图片描述


视图变为通用视图,如下图所示。
现在您可以演示手势和音频单元事件了。


  1. 单击并按住通用视图 中的一个滑块,如图所示。
    单击时,可观察到自定义视图中的十字准线 以亮蓝色突出显示。
    只要按住鼠标按钮,它们就会保持突出显示状态。

在这里插入图片描述


当您在通用视图中移动滑块时,自定义视图中的频率响应曲线会与新设置保持同步。

  • 当您单击并释放通用视图中的滑块时,自定义视图中十字线的突出显示 和取消突出显示 是由手势事件引起的。
  • 当您在通用视图中移动滑块时,自定义视图 中频率响应曲线的变化 是由参数变化事件引起的

  1. 最后,单击并按住自定义视图中十字线的交叉点。
    单击时,可观察到通用视图中的滑块突出显示。
    当您移动自定义视图中的十字线时,通用视图中的滑块会与新设置保持同步。

在这里插入图片描述


这表明两个视图以及音频单元本身通过音频单元事件保持同步。


五、Core Audio SDK 快速导览

您可以直接使用 Core Audio 框架 从头开始构建音频单元。
但是,正如本文档所述,Apple 强烈建议您从 免费下载的 Core Audio 软件开发工具包 (SDK) 开始。
Apple 使用该 SDK 构建 OS X 中附带的所有音频单元。

SDK 为音频单元开发人员提供了许多优势。SDK:

  • 让你免于处理 OS X 组件管理器的几乎所有复杂性;
  • 丰富的 C++ 类层次结构大大简化了开发工作。
    在许多情况下,您可以使用一些方法覆盖来创建音频单元。
    这样,您就可以构建功能齐全、商业品质的音频单元,而无需直接调用 Core Audio 框架中的任何 API。
  • 通过 Xcode 音频单元项目模板提供一个简单的起点。

1、获取核心音频 SDK

安装最新的 Core Audio SDK。
它是 OS X DVD 上 Xcode Tools 安装的一部分。
您也可以从 Apple 的开发者网站下载它,网址为:http://developer.apple.com/sdk/

Core Audio 团队经常更新 SDK,因此请确保您使用的是最新版本。

安装程序包将 SDK 放置在 下面 CoreAudio 路径命名的文件夹中:
您的启动卷的 /Developer/Examples/CoreAudio/


2、在 Core Audio SDK 中导航

注意: 本节介绍 Core Audio SDK 的 1.4.3 版本。Core Audio 团队会经常更新 SDK。与所有 Apple SDK 一样,请使用最新版本。

Core Audio SDK 包含使用音频单元、音频文件、编解码器、MIDI 和 HAL 的资料。
CoreAudio 文件夹中的 ReadMe.rtf 文件可帮助您了解情况。
它包括 SDK 中文件夹的注释列表、Core Audio 团队相关资源的指针以及发行说明。


对于音频单元开发,SDK 中最重要的部分位于以下文件夹中:

  • AudioUnits
  • PublicUtility
  • Services

本节依次描述每个文件夹。


AudioUnits 文件夹

AudioUnits文件夹包含用于构建音频单元的 C++ 类层次结构。
它位于AudioUnits/AUPublic文件夹中。
音频单元 Xcode 模板依赖于此类层次结构。

AudioUnits/CarbonGenericView文件夹包含 Carbon 通用视图的源文件。

主机开发人员发现 Apple 的 Cocoa 通用视图的接口 不是在 Core Audio SDK 中,而是在 OS X 的CoreAudioKit框架中。

除了AUPublicCarbonGenericView源文件夹外, AudioUnits文件夹还包含几个完整的音频单元示例 Xcode 项目。
您可以通过多种方式使用这些项目:

  • 直接使用这些项目构建的音频单元
  • 研究源代码以了解如何设计和实现音频单元
  • 使用项目作为您自己的音频单元的起点

SDK 包含以下示例音频单元项目:

  • DiagnosticAUs 项目构建了三个音频单元,您在开发音频单元时可能会发现它们对于故障排除和分析很有用:
    • AUValidSamples 可检测通过它的超出范围或无效的样本。
      您可以从音频单元主机(例如 AU Lab)中的 Apple_DEBUG 组中选择此音频单元。
    • AUDebugDispatcher 有助于音频单元调试。
      您可以在音频单元主机(例如 AU Lab)中从 Acme Inc 组中选择此音频单元。
    • AUPulseDetector 测量主机应用程序中的延迟
  • FilterDemo 项目使用自定义视图构建了一个基本的谐振低通滤波器。
  • MultitapDelayAU 项目使用自定义视图构建多点延迟。
  • SampleAUs 项目构建了一个包含预设和多种参数类型的直通音频单元。
  • SinSynth 项目构建了一个简单的仪器单元,您可以在 GarageBand 等主机应用程序中使用它。

PublicUtility 文件夹

此文件夹包含 Core Audio SDK 的音频单元类层次结构和 SDK 中的示例项目使用的 C++ 和 Objective-C 源文件的各种集合。
您可能需要检查这些文件以深入了解类层次结构的工作原理。


Services 文件夹

此文件夹包含多个 Core Audio Xcode 项目,用于构建工具、音频单元和音频单元主机应用程序。
与文件夹中的项目一样AudioUnits,您可以在音频单元开发过程中以多种方式使用这些项目:

  • 直接使用这些项目构建的工具、音频单元和主机应用程序
  • 研究源代码以了解核心音频的工作原理,以及如何设计和实现工具、音频单元和主机应用程序
  • 使用项目作为您自己的工具、音频单元和主机应用程序的起点

Services 文件夹包含以下 Xcode 项目:

  • 音频文件工具
    该项目构建了一组八个命令行工具,用于播放、录制、检查和处理音频文件。
  • AudioUnitHosting
    构建基于 Carbon 的音频单元主机应用程序的项目。
    对于主机开发人员而言,它可用作示例代码,但已不再用作测试音频单元的主机。
    (对于音频单元测试,请使用 AU Lab。)
  • AUMixer3D测试
    构建使用 3D 混音器音频单元的应用程序的项目。
  • AUView测试
    基于音频处理图构建无窗口应用程序的项目。
    该图包括乐器单元、效果单元和输出单元。
    有一个菜单项可以打开效果单元的视图。
    AUViewTest 应用程序使用音乐播放器对象通过乐器单元播放重复序列。
  • CocoaAUHost
    构建基于 Cocoa 的音频单元主机应用的项目。
    此项目对于主机开发人员而言,是有用的示例代码,但不适合用作测试音频单元的主机。
    (对于音频单元测试,请使用 AU Lab。)
  • 矩阵混音器测试
    使用自定义 Cocoa 视图构建示例混合器单元的项目。
  • OpenALE示例
    该项目基于 OpenAL API 和 Apple 扩展构建应用程序。
    该应用程序演示了如何在二维音频源布局中控制听众的位置和方向。

六、教程:使用通用视图构建简单的效果单元

本教程章节将引导您完成具有通用视图的效果单元的完整设计和开发。
通过学习本教程,您将在学习如何开发自己的音频单元方面迈出一大步。

本章的格式让您无需阅读任何前面的材料即可成功完成。
但是,要了解您正在做的事情,您需要了解 音频单元开发基础知识音频单元 中的信息。

具体来说,为了从本章中获得最大收获,您应该理解:

  • 属性、参数和出厂预设
  • 音频单元类型、子类型、制造商 ID 和版本号
  • 音频单元生命周期包括实例化、初始化和渲染
  • Core Audio SDK 在音频单元开发中的作用
  • AU Lab 应用程序和 auval 工具在音频单元开发中的作用

1、概述

“简单”效果单元对单个音频样本进行操作,无需考虑一个音频样本与另一个音频样本的关系,也无需使用处理缓冲区。
您将在本章中构建这样的效果单元,并通过省略一些高级音频单元功能(自定义视图和自定义属性)进一步简化。

简单的效果单元可以做非常基本的DSP,例如:

  • 更改级别
  • 添加颤音

单声道颤音是通过改变音频通道在低频的增益(大约几赫兹)而产生的连续波动。
下图说明了从全增益到静音的单声道颤音变化:

图 5-1 单耳颤音

在这里插入图片描述


立体声颤音类似,但涉及低频的连续左/右平移。

在本章中,您将设计和构建使用通用视图的单声道颤音效果。
以下部分将详细介绍这些步骤:

  1. 如果还没有安装 Core Audio 开发套件,请先安装。
  2. 执行一些设计工作,包括:
    • 指定音频单元将执行的 DSP 类型
    • 设计参数接口
    • 设计工厂预设界面
    • 确定音频单元包的配置信息,例如子类型和制造商代码以及版本号
  3. 创建并配置 Xcode 项目。
  4. 实现你的音频单元:
    • 实现参数和预设接口。
    • 实现信号处理——音频单元的核心。
  5. 最后,验证并测试您的音频单元。

用于构建任何音频单元的开发环境(简单或其他)应包括 简介音频单元开发所需工具 下描述的部分。
您无需参考 Xcode 文档或 Core Audio SDK 文档即可完成本章中的任务。


2、安装核心音频开发套件

  1. 如果您还没有安装最新的 Core Audio SDK,请安装它。 它是 OS X DVD 上 Xcode Tools 安装的一部分。 您也可以从 Apple 的开发者网站下载它,网址为:http://developer.apple.com/sdk/
    Core Audio SDK 安装程序将 C++ 超类、示例 Xcode 项目和文档放置在系统的此位置:
    /Developer/Examples/CoreAudio
    SDK 还会在您系统的以下位置 安装音频单元的 Xcode 项目模板:
    /Library/Application Support/Apple/Developer Tools/Project Templates/
  2. 安装 SDK 后,确认 Xcode 可以识别音频单元模板,如下所示:
  • 启动 Xcode,然后选择文件 > 新建项目。
  • 在助手窗口中,确认有一个音频单元组。

图 5-2 确认音频单元模板已安装
在这里插入图片描述


确认音频单元模板组存在后,单击取消,然后退出 Xcode。

注意: 如果没有 Audio Units 模板组,请检查 SDK 文件是否确实按预期安装。如果没有,请尝试重新安装。
另外,请确保您使用的是最新版本的 Xcode。


3、指定音频单元的功能

有了工具后,您可以通过描述 其数字信号处理 来开始开发效果单元。
在本例中,您将指定一个音频单元,该单元以用户可选择的速率提供单声道颤音。
稍后您将实现 DSP,但预先建立鸟瞰视图可以让您缩小实现步骤。

接下来,根据音频单元将执行的功能选择音频单元类型。
查看音频单元框架文件中的各种标准音频单元类型。
另外,请参阅音频单元规范AUComponent.h中的类型描述。
带有四个字符 'aufx'kAudioUnitType_Effect 看起来适合此目的。


4、设计参数接口

在本部分中,您将设计音频单元与其视图之间的编程接口的参数部分。
最终用户将改变这些参数以实时控制您的音频单元。

首先,指定您希望用户能够控制哪些声音属性。
对于此项目,请使用以下内容:

  • 颤音频率——每秒的颤音周期数。
  • 颤音深度——输入信号与应用了颤音的信号之间的混合。
    深度为 0% 时,没有颤音效果。
    深度为 100% 时,效果范围从全振幅到静音。
  • 颤音波形——颤音效果的形状,例如正弦波或方波。

其次,为每个参数指定:

  • 音频单元视图中显示的 用户界面名称
  • 程序名称,也称为参数ID,用于GetParameterInfo音频单元实现文件中的方法
  • 测量单位,例如增益、分贝或赫兹
  • 最小值
  • 最大值
  • 默认值

下表指定了参数设计。
您将直接在代码中使用其中的大多数值。
在开发和扩展音频单元时,指定“说明”对您很有帮助。
您稍后可以在在线帮助或用户指南中重复使用该说明。


Table 5-1 Specification of tremolo frequency parameter

参数属性价值
用户界面名称频率
描述颤音效果的频率。当此参数设置为 2 Hz 时,每秒有两个颤音效果周期。
此参数的值是连续的,因此用户可以在可用范围内设置任意值。
用户使用滑块调整颤音频率。
程序名称kTremolo_Frequency
测量单位Hz
最小值0.5
最大值10.0
默认值2.0

Table 5-2 Specification of tremolo depth parameter

参数属性价值
用户界面名称深度
描述颤音效果的深度或强度。
设置为 0% 时,无颤音效果。
设置为 100% 时,颤音效果在每个周期内从静音到单位增益变化。
此参数的值是连续的,因此用户可以在可用范围内设置任何值。
用户使用滑块调整颤音深度。
程序名称kTremolo_Depth
测量单位Percent
最小值0.0
最大值100.0
默认值50.0

Table 5-3 Specification of tremolo waveform parameter

参数属性价值
用户界面名称波形
描述颤音效果遵循的波形。
此参数可以采用一组离散值。
用户从菜单中选择颤音波形。
程序名称kTremolo_Waveform
测量单位Indexed
一个值Sine
另一个值Square 正方形
默认值Sine 正弦

稍后添加、删除和优化参数非常容易。
例如,如果您要创建音频电平调整参数,您可能先从线性刻度开始,然后更改为对数刻度。
对于您正在构建的颤音单元,您可能稍后决定添加其他颤音波形。


5、设计工厂预设

现在您已经指定了参数,您可以指定有趣的设置组合或出厂预设。
对于颤音单元,请指定两个出厂预设。
这两个是为本教程发明的,可提供两种易于区分的效果:


Table 5-4 Specification of Slow & Gentle factory preset

范围价值
用户界面名称Slow & Gentle 缓慢而温柔
描述A gentle waver 轻轻一挥
kTremolo_Frequency2.0 赫兹
kTremolo_Depth50%
kTremolo_WaveformSine 正弦

Table 5-5 Specification of Fast & Hard factory preset

范围价值
用户界面名称Fast & Hard 又快又硬
描述狂热、震撼、激烈
kTremolo_Frequency20.0 赫兹
kTremolo_Depth90%
kTremolo_Waveform正方形

6、收集音频单元包的配置信息

现在确定音频单元包的配置信息,例如名称和版本号。
您可以将这些信息输入到项目的源文件中,如后面在 Xcode 中设置公司名称 中所述。
以下是您预先确定和收集的信息:

  • 音频单元包英文名称,例如 Tremolo Unit。
    主机应用程序将显示此名称,以便用户挑选您的音频单元。
  • 音频单元编程名称,例如 TremoloUnit。
    您将使用它来命名您的 Xcode 项目,而 Xcode 项目又会将此名称用作音频单元的主类。
  • 音频单元包版本号,例如 1.0.2。
  • 音频单元包的简要说明,例如 “Tremolo Unit 版本 1.0,版权所有 © 2006,Angry Audio”。
    此说明显示在 Finder 中音频单元包的 “获取信息” 窗口的“版本”字段中。
  • 音频单元包子类型,一个四字符代码,用于(向用户)提供一些有关音频单元功能的指示。
    对于此音频单元,请使用'tmlo'
  • 公司英文名称,例如 Angry Audio。
    此字符串出现在音频单元通用视图中。
  • 公司程序名称,例如angryaudio
    您将在音频单元的反向域名样式标识符中使用此字符串。
  • 公司四位代码,例如'Aaud'
    您可以使用 数据类型注册页面从 Apple 获取此代码,作为“创建者代码” 。
    为了举例说明,对于我们虚构的公司 Angry Audio,我们将使用'Aaud'
  • 反向域名标识符,例如com.angryaudio.audiounit.TremoloUnit
    组件管理器将使用此名称来识别您的音频单元。
  • 自定义图标,用于在 Cocoa 通用视图中显示 — 可选但很不错。
    这应该是所谓的“小”图标,由 Apple 的 Icon Composer 应用程序构建,可在系统的/Developer/Applications/Utilities 文件夹中找到。

7、在 Xcode 中设置您的公司名称

如果您尚未设置公司名称,请在 Xcode 中设置(Xcode 未运行)。
在终端中一行输入以下命令:

defaults write com.apple.Xcode PBXCustomTemplateMacroDefinitions '{ORGANIZATIONNAME = "Angry Audio";}'

现在您已经设置了这个所谓的“专家”偏好,Xcode 会将您公司的名称放在任何新项目的源文件中的版权声明中。


8、创建并配置项目

现在,您将为音频单元创建并配置一个 Xcode 项目。
本节可能看起来很长——它包含许多步骤和屏幕截图——但在您熟悉配置过程后,您可以在大约五分钟内完成它。

启动 Xcode 并按照以下步骤操作:

  1. 选择“文件”>“新建项目”
  2. 在新建项目助手对话框中,选择音频单元效果模板,然后单击下一步。

在这里插入图片描述


  1. 将项目命名为 TremoloUnit,并指定项目目录。然后单击“完成”。

在这里插入图片描述


Xcode 为您的音频单元创建项目文件,并打开 Xcode 项目窗口。

此时,Xcode 已使用音频单元项目模板文件 创建了 AUEffectBase 类的子类。
您的自定义子类 根据项目名称命名。
您可以在 Xcode Groups & Files 窗格中的 AU Source 组中找到自定义子类的实现文件 TremoloUnit.cpp,如下所示。


在这里插入图片描述


在后面的步骤中,您将编辑自定义子类中的方法来覆盖超类的方法。
TremoloUnit.cpp还包含来自 AUKernelBase 辅助类的几个方法;这些是您稍后修改以执行数字信号处理的方法。


  1. 打开 AU 源组(如上一步所示),单击TremoloUnitVersion.h
    然后单击编辑器工具栏按钮(如有必要)以显示文本编辑器。
    此文件中有三个值可自定义:kTremoloUnitVersionTremoloUnit_COMP_SUBTYPETremoloUnit_COMP_MANF

在编辑窗格中向下滚动到 TremoloUnit_COMP_SUBTYPETremoloUnit_COMP_MANF 的定义。
使用您选择的四个字符的子类型代码自定义子类型字段。
在此示例中,'tmlo'表示(对开发人员和用户)音频单元允许用户添加颤音。


在这里插入图片描述


还可以使用 可识别您公司的唯一四个字符 的字符串 来定制制造商名称。

注意: TremoloUnitVersion.h文件中 没有组件类型的 #define 声明,因为您在为音频单元 选择 Xcode 模板时 指定了类型 - 效果单元。
音频单元包类型 在 AU Source/TremoloUnit.r 资源文件中指定。

现在设置音频单元的版本号。
TremoloUnitVersion.h文件中,就在子类型和制造商定义的上方,您将看到 kTremoloUnitVersion 常量的定义语句。
默认情况下,模板将此常量的值设置为 1.0.0,以十六进制数表示0x00010000
如果您愿意,可以更改此值。
有关如何构造十六进制版本号,请参阅音频单元标识。
保存TremoloUnitVersion.h文件。


  1. 单击 “组和文件”窗格中 “Source/AU Source” 组中的 TremoloUnit.r 资源文件。
    此文件中有两个值可供自定义:NAMEDESCRIPTION
  • NAME用于通用视图 显示您的公司名称 和 音频单元名称
  • DESCRIPTION作为用户 从主机应用程序中 选择音频单元的菜单项。

为了与通用视图正确协作,NAME 的值必须遵循特定的模式:

<company name>: <audio unit name>

对于此示例,使用:

Angry Audio: Tremolo Unit

如果您已经按照前面所述使用 Xcode 专家偏好 设置了公司名称,那么它将已经存在于NAME该项目的变量中;
按照这个例子,您需要做的就是在 音频单元名称本身的 Tremolo 和 Unit 之间添加一个空格。

Xcode 模板为 提供了默认值DESCRIPTION
如果您对其进行自定义,请将其保持简短,以便字符串能够很好地与弹出菜单配合使用。
下图显示了一个自定义的DESCRIPTION


在这里插入图片描述


如图所示,资源文件使用#include语句 导入您在步骤 4 中自定义的 Version 头文件 TremoloUnitVersion.h
资源文件 使用该头文件中的值 来定义一些变量,例如组件子类型 ( COMP_SUBTYPE) 和制造商 ( COMP_MANUF)。
保存TremoloUnit.r资源文件。


  1. 在Xcode项目窗口的 Groups & Files 窗格中打开 Resources 组,然后单击 InfoPlist.strings文件。

在这里插入图片描述


使用编辑器自定义 CFBundleGetInfoString 值,使用您为音频单元简要说明选择的值。下图提供了一个示例。
此字符串出现在 Finder 中音频单元包的“获取信息”窗口的“版本”字段中。
保存文件InfoPlist.strings


  1. 在 Xcode 项目窗口的 Groups & Files 窗格中打开 Targets 组。
    双击音频单元包,该包与您的项目同名 — 在本例中为 TremoloUnit。

在这里插入图片描述


目标信息窗口打开。单击属性选项卡。

在这里插入图片描述


在目标信息窗口的属性选项卡中,提供标识符、创建者、版本的值,以及(可选)放置在 Bundle 的Resources 文件夹中的 bundle 的 Finder 图标文件路径。

音频单元包标识符字段应遵循以下模式:

com.<company_name>.audiounit.<audio_unit_name>

对于此示例,使用标识符:

com.angryaudio.audiounit.TremoloUnit

对于 Creator 值,请使用与步骤 4 中 制造商字段相同的四个字符的字符串。

Xcode 将“属性”选项卡中的所有信息 传输到音频单元包的Info.plist文件中。
如果您想检查文件,可以直接从此对话框中使用窗口底部的 “将 Info.plist 作为文件打开” 按钮来打开Info.plist 文件。
完成后,关闭Info.plist文件(如果已打开)或关闭目标信息窗口。


  1. 现在配置 Xcode 项目的构建过程,将您的音频单元包复制到适当的位置,以便主机应用程序可以使用它。
    在项目窗口中,公开产品组和目标组,如图所示,以便您可以看到音频单元包本身的图标(TremoloUnit.component)以及构建阶段(Targets/TremoloUnit下)。

在这里插入图片描述


  1. 现在添加一个新的构建阶段。
    右键单击(或按住 Control 键单击)TremoloUnit 的最终构建阶段,然后选择 添加 > 新构建阶段 > 新复制文件构建阶段。

在这里插入图片描述


新的“复制文件”构建阶段出现在列表末尾,并打开一个对话框,标题为“TremoloUnit”信息的复制文件阶段。


在这里插入图片描述


将目标弹出窗口更改为绝对路径,如图所示。

在完整路径字段中输入构建的音频单元包的绝对目标路径。

注意: 如果您输入波浪符号 ( ~) 来表示您的主文件夹,则复制阶段将不起作用 。
在 Xcode 2.4 中,只有当您首先单击“完整路径”字段时,“完整路径”字段才会允许您通过将目标文件夹拖到文本字段中来输入路径。

您可以使用音频单元包的有效路径之一,如音频单元安装和注册中所述。
输入完整路径后,关闭对话框。

现在将TremoloUnit.component图标从产品组拖到新的构建阶段。


在这里插入图片描述


如果需要,您可以稍后通过双击灰色的“复制文件”构建阶段图标来更改“复制文件”位置。
或者,单击“复制文件”图标,然后单击工具栏中的“信息”按钮。

此时,您已经具备了制作一个可运行的音频单元的条件。
您尚未自定义它以执行您想要它执行的任何操作(在我们目前的例子中,提供单通道颤音效果)。
最好确保您可以无错误地构建它、可以使用 auval 工具验证它以及可以在主机应用程序中使用它。
在下一步中执行此操作。


  1. 构建项目。
    您可以采用任何标准方式执行此操作:单击工具栏中的“构建”按钮,或从“构建”按钮的菜单中选择“构建”,或从主菜单中选择“构建”>“构建”,或键入 command-B。

在这里插入图片描述


如果一切正常,您的项目将会顺利构建。

您在上一步中添加的复制文件构建阶段可确保音频单元包的副本放置在适当的位置,以便组件管理器在主机应用程序启动时找到它。
下一步可确保这一点,并让您测试它是否在主机应用程序中有效。


9、测试未修改的音频单元

要测试新建的音频单元,请使用 AU Lab 应用程序:

  • 使用 auval工具验证 OS X 是否识别你的新音频单元包
  • 启动 AU Lab 应用程序
  • 配置 AU Lab 来测试你的新音频单元,并对其进行测试

Apple 的 Core Audio 团队在/Developer/Applications/Audio文件夹中提供了 AU Lab 以及文档。
您无需参考 AU Lab 的文档即可完成此任务。
auval命令行工具是标准 OS X 安装的一部分。


  1. 在终端中输入命令auval -a
    如果 OS X 识别出你的新音频单元包,你会看到类似下面的列表:

在这里插入图片描述


如果您的 Xcode 项目构建时没有出现错误,但是您在工具报告的列表中没有看到新的音频单元包,请仔细检查您是否在复制文件阶段输入了正确的路径,如在 Xcode 中设置您的公司名称auval第 8 步所述。

注意:如果您阅读过“音频单元开发基础”中的“教程:在主机应用程序中使用音频单元”, 那么本教程中的接下来几个步骤对您来说会很熟悉。


  1. 启动 AU Lab 并创建一个新的 AU Lab 文档。
    除非您已将 AU Lab 配置为使用默认文档样式,否则将打开“创建新文档”窗口。
    如果 AU Lab 已在运行,请选择文件 > 新建以打开此窗口。

在这里插入图片描述


确保配置与图中所示的设置相匹配:音频设备为内置音频、输入源为线路输入、输出通道为立体声。
不要配置窗口的“输入”选项卡;稍后您将指定输入。
单击“确定”。

将打开一个新的 AU Lab 窗口,显示您指定的输出通道。


在这里插入图片描述


  1. 选择“编辑”>“添加音频单元生成器”。
    AU Lab 窗口会打开一个对话框,让您指定要用作音频源的生成器单元。

在这里插入图片描述


在对话框中,确保在 Generator 弹出窗口中选择了 AUAudioFilePlayer 单元。
按照此示例,将 Group Name 更改为 Player。
单击 OK。

注意: 您可以随时通过在 AU Lab 窗口中双击组名来更改组名。

AU Lab 窗口现在显示立体声输入轨道。
此外,播放器单元的检查器窗口已打开。
如果您关闭检查器,可以通过单击播放器轨道顶部附近的矩形“AU”按钮重新打开它。


在这里插入图片描述


  1. 将音频文件添加到播放器检查器窗口中的音频文件列表中。
    通过从 Finder 中拖动音频文件来执行此操作,如图所示。
    将音频文件放入播放器检查器窗口可让您通过新的音频单元发送音频。
    几乎任何音频文件都可以,尽管连续音调对测试很有帮助。

在这里插入图片描述


现在 AU Lab 已配置完毕并准备测试您的音频单元。


  1. 点击播放器轨道中效果部分第一行的三角形菜单按钮,如图。

在这里插入图片描述


打开菜单,列出系统上可用的所有音频单元,按类别和制造商排列。
弹出窗口中有一个 Angry Audio 组,如下图所示。

从效果第一行弹出窗口中选择新的音频单元。


在这里插入图片描述


AU Lab 打开您的音频单元的 Cocoa 通用视图,该视图显示为一个实用程序窗口。


在这里插入图片描述


通用视图显示音频单元的界面,因为它直接来自 Xcode 模板提供的参数和属性定义。
模板定义了一个提供电平调整的音频单元。
其视图由 AU Lab 主机应用程序构建,具有增益控制。
您可以通过更改音频单元的参数定义在本任务的后续步骤中修改视图。

有关在何处定义通用视图的每个用户界面元素的信息,请参阅表 3-1


  1. 单击 AUAudioFilePlayer 检查器中的“播放”按钮,通过未修改的音频单元发送音频。
    这样可以确保音频确实通过了音频单元。
    在收听音频时,改变通用视图中的滑块,以确保参数正常工作。
  2. 保存 AU Lab 文档以供稍后使用,并为其命名,例如“Tremolo Unit Test.trak”。
    您将在本章的最后一节 “测试已完成的音频单元”中使用它。

接下来,您将定义颤音效果单元的参数界面,以便用户控制颤音速率、深度和波形。


10、实现参数接口

到目前为止,在开发音频单元的过程中,您接触的代码很少。
在这里,您可以通过编辑音频单元自定义子类的源文件来定义音频单元参数:TremoloUnit.hTremoloUnit.cpp

注意: 本章和本章剩余的任务中有许多步骤需要执行。
最好在每个部分之后构建音频单元包以检查错误,然后纠正错误。


要定义音频单元参数,您需要做三件事:

  • 在自定义子类的头文件中命名参数并赋值
  • 在音频单元的构造函数方法中添加语句,用于设置音频单元实例化时的参数
  • 在自定义子类的实现文件中,重写GetParameterInfoSDKAUBase类中的方法来定义参数

您在此处实现的代码本身并不使用参数。
稍后在 “实现信号处理” 中实现的 DSP 代码会使用这些参数。
在这里,您只需定义参数,以便它们出现在音频单元通用视图中,并在您实现 DSP 代码时随时可用。


命名参数并设置值

首先,命名音频单元的参数并为其提供值。
通过将 Xcode 模板提供的 TremoloUnit.h 头文件中的默认参数 定义代码,替换为以下代码来执行此操作。
此清单根据 设计参数接口 中的表格实现了参数设计。


例 5-1 参数名称和值(TremoloUnit.h

#pragma mark ____TremoloUnit Parameter Constants
 
static CFStringRef kParamName_Tremolo_Freq      = CFSTR ("Frequency");    // 1
static const float kDefaultValue_Tremolo_Freq   = 2.0;                    // 2
static const float kMinimumValue_Tremolo_Freq   = 0.5;                    // 3
static const float kMaximumValue_Tremolo_Freq   = 20.0;                   // 4
 
static CFStringRef kParamName_Tremolo_Depth     = CFSTR ("Depth");        // 5
static const float kDefaultValue_Tremolo_Depth  = 50.0;
static const float kMinimumValue_Tremolo_Depth  = 0.0;
static const float kMaximumValue_Tremolo_Depth  = 100.0;
 
static CFStringRef kParamName_Tremolo_Waveform  = CFSTR ("Waveform");     // 6
static const int kSineWave_Tremolo_Waveform     = 1;
static const int kSquareWave_Tremolo_Waveform   = 2;
static const int kDefaultValue_Tremolo_Waveform = kSineWave_Tremolo_Waveform;
 
// menu item names for the waveform parameter
static CFStringRef kMenuItem_Tremolo_Sine       = CFSTR ("Sine");         // 7
static CFStringRef kMenuItem_Tremolo_Square     = CFSTR ("Square");       // 8
 
// parameter identifiers
enum {                                                                    // 9
    kParameter_Frequency  = 0,
    kParameter_Depth      = 1,
    kParameter_Waveform   = 2,
    kNumberOfParameters   = 3
};

代码的工作原理如下:

  1. 为频率(kParamName_Tremolo_Freq)参数提供用户界面名称。
  2. 为颤音单元的频率参数的默认值定义一个常数,预计在实现文件中定义赫兹单位。
  3. 定义频率参数的最小值常数。
  4. 定义频率参数的最大值常数。
  5. 为 Depth ( kParamName_Tremolo_Depth) 参数提供用户界面名称。
    以下三行定义 Depth 参数的默认值、最小值和最大值的常量。
  6. 为 Waveform ( kParamName_Tremolo_Waveform) 参数提供用户界面名称。
    以下三行定义了 Waveform 参数的最小值、最大值和默认值的常量。
  7. 定义波形参数的正弦波选项的菜单项字符串。
  8. 定义波形参数的方波选项的菜单项字符串。
  9. 定义用于识别参数的常量;定义参数的总数。

对于您在文件中定义的每个参数TremoloUnit.h,您的音频单元需要:

  • 构造函数方法中的相应 SetParameter 语句,如下面编辑构造函数方法中所述
  • GetParameterInfo 方法中相应的参数定义,如后面 定义参数中所述

注意: 此时,构建音频单元将失败,因为您尚未编辑实现 TremoloUnit.cpp 文件以使用新的参数定义。
您将在接下来的几节中 处理 TremoloUnit.cpp 文件。


编辑构造函数方法

接下来,用本节中的代码替换 TremoloUnit.cpp 文件中的自定义子类 构造函数方法。
此代码实例化音频单元,其中包括设置您在上一节中定义的参数名称和值。

目前,此处显示的 SetParameter 语句是构造函数方法中唯一需要自定义的行。
在后面的步骤中,您将添加代码来定义默认的出厂预设。


例 5-2 在构造函数中设置参数(TremoloUnit.cpp

TremoloUnit::TremoloUnit (AudioUnit component) : AUEffectBase (component) {
 
    CreateElements ();
    Globals () -> UseIndexedParameters (kNumberOfParameters);
 
    SetParameter (                                       // 1
        kParameter_Frequency,
        kDefaultValue_Tremolo_Freq
    );
 
    SetParameter (                                       // 2
        kParameter_Depth,
        kDefaultValue_Tremolo_Depth
    );
 
    SetParameter (                                       // 3
        kParameter_Waveform,
        kDefaultValue_Tremolo_Waveform
    );
 
    #if AU_DEBUG_DISPATCHER
        mDebugDispatcher = new AUDebugDispatcher (this);
    #endif
}

代码的工作原理如下:

  1. 根据头文件中的值设置音频单元的第一个参数。
    在此项目中,此参数控制颤音频率。
    SetParameter方法继承自 Core Audio SDK 中的超类。
  2. 根据头文件中的值设置音频单元的第二个参数。在此项目中,此参数控制颤音深度。
  3. 根据头文件中的值设置音频单元的第三个参数。在此项目中,此参数控制颤音波形。

定义参数

替换 Xcode 模板中方法的默认重写,该方法仅定义一个默认 (Gain) 参数。
这里,使用前面在 设计参数接口 GetParameterInfo 中描述的三参数设计。

注意: 您可能需要参考“音频单元中的 定义和使用参数” 来了解此方法在音频单元操作中所起的作用。
您可能需要参考“音频单元范围” 来了解此方法中 使用的全局范围 的背景知识。


例 5-3 自定义GetParameterInfo方法(TremoloUnit.cpp

#pragma mark ____Parameters
ComponentResult TremoloUnit::GetParameterInfo (
        AudioUnitScope          inScope,
        AudioUnitParameterID    inParameterID,
        AudioUnitParameterInfo  &outParameterInfo
) {
    ComponentResult result = noErr;
 
    outParameterInfo.flags =    kAudioUnitParameterFlag_IsWritable        // 1
                                | kAudioUnitParameterFlag_IsReadable;
 
    if (inScope == kAudioUnitScope_Global) {                              // 2
        switch (inParameterID) {
            case kParameter_Frequency:                                    // 3
                AUBase::FillInParameterName (
                    outParameterInfo,
                    kParamName_Tremolo_Freq,
                    false
                );
                outParameterInfo.unit =                                   // 4
                    kAudioUnitParameterUnit_Hertz;
                outParameterInfo.minValue =                               // 5
                        kMinimumValue_Tremolo_Freq;
                outParameterInfo.maxValue =                               // 6
                        kMaximumValue_Tremolo_Freq;
                outParameterInfo.defaultValue =                           // 7
                        kDefaultValue_Tremolo_Freq;
                outParameterInfo.flags                                    // 8
                        |= kAudioUnitParameterFlag_DisplayLogarithmic;
                break;
 
            case kParameter_Depth:                                        // 9
                AUBase::FillInParameterName (
                    outParameterInfo,
                    kParamName_Tremolo_Depth,
                    false
                );
                outParameterInfo.unit =                                   // 10
                    kAudioUnitParameterUnit_Percent;
                outParameterInfo.minValue =
                    kMinimumValue_Tremolo_Depth;
                outParameterInfo.maxValue =
                    kMaximumValue_Tremolo_Depth;
                outParameterInfo.defaultValue =
                     kDefaultValue_Tremolo_Depth;
                break;
 
            case kParameter_Waveform:                                     // 11
                AUBase::FillInParameterName (
                    outParameterInfo,
                    kParamName_Tremolo_Waveform,
                    false
                );
                outParameterInfo.unit =                                   // 12
                    kAudioUnitParameterUnit_Indexed;
                outParameterInfo.minValue =
                    kSineWave_Tremolo_Waveform;
                outParameterInfo.maxValue =
                    kSquareWave_Tremolo_Waveform;
                outParameterInfo.defaultValue =
                    kDefaultValue_Tremolo_Waveform;
                break;
 
            default:
                result = kAudioUnitErr_InvalidParameter;
                break;
        }
    } else {
        result = kAudioUnitErr_InvalidParameter;
    }
    return result;
}

代码的工作原理如下:

  1. 为音频单元的所有参数添加两个标志,向主机应用程序指示它应该将所有音频单元的参数视为可读写。
  2. 该音频单元的所有三个参数都属于“全局”范围。
  3. 当视图需要参数信息时调用 switch 语句中的第一个 case,kTremolo_Frequency定义如何在用户界面中表示此参数。
  4. 将频率参数的测量单位设置为赫兹。
  5. 设置频率参数的最小值。
  6. 设置频率参数的最大值。
  7. 设置频率参数的默认值
  8. 添加一个标志来向主机指示它应该对频率参数使用对数控制。
  9. 当视图需要参数信息时调用 switch 语句中的第二种情况,kTremolo_Depth定义了如何在用户界面中表示此参数。
  10. 将 Depth 参数的测量单位设置为百分比。
    以下三个语句设置 Depth 参数的最小值、最大值和默认值。
  11. 当视图需要参数信息时调用 switch 语句中的第三个 case,语句定义如何在用户界面中表示 kTremolo_Waveform 参数。
  12. 将波形参数的测量单位设置为“索引”,允许它在通用视图中显示为弹出菜单。
    以下三个语句设置深度参数的最小值、最大值和默认值。
    这三个都是参数用户界面正常运行所必需的。

为波形弹出菜单提供字符串

现在您实现 GetParameterValueStrings方法,它允许音频单元的通用视图 将波形参数显示为弹出菜单。

此代码的方便位置是GetParameterInfo方法定义之后。
如果您按照建议在此处添加此方法,请确保在实现文件的其他位置删除 Xcode 模板提供的占位符方法。


例 5-4 自定义GetParameterValueStrings方法(TremoloUnit.cpp

ComponentResult TremoloUnit::GetParameterValueStrings (
    AudioUnitScope          inScope,
    AudioUnitParameterID    inParameterID,
    CFArrayRef              *outStrings
) {
    if ((inScope == kAudioUnitScope_Global) &&             // 1
        (inParameterID == kParameter_Waveform)) {
 
        if (outStrings == NULL) return noErr;              // 2
 
        CFStringRef strings [] = {                         // 3
            kMenuItem_Tremolo_Sine,
            kMenuItem_Tremolo_Square
        };
 
        *outStrings = CFArrayCreate (                      // 4
            NULL,
            (const void **) strings,
            (sizeof (strings) / sizeof (strings [0])),     // 5
            NULL
        );
        return noErr;
    }
    return kAudioUnitErr_InvalidParameter;
}

代码的工作原理如下:

  1. 该方法仅适用于波形参数,该参数属于全局范围。
  2. 当该方法被 AUBase::DispatchGetPropertyInfo 方法调用时,如果为 outStrings 参数提供空值,则直接返回而无错误。
  3. 定义一个包含弹出菜单项名称的数组。
  4. 创建一个包含菜单项名称的新不可变数组,并将该数组放置在outStrings 输出参数中。
  5. 计算数组中的菜单项的数量。

11、实现出厂预设接口

接下来,您再次通过编辑音频单元 自定义子类的源文件 来定义音频单元工厂预,TremoloUnit.hTremoloUnit.cpp

下面详细描述的步骤如下:

  • 命名工厂预设并赋予其值。
  • 通过添加用于处理工厂预设的方法签名来修改 TremoloUnit 类声明。
  • 编辑音频单元的构造函数方法来设置默认的工厂预设。
  • 重写GetPresets方法来设置工厂预设数组。
  • 重写NewFactoryPresetSet方法来定义工厂预设。

注意: 您在此处所做的工作本身并不使用工厂预设。
您在 “实现信号处理” 中实现的 DSP 代码会利用预设。
在这里,您只是定义预设,以便它们出现在音频单元通用视图中,并在您实现 DSP 代码时随时可用。


命名工厂预设并赋予其值

定义工厂预设的常量。
此代码在TremoloUnit.h头文件中的方便位置是参数常量部分下方。


例 5-5 工厂预设名称和值(TremoloUnit.h

#pragma mark ____TremoloUnit Factory Preset Constants
static const float kParameter_Preset_Frequency_Slow = 2.0;    // 1
static const float kParameter_Preset_Frequency_Fast = 20.0;   // 2
static const float kParameter_Preset_Depth_Slow     = 50.0;   // 3
static const float kParameter_Preset_Depth_Fast     = 90.0;   // 4
static const float kParameter_Preset_Waveform_Slow            // 5
    = kSineWave_Tremolo_Waveform;
static const float kParameter_Preset_Waveform_Fast            // 6
    = kSquareWave_Tremolo_Waveform;
enum {
    kPreset_Slow    = 0,                                      // 7
    kPreset_Fast    = 1,                                      // 8
    kNumberPresets  = 2                                       // 9
};
 
static AUPreset kPresets [kNumberPresets] = {                 // 10
    {kPreset_Slow, CFSTR ("Slow & Gentle")},
    {kPreset_Fast, CFSTR ("Fast & Hard")}
};
 
static const int kPreset_Default = kPreset_Slow;              // 11

代码的工作原理如下:

  1. 为“慢而柔和”工厂预设的频率值定义一个常数。
  2. 为“快速和困难”工厂预设的频率值定义一个常数。
  3. 为“慢而轻柔;用力”工厂预设的深度值定义一个常数。
  4. 为“Fast & Hard”工厂预设的深度值定义一个常数。
  5. 为“慢而柔和”工厂预设的波形值定义一个常数。
  6. 为“Fast & Hard”工厂预设的波形值定义一个常数。
  7. 为“慢而柔和”工厂预设定义一个常量。
  8. 为“Fast & Hard”工厂预设定义一个常数。
  9. 定义一个代表工厂预设总数的常量。
  10. 定义一个包含两个 Core Foundation 字符串对象的数组。
    这些对象包含与出厂预设相对应的用户界面菜单项的值。
  11. 定义一个代表默认工厂预设的常量,在本例中为“慢速且轻柔”预设。

添加工厂预设的方法声明

现在,提供方法声明以覆盖 AUBase 超类中的GetPresetsNewFactoryPresetSet方法。
将这些方法声明添加到 TremoloUnit.h头文件 中的 public: 类声明部分。
您将在本章的后面步骤中实现这些方法。


例 5-6 工厂预设方法声明(TremoloUnit.h

#pragma mark ____TremoloUnit
class TremoloUnit : public AUEffectBase {
 
public:
    TremoloUnit (AudioUnit component);
...
    virtual ComponentResult GetPresets (       // 1
        CFArrayRef        *outData
    ) const;
 
    virtual OSStatus NewFactoryPresetSet (     // 2
        const AUPreset    &inNewFactoryPreset
    );
protected:
...
};

代码的工作原理如下:

  1. GetPresets 方法的声明,覆盖 AUBase 超类的方法。
  2. 声明 NewFactoryPresetSet 方法,覆盖 AUBase 超类中的方法。

设置默认出厂预设

现在返回 TremoloUnit 构造函数方法,之前您已在其中添加了用于设置音频单元参数的代码。
在这里,您可以添加一条语句来设置默认出厂预设,并利用 kTremoloPreset_Default 常量。


例 5-7 在构造函数中设置默认工厂预设(TremoloUnit.cpp

TremoloUnit::TremoloUnit (AudioUnit component) : AUEffectBase (component) {
 
    CreateElements ();
    Globals () -> UseIndexedParameters (kNumberOfParameters);
    // code for setting default values for the audio unit parameters
    SetAFactoryPresetAsCurrent (                    // 1
        kPresets [kPreset_Default]
    );
    // boilerplate code for debug dispatcher
} 

代码的工作原理如下:

  1. 设置默认工厂预设。

实现 GetPresets 方法

为了让用户能够使用您定义的出厂预设,您必须添加 GetPresets方法的通用实现。
以下通用代码适用于任何可以支持出厂预设的音频单元。

TremoloUnit.cpp 实现文件中,此代码的方便位置是在GetPropertyInfoGetProperty方法之后。

注意: 您可以参考控制代码:参数、工厂预设和属性,了解 GetPresets 方法的架构描述,以及它如何适应音频单元操作。


例 5-8 实现GetPresets方法(TremoloUnit.cpp

#pragma mark ____Factory Presets
ComponentResult TremoloUnit::GetPresets (                     // 1
    CFArrayRef *outData
) const {
 
    if (outData == NULL) return noErr;                        // 2
 
    CFMutableArrayRef presetsArray = CFArrayCreateMutable (   // 3
        NULL,
        kNumberPresets,
        NULL
    );
 
    for (int i = 0; i < kNumberPresets; ++i) {                // 4
        CFArrayAppendValue (
            presetsArray,
            &kPresets [i]
        );
    }
 
    *outData = (CFArrayRef) presetsArray;                     // 5
    return noErr;
}

代码的工作原理如下:

  1. GetPresets方法接受一个参数,即指向 CFArrayRef 对象的指针。
    此对象保存此方法生成的出厂预设数组。
  2. 检查该音频单元是否已实现出厂预设。
  3. 实例化可变的 Core Foundation 数组来保存工厂预设。
  4. 使用 TremoloUnit.h 文件中定义的值填充工厂预设数组。
  5. 将工厂预设数组存储在 outData位置。

定义工厂预设

NewFactoryPresetSet方法定义了音频单元的所有出厂预设。
基本上,对于每个预设,它都会调用一系列SetParameter调用。

TremoloUnit.cpp实现文件中,此代码的一个方便位置是在 GetPresets 方法实现之后。


例 5-9NewFactoryPresetSet在方法 ( TremoloUnit.cpp) 中定义工厂预设

OSStatus TremoloUnit::NewFactoryPresetSet (                         // 1
    const AUPreset &inNewFactoryPreset
) {
    SInt32 chosenPreset = inNewFactoryPreset.presetNumber;          // 2
 
    if (                                                            // 3
        chosenPreset == kPreset_Slow ||
        chosenPreset == kPreset_Fast
    ) {
        for (int i = 0; i < kNumberPresets; ++i) {                  // 4
            if (chosenPreset == kPresets[i].presetNumber) {
                switch (chosenPreset) {                             // 5
 
                    case kPreset_Slow:                              // 6
                        SetParameter (                              // 7
                            kParameter_Frequency,
                            kParameter_Preset_Frequency_Slow
                        );
                        SetParameter (                              // 8
                            kParameter_Depth,
                            kParameter_Preset_Depth_Slow
                        );
                        SetParameter (                              // 9
                            kParameter_Waveform,
                            kParameter_Preset_Waveform_Slow
                        );
                        break;
 
                    case kPreset_Fast:                             // 10
                        SetParameter (
                            kParameter_Frequency,
                           kParameter_Preset_Frequency_Fast
                        );
                        SetParameter (
                            kParameter_Depth,
                            kParameter_Preset_Depth_Fast
                        );
                        SetParameter (
                            kParameter_Waveform,
                            kParameter_Preset_Waveform_Fast
                        );
                        break;
                }
                SetAFactoryPresetAsCurrent (                        // 11
                    kPresets [i]
                );
                return noErr;                                       // 12
            }
        }
    }
    return kAudioUnitErr_InvalidProperty;                           // 13
}

代码的工作原理如下:

  1. 此方法采用单个类型的 AUPreset 参数,即包含工厂预设名称和编号的结构。
  2. 获取所需工厂预设的编号。
  3. 测试所需的工厂预设是否已定义。
  4. for循环及其if后跟的语句允许不连续的预设数字。
  5. 根据工厂预设号码选择适当的案例陈述。
  6. “慢而柔和”工厂预设的设置。
  7. 设置“慢而柔和”工厂预设的频率音频单元参数。
  8. 设置“Slow & Gentle”工厂预设的深度音频单元参数。
  9. 设置“Slow & Gentle”工厂预设的波形音频单元参数。
  10. “Fast & Hard” 工厂预设的设置。 SetParameter以下三个语句的工作方式与其他工厂预设相同。
  11. 更新通用视图中的预设菜单以显示新的出厂预设。
  12. 如果成功则返回值为noErr
  13. 如果主机应用程序尝试设置未定义的工厂预设,则返回错误。

12、实现信号处理

有了参数和预设代码,现在您可以开始处理问题的核心:数字信号处理代码。
合成、处理和数据格式转换代码 中所述, n 对 n 通道 效果单元执行的 DSP发生在类中, AUKernelBase 类是 AUEffectBase 类的辅助类。
有关 DSP 在音频单元中的工作原理的更多信息,请参阅 处理:问题的核心

要在nn通道效果单元中实现信号处理,请重写AUKernelBase类中的两个方法:

  • Process执行信号处理的方法
  • Reset方法将音频单元返回到其原始的初始化状态

在此过程中,您会更改 TremoloUnit.h 头文件中的默认 TremoloUnitKernel 类声明,并修改TremoloUnit.cpp中的 TremoloUnitKernel构造函数方法。


颤音效果的 DSP 设计

DSP 代码的设计和实现是实际音频单元开发的核心 — 但正如简介中所述,它们超出了本文的范围。
尽管如此,本节仍将介绍本项目中使用的简单 DSP,以说明将此类代码添加到效果单元所涉及的一些问题。

当您使用 Xcode 模板创建新的音频单元项目时,您将获得一个具有最少 DSP 的音频单元。
它仅包含一个乘法,该乘法将增益参数的值应用于音频信号的每个样本。
在这里,您仍然使用简单的乘法 - 但通过预先进行一些数学运算,您最终会得到一些更有趣的东西;即颤音。
在添加 DSP 代码时,您会看到每个部分的位置以及它如何适应音频单元框架。

颤音单元设计使用波表来描述颤音波形的一个周期。
TremoloUnitKernel 类在实例化期间构建波表。
为了使项目更有用和更有指导意义,该类构建了两个波表,而不是一个:一个代表正弦波,一个代表伪方波。

在处理过程中,颤音单元使用来自其波表之一的连续值作为增益因子,逐个样本应用于音频信号。
有代码可以确定将波表中的哪个点应用于给定的音频样本。

按照本章前面描述的音频单元参数设计,有代码可以实时改变颤音频率、颤音深度和颤音波形。


在内核类声明中定义成员变量

要开始为颤音单元实现 DSP,请将一些常量作为私有成员变量添加到TremoloUnitKernel类声明中。
将这些常量定义为私有成员变量,可确保它们对于 TremolUnitKernel 对象来说是全局的,而在其他地方是不可见的。

注意: 如下面的评论(1)所述,此处为内核辅助类使用的构造函数签名与 Xcode 模板中提供的签名不同。


例 5-10 TremoloUnitKernel成员变量(TremoloUnit.h

class TremoloUnit : public AUEffectBase
{
public:
    TremoloUnit(AudioUnit component);
 
...
 
protected:
    class TremoloUnitKernel : public AUKernelBase {
        public:
            TremoloUnitKernel (AUEffectBase *inAudioUnit); // 1
 
            virtual void Process (
                const Float32    *inSourceP,
                Float32          *inDestP,
                UInt32           inFramesToProcess,
                UInt32           inNumChannels,   // equal to 1
                bool             &ioSilence
        );
 
        virtual void Reset();
 
        private:
            enum     {kWaveArraySize = 2000};             // 2
            float    mSine [kWaveArraySize];              // 3
            float    mSquare [kWaveArraySize];            // 4
            float    *waveArrayPointer;                   // 5
            Float32  mSampleFrequency;                    // 6
            long     mSamplesProcessed;                   // 7
            enum     {sampleLimit = (int) 10E6};          // 8
            float    mCurrentScale;                       // 9
            float    mNextScale;                          // 10
    };
};

代码的工作原理如下(如果您对数学不感兴趣,请跳过此解释):

  1. Xcode 模板中的构造函数签名包含对超类构造函数的调用,以及表示方法主体的空括号。
    删除所有这些,因为您将在实现文件中实现构造函数方法,如下一节所述。
  2. 每个波表中的点数。
    每个波包含一个颤音波形周期。
  3. 颤音正弦波的波表。
  4. 颤音伪方波的波表。
  5. 应用于当前音频输入缓冲区的波表。
  6. 待处理的音频信号的采样频率,或通常所说的“采样率”。
  7. 自音频单元开始渲染以来或自上次将此变量设置为以来处理的样本数0。DSP 代码会跟踪处理的样本总数,因为:
  • 主处理循环基于放入输入缓冲区的样本数量……
  • 但是 DSP 必须独立于输入缓冲区大小。
  1. 为了将 mSamplesProcessed 值保持在合理的范围内,代码中有一个测试,当它达到该值时会重置它。
  2. 当前使用的缩放因子。
    DSP 使用缩放因子将波表中的点与音频信号采样频率关联起来,以产生所需的颤音频率。
    内核对象会跟踪“当前”和“下一个”缩放因子,以支持从一个颤音频率切换到另一个颤音频率而不会产生可听见的故障。
  3. 需要使用的缩放因子,由用户对不同颤音频率的请求决定。

编写 TremoloUnitKernel 构造函数方法

在该类的自定义子类的构造函数方法中AUKernelBase,处理内核对象每次实例化时可以完成的所有 DSP 工作。
就我们正在构建的颤音单元而言,这包括:

  • 初始化需要初始化的成员变量
  • 填充两个波表
  • 获取音频流的采样率。

构造函数的一个方便的位置是在TremoloUnitEffectKernel指令标记的正下方。


例 5-11 对构造函数的修改TremoloUnitKernelTremolUnit.cpp

#pragma mark ____TremoloUnitEffectKernel
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//    TremoloUnit::TremoloUnitKernel::TremoloUnitKernel()
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TremoloUnit::TremoloUnitKernel::TremoloUnitKernel (AUEffectBase *inAudioUnit) :
    AUKernelBase (inAudioUnit), mSamplesProcessed (0), mCurrentScale (0) // 1
{
    for (int i = 0; i < kWaveArraySize; ++i) {                           // 2
        double radians = i * 2.0 * pi / kWaveArraySize;
        mSine [i] = (sin (radians) + 1.0) * 0.5;
    }
 
    for (int i = 0; i < kWaveArraySize; ++i) {                           // 3
        double radians = i * 2.0 * pi / kWaveArraySize;
        radians = radians + 0.32;
        mSquare [i] =
            (
                sin (radians) +
                0.3 * sin (3 * radians) +
                0.15 * sin (5 * radians) +
                0.075 * sin (7 * radians) +
                0.0375 * sin (9 * radians) +
                0.01875 * sin (11 * radians) +
                0.009375 * sin (13 * radians) +
                0.8
            ) * 0.63;
    }
    mSampleFrequency = GetSampleRate ();                                 // 4
}

代码的工作原理如下:

  1. 构造函数方法声明器和构造函数初始化器。
    除了调用适当的超类之外,此代码还初始化两个成员变量。
  2. 生成一个表示正弦波一个周期的波表,并对其进行标准化,使其永远不会变为负值并且范围在 0 到 1 之间。
    该正弦波与深度参数的值一起指定了如何在正弦波颤音的一个周期内改变音频增益。
  3. 生成一个波表,表示伪方波的一个周期,并对其进行归一化,使其永远不会变为负值,并且其范围在 01之间。
  4. 获取要处理的音频流的采样率。

重写 Process 方法

AUKernelBase将您的 DSP 代码放入类方法的覆盖中Process
Process每次主机应用程序填充音频样本输入缓冲区时,都会调用该方法一次。
该方法逐个样本处理缓冲区内容,并将处理后的音频放入音频样本输出缓冲区中。

尽可能将处理工作放在实际处理循环之外非常重要。
TremoloUnit 项目的代码表明,在处理循环之外,每个音频样本缓冲区只完成一次以下工作:

  • 声明方法中使用的所有变量
  • 通过音频单元的视图获取用户设置的所有参数的当前值
  • 检查参数以确保它们在范围内,如果不在范围内,则采取适当的措施
  • 执行不需要随每个样本更新的计算。
    在本项目中,这意味着计算应用颤音波表时要使用的缩放因子。

在处理循环内部,仅执行必须逐个样本执行的工作:

  • 执行必须逐个样本更新的计算。
    在这种情况下,这意味着计算波表中用于颤音增益的点
  • 避免产生伪影,从而对参数变化做出响应。
    在这种情况下,这意味着,如果用户要求,则切换到新的颤音频率,同时避免增益突然跳跃。
  • 计算要应用于每个输入样本的变换。
    在本例中,这意味着计算 a) 基于波表中当前点的颤音增益,以及 b) 深度参数的当前值。
  • 计算与当前输入样本相对应的输出样本。
    在本例中,这意味着将颤音增益和深度因子应用于当前输入样本。
  • 推进输入和输出缓冲区中的索引
  • 推进 DSP 中涉及的其他索引。
    在本例中,这意味着增加mSamplesProcessed变量。

例 5-12 方法Process( TremoloUnit.cpp)

void TremoloUnit::TremoloUnitKernel::Process (                        // 1
    const Float32   *inSourceP,                                       // 2
    Float32         *inDestP,                                         // 3
    UInt32          inSamplesToProcess,                               // 4
    UInt32          inNumChannels,                                    // 5
    bool            &ioSilence                                        // 6
) {
    if (!ioSilence) {                                                 // 7
 
        const Float32 *sourceP = inSourceP;                           // 8
 
        Float32  *destP = inDestP,                                    // 9
                 inputSample,                                         // 10
                 outputSample,                                        // 11
                 tremoloFrequency,                                    // 12
                 tremoloDepth,                                        // 13
                 samplesPerTremoloCycle,                              // 14
                 rawTremoloGain,                                      // 15
                 tremoloGain;                                         // 16
 
        int      tremoloWaveform;                                     // 17
 
        tremoloFrequency = GetParameter (kParameter_Frequency);       // 18
        tremoloDepth     = GetParameter (kParameter_Depth);           // 19
        tremoloWaveform  =
            (int) GetParameter (kParameter_Waveform);                 // 20
 
        if (tremoloWaveform != kSineWave_Tremolo_Waveform             // 21
            && tremoloWaveform != kSquareWave_Tremolo_Waveform)
                tremoloWaveform = kSquareWave_Tremolo_Waveform;
 
        if (tremoloWaveform == kSineWave_Tremolo_Waveform)  {         // 22
            waveArrayPointer = &mSine [0];
        } else {
            waveArrayPointer = &mSquare [0];
        }
 
        if (tremoloFrequency < kMinimumValue_Tremolo_Freq)            // 23
            tremoloFrequency = kMinimumValue_Tremolo_Freq;
        if (tremoloFrequency > kMaximumValue_Tremolo_Freq)
            tremoloFrequency = kMaximumValue_Tremolo_Freq;
 
        if (tremoloDepth     < kMinimumValue_Tremolo_Depth)           // 24
            tremoloDepth     = kMinimumValue_Tremolo_Depth;
        if (tremoloDepth     > kMaximumValue_Tremolo_Depth)
            tremoloDepth     = kMaximumValue_Tremolo_Depth;
 
        samplesPerTremoloCycle = mSampleFrequency / tremoloFrequency; // 25
        mNextScale = kWaveArraySize / samplesPerTremoloCycle;         // 26
 
        // the sample processing loop 
        for (int i = inSamplesToProcess; i > 0; --i) {                // 27
 
            int index =                                               // 28
                static_cast<long>(mSamplesProcessed * mCurrentScale) %
                    kWaveArraySize;
 
            if ((mNextScale != mCurrentScale) && (index == 0)) {      // 29
                mCurrentScale = mNextScale;
                mSamplesProcessed = 0;
            }
 
            if ((mSamplesProcessed >= sampleLimit) && (index == 0))   // 30
                mSamplesProcessed = 0;
 
            rawTremoloGain = waveArrayPointer [index];                // 31
 
            tremoloGain       = (rawTremoloGain * tremoloDepth -      // 32
                                tremoloDepth + 100.0) * 0.01;
            inputSample       = *sourceP;                             // 33
            outputSample      = (inputSample * tremoloGain);          // 34
            *destP            = outputSample;                         // 35
            sourceP           += 1;                                   // 36
            destP             += 1;                                   // 37
            mSamplesProcessed += 1;                                   // 38
        }
    }
}

代码的工作原理如下:

  1. Process 方法签名。此方法在 AUKernelBase类中声明。
  2. 音频样本输入缓冲区。
  3. 音频样本输出缓冲区。
  4. 输入缓冲区中的样本数量。
  5. 输入通道数。此值始终等于 1,因为每个音频通道始终实例化一个内核对象。
  6. 一个布尔标志,指示音频单元的输入是否由静音组成,其TRUE值表示静音。
  7. 如果音频单元的输入是静音,则忽略执行 Process 方法的请求。
  8. 将指针变量分配到音频样本输入缓冲区的开头。
  9. 将指针变量分配到音频样本输出缓冲区的开头。
  10. 当前要处理的音频样本。
  11. 处理循环一次迭代产生的当前音频输出样本。
  12. 用户通过音频单元的视图请求的颤音频率。
  13. 用户通过音频单元的视图请求的颤音深度。
  14. 颤音波形一个周期内的音频样本数。
  15. 当前音频样本的颤音增益,存储在波表中。
  16. 考虑到深度参数,调整当前音频样本的颤音增益。
  17. 用户通过音频单元的视图请求的颤音波形类型。
  18. 获取频率参数的当前值。
  19. 获取深度参数的当前值。
  20. 获取波形参数的当前值。
  21. 对波形参数执行边界检查。如果参数超出边界,则提供一个合理的值。
  22. 将指针变量分配给与用户选择的颤音波形相对应的波表。
  23. 对频率参数执行边界检查。
    如果参数超出边界,则提供合理的值。
  24. 对 Depth 参数执行边界检查。
    如果参数超出边界,则提供一个合理的值。
  25. 计算颤音频率每个周期的音频样本数。
  26. 计算将波表应用于当前采样频率和颤音频率的比例因子。
  27. 对音频样本输入缓冲区进行迭代的循环。
  28. 计算波表中用于当前样本的点。
    这与注释中的 mNextScale 值的计算一起,是此效果在 DSP 中唯一的精细数学运算。
  29. 测试缩放因子是否应更改,以及是否可以安全地在当前样本处更改它以避免伪影。
    如果两个条件都满足,则切换缩放因子并重置 mSamplesProcessed 变量。
  30. 测试 mSamplesProcessed 变量是否已增长到较大的值,以及是否可以安全地在当前样本处重置它以避免伪影。
    如果两个条件都满足,则重置 mSamplesProcessed 变量。
  31. 从波表中的适当点获取颤音增益。
  32. 通过应用深度参数来调整颤音增益。
    深度为 100% 时,将应用完整的颤音效果。
    深度为 0% 时,将完全不应用颤音效果。
  33. 从音频样本输入缓冲区中的适当位置获取音频样本。
  34. 计算相应的输出音频样本。
  35. 将输出音频样本放置在音频样本输出缓冲区的适当位置。
  36. 增加音频样本输入缓冲区的位置计数器。
  37. 增加音频样本输出缓冲区的位置计数器。
  38. 增加已处理的音频样本的数量。

重写 Reset 方法

在自定义子类的AUKernelBaseReset方法覆盖中,您可以做一切必要的工作来将音频单元返回到其原始的初始化状态。


例 5-13 方法Reset( TremoloUnit.cpp)

void TremoloUnit::TremoloUnitKernel::Reset() {
    mCurrentScale        = 0;                    // 1
    mSamplesProcessed    = 0;                    // 2
}

代码的工作原理如下:

  1. 将成员变量mCurrentScale 重置为其最新初始化的值。
  2. 将成员变量mSamplesProcessed 重置为其最新初始化的值。

13、实现尾部时间属性

最后,为了确保您的音频单元在主机应用程序中播放良好,请实现尾部时间属性 kAudioUnitProperty_TailTime

为此,只需在 TremoloUnit 类定义中声明您的音频单元支持该属性,方法是将 SupportsTail 方法的返回值更改为 true


例 5-14 实现尾部时间属性(TremoloUnit.h

virtual bool SupportsTail () {return true;}

鉴于音频单元执行的 DSP 的性质,其尾部时间是0秒,因此您无需重写 GetTailTime方法。
AUBase超类中,此方法报告以0秒为单位的尾部时间,这正是您希望音频单元报告的时间。


14、验证已完成的音频单元

现在您已实现颤音单元项目的所有代码。
构建项目并更正您看到的任何错误。
然后使用 auval 工具验证您的音频单元。

  1. 在终端中,输入命令以验证颤音单元。
    该命令由命令auval名称、-v调用验证的标志以及识别颤音单元的类型、子类型和制造商代码组成。
    此音频单元的完整命令为:
auval -v aufx tmlo Aaud

如果一切正常,auval应该会报告您的新音频单元确实有效。
该图显示了由 auval 创建的日志的最后一部分:


在这里插入图片描述


15、测试已完成的音频单元

您的音频单元已准备好在主机应用程序中进行测试。
Apple 建议使用 AU Lab 应用程序进行音频单元测试。
您可能还想在其他主机中测试您的音频单元。
本节介绍使用 AU Lab 进行测试。
请按照以下步骤操作:

1. 打开您在测试未修改的音频单元部分保存的 AU Lab 文档。
如果您没有该文档,请再次执行该部分中的步骤以设置 AU Lab 来测试您的音频单元。


  1. 您完成的音频单元将以通用视图出现,如下图所示:

在这里插入图片描述


(您可能需要退出并重新打开 AU Lab 才能显示已完成的音频单元视图。)

请注意频率和深度参数的滑块、波形参数的弹出菜单以及工厂预设弹出菜单中显示的默认预设。


  1. 单击 AUAudioFilePlayer 实用程序窗口中的播放按钮。
    如果一切正常,您在播放器中选择的文件将通过您的音频单元播放,您将听到颤音效果。
  2. 试验颤音器提供的各种效果:在聆听 AU Lab 的输出的同时调整频率、深度和波形控制。
  3. 在通用视图右上角的预设菜单中,选择“显示预设”。

在这里插入图片描述


预设抽屉打开。


在这里插入图片描述


打开工厂组中的预设,并验证使用 NewFactoryPresetSet方法添加到音频单元的预设是否存在。
双击预设即可加载。

您可以按如下方式将用户预设添加到音频单元:

  • 根据需要设置参数
  • 在预设菜单中,选择将预设另存为
  • 在出现的对话框中,输入预设的名称。

七、附录:音频单元类层次结构

本附录介绍了 Core Audio SDK 音频单元类层次结构,包括常见类型音频单元的起点。


1、Core Audio SDK 音频单元类层次结构

下图说明了 Core Audio SDK 音频单元类层次结构中的主要类和类关系(针对 Core Audio SDK v1.4.3)。

图 6-1 Core Audio SDK 音频单元类层次结构

在这里插入图片描述


2、常见音频单元的起点

  • 对于一般的nm 个通道效果单元,从AUBase类开始
  • 对于nn通道效果单元,将每个输入通道映射到相应的输出通道,从 AUEffectBase 类开始
  • 对于单音色乐器单元(单音或复音),从AUMonotimbralInstrumentBase类开始
  • 对于多音色乐器单元,从AUMultitimbralInstrumentBase 类开始
  • 对于格式转换器或生成器音频单元,从AUBase类开始
  • 对于音乐效果单元,从AUMIDIEffectBase 类开始

2024-06-02 (日)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程乐园

请我喝杯伯爵奶茶~!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值