合并自定义和 Windows APO

音频处理对象 (APO),为 Windows 音频流提供可自定义的基于软件的数字信号处理。 可以将 Microsoft 提供的 API 与合作伙伴开发的代码相结合,并包装和自定义现有功能。

有关 APO 的一般信息,请参阅以下主题。

APO 最初是在 Windows Vista 中引入的,你可能会看到对早期系统 APO - sAPO 的引用。 有关详细信息,请参阅 Windows Vista 中的自定义音频效果白皮书。 本白皮书可能引用较旧的 COMUI 开发主题。

如何合并自定义和 Windows APO

本部分包含有关通过创建围绕相应 APO 的精简包装器来实现自定义音频系统效果 APO 的准则。 自定义 APO 是指 IHV 的 APO 实现。

有两种类型的 APO:SFX(流)和 MFX(模式)。 在 Windows 8.1 中,SFX 称为 LFX(本地),MFX 被指为 GFX(全局)API。

IHV 可以实现自定义音频系统效果 APO 来替换 Windows SFX 和 MFX 自定义音频系统效果 APO 中的一种,或同时替换这两种。 从广义上讲,IHV 或 OEM 有两种基本策略,用于将自定义音频系统效果 APO 与 Windows 提供的 APO 组合在一起。 这些策略使 IHV 能够灵活地将自定义效果与 Windows 的自定义效果集成。

替换

详细了解要替换的 Windows APO 及其功能。 根据该理解来实现一个自定义 APO,以从目标用户体验的角度对 IHV 最有意义的方式调用 Windows APO。 此策略最适合具有以下需求的 IHV 或 OEM:

  • 将自定义效果与 Windows 效果无缝集成。
  • 实现自己的 UI 来控制其效果和 Windows APO 实现的效果。

有关编写 APO 的详细信息,请参阅 Windows 音频处理对象

精简包装器

将自定义 APO 编写为围绕 Windows APO 的精简包装器。 此策略最适合具有以下需求的 IHV 或 OEM:

  • 以最简单的方式添加其自定义效果。
  • 让 Windows UI 继续控制效果。

选择精简包装器策略选项的 IHV 或 OEM 仍应查看 Windows 音频处理对象,以便全面了解 Windows 自定义音频系统效果。

注意:使用精简包装器策略的 IHV 无法添加 UI 来控制其向 Windows“增强功能”选项卡添加的自定义音频系统效果。只有一个“增强功能”选项卡,它必须与 Windows APO 的属性页保持关联。 IHV 的 UI 必须以其他某种方式(例如单独的控制面板应用程序)实现。

编程信息

本部分介绍实现自定义 APO 时必须解决的常规编程问题。

SFX(流)和 MFX(模式)自定义音频系统效果 APO 具有以下一般特征:

  • 它们必须注册为 COM 进程内服务器对象,这些对象可以使用 CoCreateInstance 进行实例化。
  • SFX 和 MFX APO 的 CLSID 分别为 CLSID_CWMAudioLFXAPOCLSID_CWMAudioGFXAPO。 CLSID 在 wmcodecdsp.h 中声明,并在 wmcodecdspuuid.lib 中定义。
  • 它们必须支持 COM 聚合。 但是,聚合不应用于自定义音频系统效果方案,因此它不应造成重大问题。

初始化

自定义 APO 必须通过调用其 IAudioSystemEffects::Initialize 方法初始化 Window APO。 这通常是从自定义 APO 的 Initialize 方法完成的。 传递给自定义 APO Initialize 方法的所有参数都应直接传递到 Windows APO 的 Initialize 方法。 这样,APO 就可以从 APOInitSystemEffects 结构中的终结点和 Fx 属性存储区提取其设置。 可以让自定义 APO 提取设置,并选择性地将其传递给 APO,但实质上是策略 A。

如果自定义 APO 取代了某个功能,通常建议关闭 APO 上的相应功能。 但是,根据功能的工作原理,关闭该功能可能并非绝对必要。 若要关闭某个功能,请查询 APO 以获取其 IPropertyStore 接口并调用 IPropertyStore::SetValue。 本主题后面的“支持的 IPropertyStore 属性”中介绍了 APO 属性存储区支持的属性。

有关如何与 Windows 自定义音频系统效果 APO 属性存储区进行通信的示例,请参阅 GitHub 上的示例:https://github.com/Microsoft/Windows-driver-samples/tree/main/audio/sysvad/APO

查询 APO 的功能状态

如果自定义 APO 仅替换 Windows 音频效果功能,而没有自己的配置 UI 或设置存储,则可能需要确定在相应 APO 上启用哪些功能。

