深入探究:VideoNG

Dale Curtis
Dale Curtis

我叫 Dale Curtis 是 Chromium 中媒体播放的工程主管我的团队负责面向 Web 的视频播放 API(例如 MSEWebCodecs),以及分流、解码以及渲染音频和视频所涉及的特定于平台的内部机制。

在本文中,我将向大家介绍 Chromium 的视频呈现架构。虽然有关可扩展性的一些细节可能因 Chromium 而异,但此处讨论的大多数概念和设计也适用于其他渲染引擎甚至原生播放应用。

多年来,Chromium 的播放架构发生了显著变化。虽然我们并没有按照本系列的第一篇博文所述的那种成功金字塔的概念开始,但我们最终遵循了类似的步骤:可靠性、性能和可扩展性。

一开始,视频渲染非常简单 - 一个 for 循环用来选择将哪些软件解码视频帧并发送给合成器。多年来,这已经足够可靠,但随着 Web 复杂性的增加,对性能和效率的需求迫使架构上的改变。许多改进都需要特定于操作系统的基元;因此,我们的架构也必须更加可扩展,以覆盖 Chromium 的所有平台。

不同 Chromium 平台的渲染流程示意图。

视频渲染可分为两个步骤:选择要呈现的内容和高效地提供相应信息。为了提高可读性,在深入了解 Chromium 如何选择要传送的内容之前,我会先介绍高效传送。

一些术语和布局

由于本文重点介绍渲染,因此我将只简要介绍流水线的解复用和解码方面。

传入的字节数和流出的结构化数据包。

如今,注重安全意识的环境中的解码和解混需要非常小心。二进制解析器是丰富的目标环境,媒体播放充满了二进制解析。因此,媒体解析器中的安全问题极为常见。

Chromium 实行深度防御,以降低用户遇到安全问题的风险。实际上,这意味着解多路复用和软件解码始终在低特权进程中进行,而硬件解码发生在仅具有与系统 GPU 通信的特权的进程中。

适用于渲染程序、GPU 和音频进程的 Chromium 沙盒。

Chromium 的跨进程通信机制称为 Mojo。虽然我们在本文中不会详细介绍 Mojo,但作为进程之间的抽象层,它还是 Chromium 可扩展媒体流水线的基石。在我们了解播放流水线时,请务必注意这一点,因为它可以告知跨进程组件的复杂编排,这些组件通过交互来接收、多路复用、解码,并最终显示媒体。

太多位

要了解当今的视频渲染流水线,需要了解视频为何独一无二:带宽。以每秒 60 帧的 3840x2160 (4K) 分辨率播放将使用 9-12 GB/秒的内存带宽。虽然现代系统的峰值带宽可能达到数百 GB/秒,但视频播放仍然占很大比例。如果不谨慎,由于 GPU 和 CPU 内存之间的复制或切换操作,总带宽很容易成倍增加。

任何注重效率的现代视频播放引擎的目标是最大限度地减少解码器与最终渲染步骤之间的带宽。因此,视频渲染在很大程度上脱离了 Chromium 的主渲染管道。具体而言,从我们的主渲染管道的角度来看,视频只是一个具有不透明度的固定大小的孔。Chromium 利用一个名为 Surface 的概念来实现这一点,每个视频都会直接与 Viz 对话。

一个网页,上面有一个孔和一个内容为“视频请点击此处”的箭头。

由于移动计算的普及,功耗和效率已成为当今世代的关注重点。其结果是,解码和渲染在硬件级别的耦合程度更胜以往,导致视频看起来像一个不透明的孔,甚至对操作系统本身也是如此! 平台级解码器通常仅提供 Chromium 以叠加层形式传递到平台级合成系统的不透明缓冲区。

一个网页,上面有一个孔和一个内容为“请在此处播放视频”的箭头,周围环绕着一个表示操作系统的方框。

每个平台都有自己的叠加层形式,其平台解码 API 可与其搭配使用。Windows 具备直接组合Media Foundation Transform,macOS 具备 CoreAnimation LayersVideoToolbox,Android 具备 SurfaceViewMediaCodec,Linux 及 VASurfacesVA-API。 Chromium 的这些概念的抽象化分别由 OverlayProcessormojo::VideoDecoder 接口处理。

