本文翻译自 Video Encoding and Decoding with Vulkan Compute Shaders in FFmpeg,原载于 Hacker News。
引言:硬件加速的进化与挑战
对于普通用户来说,互联网上的视频编码和解码问题已经基本解决了。如今大多数消费级设备都配备了专用的硬件加速芯片,而 Vulkan Video 扩展等 API 可以直接访问这些硬件。与此同时,新编解码器越来越多地采用免版税的开放规范——或者简单地因专利限制过期而变得人人可用。
然而,人们很容易忘记,仅仅 18 年前,720p H.264 解码对 CPU 来说还是一个巨大的挑战。正是这种挑战推动了软件实现的激烈竞争和优化,将性能推向极限,直到硬件解码最终变得普及。
但在专业工作流中,性能瓶颈依然存在。
剪辑师浏览数天的原始摄像机素材,调色师处理 8K 16-bit 母版,VFX 艺术家渲染 32-bit 浮点 ACEScg 视频,档案管理员处理超高分辨率无损胶片扫描——这些都仍然受到性能限制。普通用户曾经可以容忍偶尔的丢帧,但今天的专业人士往往被迫选择昂贵的专有解决方案,或者配备液冷、数百核心和数百 GB 内存的工作站。
本文探讨 FFmpeg 如何利用 Vulkan Compute 在消费级 GPU 上无缝加速甚至专业级视频的编码和解码——无需专用硬件即可大规模解锁 GPU 计算并行能力。这种方法补充了 Vulkan Video 的固定功能编解码器支持,将其加速扩展到未覆盖的格式和工作流。
编解码器的本质:并行化的挑战
编解码器是利用信号中的冗余和模式来压缩数据以进行存储或传输的算法。那么,在 GPU 上并行化编解码器处理有多难呢?
以 JPEG 为例——它就像压缩编解码器界的”秀丽隐杆线虫”(C. elegans,生物学研究中常用的模式生物)。编码一张图像需要:
- 2D 频率变换(部分可并行化,先处理行再处理列)
- DC 值预测(完全串行)
- 量化以丢弃感知上无关的信息(完全并行)
- 霍夫曼编码(极度串行)
并行步骤和串行步骤的混合成为 GPU 编解码器加速的核心挑战。解码过程逆转这些步骤——但串行瓶颈同样棘手。
这就是根本的矛盾:编解码器管道充满了串行依赖,而 GPU 专为同时执行数千个独立、不相关的操作而设计。
历史的教训:混合解码的失败
历史上显而易见的方法是混合解码:在 CPU 上处理串行步骤(如系数解码),将中间结果上传到 GPU,然后让 GPU 运行它擅长的并行步骤。
在实践中,这遇到了一个根本问题:GPU 在物理上距离系统内存很远。即使有 DMA 和高带宽传输,往返延迟往往使混合解码比直接在 CPU 上执行并行步骤还要慢——特别是考虑到现代支持 SIMD 的 CPU 已经非常强大。
混合编解码器实现的实际结果证实了这一点:
- dav1d 解码器曾尝试将其最后的滤波器通道(复杂但高度可并行化)卸载到 GPU,但即使在移动设备上也看不到比 CPU 更快的速度
- x264 添加了基本的 OpenCL 支持,但帧上传延迟扼杀了任何性能优势,代码最终也废弃了
这些失败使混合实现在多媒体社区声名狼藉。教训很清楚:要实现持续快速、可维护和广泛采用,基于计算的编解码器实现需要完全驻留在 GPU 中——没有 CPU 的交接。
时过境迁:GPU 计算编解码器的曙光
大多数编解码器是为 ASIC 硬件设计的——即现代 GPU 上的专用视频引擎,通过 Vulkan Video 暴露。但即使是 ASIC 也不是无限快的:编解码器通常会妥协并定义一个最小可并行工作单元,称为 slice(切片)或 block(块),代表可以独立处理的最小块。
大多数流行的编解码器是在几十年前设计的,当时视频分辨率要小得多。随着分辨率的爆发,那些固定大小的最小单元现在只占帧的一小部分——这意味着可以并行处理更多的单元。现代 GPU 还获得了启用跨调用通信的功能,开辟了进一步的优化机会。
这些趋势共同使得今天完全在计算着色器中实现某些编解码器成为真正可行的——不需要 CPU 参与。
基于计算的编码器还有一个容易被忽视的优势:它们在内存使用和搜索时间上不受限制。有足够的线程来穷尽扫描每个块,匹配甚至超越软件编码器的质量是完全可实现的。
FFmpeg:让 GPU 加速触手可及
FFmpeg 是一个免费开源的库和工具集合,用于处理多媒体流,无论格式或编解码器如何。除了以跨平台手写汇编优化而闻名外,FFmpeg 还提供对硬件加速器的便捷访问。
关键是,FFmpeg 中的硬件加速建立在软件编解码器之上。头部解析、线程调度、帧/切片调度和错误纠正/处理都在软件中完成。只有所有视频数据的解码被卸载。这将健壮、经过良好测试的代码与硬件加速结合起来。我们可以直接翻译软件实现所做的独立帧线程调度,通过调度多个帧进行并行解码来完全饱和 GPU。
这也允许用户通过切换开关在软件和硬件实现之间动态切换,无需区分硬件解码是使用 Vulkan Video 还是 Vulkan Compute 着色器实现的。
FFmpeg 在编辑软件、媒体播放器和浏览器中的广泛使用,加上向任何软件实现添加硬件加速器支持的能力,使其成为让基于计算的编解码器实现广泛可及的理想起点,而不是专门的库实现。
FFv1:档案级无损压缩的 GPU 加速
FFmpeg Video Codec version #1 已成为档案社区的标配,也用于需要无损压缩的应用程序。它是开放的、免版税的,是官方 IETF 标准。
在 FFmpeg 中用计算着色器实现编解码器的工作就是从这里开始的。FFv1 编码器和解码器在 CPU 上运行非常慢,尽管支持多达 1024 个切片。这部分是由于高分辨率 RGB 视频所需的巨大带宽,以及熵编码设计的一些瓶颈。
FFv1 版本 3 是在 10 多年前设计的,正是因为档案社区的采用才获得了广泛使用。然而,这些瓶颈使得高分辨率档案胶片扫描的编码和解码变得极其耗时。
技术挑战与解决方案:
编码 FFv1 时最大的挑战是 range coder 系统,它缺乏 AV1 的 range coder 那样的优化。每个符号(像素差值)的每一位都有自己的 8-bit 适应值,因此在编码或解码时需要从数千个(每个平面!)中随机查找 32 个连续值。
我们通过使用 32 的工作组大小来加速,每个本地调用并行查找并执行适应,而单个调用执行实际的编码或解码。
对于 RGB,执行可逆颜色变换(RCT)以进一步去相关像素值。最初,为此使用单独的着色器,编码到单独的图像。然而,对非常高分辨率的图像这样做的带宽要求超过了优势。由于只需要 2 行来解码或编码图像,我们分配 widthhorizontal_slices2 图像,并在 32 个助手调用的帮助下,在编码每行之前执行 RCT。
APV:为并行而生的新一代编解码器
APV 是三星设计的新编解码器,作为中间视频压缩的免版税、开放替代方案。最近,它也成为了 IETF 标准。它在 VFX 和专业媒体制作社区以及智能手机摄像头录制格式中获得了关注。
与本文中提到的大多数编解码器不同,APV 从头开始就是为并行设计的。类似于 JPEG,每帧被细分为组件,每个组件被细分为瓦片,每个瓦片具有多个块。每个块简单地变换、通过标量量化器量化(简单除法)并通过可变长度代码编码。甚至没有 DC 预测。
为了将其实现为计算着色器,我们首先在一个着色器中处理每个瓦片的解码,然后运行第二个着色器,每次调用转换单个块的一行。
ProRes:专业编辑的事实标准
ProRes 是事实上的中间编解码器标准,用于编辑、摄像机素材和母版制作。它是一个相对简单的编解码器,类似于 JPEG 和 APV,这使得实现解码器成为可能,由于需求量大,还有编码器。
对于解码,我们执行与 APV 本质上相同的过程。但对于编码,我们通过运行着色器来找到使块适合帧比特预算的量化器来进行适当的速率控制和估计。
注意: 与列表中的其他编解码器不同,ProRes 编解码器既不免版税,也没有开放规范。FFmpeg 中的实现是非官方的。但由于其极高的流行度,此类实现对于与专业世界的互操作性是必要的。尽管如此,开发人员在这些实现上进行了大量测试,并监控其输出以匹配官方实现。
ProRes RAW:RAW 视频的专用压缩
ProRes RAW 具有一个与 ProRes 共同点很少的比特流,因为它是为压缩 RAW(未去马赛克)有损传感器数据而设计的。它使用对每个组件执行的 DCT,以及一个系数编码器,该编码器跨组件预测 DC 并以正常的之字顺序高效编码来自多个组件的 AC 值。熵编码系统不完全像传统的可变长度代码,而是更接近指数编码。
切片具有多个块,每个组件可以并行解码。与 FFv1 不同,每图像的瓦片数量没有限制,这可能需要解码数十万个独立的块。这对并行性很有好处,可以实现高效的实现。
解码器采用两遍方法实现:第一个着色器解码每个瓦片,第二个着色器使用行/列并行性(称为 shred 配置,因为可以完全饱和 GPU 的工作组大小限制)转换每个瓦片内的所有块。
DPX:古老但不可忽视的像素容器
DPX 不是编解码器,而是一个带有头部的打包像素容器。它是官方 SMPTE 标准,在胶片扫描仪中相当流行。它不是以最佳方式布局和紧密打包像素,而是可以以 32-bit 块打包像素,如果需要则填充。或者它也可以……不解包像素,取决于头部开关。
作为一个几十年前制定的松散规定的未压缩格式,意味着供应商在解释规范方面非常”有创意”,以完全破坏解码的方式。幸运的是,头部中留有一个文本”producer”字段,供此类实现签署其艺术作品,可用于找出如何正确解包而不会看到外星彩虹。
所有这些都归结为在着色器中编写启发式规则。开销永远不会是找到像素集合所需的计算,而是实际从内存中拉取数据并将其写入其他地方。
VC-2:BBC 的小波变换尝试
VC-2 是另一个中间编解码器。由 BBC 编写,基于其 Dirac 编解码器,它是免版税的,有官方 SMPTE 规范。它的主要用例是实时流媒体,特别适合在千兆连接上传输高分辨率视频,具有亚帧延迟。与 APV 或 ProRes 不同,它基于小波变换。每帧被细分为 2 的幂大小的切片。
小波作为变换相当有趣。它们将帧细分为四分之一分辨率的图像,以及另外三个四分之一分辨率的残差图像。与 DCT 不同,它们高度局部化,这意味着可以在每个切片上单独执行,但组装时它们的功能就像整个帧被变换了一样。这消除了所有基于 DCT 的编解码器所遭受的块效应。
这意味着它们的编码效率较低,因为频率分解受损。此外,它们的失真特性在视觉上远不如 DCT 的模糊吸引人。这是它们未能获得 2000 年代后编解码器关注的主要原因之一。
生成的系数通过简单的交错 Golomb-exp 代码编码,虽然不可并行化,但可以在解码器中美妙地简化,以消除所有比特解析,而是对整个字节进行操作。
JPEG:意想不到的并行化突破
本文开头作为示例给出的编解码器,结果发现有一个非常有趣的攻击方法,不仅打开了并行化的大门,还打开了任意数据压缩标准(如 DEFLATE)的并行化。
核心洞察: 虽然 VLC 流缺乏任何并行化的方法,但 VLC 解码器——实际上所有满足 Kraft-McMillan 不等式的代码——可以虚假地重新同步。在惊人的短暂延迟后,VLC 解码器倾向于输出有效数据。
所需要的只是运行 4 个着色器来逐渐同步每个 JPEG 流中的起始点。JPEG 也有多个变体,如渐进式和无损配置文件,也可以被并行化到这种程度。
DC 预测可以通过并行前缀和来完成,这是通过计算着色器执行的最常见操作之一。DCT 可以通过 shred 配置完成,就像其他编解码器一样。
现状与未来展望
FFmpeg 8.1 已实现的功能:
- FFv1 编码和解码
- ProRes 编码和解码
- ProRes RAW 解码
- DPX 解包
如果启用了 Vulkan 加速解码,基于 GPU 的处理会自动启用并使用。
仍在进行中的功能:
- VC-2 编码器和解码器
- JPEG 和 APV 解码器
它们需要额外的工作才能合并。
未来展望:
剩下的具有有意义 GPU 加速潜力的编解码器只有 JPEG2000 和 PNG——其余的要么实际用例有限,要么不能从基于计算的加速中受益。
遗憾的是,JPEG2000——以及 JPEG2000HT——不像大多数现代编解码器,它负担着多个组合的最差特性:一个需要广泛领域知识的半序列化编码系统,以及一个复杂到让大多数现代官僚机构都头疼的比特流。JPEG2000 的软件解码在所有广泛使用的编解码器中是最慢的之一,归因于其以 ASIC 为中心的设计和工程不足的算术编码器。尽管如此,它仍然是数字电影、医学和取证中使用的主要编解码器。
PNG 加速是一个开放问题:它作为 GPU 目标的可行性将取决于 DEFLATE 可以被有效并行化的程度。
为什么选择 Vulkan Compute?
Vulkan 经常被狭隘地归类为带有附加计算功能的图形 API——但这种框架已经过时。它的计算能力已经发展到匹配甚至在某些情况下超过专用计算 API。现代 Vulkan 提供:
- 指针支持
- 广泛的子组操作
- 共享内存别名
- 原生位操作
- 定义良好的内存模型
- 着色器特化
- 64-bit 寻址
- 直接访问 GPU 矩阵单元
这些功能共同使程序员能够以比更抽象的 API 更低的级别进行优化。
即便如此,Vulkan Compute API 仍未发挥其全部潜力,因为它尚未暴露 SPIR-V 的全部功能。SPIR-V 作为中间表示具有惊人的表达能力。对更广泛 SPIR-V 功能集的支持正在积极扩展——无类型指针和 64-bit 寻址已经可用,对非 32-bit 整数类型的位操作支持也在路上。
GPU 供应商的竞争性计算 API 通常捆绑数百个专门和特别优化的算法实现,通过更舒适的编程语言访问——一个诱人的方案。当然,代价是供应商锁定,这对于像 FFmpeg 这样可移植、长寿命的软件来说可能是一个严重的问题。
FFmpeg 并不陌生,为了避免依赖而编写自己流行的算法实现,如哈希函数、排序算法、CRC 或频率变换。
但另一方面,广泛、面向对象的 API 实际上是必要的吗?通常,格式数据以供常见实现使用比简单地为特定用例编写算法的小型特化实现花费更长时间并产生更不优化的代码。OOP 在很多情况下可以通过预处理器模板化来处理。链接多段代码可以只是一个 #include。而且,针对供应商 API 的单一版本的脆弱代码(而该 API 又依赖于特定的旧 gcc 版本)可以被可靠、持久、自给自足的着色器所取代。
Vulkan 无处不在——从微型 SoC,到平板电脑、嵌入式 GPU、独立 GPU 和专业服务器 GPU——其行业主导的治理模式创造了广泛支持新扩展的强大激励。使用全面的符合性测试套件进行持续的自动化测试。最后,Vulkan 拥有广泛的调试、优化和分析工具生态系统,以及庞大的全球开发者社区,这意味着几乎任何你发现的 GPU 特性或优化技巧都已经被发现、记录并反馈到规范中。
无论使用 Vulkan Video 还是 Vulkan 计算着色器,Vulkan 已经成为访问 GPU 加速视频处理的引人注目的 API。
总结
这篇文章深入探讨了 FFmpeg 如何利用 Vulkan Compute 着色器在消费级 GPU 上实现视频编解码的硬件加速。几个关键要点:
-
纯 GPU 实现是关键:历史上的混合 CPU-GPU 方案因内存传输延迟而失败,成功的实现必须完全驻留在 GPU 中
-
技术可行性:随着视频分辨率的提高,编解码器原本的并行单元(slice/block)相对于整帧变得更小,使得更多并行处理成为可能
-
已实现的编解码器:FFv1、ProRes、ProRes RAW、DPX 已在 FFmpeg 8.1 中实现,VC-2、JPEG、APV 正在开发中
-
Vulkan 的优势:跨平台、无供应商锁定、广泛的生态系统支持,使其成为长期项目的理想选择
对于处理高分辨率视频的专业人士来说,这些 GPU 加速的编解码器实现意味着在普通消费级硬件上就能获得专业级的性能,而不需要昂贵的专有解决方案。
FFmpeg 下载地址:https://ffmpeg.org/download.html