至少可通过两种方式获取该信息:

  • 选项 A:直接查询 Fx 属性存储区。

  • 选项 B:间接,通过实例化 APO 并使用其 IPropertyStore 接口查询属性存储区。

选项 A

此选项的优点是可以在不实例化 APO 的情况下完成。 此外,如果自定义 APO 想要监视 Fx 属性存储区,选项 A 是接收实时属性更改通知的唯一方法。 有关选项 A 的示例,请参阅“压缩”示例。

使用选项 A 时,自定义 APO 会查询主终结点属性存储区(而非 Fx),以获取 PKEY_AudioEngine_DeviceFormat。 然后,它将来自该格式的声道掩码用作用于查询 Fx 属性存储区的属性键的 PID。 用于查询 Fx 属性存储区的属性键的 GUID (fmtid) 是 wmcodecdsp.h 中的某个 XXX_XXX_KEY_GUID 值。 KEY_GUID 名称以明显方式与 本主题前面讨论的 MFPKEY 名称相对应。

选项 B

此选项的优点是,它可以正确处理以下可能性:如果 Fx 属性存储区中的相应属性不存在,Windows APO 最终可能默认启用其部分功能。

使用选项 B,自定义 APO 只需查询 APO 以获取其 IPropertyStore 接口,并使用本主题前面讨论的某个 MFPKEY_XXX 键调用 IPropertyStore::GetValue

格式协商

实现包装 SFX APO 的自定义 SFX APO 时,请勿在自定义 APO 的注册属性中指定 APO_FLAG_FRAMESPERSECOND_MUST_MATCH。 无论自定义 APO 是否可以更改声道格式,都应遵循此规则。 如果自定义 SFX APO 指定此标志,则将阻止相应的 SFX 执行扬声器填充、耳机虚拟化或虚拟环绕。

自定义 SFX APO 实现必须实现或替代 IAudioProcessingObject::IsInputFormatSupported。 基类 IsInputFormatSupported 实现不太可能准确反映自定义 SFX APO 和 SFX APO 实现的一组可能的声道转换。

自定义 SFX APO 的 IsInputFormatSupported 方法应调用相应 APO 的 IsInputFormatSupported。 这可确保 SFX APO 处理自定义 SFX APO 未处理的所有声道转换。 请注意,SFX APO 可能会更新,以支持将来的 Windows 版本中的更多转换。 调用 APO 的 IsInputFormatSupported 方法是确保自定义 APO 支持的声道转换集完全包含 SFX APO 支持的声道转换集。

自定义 APO 应使用 SFX APO 的 IsInputFormatSupported 方法的返回值执行的操作取决于自定义 SFX APO 支持哪些声道转换(如果有)。

如果自定义 SFX APO 不支持自己的任何声道转换,则其 IsInputFormatSupported 方法可以直接将 SFX APO 的 IsInputFormatSupported 方法返回的值返回到调用方。 有关示例,请参阅“交换”和“压缩”示例。

如果自定义 SFX APO 支持自己的声道转换,则 SFX APO 的 IsInputFormatSupported 方法中的负返回值(包括 S_FALSE)不一定转换为对调用方的负返回值。 例如,自定义 SFX APO 可以支持相应 APO 不支持的声道转换。 在这种情况下,自定义 SFX APO 必须将 SFX APO 的 IsInputFormatSupported 方法的返回值与自己的逻辑组合在一起,以确定支持的输入。 请注意,“组合”的最佳含义取决于应优先使用哪种类型的声道转换。 最佳方法取决于自定义实现的具体设计。

SFX APO 上的 IsOutputFormatSupported 方法无意义,因为 SFX APO 的输出格式是设备的混合格式。 此格式基于外部注意事项,不受 SFX APO 或其输入格式的影响。 因此,这些示例不会尝试为 IsOutputFormatSupported 实现正确的逻辑。

上述注意事项不适用于 MFX APO,因为 MFX APO 不会实现任何需要或暗示更改声道格式的功能。 因此,MFX 示例对 IsInputFormatSupported 或 IsOutputFormatSupported 没有任何特殊作用。 自定义 MFX APO 的格式协商逻辑不受包装 MFX APO 这一事实的影响。

LockForProcess/UnlockForProcess

自定义 APO 的 IAudioProcessingObjectConfiguration::LockForProcess 方法应调用 APO 上的相应方法。 LockForProcess() 是一个很好的位置,可以决定各种处理阶段的发生顺序。 例如,它可以决定是先应用自定义 APO 处理还是先应用 APO 的处理。 这三个示例都提供了此类决策逻辑的示例,示例中的注释提供了一些背景。 但是,无法就本文档中的该主题提供完全一般的指导,因为它需要了解自定义 APO 的特定功能以及它们如何与 APO 功能进行交互。

