Skip to main content

简介

可扩展硬件音频处理引擎 (Scalable Hardware Audio Processing Engine, SHAPE) 为音频播放与处理中最常见的许多构建块提供硬件加速。 游戏完全控制 SHAPE 处理块的配置和顺序,由音频控制处理器 (Audio Control Processor, ACP) 负责管理。ACP 是一个硬件组件。游戏创建定义好的路径,将一个或多个 SHAPE 组件从输入端串联到输出端。这些路径被称为 流程图 (flowgraph)。创建后,每个流程图会被作为一系列命令提交给 ACP 处理。 本主题讨论创建并提交流程图的最佳实践,帮助你顺利实现最常见的运行时场景,以及为发挥最佳性能而应避免的配置。 本主题涵盖以下内容:

SHAPE 组件及能力

当流程图被提交到硬件时,命令队列 会由 ACP 自动管理并填充。 下表汇总列出了各组件及其实例化能力。 下表汇总列出了各组件及其混音缓冲区路由规则。 | SHAPE 组件| 每实例输入混音缓冲区数| 每实例输出混音缓冲区数| | --- | --- | --- | --- | --- | --- | --- | --- | --- | | XMA| 不适用。从内存读取。| 不适用。解码到内存,通常由 SRC 消费。| | SRC| 0;从内存读取。| 单声道为 1,立体声为 2。| | FLT/VOL| 1。| 1。| | EQ/CMP| 1(侧链配置为 2)。| 1。| | MB| 不适用。混音缓冲区不能直接从其他混音缓冲区读取。| 不适用。混音缓冲区不能直接写入其他混音缓冲区。| | DMA| 1;每次传输最多 32 个音频帧。| 1;每次传输最多 32 个音频帧。| 有关 SHAPE 构建块的更多细节,请参见相关的 Xfest 演讲 The Sound of XBOX One (Conference Material > Xfest 2012) 和白皮书 The Sound of the Future (Developer Education Materials > All NDA Whitepapers)

关于流程图使用场景的说明

许多游戏可能永远不会直接实现流程图。XAudio2 会隐式使用 SHAPE 组件,音频中间件也可能将硬件流程图从游戏开发者视野中抽象掉。不过,构建流程图能让你直接利用 XBOX One 主机的音频加速能力——尤其是在你自行开发并实现音频渲染方案,或者你的自定义配置未被 XAudio2 或中间件支持时。 在你开发的所有游戏中,理解 SHAPE 的能力(后文将进一步讨论)并提前规划可能的流程图拓扑,都是有价值的。

非高效场景与病态情形

以下各节介绍可能导致性能问题的常见场景,并给出规避这些问题的建议。

畸形流程图

你可能会创建构造出不可能的信号流或永远无法在输出端实现的数据的流程图。以下是创建流程图时最常见的一些错误。
  • 引用了未创建或未分配的 SHAPE 对象
  • 非法命令或参数
  • 混音缓冲区输入/输出引用计数不准确
  • 重新分配了已有的混音缓冲区虚拟 ID
  • 在 SHAPE 组件之间遗漏了连接用的混音缓冲区
  • 为 SHAPE 组件使用了多于一个的输出混音缓冲区(SRC 块除外,它在渲染立体声内容时可路由到两个混音缓冲区)
除了较为显眼的流程图 bug(例如引用了未分配或未创建的 SHAPE 对象)之外,畸形流程图最常见的场景还包括:
  • 混音缓冲区中的循环引用 只有在混音缓冲区的所有输入都到位并且所有输出都被消费之后,该混音缓冲区才会变为可用。如果混音缓冲区中存在循环引用,混音缓冲区实际上会有无限个输入,永远不会变为可用,从而使整个图挂起。
  • 孤立的混音缓冲区 没有输出的混音缓冲区会在其最后一个输入被处理后立即变为可用。用于向音频设备(例如扬声器或耳机)渲染的混音缓冲区应以一个 DMA 输出块终止。这样才会将混音缓冲区提交到主内存以进行最终混音。

命令顺序