在某些情况下,这些缓冲区可以映射到系统内存,因此它们甚至不需要不透明,并且在被访问之前不会占用任何带宽。Chromium 会调用这些 GpuMemoryBuffers。在 Windows 上,这些缓冲区由 DXGI 缓冲区、macOS IOSurfaces、Android AHardwareBuffers 和 Linux DMA 缓冲区提供支持。虽然视频播放通常不需要这种访问权限,但这些缓冲区对于视频拍摄非常重要,可确保捕获设备和最终编码器之间的带宽最小。

上文提到的缓冲区的示意图。

由于 GPU 通常同时负责解码和显示,因此使用这些(通常也是)不透明缓冲区可确保高带宽视频数据实际上绝不会离开 GPU。如前所述,将数据保存在 GPU 上对于效率极其重要,尤其是在高分辨率和帧速率下。

我们充分利用叠加层和 GPU 缓冲区等操作系统基元越多,不必要地在视频字节上浪费的带宽就越少。从解码到渲染,一切都可在一个位置管理,能带来惊人的能效。 例如,当 Chromium 在 macOS 上启用叠加层后,全屏视频播放时的功耗减半! 在 WindowsAndroidChromeOS 等其他平台上,即使在非全屏情况下,我们也可以使用叠加层,几乎在所有平台上都能节省高达 50% 的费用。

渲染

我们已经介绍了最佳分发机制,接下来,我们可以探讨 Chromium 如何选择要分发的内容。 Chromium 的播放堆栈使用基于“拉取”的架构,这意味着堆栈中的每个组件都会按层次结构顺序从其下方的组件请求输入。堆栈的顶部是音频和视频帧的渲染,接下来是解码,接着是解复用,最后是 I/O。每个渲染的音频帧都会让时钟提前一个时钟,该时钟用于选择要渲染的视频帧,当与呈现时间间隔结合时。

在每个呈现间隔(屏幕的每次刷新)上,视频渲染程序都会通过附加到 SurfaceLayerCompositorFrameSink 来提供视频帧。对于帧速率低于显示速率的内容,这意味着多次显示同一个帧;如果帧速率高于显示速率,则某些帧将永远不会显示。

以观看者喜欢的方式同步音频和视频还有很多。 请参阅 Project Butter,详细讨论如何在 Chromium 中实现最佳视频流畅性。 它解释了如何将视频渲染细分为理想的序列,这些序列代表每一帧应该显示的次数。 例如:“每个显示间隔为 1 帧([1],60 fps,采用 60 Hz”)、“每 2 个间隔 1 帧([2],30 fps,60 Hz)”或更复杂的模式,例如 [2:3:2:3:2](60 Hz 为 25 fps,涵盖多个不同的帧)。视频呈现器越贴近这种理想模式,用户就越有可能认为播放流畅。

解复用、解码和渲染的顺序。

虽然大多数 Chromium 平台会逐帧渲染,但并非所有 Chromium 平台都能如此。我们可扩展的架构还支持批量渲染。 批量渲染是一种效率技术,采用这种技术时,操作系统级别合成器会提前获知多帧的相关信息,并按照应用提供的时间安排处理这些帧的发布。

未来是现在?

我们重点关注了 Chromium 如何利用操作系统基元提供一流的播放体验。 但是,网站不仅限于基本的视频播放功能,该怎么办呢? 我们能否向他们提供同样强大的基元,就像 Chromium 本身用来引导下一代网页内容的呢?

我们认为答案是肯定的! 可扩展性是当今我们对 Web 平台的看法的核心。我们一直在与其他浏览器和开发者合作,以打造 WebGPUWebCodecs 等新技术,以便 Web 开发者在与操作系统通信时可以使用 Chromium 提供的相同基元。WebGPU 带来了对 GPU 缓冲区的支持,而 WebCodecs 提供了与上述叠加层和 GPU 缓冲区系统兼容的平台解码和编码基元。

WebCodecs 和 WebGPU 之间的关系。

直播结束

感谢阅读!希望您对现代播放系统以及 Chromium 如何驱动每天的观看时长几亿小时的观看时长有了更深入的了解。 如果您想进一步了解编解码器和现代网络视频,建议您阅读 Sid Bala 的 H.264 is Magic、Erica Beaves 的 Modern Video Players Work 以及 Cyril Concolato 的将获奖节目打包到一起

一张是 Una Kravets 创作的插图,很漂亮!