《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看!
“纸上得来终觉浅,绝知此事要躬行”,只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会!
链接地址:H.264/AVC视频编解码技术详解
GitHub代码地址:点击这里
一、参考帧列表
在上一篇博文中我们已知,对于每一个P帧和B帧的解码都需要从解码图像缓存DPB中选择某个参考帧。DPB中的参考帧可分为短期参考帧和长期参考帧两种,分别按照PicNum和LongTermFrameIdx索引。通过这两个索引值可以在参考帧列表中获取对应的参考帧图像。
解码不同的帧类型时,参考帧列表不同。当解码一个P或SP帧时,使用一个参考帧列表RefPicList0;当解码一个B帧时,使用两个参考帧列表RefPicList0和RefPicList1。执行过程如下:
- 对参考帧列表进行初始化;
- 若ref_pic_list_modification_flag_l0或者ref_pic_list_modification_flag_l1(对B帧)的值为1,参考帧列表RefPicList0和RefPicList1(对B帧)会进行修改操作;
两个标识位ref_pic_list_modification_flag_l0和ref_pic_list_modification_flag_l1保存在slice_header中的ref_pic_list_modification结构中,该结构的定义如下:
二、参考帧列表的初始化
当解码某个P/SP帧或B帧时,进行参考帧列表的初始化操作,该过程定义在标准文档的8.2.4.2节。在参考帧列表初始化后,还需要一项附加操作,即根据图像参数集PPS的参数来计算参考帧列表中图像数目的上限,该参数即为:
- num_ref_idx_l0_active_minus1;
- num_ref_idx_l1_active_minus1;
以P帧为例,设参数num_ref_idx_l0_active_minus1 + 1为门限值threshold。当初始化过程完成后,如果参考帧列表RefPicList0中的总帧数超过了threshold值,则多余的值将被丢弃;如果参考帧列表RefPicList0中的总帧数小于threshold值,则不足的部分被认为是“无参考图像”。对于B帧和RefPicList1,判断方法类似。
在初始化P/SP帧或B帧的参考帧列表过程中,DPB中至少要存在一个有效的、即被标记为“用于短期或长期参考”的参考帧。
2.1 解码P/SP帧的参考帧列表初始化过程
在P帧的参考帧列表RefPicList0中,短期参考帧排列在长期参考帧的前面,即短期参考帧的索引值均小于长期参考帧的索引。
排列短期参考帧在排列时按照PicNum的顺序降序排列,即从PicNum最高的帧开始,一直到PicNum最低的帧为止。而在排列长期参考帧时的顺序与短期参考帧相反,是按照LongTermPicNum升序排列,即从LongTermPicNum最低的帧开始,一直到LongTermPicNum最高的帧为止。
举例如下,假设DPB最大容量为8,其中包含了5个短期参考帧和3个长期参考帧,那么P帧解码时的参考帧列表可用下图表示:
2.2 解码B帧的参考帧列表初始化过程
初始化B帧参考帧列表的过程与P/SP稍有不同,主要体现在参考帧的排列顺序上。在两个参考帧列表RefPicList0和RefPicList1中,短期参考帧的顺序按照显示顺序,即POC进行排列。在排列短期参考帧时,会将当前帧的POC与DPB中参考帧的POC进行比较,然后根据结果进行以下操作:
对参考帧列表refPicList0:
- 如果DPB中短期参考帧的POC小于当前帧的DPB,则短期参考帧按照POC的降序排列在参考帧列表refPicList0的前部,其余短期参考帧按照POC的升序紧随其后排列;
- DPB中的长期参考帧按照LongTermPicNum递增的顺序在短期参考帧之后排列;
对参考帧列表refPicList1:
- 如果DPB中短期参考帧的POC大于当前帧的DPB,则短期参考帧按照POC的升序排列在参考帧列表refPicList1的前部,其余短期参考帧按照POC的降序紧随其后排列;
- DPB中的长期参考帧按照LongTermPicNum递增的顺序在短期参考帧之后排列;
- 若refPicList1包含多于1个参考帧,且refPicList1与refPicList0等同时,refPicList1中前两个参考帧refPicList1[0]和refPicList1[1]将进行交换。
类似P帧的情况,解码B帧时的参考帧列表可用下图表示:
三、参考帧列表修改过程
在slice_header结构中保存了ref_pic_list_modification结构,保存了修改参考帧过程的部分数据。该结构的定义可见第一节插图。
其中,ref_pic_list_modification_flag_l0为1时,对参考帧列表RefPicList0进行修改,ref_pic_list_modification_flag_l1为1时,对参考帧列表RefPicList1进行修改。本节中以P帧解码时修改参考帧列表RefPicList0为例讨论其执行过程。
- 首先设定一个值refIdxL0表示参考帧列表中参考帧的索引值,并初始化为0;
- 读取码流中的modification_of_pic_nums_idc值,并根据其取值进行计算:
- 若modification_of_pic_nums_idc为0或1,执行短期参考帧的修改过程;
- 若modification_of_pic_nums_idc为2,执行长期参考帧的修改过程;
- 若modification_of_pic_nums_idc为3,参考帧列表的修改过程完成;
参考帧列表修改过程以refIdxL0作为输入参数,执行完成后的结果也返回给refIdxL0。
3.1 短期参考帧的修改
修改短期参考帧主要步骤如下:
3.1.1 计算picNumLXPred
picNumLXPred可以认为是下一步骤中要计算的变量picNumLXNoWrap的预测值。当slice_header中出现第一个modification_of_pic_nums_idc值时,picNumLXPred设置为CurrPicNum,即当前帧的frame_num;随后,每当计算得到一个picNumLXNoWrap后,这一个picNumLXNoWrap值都会赋值给picNumLXPred。
3.1.2 计算picNumLXNoWrap
计算picNumLXNoWrap的方法根据modification_of_pic_nums_idc的取值不同而不同。
当modification_of_pic_nums_idc取值为0时,其含义为码流中读出的abs_diff_pic_num_minus1为picNumLXNoWrap为相对于picNumLXPred的负增量,即需要从picNumLXPred中减去该值。计算方法如下:
if( picNumLXPred − ( abs_diff_pic_num_minus1 + 1 ) < 0 )
picNumLXNoWrap = picNumLXPred − ( abs_diff_pic_num_minus1 + 1 ) + MaxPicNum
else
picNumLXNoWrap = picNumLXPred − ( abs_diff_pic_num_minus1 + 1 )
当modification_of_pic_nums_idc取值为0时,其含义为码流中读出的abs_diff_pic_num_minus1为picNumLXNoWrap为相对于picNumLXPred的正增量,即需要从picNumLXPred中加上该值。计算方法如下:
if( picNumLXPred + ( abs_diff_pic_num_minus1 + 1 ) >= MaxPicNum )
picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1 + 1 ) − MaxPicNum
else
picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1 + 1 )
3.1.3 计算picNumLX
picNumLX的值通过picNumLXNoWrap与当前frame_num的值比较后计算得到,具体计算方式如下:
if( picNumLXNoWrap > CurrPicNum )
picNumLX = picNumLXNoWrap − MaxPicNum
else
picNumLX = picNumLXNoWrap
该步骤中得到的picNumLX应等于参考帧列表中的某一个短期参考帧的PicNum值。
3.2.4 修改参考帧列表
在计算得到picNumLX后,配合传入的的索引值refIdxLX,接着进行参考帧列表的修改。其方法为将picNumLX对应的短期参考帧置于refIdxLX位置,并且清除掉列表中PicNum等于picNumLX的参考帧。具体计算方法如下:
for( cIdx = num_ref_idx_lX_active_minus1 + 1; cIdx > refIdxLX; cIdx− − )
RefPicListX[ cIdx ] = RefPicListX[ cIdx − 1]
RefPicListX[ refIdxLX++ ] = short-term reference picture with PicNum equal to picNumLX
nIdx = refIdxLX
for( cIdx = refIdxLX; cIdx <= num_ref_idx_lX_active_minus1 + 1; cIdx++ )
if( PicNumF( RefPicListX[ cIdx ] ) != picNumLX )
RefPicListX[ nIdx++ ] = RefPicListX[ cIdx ]
3.2 长期参考帧的修改
修改长期参考帧的方法相对简单。在ref_pic_list_modification结构中的long_term_pic_num即表示待操作的长期参考帧索引。修改的方式类似短期参考帧的修改。具体计算方法如下:
for( cIdx = num_ref_idx_lX_active_minus1 + 1; cIdx > refIdxLX; cIdx− − )
RefPicListX[ cIdx ] = RefPicListX[ cIdx − 1]
RefPicListX[ refIdxLX++ ] = long-term reference picture with LongTermPicNum equal to long_term_pic_num
nIdx = refIdxLX
for( cIdx = refIdxLX; cIdx <= num_ref_idx_lX_active_minus1 + 1; cIdx++ )
if( LongTermPicNumF( RefPicListX[ cIdx ] ) != long_term_pic_num )
RefPicListX[ nIdx++ ] = RefPicListX[ cIdx ]