三、slice头相关的一些细节
1.关于POC的计算
图像序列号(POC)主要用于标识图象的播放顺序,同时还用于在对帧间预测片解码时,标记参考图像的初始图像序号。 对于每个编码帧有两个图像序列号,分别称为顶场序列号(TopFieldOrderCnt)和底场序列号(BottomFieldOrderCnt );对于每个编码场有一个图像序列号,对于一个编码顶场其称为TopFieldOrderCnt,对于编码底场,其称为 BottomFieldOrderCnt ;对于每个编码场对有两个图像序列号,TopFieldOrderCnt 和BottomFieldOrderCnt 分别用于标记该场对的顶场和底场。 opFieldOrderCnt 和BottomFieldOrderCnt 分别指明了相应的顶场/ 底场相对于前一个IDR 图像,或解码顺序中前一个包含memory_management_control_operation=5的参考图像(此值为5表示清空参考帧队列,因 此有着跟IDR同样的效果),的第一个输出场的相对位置。这也意味着,只要遇到IDR图像或 memory_management_control_operation=5的参考图像,相应的POC就等于零。 在H.264中,由于B帧可以进行双向预测,因此图像的解码顺序可以不同于播放顺序。 前面已经提到,计算POC(也就是计算TopFieldOrderCnt和BottomFieldOrderCnt),有三种方法,具体使用哪种由序列参数集中的pic_order_cnt_type元素指定。下面一次介绍三种方法。 (1) pic_order_cnt_type=0 本过程的输入是在本节规定的解码顺序中前一图像的PicOrderCntMsb,也即prevOrderCntMsb 。 关于Msb:POC由高位Msb和低位Lsb两部分组成,当Lsb发生溢出时,会向Msb进位,Msb+Lsb=POC。 本过程的输出是TopFieldOrderCnt 和BottomFieldOrderCnt ,或者其中之一。
大致流程:首先计算变量 prevPicOrderCntMsb,然后计算当前图像的PicOrderCntMsb (刚刚提到POC=Msb+Lsb,Lsb已经在码流的slice_header中由pic_order_cnt_lsb指定,因此主要任务其实就是计算 Msb),最后计算当前图像的TopFieldOrderCnt 和(或) BottomFieldOrderCnt。
流程图:
其中,MaxPicOrderCntLsb由序列参数集中的log2_max_pic_order_cnt_lsb_minus4元素确定;delta_pic_order_cnt_bottom在片头中指定,上文介绍POC时已介绍过。 图中所提到的“乱序”,指的是,解码顺序是否与播放顺序不一致,准确的说,如果当前图像的播放顺序POC比上一个解码图像的播放顺序prePOC小,就是 发生了乱序。那如何判断是否发生乱序了呢?一般情况下,Msb在解码顺序相邻的两个图片间不发生变化时(即Lsb不发生溢出或借位),只要判断当前解码的 Lsb是否比之前解码的Lsb小即可,如果比之前的小,说明乱序发生;但是如果Lsb发生溢出或借位时,就不能这样简单的判断了(前后两帧的Msb将不再 相同),那如何判断Lsb是否发生了溢出或借位呢?这就要用到一个规则:解码顺序相邻的两个图像,他们的播放顺序POC之差(的绝对值)不会超过MaxPicOrderCntLsb / 2,根据这个规则,计算解码顺序相邻的两幅图像的Lsb之差,并与MaxPicOrderCntLsb/2进行比较,就可判断Lsb是否发生了溢出或借位。在标准中,关于Msb的计算是这样描述的:
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
(1) pic_order_cnt_type=1 本过程的输入是在本节规定的解码顺序中前一图像的FrameNumOffset。 本过程的输出是TopFieldOrderCnt 和BottomFieldOrderCnt ,或者其中之一。 计算过程中涉及到两个变量prevFrameNum 和prevFrameNumOffset ,其中prevFrameNum 是前一图像的frame_num ,而对于prevFrameNumOffset ,如当前图像不是IDR ,而前一图像的memory_management_control_operation等于5 ,prevFrameNumOffset 设为0;否则,prevFrameNumOffset 设置等于前一图像的FrameNumOffset;注意,当序列参数及中的gaps_in_frame_num_value_allowed_flag 等于1 时(表示相邻解码图像的frame_num可以出现间隔),通过frame_num 间隔的解码过程可能会推断出解码顺序中的前一幅图像为“不存在”帧。 大致流程: 毕厚杰书中的插图并没有完全解释清楚,有很多细节没有说明,看了这个图还是感觉什么都没看懂,所以这部分还是结合标准中的说明来看比较好,但标准中只是说了每个值该如何计算,而没有详细讲解,因此要想搞懂每一步的意义也要费点脑细胞才行。 a.关于absFrameNum:可以理解成“绝对帧序号”,而原来的frame_num则应理解为相对帧序号。前面讲frame_num时提到, 当一个序列中的参考帧数量超过MaxFramenum时,frame_num在达到MaxFramenum后会重新从0开始循环计数,这样的话,一个序列 中可能会存在两个或多个参考图像拥有相同的“相对帧序号(即frame_num)”的情况 。因此,如果需要一个符号来唯一地标识一个序列中的所有参考帧,用相对帧序号是不行的,于是就需要为每个参考帧分配一个绝对帧序 号:absFrameNum=FrameNumOffset + frame_num,FrameNumOffset代表当前序列中frame_num已经循环的次数与MaxFrameNum的积。 absFrameNum的具体计算过程如下: if(num_ref_frames_in_pic_order_cnt_cycle != 0 ) absFrameNum = FrameNumOffset +frame_num else absFrameNum = 0 if( nal_ref_idc = = 0 && absFrameNum > 0 ) absFrameNum = absFrameNum ? 1
其中,num_ref_frames_in_pic_order_cnt_cycle在序列参数集中指定,其取值范围[0,255],它是数组 offset_for_ref_frame[]的变化周期(或者说数组长度),这个数组也是在序列参数集中指定的,这个周期和这个数组到底什么意思呢?在 用第二种POC计算方法时,一个参考帧跟下一个参考帧,他们POC的差值不能是任意的,必须是周期变化的,即每隔 num_ref_frames_in_pic_order_cnt_cycle个参考帧,相邻参考帧之间POC的差值循环一次,而每个周期中,第i个差值 即为offset_for_ref_frame[i],正是这个规律,才使得第二种POC算法变得可行,在步骤c、d中将会看到这个数组的作用。 nal_ref_idc = = 0 表示当前图像不是参考图像。当当前图像不是参考图像时,absFrameNum要额外减1。
b. picOrderCntCycleCnt 和 frameNumInPicOrderCntCycle 这俩分别代表absFrameNum对num_ref_frames_in_pic_order_cnt_cycle(前面说到的周期值)取模和取余。当 absFrameNum 大于0 时,二者的值由如下方法得到: if(absFrameNum > 0 ) { picOrderCntCycleCnt=(absFrameNum? 1 ) / num_ref_frames_in_pic_order_cnt_cycle ; //得到完整周期数。 frameNumInPicOrderCntCycle=(absFrameNum?1)%num_ref_frames_in_pic_order_cnt_cycle; //得到最后一个不完整的周期中参考帧的数量 }
c.关于expectedDeltaPerPicOrderCntCycle:代表每隔一个完整周期,POC总的变化量。求这个值只需将上面说到的数组中各个元素相加即可。 expectedDeltaPerPicOrderCntCycle = 0 for( i = 0; i <num_ref_frames_in_pic_order_cnt_cycle; i++ ) expectedDeltaPerPicOrderCntCycle += offset_for_ref_frame[ i ]
d.关于expectedPicOrderCnt:期望的POC值。要得到这个值,只需用POC在一个完整周期内的总变化量乘以周期数,再加上最后一个不完整周期跨越的POC数量即可。 if(absFrameNum > 0 ){ expectedPicOrderCnt=picOrderCntCycleCnt* expectedDeltaPerPicOrderCntCycle for( i = 0; i <= frameNumInPicOrderCntCycle; i++ ) //循环计算最后一个不完整周期所跨越的POC数量。 expectedPicOrderCnt = expectedPicOrderCnt + offset_for_ref_frame[ i] } else expectedPicOrderCnt = 0 if( nal_ref_idc = = 0 ) //对于非参考图像 expectedPicOrderCnt = expectedPicOrderCnt + offset_for_non_ref_pic 其中,offset_for_ref_frame[ i ](前面已经说过)和offset_for_non_ref_pic都是在序列参数集中指定,他们的取值范围都是[-2^31,2^31-1]。
e.变量TopFieldOrderCnt 或 BottomFieldOrderCnt 的值由如下方法得到: if( !field_pic_flag ) { //当前图像不是场图像 TopFieldOrderCnt = expectedPicOrderCnt + delta_pic_order_cnt[ 0 ] BottomFieldOrderCnt = TopFieldOrderCnt + offset_for_top_to_bottom_field + delta_pic_order_cnt[ 1 ] } else if( !bottom_field_flag ) TopFieldOrderCnt = expectedPicOrderCnt + delta_pic_order_cnt[ 0 ] else BottomFieldOrderCnt = expectedPicOrderCnt + offset_for_top_to_bottom_field +delta_pic_order_cnt[ 0 ]
其中,offset_for_top_to_bottom_field在序列参数集中指定,delta_pic_order_cnt[ 0或1 ]在片头指定。 补充:从上面的过程中可以看到,当图像序列中出现两个或多个连续的非参考帧时,这些非参考帧将具有相同的POC期望值(即 expectedPicOrderCnt),但是他们的顶场序号和底场序号可以通过各自的delta_pic_order_cnt[0和1 ]加以区别。因此第二种POC计算方法也是支持图像序列中出现连续非参考帧的。而下面将介绍的第三种POC计算方法则不支持连续的非参考帧。
(1) pic_order_cnt_type=2 第三种POC计算方法所依赖的额外参数最少(可以节省片头的比特数),它只根据frame_num就可以得到顶、底场的序列号。但是缺点是,用这种方法时,不允许图像序列中出现连续的非参考帧。 大致流程:
这个方法不像第二种那么麻烦,从图示中已可以看出完整的过程。唯一需要说的一点是,最后计算顶、底场序号时的规则(这个规则在三种方法中各不相同): if(!field_pic_flag ) { TopFieldOrderCnt = tempPicOrderCnt BottomFieldOrderCnt = tempPicOrderCnt } else if(bottom_field_flag ) BottomFieldOrderCnt = tempPicOrderCnt else TopFieldOrderCnt = tempPicOrderCnt
在这种方法中,如果解码顺序相邻的两个帧具有相同的frame_num,那么其中必有一个是参考帧,而另一个则是非参考帧,而且参考帧总是在非参考帧之前进行编解码(和传输),但非参考帧比参考帧先采样和播放(即非参考帧的POC更靠前)。
|
|