JM86 中 POC 的计算方法



来自:http://hi.baidu.com/snowxshy/item/8fcad08318b86adfd1f8cdcb

一·参数说明

这一节阐述的是 encoder.cfg 中的参数对编码过程的影响
要注意的是 encoder.cfg 中的参数跟 input 结构体中的变量是一一对应的

StartFrame:从视频流的第几帧开始编码
FramesToBeEncoded:指明了除去 B 帧后将要被编码的帧数
input->no_frames = FramesToBeEncoded
FrameSkip:指明了编码过程中跳过的帧数,中间有 B 帧也算跳过一帧。
NumberBFrames:相邻 I、P 帧或相邻的 P 帧之间的 B 帧个数,必须有
     NumberBFrames< FrameSkip
     input->successive_Bframe = NumberBFrames
IntraPeriod:I 帧出现的频率。若 IntraPeriod=3,则每 3 帧(不含 B 帧)中有一 I 帧;
                                                          IntraPeriod=0 时只有第一帧是 I 帧。
IDRIntraEnable:此值为 1 时每个 I 帧都是 IDR,否则只有第一个 I 帧是 IDR。

举例:在 StartFrame=0
                  FramesToBeEncoded=5
                  FrameSkip=3
                  NumberBFrames=2
                  IntraPeriod=3
                  IDRIntraEnable=1
            的情况下编码情况如下,其中红色代表 IDR 帧  
表 1
视频流    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
编码流    I B B    P B B    P B B      I   B   B      P   
编码顺序 0 2 3    1 5 6    4 8 9      7 11 12      10   

二·pic_order_cnt_type 为 0 的情况
这种情况下显式的计算 POC
(1) 编码端 I 帧或 P 帧 toppoc 的计算
这个过程在 main()函数的组循环
      “for (img->number=0; img->number < input->no_frames; img->number++){ }”
中实现
   IntraPeriod 或 IDRIntraEnable 为零时
这种情况下只有第一个 I 帧是 IDR 帧,比较简单。对于 I 帧或 P 帧,其顶场的 POC 为
                    (img->number) * (2*(input->successive_Bframe+1))

IntraPeriod 和 IDRIntraEnable 都不为零时
这种情况下每个 I 帧都是 IDR 帧,其 POC 必须设置为零,I 帧出现的频率为 IntraPeriod,
故其 toppoc 为
        (img->number % input->intra_period) * (2*(input->successive_Bframe+1))
z    说明:
原程序中使用了宏定义 IMG_NUMBER
      “#define IMG_NUMBER (img->number – start_frame_no_in_this_IGOP)”
通过搜start_frame_no_in_this_IGOP 可知这个变量在NumberOfFrameInSecondIGOP 为0
(encoder_main.cfg 中就是这样设置的)时恒为 0,故有
                      IMG_NUMBER = img->number

(2) 编码端 B 帧 POC 的计算
由表一可知,在编完一 I 帧或 P 帧之后才开始对它前面的 B 帧进行编码

    for (img->number=0; img->number < input->no_frames; img->number++)
    {
        ……I,P 帧编码……
        if ((input->successive_Bframe != 0) && (IMG_NUMBER > 0))  
{
      ……
      for(img->b_frame_to_code=1; img->b_frame_to_code<=input->successive_Bframe;      
                                                                                                    img->b_frame_to_code++)
      {
      }

    IntraPeriod 或 IDRIntraEnable 为零时 toppoc 等于
                2+(img->number-1) * (2*(input->successive_Bframe+1))  
                  +2* (img->b_frame_to_code-1)
a)    第一个 2 指得是 IDR 的两个场;
b)    img->number 要减一是因为要对当前帧(img->number)前面的 B 帧进行编码;

    IntraPeriod 和 IDRIntraEnable 都不为零时 toppoc 等于
      2+(img->number % input->intra_period-1) * (2*(input->successive_Bframe+1))+2* (img->b_frame_to_code-1)
IDR 帧前面

(3) toppoc 到 pic_order_cnt_lsb 的转化
                img->pic_order_cnt_lsb
              =img->toppoc &
                ~((((unsigned int)( –1)) << (log2_max_pic_order_cnt_lsb_minus4+4)))

    (unsigned int)(-1)的十六进制形式是 0xffffffff,即它的每一位都是 1;
    log2_max_pic_order_cnt_lsb_minus4+4 是图象数目(包括 B 帧)最大值的位数

