欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

H264---帧间编码---帧间预测编码(2):解码、显示顺序与图像管理

程序员文章站 2022-07-06 22:54:41
...

H.264/AVC视频编解码技术详解
GitHub代码地

一、基本概念

nalu按照解码顺序frame_num送入解码器解码
解码后的图像按照播放顺序POC(picture_order_count)进行存储和播放
(由于B帧的存在,解码顺序和显示顺序会有差异)

H264---帧间编码---帧间预测编码(2):解码、显示顺序与图像管理
frame_num和POC通过slice_header传输:
H264---帧间编码---帧间预测编码(2):解码、显示顺序与图像管理

某一帧图像在解码完成后,可能会被保存于解码图像缓存中,用于后续图像帧间预测的参考帧。在解码图像缓存中,用于P帧或B帧帧间编码的参考帧图像保存为一个或两个参考帧列表,列表中参考帧的顺序可以通过某个专门的过程进行重排列。

二、解码顺序计数值frame_num(当前slice在GOP中的解码顺序)

在一个GOP中,第一个slice即作为随机接入点的IDR slice,其frame_num值为0,表示当前slice是一个GOP的起点。
GOP中的其他slice按照相应距离IDR的顺序按1递增。
当另一个语法元素gaps_in_frame_num_value_allowed存在时,slice可以以大于1的值递增,此时缺失的frame_num值需要解码器用空slice数据进行填充。

三、显示顺序标志值POC(picture_order_count)

视频中IDR的第一个field作为POC的开始,其值为0。在H.264的标准中,POC的计算方法在标准文档中的8.2.1节中定义。

对于H.264的码流,有三种结构会被赋予POC的值:coded frame(编码帧), coded field(编码场)和complementary field pair(互补参考场对),每种类型的POC都由TopFieldOrderCnt和BottomFieldOrderCnt这两个值的一个或两个组成:
 1、对于每一个编码帧,poc包含两个值TopFieldOrderCnt和BottomFieldOrderCnt;
 2、对于每一个编码场,poc包含一个值,如果该field为顶场则为TopFieldOrderCnt,如果是底场为BottomFieldOrderCnt;
 3、对于每一个互补参考场对,POC包含两个值,对顶场为TopFieldOrderCnt,对底场为BottomFieldOrderCnt;
在H.264中,TopFieldOrderCnt和BottomFieldOrderCnt共定义了3种解析方法,由sps中的值pic_order_cnt_type决定。

3.1 直传模式:pic_order_cnt_type=0

POC的值通过slice_header中的数据计算得到

1、获得中间变量prevPicOrderCntMsb和prevPicOrderCntLsb
2、计算当前帧的PicOrderCntMsb
3、计算TopFieldOrderCnt和BottomFieldOrderCnt
H264---帧间编码---帧间预测编码(2):解码、显示顺序与图像管理

3.2 预测与差分方式(pic_order_cnt_type=1)

sps中:
H264---帧间编码---帧间预测编码(2):解码、显示顺序与图像管理

1、获得中间变量prevFrameNum、FrameNumOffset、prevFrameNumOffset
2、计算中间变量absFrameNum、picOrderCntCycleCnt、frameNumInPicOrderCntCycle和expectedPicOrderCnt

if (sps中)num_ref_frames_in_pic_order_cnt_cycle != 0
	absFrameNum = FrameNumOffset + frame_num
else
	absFrameNum = 0
end

另外,若nal_ref_idc值为0且absFrameNum非0,absFrameNum需再减去1:

absFrameNum = absFrameNum − 1

当absFrameNum大于0时,picOrderCntCycleCnt和frameNumInPicOrderCntCycle分别为absFrameNum - 1除以num_ref_frames_in_pic_order_cnt_cycle的商和余数:

picOrderCntCycleCnt = ( absFrameNum − 1 ) / num_ref_frames_in_pic_order_cnt_cycle
frameNumInPicOrderCntCycle = ( absFrameNum − 1 ) % num_ref_frames_in_pic_order_cnt_cycle

