使用 NVDEC 加速视频解码
作者: Moto Hira
本教程展示了如何将 NVIDIA 的硬件视频解码器 (NVDEC) 与 TorchAudio 结合使用,以及它如何提升视频解码的性能。
本教程需要启用硬件加速编译的 FFmpeg 库。
有关如何构建启用硬件加速的 FFmpeg,请参考 启用 GPU 视频解码器/编码器。
检查前提条件
首先,我们检查 TorchAudio 是否正确检测到支持硬件解码器/编码器的 FFmpeg 库。
我们将使用以下视频,它具有以下属性:
-
编解码器: H.264
-
分辨率: 960x540
-
帧率: 29.97
-
像素格式: YUV420P
使用 NVDEC 解码视频
要使用硬件视频解码器,您需要在定义输出视频流时通过向 add_video_stream()
方法传递 decoder
选项来指定硬件解码器。
视频帧被解码并以 NCHW 格式的张量返回。
默认情况下,解码后的帧会被发送回 CPU 内存,并创建 CPU 张量。
通过指定 hw_accel
选项,您可以将解码的帧转换为 CUDA 张量。hw_accel
选项接受字符串值并将其传递给 torch.device
。
目前,
hw_accel
选项和add_basic_video_stream()
不兼容。add_basic_video_stream
添加了后解码处理,该处理是为 CPU 内存中的帧设计的。请使用add_video_stream()
。
当有多个 GPU 可用时,
StreamReader
默认使用第一个 GPU。您可以通过提供"gpu"
选项来更改此设置。
"gpu"
选项和hw_accel
选项可以独立指定。如果它们不匹配,解码的帧会自动传输到hw_accel
指定的设备。
可视化
让我们看看硬件解码器解码的帧,并将其与软件解码器的等效结果进行比较。
以下函数会跳转到给定的时间戳,并使用指定的解码器解码一帧。
目前,硬件解码器不支持色彩空间转换。解码后的帧为 YUV 格式。以下函数执行 YUV 到 RGB 的转换(以及用于绘图的轴重排)。
现在我们来可视化结果。
在作者看来,它们是无法区分的。如果您发现了什么,请随时告知我们。 :)
硬件调整和裁剪
您可以使用 decoder_option
参数来提供特定于解码器的选项。
以下选项在预处理中通常相关。
-
resize
: 将帧大小调整为(width)x(height)
。 -
crop
: 裁剪帧(top)x(bottom)x(left)x(right)
。注意,指定的值是要移除的行/列数。最终图像大小为(width - left - right)x(height - top - bottom)
。如果同时使用crop
和resize
选项,crop
会先执行。
有关其他可用选项,请运行 ffmpeg -h decoder=h264_cuvid
。
比较调整大小的方法
与软件缩放不同,NVDEC 不提供选择缩放算法的选项。在机器学习应用中,通常需要构建具有相似数值特性的预处理管道。因此,我们在这里比较了硬件缩放与不同算法的软件缩放的结果。
我们将使用以下视频,该视频包含使用以下命令生成的测试图案。
以下函数用于解码视频并应用指定的缩放算法。
以下函数使用硬件解码器来解码视频并调整大小。
现在我们执行它们并可视化生成的帧。
没有一个完全相同。在作者看来,lanczos(1) 与 NVDEC 最为相似。bicubic 看起来也很接近。
使用 StreamReader 进行 NVDEC 基准测试
在本节中,我们将比较软件视频解码和硬件视频解码的性能。
解码为 CUDA 帧
首先,我们比较软件解码器和硬件编码器解码同一视频所需的时间。为了使结果具有可比性,在使用软件解码器时,我们将生成的张量移动到 CUDA。
测试过程如下所示
-
使用硬件解码器并直接将数据放置在 CUDA 上
-
使用软件解码器,生成 CPU 张量并将其移动到 CUDA。
以下函数实现了硬件解码器的测试用例。
以下函数实现了软件解码器的测试用例。
对于每个视频分辨率,我们使用不同数量的线程运行多个软件解码器测试用例。
现在,我们使用不同分辨率的视频来运行测试。
QVGA
VGA
XGA
结果
现在我们绘制结果。
我们观察到以下几点
-
增加软件解码中的线程数量可以加快处理速度,但性能在约8个线程时趋于饱和。
-
使用硬件解码的性能提升取决于视频的分辨率。
-
在较低分辨率(如QVGA)下,硬件解码比软件解码更慢。
-
在较高分辨率(如XGA)下,硬件解码比软件解码更快。
值得注意的是,性能提升还取决于 GPU 的类型。我们观察到,在使用 V100 或 A100 GPU 解码 VGA 视频时,硬件解码器比软件解码器更慢。但使用 A10 GPU 时,硬件解码器比软件解码器更快。
解码与调整大小
接下来,我们将调整大小操作添加到管道中。我们将比较以下管道。
-
使用软件解码器解码视频,并将帧读取为 PyTorch Tensor。使用
torch.nn.functional.interpolate()
调整张量大小,然后将结果张量发送到 CUDA 设备。 -
使用软件解码器解码视频,通过 FFmpeg 的滤镜图调整帧大小,将调整后的帧读取为 PyTorch 张量,然后将其发送到 CUDA 设备。
-
使用硬件解码器同时解码和调整视频大小,将结果帧读取为 CUDA 张量。
管道 1 代表了常见的视频加载实现。
管道 2 使用了 FFmpeg 的滤镜图,允许在将原始帧转换为 Tensors 之前对其进行处理。
管道 3 具有从 CPU 到 CUDA 的最小数据传输量,这显著提升了数据加载的性能。
以下函数实现了管道 1。它使用了 PyTorch 的 torch.nn.functional.interpolate()
。我们使用了 bincubic
模式,因为我们发现生成的帧最接近 NVDEC 的缩放效果。
以下函数实现了管道 2。帧在解码过程中被调整大小,然后发送到 CUDA 设备。
我们使用 bincubic
模式,以使结果与上述基于 PyTorch 的实现具有可比性。
以下函数实现了管道3。调整大小操作由NVDEC执行,生成的张量被放置在CUDA内存中。
以下函数在给定的源上运行基准测试函数。
现在我们运行测试。
QVGA
VGA
XGA
结果
现在我们绘制结果。
硬件解码器显示出与之前实验相似的趋势。实际上,性能几乎相同。硬件调整大小在缩小帧时几乎没有任何开销。
软件解码也显示出相似的趋势。在解码过程中进行大小调整速度更快。一个可能的解释是,视频帧在内部以 YUV420P 格式存储,其像素数量是 RGB24 或 YUV444P 的一半。这意味着如果在将帧数据复制到 PyTorch 张量之前进行大小调整,所操作和复制的像素数量比在帧转换为张量后进行调整的情况要少。
标签: torchaudio.io