ACP 会自动为每个 SHAPE 组件填充命令队列。填充队列时,ACP 会跳过某个组件的后续命令,直到该组件的条目变为空闲。 一般来说,跳过命令带来的性能损失可以忽略不计。但根据命令顺序的不同,游戏可能创建出次优的 SHAPE 组件顺序。这会导致一个或多个未满的队列必须等待其他队列。这种等待会减少每帧可用的实例数量。 图 1 展示了命令顺序如何影响性能。最佳做法是按流程图从上到下发出 FLT/VOL 命令,而不是从左到右,这样可以让更多语音完成处理。此方法允许流程图底部所示的 EQ/CMP 并行处理。 图 1. 一张展示如何自上而下(而非从左到右)发出 FLT/VOL 命令、以便让 EQ/CMP 并行处理并提高性能的流程图。 对同一类型的命令进行流程图排序的最佳实践是:优先沿着一条语音的处理路径深入,直到到达子混音阶段或复用的 SHAPE 组件;然后对共享公共处理顺序的语音,优先在广度上扩展。 图 1 显示了最优(也是典型)的做法:先为最左列的 FLT/VOL 创建命令,然后再针对顶部立体声语音的两个通道处理其余的 FLT/VOL 命令。

混音缓冲区实例的分配

混音缓冲区在被写入时是锁定的。在所有输入都参与混音之前,无法从中读取输出。从实现的角度看,混音缓冲区使用 numInnumOut 字段来管理这一过程。因此,可能出现在同一时刻需要超过 128 个物理混音缓冲区的情形。 SHAPE 会对混音缓冲区做虚拟化处理,因此即便复调数很高,通常也不会出现此类问题。但你可能会构建出需要同时访问大量混音缓冲区的流程图。特别是承担多个语音子混音或母带混音的混音缓冲区,会一直锁定,直到最后一个语音混入它,并且直到混音缓冲区的最后一个输出被后续的 SHAPE 块消费。 在典型场景下——只有一个 7.1 母带语音,所有 SHAPE 语音都混入其中——128 个中会有 8 个在整帧内被锁定。如果存在大量带流程图依赖链的多通道子混音,额外的混音缓冲区可能会在大部分帧时间内保持锁定(从而减少虚拟混音缓冲区的容量)。另外,如果你为一款四人游戏创建了每位玩家单独的一个主扬声器和一个 7.1 耳机混音,则会创建更多母带语音,可能同时消耗 40 个混音缓冲区。 一般来说,需要同时使用超过 128 个物理混音缓冲区的情形属于病态情况。然而,那些整帧都被不必要锁定的混音缓冲区会降低你的游戏发挥潜在吞吐量的能力。图 2 演示了不必要的子混音是如何降低某一时刻可用的混音缓冲区数量的。 根据命令顺序(图 2),这 5 个独立的 7.1 母带混音缓冲区几乎在整帧内都可能被锁定,也就是 128 个物理混音缓冲区中的 40 个。虽然此路径实际上只需要 2 个其他混音缓冲区在同一时间被使用——即一个 FLT/VOL 或 EQ/CMP 的成对输入与输出——你可以考虑先处理所有进入扬声器的混音缓冲区。然后释放这些混音缓冲区,再处理耳机 1、2 等等。 图 2. 一张展示因命令顺序不佳而对已分配的混音缓冲区实例进行不必要子混音与锁定的流程图。

SHAPE 组件的选择与均衡(FLT/VOL 与 EQ/CMP)

如果你连续多次使用相同的 SHAPE 组件,会降低达到最大吞吐量的能力。相对地,通过在不同组件之间交替,可以确保每个 SHAPE 块都在执行有意义的并行处理。 图 3 展示了一个常见示例:所有 FLT/VOL 无法完全并行处理,性能可能受损。 图 3. 一张试图并行处理所有 FLT/VOL 的流程图。 第二组 FLT/VOL 必须等第一组完成,其间其他 SHAPE 块可能处于空闲状态。如果其中任意一组仅用于滤波,将其中之一改用 EQ/CMP 可能会是更好的选择,特别是当相同配置的流程图(图 4)拥有更多语音时。这样,第一组处理其 EQ/CMP 块的同时,就可以开始它们的 FLT/VOL 处理。 图 4 展示了更接近 SHAPE 理想能力的性能。此流程图将假定只用于滤波的一组 FLT/VOL(见图 3)替换为 EQ/CMP,从而允许这两种 SHAPE 组件并行处理。 图 4. 一张试图并行处理 FLT/VOL 与 EQ/CMP 的流程图。 在考虑各类组件的最大实例数时,SHAPE 组件的均衡也很重要。一帧内可容纳 2560 个 FLT/VOL 组件,让 FLT/VOL 硬件的运算量大约是 512 个 EQ/CMP 块所能实现的五倍。 如果你让 EQ/CMP 块与单个 FLT/VOL 串联运行,实际性能会低于让多个 FLT/VOL 组件与一个 EQ/CMP 搭配的做法,因为前者的处理会被后者的消费速度所限制。