下一步根据absFrameNum的计算expectedPicOrderCnt的值。若absFrameNum的值为0,则expectedPicOrderCnt的值亦为0;否则该值由picOrderCntCycleCnt、ExpectedDeltaPerPicOrderCntCycle和offset_for_ref_frame共同计算得到:

if( absFrameNum > 0 ){
	expectedPicOrderCnt = picOrderCntCycleCnt * ExpectedDeltaPerPicOrderCntCycle;
	for( i = 0; i <= frameNumInPicOrderCntCycle; i++ )
		expectedPicOrderCnt = expectedPicOrderCnt + offset_for_ref_frame[ i ];
} else
	expectedPicOrderCnt = 0

如果nal_ref_idc的值为0,expectedPicOrderCnt再增加offset_for_non_ref_pic:

expectedPicOrderCnt = expectedPicOrderCnt + offset_for_non_ref_pic;

3.2.3 计算TopFieldOrderCnt和BottomFieldOrderCnt

对于帧编码的slice,TopFieldOrderCnt和BottomFieldOrderCnt通过上面计算得到的expectedPicOrderCnt,以及sps中读取的语法元素delta_pic_order_cnt和offset_for_top_to_bottom_field计算得到:

TopFieldOrderCnt = expectedPicOrderCnt + delta_pic_order_cnt[0];
BottomFieldOrderCnt = TopFieldOrderCnt +offset_for_top_to_bottom_field + delta_pic_order_cnt[1];

3.3 显示顺序与解码顺序一致(pic_order_cnt_type=2)

FrameNumOffset和prevFrameNumOffset同模式2中的计算方法类似,FrameNumOffset计算方法根据当前帧是否是IDR,以及prevFrameNum与frame_num的比较关系计算得到:

1、若当前帧为IDR,则FrameNumOffset为0;
2、若当前帧非IDR,且prevFrameNum大于frame_num,则计算方式为:

FrameNumOffset = prevFrameNumOffset + MaxFrameNum

3、若当前帧非IDR,且prevFrameNum小于frame_num,则FrameNumOffset的值即为prevFrameNumOffset;

计算tempPicOrderCnt:
1、若当前帧为IDR,则tempPicOrderCnt值为0;
2、若当前帧为非IDR,且nal_ref_idc为0,则tempPicOrderCnt的计算方法为

tempPicOrderCnt = 2 * ( FrameNumOffset + frame_num )1

3、否则,tempPicOrderCnt的计算方法为:

tempPicOrderCnt = 2 * ( FrameNumOffset + frame_num )

最后,在帧编码的条件下,TopFieldOrderCnt和BottomFieldOrderCnt的值都与tempPicOrderCnt相等:

TopFieldOrderCnt = tempPicOrderCnt
BottomFieldOrderCnt = tempPicOrderCnt

四、图像管理

当H.264作为参考帧的某一帧解码完成后,该帧的数据将会保存在解码图像缓存区中,并且按照相应的规则标记为短期或长期参考帧。其中,短期参考帧由上文中提到的frame_num标记,长期参考帧由另一个值LongTermPicNum标记。

每一个P帧的解码对应一个参考帧列表RefPicList0,每一个B帧对应两个独立的参考帧列表RefPicList0和RefPicList1。

4.1 计算图像序号

参考帧的索引值用于从参考帧列表中获取数据。在参考帧列表的初始化、更新,参考帧的标记,以及处理非连续的frame_num时,需要计算参考帧的图像序号,其中主要有FrameNum, FrameNumWrap, PicNum, LongTermFrameIdx 和 LongTermPicNum等。

对于一个短期参考帧,计算FrameNum和FrameNumWrap。当前帧的FrameNum和FrameNumWrap计算方法为:

首先设FrameNum的值为对应的短期参考帧的frame_num的值;
如果FrameNum的值大于当前帧slice_header中解析出的frame_num值,则FrameNumWrap的计算方式为:

FrameNumWrap = FrameNum - MaxFrameNum

否则,FrameNumWrap的计算方式为:

