音频处理对象(APO),为 Windows 音频流提供基于自定义的软件的数字信号处理。 可以将Microsoft提供的 API 与合作伙伴开发的代码相结合,包装和自定义现有功能。
有关 APO 的一般信息,请参阅这些主题。
APO 最初是在 Windows Vista 中引入的,你可能会看到对早期系统 APO - sAPO 的引用信息。 有关详细信息,请参阅 Windows Vista 白皮书中的自定义音频效果 。 本白皮书可能引用较旧的 COM 和 UI 开发 主题。
如何组合自定义和 Windows API
本部分包含通过围绕相应 APO 创建精简包装器来实现自定义音频系统效果 APO 的准则。 自定义 APO 是指 IHV 的 APO 实现。
有两种类型的 APO:SFX(流式处理)和 MFX(模式)。 在 Windows 8.1 中,SFX 称为 LFX(本地),MFX 被指为 GFX(全球)API。
IHV 可以实现自定义音频系统效果 APO 来替换 Windows 的 SFX 和 MFX 自定义音频系统效果 APO。 从广义上讲,IHV 或 OEM 有两种基本策略,用于将自定义音频系统效果 APOS 与 Windows 提供的 APO 组合在一起。 这些策略使 IHV 能够灵活地将自定义效果与 Windows 的自定义效果集成。
替换
深入了解要替换的 Windows APO 及其功能。 使用这种理解来实现一个自定义 APO,以从目标用户体验的视角,用最符合 IHV 意义的方式调用 Windows 的 APO。 此策略最适合独立硬件供应商 (IHVs) 或原始设备制造商 (OEMs) 想要:
- 将自定义效果与 Windows 效果无缝集成。
- 实现自己的 UI 来控制其效果和 Windows APOS 实现的效果。
有关编写 APO 的详细信息,请参阅 Windows 音频处理对象。
轻量级包装器
将自定义 APO 编写为 Windows APO 周围的轻量级包装器。 此策略最适合想要:IHVs(独立硬件供应商) 或 OEMs(原始设备制造商)
- 以最简单的方式添加其自定义效果。
- 让 Windows UI 继续控制效果。
选择“精简封装器”选项的 IHV 或 OEM 仍应查看 Windows 音频处理对象,以便全面了解 Windows 自定义音频系统效果。
注意:使用精简包装器策略 IHV 无法添加 UI 来控制其向 Windows 增强功能选项卡添加的自定义音频系统效果。只有一个“增强功能”选项卡,它必须与 Windows APOS 的属性页保持关联。 IHV 的 UI 必须以某种其他方式实现,例如单独的控制面板应用程序。
编程信息
本节涵盖在实现自定义APOs时必须解决的一般编程问题。
SFX(Stream)和 MFX(Mode) 自定义音频系统效果 API 具有以下一般特征:
- 它们必须注册为 COM 进程内服务器对象,这些对象可以使用 CoCreateInstance 实例化。
- CLSID 分别为
CLSID_CWMAudioLFXAPO和CLSID_CWMAudioGFXAPO用于 SFX 和 MFX APO。 CLSID 在 wmcodecdsp.h 中声明,并在 wmcodecdspuuid.lib 中定义。 - 它们必须支持 COM 聚合。 但是,聚合不应用于自定义音频系统效果方案,因此它不应造成重大问题。
初始化
自定义 APO 必须通过调用 IAudioSystemEffects::Initialize 方法来初始化窗口 APO。 这通常是在自定义 APO 的 Initialize 方法中完成的。 传递给自定义 APO Initialize 方法的任何参数都应直接传递到 Windows APO 的 Initialize 方法。 这允许 APO 从终结点和 Fx 属性存储中提取其设置,并在 APOInitSystemEffects 结构中获取其设置。 可以让自定义 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。 然后,它将该格式中的通道掩码用作属性键的 PID,以查询 Fx 属性存储。 用于查询 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 方法返回的值返回到调用方。 有关示例,请参阅“swap”和“compress”示例。
如果自定义 SFX APO 支持其自身的通道转换,那么即使 SFX APO 的 IsInputFormatSupported 方法返回负值(包括 S_FALSE),给调用方的返回值也不一定是负值。 例如,自定义 SFX APO 可以支持相应 APO 不支持的通道转换。 在这种情况下,自定义 SFX APO 必须将 SFX APO 的 IsInputFormatSupported 方法的返回值与自己的逻辑组合在一起,以确定支持的输入。 请注意,“组合”的最佳含义取决于应优先使用哪种类型的通道转换。 最佳方法取决于自定义实现的确切设计。
SFX APO 上的 IsOutputFormatSupported 方法无关紧要,因为 SFX APO 的输出格式实际上就是设备的混合格式。 此格式基于外部注意事项,不受 SFX APO 或其输入格式的影响。 因此,示例不会尝试为 IsOutputFormatSupported 实现正确的逻辑。
上述注意事项不适用于 MFX API,因为 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 提供的 API
有关其他 Windows 提供的 API 的信息,请参阅这些主题。
特定 APO 自定义信息
音量均衡(SFX APO)
音量均衡是一种压缩的(动态)处理,由感知响度指标驱动。 房间校正 (MFX APO)
房间校正使用房间校准向导生成的配置文件。 此配置文件存储为二进制 Blob。 Blob 的格式当前未发布。
通道转换 (SFX APO)
通道转换 APO 处理多个任务。
耳机虚拟化
如果正在回放的内容的通道格式为 N.x 且等于 2.0 或更高,其中 x 可以是 0 或 1,那么此效果将被启用。 输出掩码必须是立体声(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)为 2.0 或更大时,其中 x 可以是 0 或 1,该设置就会被使用。 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或相反的方向。
下表包含通道值的完整列表。
| 名称 | 价值 |
|---|---|
| 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 |
延迟用于输出配置中不在输入配置前后范围内的通道。 相反,如果输出配置中的扬声器在前后端感知的输入配置中“介于”某些扬声器之间,则该扬声器的输出是通过混合输出通道两侧的一些输入通道生成的。
Run-Time 重用 Windows APOs 时的注意事项
本部分包含 IHV 和 OEM 在实现其自定义音频系统效果时可能发现有用的一些附加信息。
自定义 APO 实现:
- 使用 CoCreateInstance 实例化 Windows 自定义音频系统效果 API 的一个或多个实例。
- 配置每个实例以启用所需的功能集。
- 将每个实例插入自定义 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,具有特殊的优化代码,可以在扬声器补偿的同一步骤中处理反向低音管理。
Folddown与低音管理系统之间的交互作用
耳机虚拟化仅支持反向低音管理。
- 对耳机虚拟化来说,前置低音管理没有意义。
- 为简化实现,不支持低频率保护和低音提升。
当开启耳机虚拟效果、虚拟环绕编码或扬声器填充效果中的任何一项时,将在此步骤中进行低音管理反向处理。 反向低音管理仍通过 ADO 反向低音管理属性进行控制,就像它是一个单独的功能一样。 在这些情况下,反向低音管理只需控制 .1 输入通道的折叠系数。 一个尚未解决的问题是,当 LTRT 开启时,无法禁用反向低音管理功能。 在这种情况下,反向低音管理使用非常规的低音通道增益。
即使未启用任何功能,Windows 音频系统效果 API 也会应用一些次要处理(增益和延迟)。 此类处理的目标是确保在动态启用功能时,增益和延迟参数不会更改。 原因是某些功能的实现固有延迟,并且某些特征应用了增益 <1,以避免在某些情况下输出过高。 可用功能集取决于输入输出格式和某些属性,累积规范化增益和延迟也是如此。
如果功能不会动态打开或关闭,可以通过调用 IPropertyStore::SetValue 将 MFPKEY_CORR_NORMALIZATION_GAIN 属性设置为 FALSE 来禁用规范化增益。 默认情况下,该属性可能为 TRUE。
没有禁用规范化延迟的机制,因为它假定不太可能比规范化增益更令人反感。 如果对规范化延迟感到不满,可以直接绕过相关的 APO。