GetLatency

自定义 APO 的 IAudioProcessingObject::GetLatency 实现应对正在包装的 APO 调用 GetLatency。 如果自定义 APO 处理产生延迟,则应将其添加到 APO 已返回的结果,然后再将该值返回到调用方。

APOProcess

自定义 APO 的 IAudioProcessingObjectRT::APOProcess 方法应在处理之前、之后甚至处理期间调用 APO 的 APOProcess 方法。 应在 LockForProcess 中决定何时调用 APOProcess,以便可以分配任何必要的中间缓冲区。 每当 APO 的输入和输出格式相同时,都支持就地处理。 在这种情况下,自定义 APO 可以传递与 Windows APO 的输入和输出连接属性相同的 APO_CONNECTION_PROPERTY。 但是,自定义 APO 不应使用自定义 APO 的输入连接属性作为 APO 的输出连接属性。 通常,APO 不应修改其输入缓冲区。

处理 APO 错误

如果 APO 将错误返回到相应的自定义 APO,则自定义 APO 应从该点执行操作,就好像没有 APO 一样。 这些示例将所有 APO 错误视为等同于 CoCreateInstance 未能创建 APO。 (可选)自定义 APO 可以将 APO 的 LockForProcess 方法中的错误影响限制为当前会话。 换句话说,自定义 APO 在后续调用 APOProcess 方法期间不使用 APO。 但是,如果以后有另一个格式不同的 LockForProcess 调用,则自定义 APO 可能会再次尝试使用 APO。

编译和链接

若要使用 APO CLSID 和属性键定义,请添加 wmcodecdsp.h 和与 wmcodecdspuuid.lib 的链接。 有关详细信息,请参阅 wmcodecdsp.h 标头

APO 示例

有四个示例音频系统效果示例。 可在 GitHub 上找到 APO 示例:https://github.com/Microsoft/Windows-driver-samples/tree/main/audio/sysvad/APO

自定义音频系统效果的一般准则

以下是 IHV 在实现自定义音频系统效果 APO 时应遵循的一些准则。

  • 所有音频系统效果都应提供开/关选项。 不应强制用户使用音频系统效果。
  • SFX 和 MFX APO 中的功能之间的交互应由 APO 及其相关 UI 进行调解。
  • 此处指定为 SFX 或 MFX 的功能可以在自定义实现中的 SFX 和 MFX 之间移动。 但是,在执行此操作时应该知悉开/闭选项应存在,并且这些选项的可访问性和适当性不应受到影响。
  • 实现者应记住,SFX 可以具有不同的输入和输出声道掩码。 MFX APO 必须具有相同的输入和输出声道掩码。

Windows 提供的 APO

有关其他 Windows 提供的 APO 的信息,请参阅这些主题。

低音增强

低音管理

便携式计算机的增强音

响度均衡 DSP

低频保护

房间修正

扬声器填充

扬声器构成幻路

虚拟环绕

耳机的虚拟化环绕声

特定 APO 自定义信息

响度均衡 (SFX APO)

响度均衡是一种由感知响度指标驱动的压缩(动态)处理。 房间修正 (MFX APO)

房间修正使用房间修正向导生成的配置文件。 此配置文件存储为二进制 Blob。 Blob 的格式当前未发布。

声道转换 (SFX APO)

声道转换 APO 处理多个任务。

耳机虚拟化

如果所播放内容 (N.x)(其中 x 可以是 0 或 1)的声道格式为 2.0 或更高版本,则启用此效果。 输出掩码必须是立体声 (0x3)。 输入掩码限制为几个受支持的组合,下表中列出了这些组合

耳机虚拟化声道掩码

名称
MASK_STEREO MASK_FRONTLR 0x3
MASK_3_FRONT (SPEAKER_FRONT_CENTER | MASK_FRONTLR) 0x7
MASK_4_SQUARE (MASK_FRONTLR | MASK_BACKLR) 0x33
MASK_4_DIAMOND (MASK_FRONTLR | MASK_FBCENTERS) 0x107
MASK_5_BACK (MASK_FRONTLR | MASK_BACKLR | SPEAKER_FRONT_CENTER) 0x3F
MASK_5_SIDE (MASK_FRONTLR | MASK_SIDELR | SPEAKER_FRONT_CENTER) 0x60F

虚拟环绕