过多或频繁的低价值 DMA 往返

DMA 受限于 SHAPE 总线可用的读写带宽。如果你耗尽了所有可用带宽——尽管这种情形通常属于病态——DMA 块会阻塞,从而降低吞吐量。 如前所述,在 DMA 输入块之后的所有处理都必须等待 DMA 完成。因此,你可能会构建出这样的流程图:其中大部分被阻塞并等待某个 DMA 完成后才能开始处理。 你还应评估 DMA 往返相对于其为音频流带来的价值。对于大多数音频播放场景而言,DMA 增加的少量延迟(你可以按 2.667 ms 音频帧尺寸的倍数来控制通过 DMA 访问的帧数)也许并不成问题。这是因为 DMA 右侧的块处理的是来自前几帧的音频数据。特别是,如果你同时也计划将最终混音通过 DMA 送出到内存以便提交给音频端点,那么把 DMA 再送回 SHAPE 只是为了做最终混音就可能没有必要。 图 5 展示了一个可能价值较低的例子:一条语音先通过 DMA 送到内存进行 CPU/GPU 处理,随后又被送回 SHAPE,只为在 7.1 全景声中做声像并加入母带混音;然后再通过 DMA 送回内存。图 5 假定 FLT/VOL 块中没有对语音应用任何滤波,且 7.1 母带混音缓冲区还有其他语音路由进来。 图 5. 一张展示潜在低价值路由的流程图,其中包含不必要的 FLT/VOL 组件和不必要的 DMA 往返。 图 6 展示了对图 5 意图更优的路由。语音通过 DMA 送到内存进行 CPU/GPU 处理,随后可以直接在 CPU 上做 7.1 全景声声像并与其余 7.1 混音合并,无需额外的 DMA 开销、额外的音频帧延迟或占用 7 个 FLT/VOL 组件。 图 6. 一张展示更优路由的流程图,无需额外的 DMA 往返,也没有多余的 FLT/VOL 组件。 在从流程中去除 DMA 时,请确保不要去掉太多。以下情况可以为往返 DMA 提供合理理由:
  • 你打算为某条特定语音执行相当多的额外 SHAPE 处理。
  • 你希望利用混音缓冲区提供的硬件计量和削波检测。

SHAPE 流程图规划与审阅

作为一项练习,在实现之前,请先为你预计在一帧内使用的语音绘制一张流程图的可视化图,将语音分类并量化,例如以音效数量和并发音乐流数量来表示。 以可视化方式记录这些信息有助于你理解如何避免前文提到的一些性能陷阱。以下是你可以从此类流程图中得出的一些关键指标。
  • 每种 SHAPE 组件的最大数量(并发以及整帧内):
    • 验证该流程图是否远低于理论峰值。
    • 留意组件的使用不足和过度使用。必要时重新均衡各组件。
    • 如果太多混音缓冲区在流程图大部分时间内必须保持锁定,可考虑重新组织混音缓冲区的使用方式。
  • 规划通过 SHAPE 实现 3D 定位的方案:
    • 所有语音都做 7.1 声像,还是仅在 2 个或 n 个最近扬声器之间做声像?
    • 所有 7.1 声像都应在 SHAPE 中完成,还是那些已经通过 DMA 送到 CPU 的语音最好在 CPU 上进行声像?
  • 与内存之间的 DMA 转换次数,以及这些转换是否非对称。例如,某些语音进出 SHAPE 的次数比其他语音更多,这意味着它们可能会有额外延迟。
流程图的构建也可以促进音频设计师与引擎或音频中间件开发者之间的讨论,确保动态或静态创建的图能被正确地创建并按预期路由。

持久与非持久流程图

你可以选择将流程图作为持久或非持久提交处理。 持久流程图在处理后仍驻留在硬件中,等下一帧读写指针推进、上下文信息更新后再次执行相同处理。以下任一场景应使用持久流程图。
  • 高度复杂的流程图: 每帧重新组装需要大量 CPU 处理的流程图
  • 高度静态的流程图: 长时间保持相同数量和配置的语音的流程图
