音视频学习 --X264 码率控制 -- 前瞻
本文主要讲解 Lookahead 的功能,为了和之前的文章标题保持一致,暂时翻译为“前瞻”,虽然表达不准确,但是内容绝对靠谱。
1 Lookahead 介绍
lookahead 是一个非常经典而且复杂的模块,其主旨是在估计编码器模块尚未完成分析的视频帧的编码消耗或者编码成本。编解码使用这些 lookahead 的估计结果来做各种有效决策,例如:自适应B帧的选取和放置、显示加权预测,以及最重要的 VBV 模块中缓冲区约束码率控制的码率分配。由于 x264 是软编软解,考虑到性能方面的原因,lookahead 基本上仅仅计算 SATD 残差,而不是实际真正的量化或者重建,并且运行在视频帧的半分辨率版本上。
依据之前的码率控制的分析,Lookahead 工作是在 x264_encoder_open-->计算 lookahead 延迟-->构建 lookahead 线程池-->调用 x264_lookahead_init。
(1)在 OPEN 操作中,还构建了 Lookahead 的线程池,依据 i_lookahead_threads 参数完成相关线程构建,为之后的 Lookahead 操作做准备。
(2)x264_lookahead_init 主要完成以下工作内容:
其中 x264_lookahead_t 结构体如下:
完成 lookahead 线程的构建,lookahead_thread 是 lookahead 工作的入口函数,所有动作都是在该函数中完成的。
部分参数解释:i_sync_lookahead 标识:线程化帧分析缓存;ifbuf 队列表示:输入的视频帧队列;如果 i_sync_lookahead 为零,表示没有运行 lookahead thread,那所有数据可以放置在 next 队列中完成之后的操作,否则存放在 ifbuf 中;next 队列表示:需要进行 Lookahead 操作的视频帧候选队列;其值一般不小于一个 GOP 的大小。因为只有一个完整的 GOP 还可以进行 I 帧、P 帧、B 帧(如果存在)的排序和估计,否则都是 P 帧或者 B 帧的队列无法正常解码,预估比特消耗也就无意义了。
特别注意:在 init 函数中有可以看出如果 i_sync_lookahead 为零,则不在构建 lookahead 线程了。
2 代码分析
2.1 x264_lookahead_put_frame
x264_encoder_encode->x264_lookahead_put_frame 将视频帧放入 Lookahead x264_sync_frame_list_t 队列中,以便决定其 slice 类型。该过程中会依据 i_sync_lookahead 类型进行分别存放,如果 i_sync_lookahead 非零,则将视频帧存放在 ifbuf 队列中,否则存放在 next 队列中。next 队列就是之后要进行判别类型的视频帧队列。
2.2 lookahead_slicetype_decide
slicetype_frame_cost 被重复调用以计算给定 p0、p1 和 b 值的帧 SATDs。 p0 是要分析的帧的 list-0(过去)参考帧。 p1 是要分析的帧的 list-1(未来)参考帧。 b 是要分析的帧。 如果 p1 等于 b,则该帧被推断为 P 帧。 如果 p0 等于 b,则该帧被推断为 I 帧。 由于 x264_slicetype_frame_cost 可能会作为使用它的算法的一部分在相同的参数上重复调用,因此每次调用的结果都会被缓存以备将来使用。
2.3 x264_slicetype_decide
x264_slicetype_decide 的主要工作内容:
(1)依据 next 的大小完成 next 队列中视频帧的预处理,依次获取该帧的 duration 等值;
(2)针对不同场景完成帧类型的估计,包括(a)存在 stat 时,调用 x264_ratecontrol_slice_type 进行类型判断;以及(b)满足其他四个条件时调用 x264_slicetype_analyse 完成类型判断;
(3)完成 next 中所有视频帧的类型判断,依据不同条件设置 B 帧、参考 B 帧、P 帧、I 帧、IDR 帧等类型判断。
(4)依据 P 帧选中,进行一些特定处理;
(5)完成播放顺序到编码顺序的排序;该排序规则和视频帧序列中 DTS 和 PTS 的关系类似。
(6)完成视频帧的 duration 的重新计算和更新。
2.4 x264_slicetype_analyse
x264_slicetype_analyse 在 Lookahead 过程中被反复多次调用,就是因为该函数完成 slice 中类型的计算和分析,以便能够确定视频帧的类型。该函数位于 encoder\slicetype.c 文件中
其主要工作步骤:
(1)视频帧预处理;
(2)依次判断是哦服进行场景切换操作;
(3)将关键帧依据 OPENGop 的规则替换为 I 帧或者 IDR 帧
(4)IDR 前的所有 auto 帧或者 B 帧设置为 P 帧;
(5)如果 B 帧标志位置位,则依据 B 帧的自适应方法进行处理;
(6)如果 B 帧标志为未置位,则将所有的 AUTO 帧,或者 B 帧都设置为 P 帧;
(7)如果 MB-Tree 置位,则进行 MB-Tree 处理;
(8)检查关键帧条件,并更正视频帧类型;
(9)如果 VBV 置位,则进行 VBV lookahead 处理。
该函数被调用后,基本上可以确定视频帧的类型。
2.5 slicetype_frame_cost
slicetype_frame_cost 的主要作用就是计算视频帧 B 的 cost,并该估计值;
将帧 b 以 slice 为单位计算其开销,统计其总开销其中 p0 表示 b 的前向参考帧,p1 表示 b 的后向参考帧若 p0 = p1 = b,则表示没有参考帧,即 I 帧若 p1 = b,则表示只有前向参考帧,即 P 帧
作为 I 帧,所有宏块的 cost = intra cost 作为 P 帧,所有宏块的 cost = min( intra cost, inter cost)作为 B 帧,所有宏块的 cost = inter cost
其中每一个帧都带有开销矩阵 i_cost_est[b-p0][p1-b]表示帧 b 以 p0 为前向参考,p1 为后向参考时的帧 cost
2.6 slicetype_slice_cost
slicetype_slice_cost 将 slice 以 mb 为单位统计所有宏块 cost 并返回统计 cost 在函数中备注说明:在主编码预测中由于 MVs 会发挥比较重要的作用,所以这里将要进行低分辨率 lookahead 功能回溯,回溯可以显著提升 mv 的预测精度,对于编码估计和码控起到明显作用。同时说明:由于边缘宏块的特殊性,会降低当前视频帧的预测估计质量,正是由于边缘宏块空间分布的特殊性,如果没有边缘宏块其他宏块估计也就无从谈起了。所以 cost 中会充分考虑是否包含边缘宏块,以便更好统计 cost。
(1)判断是否进行边缘宏块计算;(2)计算 slice 的起始行和结束行位置;(3)计算 slice 的起始列和结束列位置;(4)逐宏行、逐列统计每一个宏块的 cost
2.7 slicetype_mb_cost
slicetype_mb_cost 主要作用就是统计当前 mb 的 cost。slicetype_mb_cost 对每个参考帧(P 帧的过去,B 帧的过去和未来)执行运动搜索。 此运动搜索通常是具有子像素细化的六边形运动搜索。MB 的计算
2.8 slicetype_path_cost
slicetype_frame_cost 计算一个路径的 cost 总和,若超过了阈值则立马返回根据 path 中每一帧的帧类型,计算其帧 cost,累加返回总体上一段一段的计算,即以 B 帧开始到下一个非 B 帧为一段
若允许 BREF,则将这一段最中间的 B 帧按照 BREF 来计算其 cost
即 return = SUM{ BBB...BBB(P/I), BBB...BBB(P/I), ... ,BBB...BBB(P/I) }
3 总结
Lookahead 部分总结已将完成,这部分要结合之前的几篇文章一起理解效果更佳;
我是爱跑步的程序员,欢迎点赞、评论、关注、转发;随时探讨,持续输出。
版权声明: 本文为 InfoQ 作者【Fenngton】的原创文章。
原文链接:【http://xie.infoq.cn/article/9c3dd2bee5d9bb3e1b46c209d】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论