此效果也称为左/右 (LTRT) 立体声表现或左/右矩阵编码。 如果所播放内容 (N.x)(其中 x 可以是 0 或 1)的声道格式为 2.0 或更高,则使用此效果。 LTRT 立体声表现通常为 4.0 到 2.0。 通常通过首先将 N.x 应用到 4.0 泛型立体声表现来处理任何其他输入格式。 但是,在我们的实现中,LTRT 立体声表现本机为 5.1 到 2.0。 通过首先将 N.x 应用到 5.1 泛型立体声表现来处理任何其他输入。

输出声道掩码必须为 0x3(立体声),输入声道数(包括低音炮,若存在)不得超过 8 个。

扬声器填充

当输入声道数 (N) 小于输出声道数 (M) 时,将使用此效果。 此效果将 N.x 声道填充到 M.x 声道,其中 x 可以是 0 或 1。

扬声器填充支持表 4 中的声道掩码,忽略 LFE 声道。 扬声器填充支持输入或输出低音炮声道状态的任意组合,因此左侧的数字只是示例。 实际配置可能有也可能没有低音炮。

扬声器填充声道掩码

名称
MASK_STEREO MASK_FRONTLR 0x3
MASK_3_FRONT (SPEAKER_FRONT_CENTER | MASK_FRONTLR) 0x7
MASK_4_SQUARE (MASK_FRONTLR | MASK_BACKLR) \ 0x33
MASK_4_DIAMOND (MASK_FRONTLR | MASK_FBCENTERS) 0x107
MASK_5_BACK (MASK_FRONTLR | MASK_BACKLR | SPEAKER_FRONT_CENTER) 0x3F
MASK_5_SIDE (MASK_FRONTLR | MASK_SIDELR | SPEAKER_FRONT_CENTER) 0x60F
MASK_7_SIDE_BACK (MASK_FRONTLR | MASK_BACKLR | SPEAKER_FRONT_CENTER | MASK_SIDELR) 0x63F
MASK_7_FRONT_SIDE (MASK_FRONTLR | MASK_SIDELR | SPEAKER_FRONT_CENTER | MASK_CENTERLR) 0x6CF
MASK_7_FRONT_BACK (MASK_FRONTLR | MASK_BACKLR | SPEAKER_FRONT_CENTER | MASK_CENTERLR) 0xFF

如果满足以下任一条件,则不支持扬声器填充:

  • 输入掩码等于输出掩码。
  • 输入和输出之间的唯一区别是,一个具有左侧/右侧声道;另一个具有左后/右后声道。
  • 输入具有比输出更多的主声道。
  • 输出掩码包括左/右居中扬声器,但输入掩码不包含。
  • 输出(而非输入)中的声道集不包含以下至少项:前中心、左后/右后或左侧/右侧。

列表中的第二项有一个例外。 如果输入和输出的唯一区别是一个具有左侧/右侧声道,另一个具有左后/右后声道,那么如果任一格式包含介于 sideLR 和 backLR 之间的声道(按声道掩码位顺序),则支持扬声器填充。 此类声道有三个:

  • SPEAKER_FRONT_LEFT_OF_CENTER
  • SPEAKER_FRONT_RIGHT_OF_CENTER
  • SPEAKER_BACK_CENTER

如果输入或输出掩码包含这三个声道中的任何一个,那么即使它不满足列表中的第二个条件(但前提是满足其他条件),也可能会支持扬声器填充。 例如,由于此原因,扬声器填充支持 MASK_7_FRONT_BACK 与 MASK_7_FRONT_SIDE 之间的扬声器填充,或从 MASK_7_FRONT_SIDE 的扬声器填充。

下表包含声道值的完整列表。

名称
SPEAKER_FRONT_LEFT 0x1
SPEAKER_FRONT_RIGHT 0x2
SPEAKER_FRONT_CENTER 0x4
SPEAKER_LOW_FREQUENCY 0x8
SPEAKER_BACK_LEFT 0x10
SPEAKER_BACK_RIGHT 0x20
SPEAKER_FRONT_LEFT_OF_CENTER 0x40
SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
SPEAKER_BACK_CENTER 0x100
SPEAKER_SIDE_LEFT 0x200
SPEAKER_SIDE_RIGHT 0x400

延迟用于“超出”输入配置中前后端范围的输出配置中的声道。 相反,如果输出配置中的扬声器“介于”输入配置中前后端范围的某些扬声器之间,则该扬声器的输出是通过混合输出声道任一侧的一些输入声道生成的。

重用 Windows APO 时的运行时注意事项

本节包含 IHV 和 OEM 在实现其自定义音频系统效果时可能发现有用的一些额外信息。