以下任一场景应使用非持久流程图。
  • 语音拓扑经常变化。
  • 你的游戏音频软件引擎的处理节拍与 SHAPE 硬件解耦;也就是说,不是 2.667 ms 的整数倍。
  • 你希望以快于实时的方式运行音频处理;也就是说,提交图后立刻消费,尽可能快地处理。

调试 SHAPE 问题

我们建议你在开发流程图时注册以帮助排查问题的消息。具体来说,请使用 IAcpHal::Connect 方法的 NumMessages 参数。 该消息系统会针对各种问题提供丰富的反馈,包括非法流程图(例如 ACP_FLOWGRAPH_TERMINATED_REASON_INVALID_GRAPH)、被阻塞的命令,以及由于试图在硬件 2.667 ms 的帧尺寸内执行更多处理而引起的超帧 (frame-out)。 该方法有助于调试。不过,在你的游戏发布之前,请尽量做到:
  1. 在运行时场景中消除所有表示错误的消息。
  2. 通过检查每个已提交流程图的 ACP_MESSAGE_TYPE_FLOWGRAPH_COMPLETED 消息(参见 ACP_MESSAGE_TYPE 枚举)来验证流程图处理始终成功。

丢失的消息

如果你使用 ACP 消息来驱动引擎状态,并且需要处理大量消息,那么在开发期间应检查 ACP_MESSAGE::droppedMessageCount。非零值表示消息队列已满,不得不丢弃消息。若出现此值,请考虑加快对队列的处理并加大队列容量。

超帧

在开发过程中,由于预算超支、次优流程图或其他原因,流程图可能在遇到音频帧结束(2.667 ms)前仍未完成。对于持久流程图,此类未完成的流程图会产生 ACP_FLOWGRAPH_TERMINATED_TIME_EXCEEDED 消息,且流程图不会完整执行。相比之下,非持久流程图不受此约束,会一直运行直至完成,即便跨越多帧。

被阻塞的命令

在下表所示的多种场景中,SRC 和 DMA 命令可能被报告为阻塞(ACP_MESSAGE_TYPE_SRC_BLOCKEDACP_MESSAGE_TYPE_DMA_BLOCKED,参见 ACP_MESSAGE_TYPE 枚举)。 一旦确定命令被阻塞——这可能发生在项被加入 SHAPE 队列进行处理之前,也可能发生在运行时——这些命令会被从图中逐出。这可能导致音频丢失。在开发期间,被阻塞的命令是你首先应关注、以改进流程图处理的地方。发布前,你的游戏也应避免命令被阻塞。 你可以通过确保音频缓冲区足够大,并根据缓冲区大小以规律的间隔流入数据,来防止 DMA 命令被阻塞。例如,如果你一次流送四个硬件帧,请确保缓冲区中始终有它们的若干倍——至少双缓冲即八帧——这样硬件就能在游戏读指针之前提前写入。也可以通过在提交下一个流程图之前排空缓冲区(先消费、更新 DMA 读指针,再提交流程图)来实现。 SRC 命令有以下三种模式(详见 ShapeSrcContext.h)。
  • SHAPE_SRC_COMMAND_TYPE_START 用于流程图中几乎所有 SRC 命令。按正常处理,并预期当前流程图之后还会有更多音频数据。
  • SHAPE_SRC_COMMAND_TYPE_STOP_IMMEDIATE 用于立即停止对源 XMA 或 PCM 数据的处理。SRC 会在这一帧输出全零缓冲区。
  • SHAPE_SRC_COMMAND_TYPE_STOP_END 用于指示一条语音的最后一个数据包。若未以 STOP_ENDSTOP_IMMEDIATE 提交,SRC 命令将不会完成。这会导致活动流程图停滞。持久流程图会在音频帧末尾终止,而非持久流程图将永远无法完成。

同步问题

如果你不遵守 SHAPE 硬件对上下文和命令的消费规范,可能会引发一系列同步问题。虽然你的游戏对 ACP 分配的内存拥有完全访问权,但要注意在上下文结构正在被使用时不要修改它。你可以提交命令(参见 IAcpHal::SubmitCommand),让其在特定帧执行、在下一帧开始时执行,或者尽快执行。 对于最后一种情形(尽快执行的命令),请注意 尽快 相对于任何游戏 CPU 处理来说仍然是异步的。有些命令可能不会立即完成——例如,如果某个上下文正处于不可中断的操作中。请等待 ACP_MESSAGE_TYPE_COMMAND_COMPLETED 消息,以确认命令确实已处理完毕。
最后修改于 2026年8月24日