H.264/AVC视频编解码技术详解二十四帧间预测编码:解码显示顺序与图像管理
Posted 取次花丛懒回顾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了H.264/AVC视频编解码技术详解二十四帧间预测编码:解码显示顺序与图像管理相关的知识,希望对你有一定的参考价值。
《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看!
“纸上得来终觉浅,绝知此事要躬行”,只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会!
链接地址:H.264/AVC视频编解码技术详解
GitHub代码地址:点击这里
一、基本概念
在H.264的解码过程中,每一帧的数据按照相应的NAL Unit在码流中的顺序传入解码器进行解码。需注意的是,首先传入解码器的视频帧的NAL unit,解码完成后其对应的图像不一定会首先显示。其原因是由于B帧的存在,视频帧在输出时会进行顺序重排。视频帧的NAL Unit数据传入解码器的顺序称之为解码顺序,而解码完成后图像显示或输出的顺序称之为显示顺序。解码顺序和显示顺序的概念如下图所示:
在H.264的码流中,表示解码顺序和显示顺序分别有相应的语法元素表示。其中解码顺序由frame_num表示,而显示顺序由picture_order_count表示。这两个值都会在码流中保存,并在读取slice信息时解析。这两个值在SliceHeader中按下列形式保存:
某一帧图像在解码完成后,可能会被保存于解码图像缓存中,用于后续图像帧间预测的参考帧。在解码图像缓存中,用于P帧或B帧帧间编码的参考帧图像保存为一个或两个参考帧列表,列表中参考帧的顺序可以通过某个专门的过程进行重排列。
二、解码顺序计数值frame_num
在每一个slice的slice_header结构中都可以解析出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数据进行填充。
三、显示顺序标志值picture_order_count
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这两个值的一个或两个组成:
- 对于每一个编码帧,poc包含两个值TopFieldOrderCnt和BottomFieldOrderCnt;
- 对于每一个编码场,poc包含一个值,如果该field为顶场则为TopFieldOrderCnt,如果是底场为BottomFieldOrderCnt;
- 对于每一个互补参考场对,POC包含两个值,对顶场为TopFieldOrderCnt,对底场为BottomFieldOrderCnt;
在H.264中,TopFieldOrderCnt和BottomFieldOrderCnt共定义了3种解析方法,由sps中的值pic_order_cnt_type决定。
3.1 Slice Header中直传模式
当pic_order_cnt_type为0时,POC的值通过slice_header中的数据计算得到。计算方式如下:
3.1.1 计算中间变量prevPicOrderCntMsb和prevPicOrderCntLsb
中间变量prevPicOrderCntMsb和prevPicOrderCntLsb可以认为是当前帧前面一帧的POC数据,其计算方式为:
- 如果当前帧为IDR帧,则prevPicOrderCntMsb和prevPicOrderCntLsb都设为0;
- 如果当前帧为非IDR帧,则prevPicOrderCntMsb和prevPicOrderCntLsb都取自按照解码顺序的前一个参考帧的数据,prevPicOrderCntMsb的值为该帧的PicOrderCntMsb,prevPicOrderCntLsb的值为该帧的pic_order_cnt_lsb。
3.1.2 计算当前帧的PicOrderCntMsb
该值可以认为是当前帧的POC的高位值。在该过程中需要两个从码流中解析出的语法元素值:
- pic_order_cnt_lsb:从slice_header结构中读取;
- MaxPicOrderCntLsb:从sps结构中的log2_max_pic_order_cnt_lsb_minus4计算得到;
获取到了上述数据之后,可依据标准文档中的公式8-3计算PicOrderCntMsb:
if((pic_order_cnt_lsb < prevPicOrderCntLsb)&&((prevPicOrderCntLsb − pic_order_cnt_lsb) >= (MaxPicOrderCntLsb / 2)))
PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb
else if((pic_order_cnt_lsb > prevPicOrderCntLsb) && ((pic_order_cnt_lsb − prevPicOrderCntLsb) > (MaxPicOrderCntLsb / 2)))
PicOrderCntMsb = prevPicOrderCntMsb − MaxPicOrderCntLsb
else
PicOrderCntMsb = prevPicOrderCntMsb
3.1.3 计算TopFieldOrderCnt和BottomFieldOrderCnt
对于一个非底场的slice,TopFieldOrderCnt的计算方式非常简单,即将PicOrderCntMsb与pic_order_cnt_lsb求和即可:
TopFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb
对于一个非顶场的slice,BottomFieldOrderCnt的计算方式根据field_pic_flag值判断。若field_pic_flag为0,即当前slice按帧编码,则BottomFieldOrderCnt的计算方法为:
BottomFieldOrderCnt = TopFieldOrderCnt + delta_pic_order_cnt_bottom
否则,当field_pic_flag为1,即当前slice为一帧的底场时,BottomFieldOrderCnt的计算方法为:
BottomFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb
3.2 预测与差分方式
当pic_order_cnt_type为1时,采用这种模式计算POC的值。计算步骤如下:
3.2.1 计算中间变量prevFrameNum、FrameNumOffset和prevFrameNumOffset
变量prevFrameNum被赋值为按照解码顺序在当前帧之前一帧的frame_num;类似地,变量prevFrameNumOffset被赋值为按照解码顺序在当前帧之前一帧的FrameNumOffset;
FrameNumOffset计算方法根据当前帧是否是IDR,以及prevFrameNum与frame_num的比较关系计算得到:
- 若当前帧为IDR,则FrameNumOffset为0;
- 若当前帧非IDR,且prevFrameNum大于frame_num,则计算方式为:
FrameNumOffset = prevFrameNumOffset + MaxFrameNum
- 若当前帧非IDR,且prevFrameNum小于frame_num,则FrameNumOffset的值即为prevFrameNumOffset;
3.2.2 计算中间变量absFrameNum、picOrderCntCycleCnt、frameNumInPicOrderCntCycle和expectedPicOrderCnt
判断从sps中读取的值num_ref_frames_in_pic_order_cnt_cycle,若该值为非0,则absFrameNum的计算方法为:
absFrameNum = FrameNumOffset + frame_num
否则,absFrameNum的值为0。另外,若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时,采用这种模式计算POC的值。计算步骤如下:
FrameNumOffset和prevFrameNumOffset同模式2中的计算方法类似,FrameNumOffset计算方法根据当前帧是否是IDR,以及prevFrameNum与frame_num的比较关系计算得到:
- 若当前帧为IDR,则FrameNumOffset为0;
- 若当前帧非IDR,且prevFrameNum大于frame_num,则计算方式为:
FrameNumOffset = prevFrameNumOffset + MaxFrameNum
- 若当前帧非IDR,且prevFrameNum小于frame_num,则FrameNumOffset的值即为prevFrameNumOffset;
计算tempPicOrderCnt:
- 若当前帧为IDR,则tempPicOrderCnt值为0;
- 若当前帧为非IDR,且nal_ref_idc为0,则tempPicOrderCnt的计算方法为
tempPicOrderCnt = 2 * ( FrameNumOffset + frame_num ) − 1
- 否则,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的计算方式为:FrameNumWrap = FrameNum - MaxFrameNum
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节中,解码参考帧的标记过程按如下步骤执行:
- 首先,确保当前帧的所有slice解码完成;
- 随后,判断当前帧的帧类型。如果当前帧为一个IDR帧,则进行以下操作:
- 将所有的参考帧标记为“不作为参考”;
- 如果long_term_reference_flag为0,则该IDR帧被标记为“作为短期参考”,且MaxLongTermFrameIdx设为“无长期参考帧索引”;
- 如果long_term_reference_flag为1,则该IDR帧被标记为“作为长期参考”,其LongTermFrameIdx设为0,并且MaxLongTermFrameIdx设为0;
- 如果当前帧为非IDR帧,则执行以下操作:
- 如果adaptive_ref_pic_marking_mode_flag为1,则进行自适应内存控制法标记参考帧;
- 如果adaptive_ref_pic_marking_mode_flag为0,则进行滑动窗口法标记参考帧;
- 如果当前帧为非IDR帧,且没有因为memory_management_control_operation的值等于6而被标记为“用于长期参考”,则该帧被标记为“用于短期参考”。
4.2.1 滑动窗口法
下面首先介绍滑动窗口法:
- 如果当前图像是一个 complementary reference field pair 中按照解码顺序的第二个场,且第一场被标记为“用作短期参考”,那么当前图像和该complementary reference field pair都被标记为“作为短期参考”。
- 否则的话,根据如下步骤执行:
- 设numShortTerm为作为短期参考的编码帧、编码场或互补场对的总数,numLongTerm为长期参考帧/场/场对的总数;
- 当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中会包含一些附加的语法元素信息,如下表所示:
从上图的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时,自适应内存控制过程会将某一个短期参考帧标记为“不作为参考”。具体的执行过程为:
- 首先计算picNumX。计算方法为:
其中,CurrPicNum为当前帧的frame_number,difference_of_pic_nums_minus1从dec_ref_pic_marking中解析得到。picNumX = CurrPicNum − ( difference_of_pic_nums_minus1 + 1 )
- 对于帧编码的图像,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这两个值。执行过程如下:
- 按照4.2.2.1中的方法计算picNumX;
- 如果long_term_frame_idx对应的长期参考帧存在,则该长期参考帧标记为“不作为参考”;
- 对于帧编码图像,由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 )规定的值。
以上是关于H.264/AVC视频编解码技术详解二十四帧间预测编码:解码显示顺序与图像管理的主要内容,如果未能解决你的问题,请参考以下文章
H.264/AVC视频编解码技术详解二十六帧间预测编码:宏块的帧间预测解码
H.264/AVC视频编解码技术详解二十六帧间预测编码:宏块的帧间预测解码
H.264/AVC视频编解码技术详解二十三帧间预测编码:帧间预测编码的基本原理
H.264/AVC视频编解码技术详解二十三帧间预测编码:帧间预测编码的基本原理