从Slice_Header学习H.264(三.1)--相关细节之 POC的计算

来源:互联网 发布:金融炼金术知乎 编辑:程序博客网 时间:2024/05/16 06:06

三、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更靠前)。