当 toppoc >0 时,img->pic_order_cnt_lsb=img->toppoc
当 toppoc <0 时,img->pic_order_cnt_lsb= max_pic_order_cnt+ img->toppoc
其中 max_pic_order_cnt=1<<( log2_max_pic_order_cnt_lsb_minus4+4)
   疑问:
不知道 toppoc 到 pic_order_cnt_lsb 这个过程有什么意义;
POC 的值会从 0 变到很大,为什么不对它进行熵编码;

(4) 解码端 toppoc 的恢复
        此过程在函数 decode_poc 中执行。其思想是对于 IDR 前的 B 帧
                      Toppoc = pic_order_cnt_lsb – max_pic_order_cnt
否则  
                            Toppoc = pic_order_cnt_lsb
是否减去 max_pic_order_cnt 由变量 PicOrderCntMsb 决定,对于 IDR 前的 B 帧
                      PicOrderCntMsb = (– max_pic_order_cnt)
否则
                      PicOrderCntMsb = 0
到这就不难理解 PicOrderCntMsb 的含义了,PicOrderCntMsb 反映了 toppoc 的值是否小于 0。
至于另外两个参数:PrevPicOrderCntMsb 总是为 0;PrevPicOrderCntLsb 在当前图象是 IDR
或 IDR 前(视频流中)的 B 帧时为 0,否则等于前一图象(编码序列中)的 PicOrderCntLsb。

三·pic_order_cnt_type 为 1 的情况
这种情况下通过 frame_num 来计算 POC
(1)frame_num 简介
参考《毕厚杰》7.3.4 节中 frame_num 条款的解释,对于表 1 中的图象序列,其 frame_num的值参考如下:

表 2
视频流      0   1   2   4   5      6      8      9     10    12    13    14 16
编码流      I   B B P   B      B      P      B      B      I      B     B      P
编码顺序    0   2   3   1   5      6      4      8      9   7     11    12 10
frame_num   0   2   2   1   3      3      2      1      1   0      2      2      1
poc         0   2   4   6   8     10    12    -4     -2      0      2      4      6

(2)算法思想以及其解码端的实现
    对于 IDR 帧,poc = 0;
    对于 I 帧或 P 帧
            poc = frame_num*2*(input->successive_Bframe+1)
或    
            poc = 2*(input->successive_Bframe+1)
                      + (frame_num – 1)*2*(input->successive_Bframe+1)

解码端实现
            poc = img->ExpectedPicOrderCnt
                      + img->delta_pic_order_cnt[0]      (在 I,P 帧下为 0)
z    对于 I 帧或 P 帧之前的 B 帧(视频流中)
            poc = (frame_num – 1)*2*(input->successive_Bframe+1)
                      – 2*(input->successive_Bframe+1 – img->b_frame_to_code)

            poc = 2*(input->successive_Bframe+1)
                      + (frame_num – 1 – 1)*2*(input->successive_Bframe+1)
                      + 2*( img->b_frame_to_code – 1)
                      – 2*input->successive_Bframe
解码端的实现
            poc = img->ExpectedPicOrderCnt
                      + img->delta_pic_order_cnt[0]
                      + active_sps->offset_for_non_ref_pic

   变量说明
a)    其中 img->b_frame_to_code 请参见标题一·(2)
b)    img->disposable_flag = (nalu->nal_reference_idc = = 0),而 nal_reference_idc 只在 B 帧时为0,即img->disposable_flag 只在B 帧时为1。这也是在B 帧情况下img->AbsFrameNum
要比 I 帧或 P 帧多减去一个 1 的原因。
c)    其它变量参见下面小题;

(3)编码端参数设置
a)    img->num_ref_frames_in_pic_order_cnt_cycle:
                这个参数在 init_poc( )函数中设置为 1 后就再没改动过;
b)    img->offset_for_ref_frame[0] :
                在 StoredBPictures 为 0 时等于 2*(input->successive_Bframe+1);
c)    img->offset_for_ref_frame[1] :
                没什么用,264 头文件中不会保存此变量;
d)    img->delta_pic_order_cnt[0]    :
                这个变量只对 B 帧有用,等于 2*(img->b_frame_to_code –1); 对于 I 帧或 P 帧, 其值为 0;
e)    active_sps->offset_for_non_ref_pic:
                只对 B 帧有用,在 StoredBPictures 为 0 时等于–2*input->successive_Bframe,

#H264

点赞