在上一篇博文中我们已知,对于每一个P帧和B帧的解码都需要从解码图像缓存DPB中选择某个参考帧。DPB中的参考帧可分为短期参考帧和长期参考帧两种,分别按照PicNum和LongTermFrameIdx索引。通过这两个索引值可以在参考帧列表中获取对应的参考帧图像。
解码不同的帧类型时,参考帧列表不同。当解码一个P或SP帧时,使用一个参考帧列表RefPicList0;当解码一个B帧时,使用两个参考帧列表RefPicList0和RefPicList1。执行过程如下:
两个标识位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的参数来计算参考帧列表中图像数目的上限,该参数即为:
以P帧为例,设参数num_ref_idx_l0_active_minus1 + 1为门限值threshold。当初始化过程完成后,如果参考帧列表RefPicList0中的总帧数超过了threshold值,则多余的值将被丢弃;如果参考帧列表RefPicList0中的总帧数小于threshold值,则不足的部分被认为是“无参考图像”。对于B帧和RefPicList1,判断方法类似。
在初始化P/SP帧或B帧的参考帧列表过程中,DPB中至少要存在一个有效的、即被标记为“用于短期或长期参考”的参考帧。
在P帧的参考帧列表RefPicList0中,短期参考帧排列在长期参考帧的前面,即短期参考帧的索引值均小于长期参考帧的索引。
排列短期参考帧在排列时按照PicNum的顺序降序排列,即从PicNum最高的帧开始,一直到PicNum最低的帧为止。而在排列长期参考帧时的顺序与短期参考帧相反,是按照LongTermPicNum升序排列,即从LongTermPicNum最低的帧开始,一直到LongTermPicNum最高的帧为止。
举例如下,假设DPB最大容量为8,其中包含了5个短期参考帧和3个长期参考帧,那么P帧解码时的参考帧列表可用下图表示:
初始化B帧参考帧列表的过程与P/SP稍有不同,主要体现在参考帧的排列顺序上。在两个参考帧列表RefPicList0和RefPicList1中,短期参考帧的顺序按照显示顺序,即POC进行排列。在排列短期参考帧时,会将当前帧的POC与DPB中参考帧的POC进行比较,然后根据结果进行以下操作:
对参考帧列表refPicList0:
对参考帧列表refPicList1:
类似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作为输入参数,执行完成后的结果也返回给refIdxL0。
修改短期参考帧主要步骤如下:
picNumLXPred可以认为是下一步骤中要计算的变量picNumLXNoWrap的预测值。当slice_header中出现第一个modification_of_pic_nums_idc值时,picNumLXPred设置为CurrPicNum,即当前帧的frame_num;随后,每当计算得到一个picNumLXNoWrap后,这一个picNumLXNoWrap值都会赋值给picNumLXPred。
计算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 )
picNumLX的值通过picNumLXNoWrap与当前frame_num的值比较后计算得到,具体计算方式如下:
if( picNumLXNoWrap > CurrPicNum )
picNumLX = picNumLXNoWrap − MaxPicNum
else
picNumLX = picNumLXNoWrap
该步骤中得到的picNumLX应等于参考帧列表中的某一个短期参考帧的PicNum值。
在计算得到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 ]
修改长期参考帧的方法相对简单。在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 ]