FrameNumWrap = FrameNum

对于一个长期参考帧,计算其LongTermFrameIdx的值。该过程在下节中详细讨论。

最后,对于每一个短期参考帧图像,计算PicNum值,对于一个长期参考帧图像,计算LongTermPicNum。如果当前帧为帧编码,即field_pic_flag为0,则二者的值分别与FrameNumWrap和LongTermPicNum相等:

PicNum = FrameNumWrap
LongTermPicNum = LongTermFrameIdx

在JM8.6代码中的体现如下:

if (currPicStructure == FRAME)  
  {
    for (i=0; i<dpb.ref_frames_in_buffer; i++)
    {
      if (dpb.fs_ref[i]->is_used==3)
      {
        if ((dpb.fs_ref[i]->frame->used_for_reference)&&(!dpb.fs_ref[i]->frame->is_long_term))
        {
          if( dpb.fs_ref[i]->frame_num > img->frame_num )
          {
            dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num - MaxFrameNum;
          }
          else
          {
            dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num;
          }
          dpb.fs_ref[i]->frame->pic_num = dpb.fs_ref[i]->frame_num_wrap;
          dpb.fs_ref[i]->frame->order_num=list0idx;
        }
      }
    }
  }

4.2 解码参考帧的标记

当NALU中的nal_ref_idc值非0,即当前NALU所代表的图像会被作为参考帧的时候,会执行参考帧的标记过程。执行该过程的主要原因可以理解为,由于当前帧会作为参考帧数据放入DPB中,当前DPB中已有的参考帧的性质可能会发生变化,即短期参考帧可能会变为长期参考帧,或者某个参考帧可能会被标记为不再用做参考,因此需要对DPB中的参考帧数据进行重新标记。

如同其命名所表示的含义一样,一个被标记为“用于短期参考”或“用于长期参考”的视频帧在解码过程中可以作为后续帧的参考数据,直到该参考帧被标记为“不再用作参考”为止。将一个参考帧标记为“不再用作参考”的方法通常有两种:

  • 滑动窗口法:通过一种“先进先出”机制进行的方法;
  • 自适应内存控制法;

在标准文档8.2.5.1节中,解码参考帧的标记过程按如下步骤执行:

  1. 首先,确保当前帧的所有slice解码完成;
  2. 随后,判断当前帧的帧类型。如果当前帧为一个IDR帧,则进行以下操作:
    • 将所有的参考帧标记为“不作为参考”;
    • 如果long_term_reference_flag为0,则该IDR帧被标记为“作为短期参考”,且MaxLongTermFrameIdx设为“无长期参考帧索引”;
    • 如果long_term_reference_flag为1,则该IDR帧被标记为“作为长期参考”,其LongTermFrameIdx设为0,并且MaxLongTermFrameIdx设为0;
  3. 如果当前帧为非IDR帧,则执行以下操作:
    • 如果adaptive_ref_pic_marking_mode_flag为1,则进行自适应内存控制法标记参考帧;
    • 如果adaptive_ref_pic_marking_mode_flag为0,则进行滑动窗口法标记参考帧;
  4. 如果当前帧为非IDR帧,且没有因为memory_management_control_operation的值等于6而被标记为“用于长期参考”,则该帧被标记为“用于短期参考”。

4.2.1 滑动窗口法

下面首先介绍滑动窗口法:

  1. 如果当前图像是一个 complementary reference field pair 中按照解码顺序的第二个场,且第一场被标记为“用作短期参考”,那么当前图像和该complementary reference field pair都被标记为“作为短期参考”。
  2. 否则的话,根据如下步骤执行:
    1. 设numShortTerm为作为短期参考的编码帧、编码场或互补场对的总数,numLongTerm为长期参考帧/场/场对的总数;
    2. 当numShortTerm与numLongTerm之和达到Max(max_num_ref_frames, 1)时,在满足numShortTerm大于0的前提下,FrameNumWrap值最小的那个作为短期参考的编码帧、编码场或互补场对将被标记为“不作为参考”。

4.2.2 自适应内存控制法