自定义 APO 实现:

  • 使用 CoCreateInstance 实例化 Windows 自定义音频系统效果 APO 的一个或多个实例。
  • 配置每个实例以启用所需的功能集。
  • 将每个实例插入自定义 APO 的内部管道中的适当位置。

为何选择一个或多个实例?

为了避免不良交互,大多数功能都需要一定的相对排序。 由于 Windows APO 在单个 APO 中实现多个功能,因此可能需要该 APO 的多个实例来确保正确的排序。 例如,假设三个已启用的功能(A、B 和 C)必须按 ABC 排序。 自定义实现会处理 B,但将 A 和 C 委托给 Windows APO。 然后,A 和 C 必须位于 Microsoft APO 的单独实例中,以便 B 的自定义实现可以在它们之间进行。

Windows 在 MFX APO 中实现房间校正,这意味着它是与 SFX APO 不同的 COM 对象。 自定义实现可以选择将房间校正委托给 Windows 实现,但将其置于自定义 SFX APO 中。 然后,自定义 SFX 实现可能需要将某些处理委托给 Windows SFX APO 实现,并将其他处理委托给 Windows MFX APO 实现。

处理不同输入输出格式组合的限制

在某些情况下,很多功能(尤其是低音管理)不起作用。 例如,如果低音扬声器配置属性为“AllSmall”或“AllLarge”,并且输出格式不包含低音炮声道或设置了 NoSub 标志,则正向低音管理未定义。 在 IPropertyStore::SetValue 调用期间,并不总是可以检测失败情况。 该方法会尝试启用此功能,但当时不知道输入和输出格式,因为 LockForProcess 必须在所有属性操作之后发生。 这意味着可以启用某个功能,看到它显然成功,但未进行相应的处理。

有两种策略可用于处理此类情况:

  • 仔细研究本文档的功能特定部分,以便能够准确预测给定功能何时会或不会成功。
  • 调用 LockForProcess 后调用 IPropertyStore::GetValue,以检查重要属性的状态。

当 LockForProcess 确定由于输入和输出格式或其他某个属性的值而无法启用特定功能时,LockForProcess 将更新属性存储区中相应属性的值。

扬声器填充与低音管理之间的交互

当扬声器填充处于开启状态并且连接了低音炮时,必须在扬声器填充之前进行正向低音管理,以避免扬声器填充的环绕延迟对低频率信号进行梳状滤波效应。

启用扬声器填充且未连接低音炮时,可以进行两种类型的正向低音管理:

  • 如果前左/右扬声器很大,则正向低音管理会将周围和中心声道的低频率部分路由到左前/右前扬声器。 在本例中,必须在扬声器填充后进行正向低音管理。
  • 如果所有扬声器都较小,则正向低音管理会成为所有主扬声器的低频率保护。

这可能发生在扬声器填充之前或之后。 但是,出于性能原因,最好在扬声器填充前进行正向低音管理。

Windows APO 可使用经过优化的特殊代码实现某些常见的扬声器填充配置,例如 2.0 => 5.1,这些代码可在与扬声器填充相同的步骤中处理反向低音管理。

立体声表现和低音管理之间的交互

耳机虚拟化仅支持反向低音管理:

  • 正向低音管理对耳机虚拟化没有意义。
  • 为简化实现,不支持低频率保护和低音增强。

当任何耳机虚拟化、虚拟环绕编码或扬声器填充效果都开启时,将在此步骤中处理反向低音管理。 反向低音管理仍通过 ADO 反向低音管理属性进行控制,就像它是一个单独的功能一样。 在这些情况下,反向低音管理只需控制 .1 输入声道的立体声表现系数。 一个未解决问题是,当 LTRT 处于开启状态时,无法禁用反向低音管理。 在这种情况下,反向低音管理使用非常规的低音炮声道增益。

即使未启用任何功能,Windows 音频系统效果 AP0 也会应用一些次要处理(增益和延迟)。 此类处理的目标是确保在动态启用某功能时,增益和延迟参数不会更改。 原因是某些功能的实现固有延迟,并且某些功能应用了 <1 的增益,以避免在某些情况下输出过高。 可用功能集取决于输入输出格式和某些属性,累积规范化增益和延迟也是如此。

如果未开启或关闭功能,可以通过调用 IPropertyStore::SetValueMFPKEY_CORR_NORMALIZATION_GAIN 属性设置为 FALSE 来禁用规范化增益。 默认情况下,该属性可能为 TRUE。

没有任何禁用规范化延迟的机制,因为该延迟被认为比规范化增益更不容易引起反感。 如果规范化延迟令人反感,只需绕过相关 APO 即可。

另请参阅