为什么使用 ffmpeg x264 进行实时编码时,切片线程会对编码产生如此大的影响?

16
我正在使用ffmpeg libx264 对从x11实时捕获的720p屏幕进行编码,帧率为30。当我使用-tune zerolatency参数时,使用基本配置文件时每帧的平均编码时间可以长达12毫秒。
经过对ffmpeg x264源代码的研究,我发现导致这种长时间编码的关键参数是启用了的sliced-threads,而这是由-tune zerolatency启用的。通过使用-x264-params sliced-threads=0禁用后,编码时间可以降至2毫秒。
而且,在禁用sliced-threads的情况下,CPU使用率将为40%,而启用时仅为20%。
有人能详细解释一下这个sliced-thread吗?特别是在实时编码中(假设没有缓冲帧要编码,只有在捕获到画面时才进行编码)。

你正在使用默认的 preset 吗?如果你使用 -preset ultrafast 会发生什么? - aergistal
以上两种情况下都使用了ultrafast。 - CurtisGuo
这是一个有趣的问题。您是否使用最新版本的 ffmpeglibx264,以及在哪个操作系统/ CPU 上?另外,您如何进行测量? - aergistal
1
这不是最新的版本,我的源代码上最后一次提交是在2014年2月23日,而libx264是在2014年2月11日(抱歉,源代码是从另一个人那里得到的,我只能从git日志中获取详细信息)。主机操作系统是ubuntu 14.04,CPU是Xeon(R) CPU E5-2630 v3。我使用了-benchmark_all选项,并将所有输出数据转储到文件中,然后使用脚本计算平均编码时间。 - CurtisGuo
x264/doc/threads.txt 表示编码器的某些部分是串行的,基于切片的线程不会很好地扩展。由于您有 8 个核心,我认为它会生成 8 个切片线程。您可以覆盖 --threads 4--slices / --slices-max,然后查看发生了什么。这类似于您的问题:https://mailman.videolan.org/pipermail/x264-devel/2010-April/007115.html 我不认为这是调度程序的问题,您的内核是最新的。 - aergistal
显示剩余2条评论
1个回答

14

文档显示,基于帧的线程比基于切片的线程具有更好的吞吐量。它还指出,由于编码器的某些部分是串行的,后者不会很好地扩展。

对于veryfast配置文件(非实时),加速比与编码线程如下:

threads  speedup       psnr
      slice frame   slice  frame
x264 --preset veryfast --tune psnr --crf 30
 1:   1.00x 1.00x  +0.000 +0.000
 2:   1.41x 2.29x  -0.005 -0.002
 3:   1.70x 3.65x  -0.035 +0.000
 4:   1.96x 3.97x  -0.029 -0.001
 5:   2.10x 3.98x  -0.047 -0.002
 6:   2.29x 3.97x  -0.060 +0.001
 7:   2.36x 3.98x  -0.057 -0.001
 8:   2.43x 3.98x  -0.067 -0.001
 9:         3.96x         +0.000
10:         3.99x         +0.000
11:         4.00x         +0.001
12:         4.00x         +0.001

主要区别似乎在于帧线程会增加帧延迟,因为它需要处理不同的帧,而在基于切片的线程中,所有线程都在同一帧上工作。在实时编码中,相对于离线编码,它需要等待更多的帧到达以填充管道。
普通线程,也称为基于帧的线程,使用巧妙的错位帧系统进行并行处理。但代价是:如前所述,每个额外线程都需要一个以上的帧延迟。基于切片的线程没有这样的问题:每个帧被分成切片,每个切片在一个内核上编码,然后将结果组合在一起形成最终帧。由于各种原因,其最大效率要低得多,但它允许至少一些并行处理而不增加延迟。
来自: x264开发者日记 无切片线程:2个线程的示例。开始编码帧#0。当它完成一半时,开始编码帧#1。线程#1现在只能访问其参考帧的顶半部分,因为其余部分尚未被编码。因此,它必须限制运动搜索范围。但这可能没关系(除非您在小帧上使用了大量线程),因为很少出现如此长的垂直运动矢量。过了一会儿,两个线程都编码了一个宏块的一行,所以线程#1仍然可以使用运动范围= +/- 1/2帧高度。稍后,线程#0完成了帧#0,并继续进行帧#2。现在线程#0获得运动限制,而线程#1没有限制。

来源:http://web.archive.org/web/20150307123140/http://akuvian.org/src/x264/sliceless_threads.txt

因此,启用sliced-threads-tune zereolatency是有意义的,因为您需要尽快发送帧而不是以效率(性能和质量方面)为主进行编码。

使用过多的线程反而会影响性能,因为维护它们的开销可能超过潜在的收益。

在实时编码中,相对于离线编码,需要等待更多的帧到达以填充管道。这是在谈论帧线程吗?无论是切片还是帧线程都会增加解码时间吗?线程数量呢?谢谢。 - CurtisGuo
是的,我在谈论帧线程,因为它适用于不同的帧。默认情况下,它是帧线程化的(#threads = 1.5 * cores),我认为这就是启用切片时看到较低值的原因。太多线程(16)= 太多开销。关于解码时间,似乎使用切片使解码器能够利用多线程并更快地解码(例如:蓝光需要4个切片)。 - aergistal
我还想知道的一件事是,如果不使用 b 帧,为什么编码器要等待后续帧,而不是仅使用先前的帧。 - CurtisGuo
请查看我的更新答案。每个额外的线程都会增加1帧延迟,因为它需要用于运动估计。 - aergistal
非常感谢您的耐心和详细回答。这真的帮了我很多。 - CurtisGuo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接