从上一小节中可以看出,滑动窗口法的效果主要在于将过期的短期参考帧从DPB中移除出去,并不涉及到对长期参考帧的操作(除非遇到IDR时将DPB全部清空)。而自适应内存控制法的操作流程比滑动窗口法要复杂得多。

执行自适应内存控制法标记参考帧的条件是adaptive_ref_pic_marking_mode_flag为1,此时slice_header中会包含一些附加的语法元素信息,如下表所示:

H264---帧间编码---帧间预测编码(2):解码、显示顺序与图像管理

从上图的dec_ref_pic_marking结构中可以看出,如果adaptive_ref_pic_marking_mode_flag的值为1,那么其中将会多出若干个值:

  • memory_management_control_operation;
  • difference_of_pic_nums_minus1;
  • long_term_pic_num;
  • long_term_frame_idx;
  • max_long_term_frame_idx_plus1

其中,memory_management_control_operation可以取的范围为1~6,分别代表了不同的操作。

4.2.2.1 将短期参考帧标记为“不作为参考”

当memory_management_control_operation为1时,自适应内存控制过程会将某一个短期参考帧标记为“不作为参考”。具体的执行过程为:

    1、首先计算picNumX。计算方法为:
picNumX = CurrPicNum − ( difference_of_pic_nums_minus1 + 1 )

其中,CurrPicNum为当前帧的frame_number,difference_of_pic_nums_minus1从dec_ref_pic_marking中解析得到。
2、对于帧编码的图像,PicNum等于picNumX的短期参考帧会被标记为“不作为参考”;

4.2.2.2 将长期参考帧标记为“不作为参考”

当memory_management_control_operation为2时,自适应内存控制过程会将某一个长期参考帧标记为“不作为参考”。具体的执行过程很简单,索引为LongTermPicNum等同于long_term_pic_num的长期参考帧将被标记为不作为参考。

4.2.2.3 将短期参考帧标记为“长期参考帧”

当memory_management_control_operation为3时,自适应内存控制过程会将某一个短期参考帧标记为“作为长期参考帧”。在这种情况下,码流中会同时包含difference_of_pic_nums_minus1以及long_term_frame_idx这两个值。执行过程如下:

  1. 按照4.2.2.1中的方法计算picNumX;
  2. 如果long_term_frame_idx对应的长期参考帧存在,则该长期参考帧标记为“不作为参考”;
  3. 对于帧编码图像,由picNumX所代表的短期参考帧,将被标记为长期参考帧,并将对应的LongTermFrameIdx设为long_term_frame_idx。

4.2.2.4 计算MaxLongTermFrameIdx

当memory_management_control_operation为4时,执行计算MaxLongTermFrameIdx的操作。计算过程如下:

  • 如果码流中解析出的max_long_term_frame_idx_plus1的值为0,则MaxLongTermFrameIdx被设置为“无长期参考帧索引”;
  • 否则,MaxLongTermFrameIdx的值设置为max_long_term_frame_idx_plus1-1。

所有被标记为“用作长期参考”且LongTermFrameIdx大于了MaxLongTermFrameIdx的图像都会被标记为“不作为参考”。

4.2.2.5 清空参考帧列表

当memory_management_control_operation为5时,执行清空参考帧列表操作。该过程会将所有参考帧标记为“不作为参考”并将MaxLongTermFrameIdx设置为“无长期参考帧索引”。

4.2.2.6 将当前帧标记为长期参考帧

当memory_management_control_operation为6时,将当前帧标记为长期参考帧。在这种情况下,需从码流中解析出long_term_frame_idx。执行过程如下:

  • 如果long_term_frame_idx对应的长期参考帧存在,则该长期参考帧标记为“不作为参考”;
  • 将当前帧标记为“作为长期参考”,并将其LongTermFrameIdx设置为long_term_frame_idx;

在当前帧标记完成后,所有被标记为“作为参考帧”的帧、场和互补场对的数量综合不能超过Max( max_num_ref_frames, 1 )规定的值。

相关标签: H264