大家好!我是大聪明-PLUS!

随着 4K(超高清)等大型视频格式的出现,视频流解码效率问题变得尤为紧迫。在普通计算机上,必须采取特殊措施才能实时处理此类视频流。本文探讨了在基于 FFmpeg 的解决方案中提高视频流解码速度的可能方法,并展示了测量 H264 和 HEVC (H265) 编码的 4K 视频流解码速度的实验结果。
1. 提高视频流解码速度的三种方法
我们将探讨三种提高视频流解码速度的方法。
- 在标准解码器中连接额外的工作线程。
- 在标准解码器中启用硬件加速(HW Acceleration)。
- 使用在图形处理器上实现解码的专用解码器。
第一个程序仅使用 CPU 功能,而另外两个程序则使用 GPU 功能。
提高视频流解码速度的可用方法很大程度上取决于操作系统、计算机硬件和 FFmpeg 配置。本文中展示的所有结果均在以下硬件和软件配置上进行了测试:操作系统 --- Windows 10,CPU --- Intel i5 8400 2.80 GHz(6 核,无超线程),集成 GPU --- Intel UHD Graphics 630,内存 --- 16 GB,FFmpeg 版本 4.2.1。
1.1 在标准解码器中连接其他工作流程
许多解码器(但并非全部)允许您设置用于解码的工作线程数。为此,请在调用之前将结构avcodec_open2()体成员设置为所需值。另一种方法是将该选项添加到作为第三个参数传递给的选项字典中。thread_count``AVCodecContext``threads``avcodec_open2()
h264用于处理大型格式( ,, )hevc的最流行的解码器vp9支持此功能,但theora不支持。
要在命令行上连接其他流,您需要使用带有键的选项-threads。
1.2. 在标准解码器中启用硬件加速
FFmpeg 支持部分解码器的硬件加速。使用 FFmpeg API 进行编程时,连接到硬件加速解码器所需的一切都位于头文件中libavutil/hwcontext.h。该文件定义了一个枚举enum AVHWDeviceType,其中每个元素对应于一种特定的硬件加速类型。可以使用以下代码查找当前 FFmpeg 版本中可用的硬件加速类型:
void` `print_hwtypes_all`()
{
`AVHWDeviceType` `hwtype` `=` `AV_HWDEVICE_TYPE_NONE`;
`while` ((`hwtype` `=` `av_hwdevice_iterate_types`(`hwtype`)) `!=`
`AV_HWDEVICE_TYPE_NONE`)
{
`printf`(`"%s\n"`, `av_hwdevice_get_type_name`(`hwtype`));
}
}`
对于上述硬件和软件配置,我们得到:
在哪里
dxva2
qsv
d3d11va
显然,cuda它需要安装 Nvidia 显卡和相应的软件,qsv使用 Intel Quick Sync Video (QSV) 技术,该技术在集成 Intel 图形处理器上实现(参见 [1]),dxva2并d3d11va使用 DirectX 视频加速技术(参见 [2]),该技术仅在 Windows 中可用,但适用于不同制造商(Intel、Nvidia、AMD)的显卡。
并非所有解码器都必须支持所有这些硬件加速类型(甚至无需支持其中任何一种)。要确定特定解码器支持哪些类型,可以使用以下代码:
void` `print_hwtypes`(`const` `char*` `dec_name`)
{
`const` `AVCodec*` `decoder` `=` `avcodec_find_decoder_by_name`(`dec_name`);
`for` (`int` `i` `=` `0`; ; `++i`) {
`const` `AVCodecHWConfig` `*config` `=`
`avcodec_get_hw_config`(`decoder`, `i`);
`if` (`config`) {
`if` (`config->methods` `&`
`AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX`) {
`printf`(`"%s\n"`,
`av_hwdevice_get_type_name`(`config->device_type`));
}
}
`else` {
`break`;
}
}
}`
对于上述硬件和软件配置,解码器支持以下h264类型的硬件加速:hevc``vp9``vc1
dxva2
d3d11va
在哪里
但theora它完全不支持硬件加速。
现在我们简要回顾一下连接硬件加速解码器的步骤。
void` `init_hwdevice`(`AVHWDeviceType` `hwtype`, `AVCodecContext` `*codec_ctx`)
{
`AVBufferRef` `*dev_ctx` `=` `NULL`;
`int` `ret` `=` `av_hwdevice_ctx_create`(`&dev_ctx`, `hwtype`, `NULL`, `NULL`, `0`);
`if` (`ret` `>=` `0`) {
`codec_ctx->get_format` `=` `get_hw_format`; `// см. hw_decode.c`
`codec_ctx->hw_device_ctx` `=` `av_buffer_ref`(`dev_ctx`);
}
}`
解码后,帧数据采用由设备类型决定的特殊格式,因此必须使用函数将其转换为标准像素格式之一av_hwframe_transfer_data()。对于dxva2和,d3d11va此格式将是NV12。
要在命令行中启用硬件加速,您需要使用带有键的选项-hwaccel。
1.3. 使用在图形处理器上实现解码的专用解码器
FFmpeg 包含两类编解码器,它们在 GPU 上实现编码和解码。
其中一款产品系列采用英特尔快速同步视频 (QSV) 技术,该技术已集成到英特尔 i3、i5、i7 和 i9 处理器系列的视频处理器中。更多详情请参见 [1]。这些编解码器带有后缀。所讨论的_qsvFFmpeg 版本包含以下解码器:h264_qsv,,,,。hevc_qsv``vp8_qsv``mpeg2_qsv``vc1_qsv
另一系列解码器使用在Nvidia 板上实现的 NVDEC和NVENC技术。这些解码器带有后缀。所讨论的_cuvidFFmpeg 版本包含以下解码器:h264_cuvid,,,,,,,。hevc_cuvid``mpeg2_cuvid``vc1_cuvid``vp8_cuvid``vp9_cuvid``mjpeg_cuvid``mpeg4_cuvid
输入流打开后,对解码器的访问通常按如下方式实现:
AVStream` `*strm`;
`// ...`
`AVCodecID` `cid` `=` `strm->codecpar->codec_id`;
`const` `AVCodec` `*decoder` `=` `avcodec_find_decoder`(`cid`);`
但是,这只会检索给定编解码器 ID 的默认解码器。要查找其他解码器,请使用解码器名称,例如:
const` `AVCodec` `*decoder` `=` (`cid` `==` `AV_CODEC_ID_H264`)
`?` `avcodec_find_decoder_by_name`(`"h264_qsv"`)
: `avcodec_find_decoder`(`cid`);`
要在命令行中使用备用解码器,需要使用带有键的选项-c:v,将其放在键之前,-i如下所示:
ffmpeg -c:v h264_qsv -i INPUT ...
2. 测量解码速度
为了测量解码速度,我们选择了两个视频:一个采用 H264 编码,另一个采用 HEVC (H265) 编码。帧大小为 3840x2160(超高清),帧速率为 30 fps。我们测试了标准解码器h264和hevc相应的 QSV 解码器。标准解码器配置了四种模式:默认、双线程、四线程和硬件加速。在我们的实验中,标准解码器的性能优于 QSV 解码器,因此后者未纳入解码速度测量。为了进行测试h264_qsv,我们编写了一个程序,该程序从文件中提取数据包并以尽可能高的速度进行解码,忽略时间戳,且不进行渲染或其他处理。该程序有两种模式:第一种模式仅执行解码,第二种模式还使用 QSV 库将解码后的帧转换为 32 位格式。 (解码器的输出帧通常为 12 位平面格式。)测量程序运行时间,并记录其相对于视频流标称持续时间的相对时间(以百分比表示)。因此,如果结果小于 100%,则有可能实时处理视频流;如果大于 100%,则不可能实时处理。同时,使用任务管理器记录了 CPU 和 GPU 的大致负载。使用的是 64 位版本的 FFmpeg。hevc_qsv``dxva2``dxva2``d3d11va``BGRAlibswscale YUV420P``NV12
| || h264 ||| 氢氯乙烯 |||
| 配置 | # | 时间 | 中央处理器 | GPU | 时间 | 中央处理器 | GPU |
|---|---|---|---|---|---|---|---|
| 默认 | 1 | 75 | 26 | 0 | 125 | 25 | 0 |
| 默认 | 2 | 132 | 28 | 0 | 180 | 27 | 0 |
| 线程数=2 | 1 | 47 | 42 | 0 | 74 | 42 | 0 |
| 线程数=2 | 2 | 79 | 48 | 0 | 104 | 46 | 0 |
| 线程数=4 | 1 | 35 | 60 | 0 | 46 | 64 | 0 |
| 线程数=4 | 2 | 60 | 54 | 0 | 71 | 70 | 0 |
| dxva2 | 1 | 45 | 14 | 72 | 34 | 28 | 70 |
| dxva2 | 2 | 107 | 28 | 35 | 99 | 30 | 36 |
| xxxx_qsv | 1 | 25 | 34 | 80 | 25 | 34 | 72 |
| xxxx_qsv | 2 | 70 | 39 | 54 | 70 | 40 | 50 |
| [表格. 解码速度测量] |
结果可能无需过多讨论。唯一值得注意的是转换成本相当高昂BGRA。更重要的是,尽管所有测试配置下的工作内容都非常相似,但这些成本却因测试配置的不同而存在显著差异。
我们还使用 32 位 FFmpeg 版本进行了实验。结果基本相似,只有一个例外:hevc在没有硬件加速的配置下,解码器的性能下降了 2-3 倍。这是一个相当出乎意料的结果。
所述测试可在命令行运行。使用全局选项-benchmark并将输出设置为零。以下是一些示例:
ffmpeg -benchmark -i INPUT -an -f null -
ffmpeg -benchmark -threads N -i INPUT -an -f null -
ffmpeg -benchmark -c:v h264_qsv -i INPUT -an -f null -
ffmpeg -benchmark -hwaccel dxva2 -i INPUT -an -f null -
ffmpeg -benchmark -i INPUT -an -pix_fmt bgra -f null -
输出将显示实际值fps,参数speed将显示该值比标称值高多少倍。如果未指定关键选项-threads或为该N选项指定了特殊值auto,则解码器将使用最大可能的线程数,CPU 负载为 100%。
3. 关于QSV解码器的说明
有问题的 FFmpeg 版本包含以下 QSV 解码器:h264_qsv,,,,。后两个解码器无法正常工作。解码器生成的图像失真,并且在发送解码数据包时返回错误。诚然,这些解码器本身并不特别重要,但hevc_qsv发布无法正常工作的组件的原因仍然不明。vp8_qsv``mpeg2_qsv``vc1_qsv``mpeg2_qsv``vc1_qsv
还有一些关于剩余解码器的投诉。它们通常都能正常工作,但有一个问题:它们无法正确处理调用avcodec_flush_buffers()。虽然没有报错,但调用之后定位功能就无法正常工作了。