poc_order_cnt_type

为什么有poc_order_cnt_type?poc_order_cnt_type是为了推断出POC,POC表示了播放的顺序,frame_num在传递参考帧后加1,这里涉及到一个播放顺序和解码顺序的问题。

poc_order_cnt_type分为三种,取值为0~2,先从最简单的poc_order_cnt_type为2说起。

poc_order_cnt_type为2

poc_order_cnt_type为2时,显示顺序与解码顺序一致,这种情况下不存在B帧,在这种情况下不存在连续的非参考帧。

协议中定义在8.2.1.3一节。对FrameNumOffset的推导过程如下

以下代码为JM中对poc_order_cnt_type=2的情况的解析,JM代码和协议规范完全一致。

if(pSlice->idr_flag) // IDR picture
{
  p_Vid->FrameNumOffset=0;     //  first pix of IDRGOP,
  pSlice->ThisPOC = pSlice->framepoc = pSlice->toppoc = pSlice->bottompoc = 0;
  if(pSlice->frame_num){
    error("frame_num not equal to zero in IDR picture", -1020);
  }
}
else{
    if (p_Vid->last_has_mmco_5){
        p_Vid->PreviousFrameNum = 0;
        p_Vid->PreviousFrameNumOffset = 0;
    }
    if (pSlice->frame_num<p_Vid->PreviousFrameNum){ 
        p_Vid->FrameNumOffset = p_Vid->PreviousFrameNumOffset + p_Vid->max_frame_num;
    }
    else{
        p_Vid->FrameNumOffset = p_Vid->PreviousFrameNumOffset;
    }  
    pSlice->AbsFrameNum = p_Vid->FrameNumOffset+pSlice->frame_num;
      .....
          
    p_Vid->PreviousFrameNum=pSlice->frame_num; 
    p_Vid->PreviousFrameNumOffset=p_Vid->FrameNumOffset;
}

下面先解释下代码中出现的变量的含义,然后再结合协议理解。

  • pSlice->AbsFrameNum

实际的frameNum,取值范围是0 ~ GOP_SIZE - 1。

  • p_Vid->max_frame_num

和协议文档中的MaxFrameNum对应,pSlice->AbsFrameNum中的低log2_max_frame_num bits值对应,因为如果我们码流中用pSlice->AbsFrameNum标识帧号,则随着帧号的增加,所用的bit也越来越多,为了节省码流,在码流中只传输低log2_max_frame_num bits作为帧号。

如下图,在SPS中,log2_max_frame_num_minus4=0,则log2_max_frame_num = 4,所以最多有16个取值,

即p_Vid->max_frame_num = 16。

  • pSlice->frame_num

为实际帧号的低log2_max_frame_num的值 ,取值范围是 0 - p_Vid->max_frame_num - 1。

  • p_Vid->PreviousFrameNum

为上一帧帧号的低log2_max_frame_num的值,取值范围是0 - p_Vid->max_frame_num - 1。

  • p_Vid->FrameNumOffset

为实际帧号的高bit的值,有代码看出

pSlice->AbsFrameNum = p_Vid->FrameNumOffset+pSlice->frame_num;

p_Vid->FrameNumOffset必为p_Vid->max_frame_num的整数倍。

  • p_Vid->PreviousFrameNumOffset

为上一帧帧号的高bit的值,同样也为p_Vid->max_frame_num的整数倍。

了解了代码中这些参数的含义,也就理解了协议的含义,本质上就是frame num的更新,而POC值一般为

pSlice->AbsFrameNum的两倍,这里也不再赘述,详细可以看协议8.2.1.3

pic_order_cnt_type为0

定义在H264协议8.2.1.1中。pic_order_cnt_type为0时,可以有B帧也可以没有B帧,但是大多数情况下都是含有B帧,因为如果没有B帧,可以设置pic_order_cnt_type为2,这样还更省码流,具体可以参考协议,这里以JM代码为例来说明,JM代码和协议也是完全吻合的。

先来介绍代码中的相关参数说明

  • MaxPicOrderCntLsb

对应码流结构中的Log2MaxPOCLsbMinus4,含义和log2_max_frame_num_minus4类似,标识POC的低Log2MaxPocLsb bit能标识的POC个数,对pic_order_cnt_type为2来说,因为解码顺序和播放顺序完全一致,所以Log2MaxPOCLsb没有存在的必要,但是如果播放顺序和解码顺序不同,所以码流中有必要有标识POC的字段。

如下图log2_max_pic_order_cnt_lsb_minus4为2,所以Log2_max_pic_order_cnt_lsb为6,所以MaxPicOrderCntLsb为64。

  • p_Vid->PrevPicOrderCntLsb

含义类似p_Vid->PreviousFrameNum,取值范围是0 - MaxPicOrderCntLsb -1,表明前一个参考帧picture的POC的低log2_max_pic_order_cnt_lsb的值。

  • p_Vid->PrevPicOrderCntMsb

含义类似p_Vid->PrevPicOrderCntLsb,表明前一个picture的POC的高bit的值。

  • pSlice->pic_order_cnt_lsb

当前slice的实际POC低log2_max_pic_order_cnt_lsb bit的值。

  • pSlice->PicOrderCntMsb

当前slice的实际POC高bit的值。

从代码来看,主要工作分为三部分,一个是PrevPicOrderCnt赋值,一个是PicOrderCnt赋值,还有一个是POC的赋值,PrevPicOrderCnt赋值比较简单,代码如下,这里不再赘述。

if(pSlice->idr_flag){
    p_Vid->PrevPicOrderCntMsb = 0;
    p_Vid->PrevPicOrderCntLsb = 0;
}
else{
    if (p_Vid->last_has_mmco_5){
        if (p_Vid->last_pic_bottom_field){
          p_Vid->PrevPicOrderCntMsb = 0;
          p_Vid->PrevPicOrderCntLsb = 0;
        }
        else{
          p_Vid->PrevPicOrderCntMsb = 0;
          p_Vid->PrevPicOrderCntLsb = pSlice->toppoc;
        }
    }
}
......
if(pSlice->nal_reference_idc){ // B帧不会进此分支
    p_Vid->PrevPicOrderCntLsb = pSlice->pic_order_cnt_lsb;
    p_Vid->PrevPicOrderCntMsb = pSlice->PicOrderCntMsb;
}  

PicOrderCnt赋值的代码如下:

// Calculate the MSBs of current picture
if( pSlice->pic_order_cnt_lsb  <  p_Vid->PrevPicOrderCntLsb  &&
      ( p_Vid->PrevPicOrderCntLsb - pSlice->pic_order_cnt_lsb )  >=  ( MaxPicOrderCntLsb / 2 ) ){
    // condition 1
    pSlice->PicOrderCntMsb = p_Vid->PrevPicOrderCntMsb + MaxPicOrderCntLsb;
}
else if ( pSlice->pic_order_cnt_lsb  >  p_Vid->PrevPicOrderCntLsb  &&
      ( pSlice->pic_order_cnt_lsb - p_Vid->PrevPicOrderCntLsb )  >  ( MaxPicOrderCntLsb / 2 ) ){
    // condition 2
    pSlice->PicOrderCntMsb = p_Vid->PrevPicOrderCntMsb - MaxPicOrderCntLsb;
}
else{
    // condition 3
    pSlice->PicOrderCntMsb = p_Vid->PrevPicOrderCntMsb;
}

下面分别看没有B帧的情况和B帧存在的情况

  • 不存在B帧

这种情况下播放顺序和解码顺序一致,假设Log2MaxPOCLsbMinus4=2,MaxPicOrderCntLsb=64,我们编码50帧。然后分析码流和代码。

前32帧都会进入condtion3分支, pSlice->pic_order_cnt_lsb每次增加2,p_Vid->PrevPicOrderCntLsb记录上一帧的pSlice->pic_order_cnt_lsb的值,PrevPicOrderCntMsb为0。

当到了第32帧时,pSlice->pic_order_cnt_lsb是62,p_Vid->PrevPicOrderCntLsb是60,

pSlice->frame_num是15,PrevPicOrderCntMsb是0。

下一帧到来时,pSlice->pic_order_cnt_lsb需要回绕,此时pSlice->pic_order_cnt_lsb是0,

p_Vid->PrevPicOrderCntLsb是62,pSlice->frame_num是0,PrevPicOrderCntMsb是0,根据条件会进入condition1分支, pSlice->PicOrderCntMsb为赋值为64。

接下来每帧都会进condition3分支,直至再次出现pSlice->pic_order_cnt_lsb回绕,再进入condition1分支,周期此过程。

  • 存在B帧

假设Log2MaxPOCLsbMinus4=2,MaxPicOrderCntLsb=64,NumberBFrames=1,B帧不作为参考帧,我们编码50帧。然后分析码流和代码。

如下所示,frame_num对应代码中的pSlice->frame_num;pic_order_cnt_lsb对应pSlice->pic_order_cnt_lsb;PrevPicOrderCntLsb对应p_Vid->PrevPicOrderCntLsb;当有参考帧传输时,frame_num加1。

Slice

Type

Used for reference

frame_num

pic_order_cnt_lsb

PrevPicOrderCntLsb

Display order

1st

I

Yes

0

0

0

0

2nd

P

Yes

1

4

0

2

3rd

B

No

2

2

4

1

4th

P

Yes

2

8

4

4

5th

B

No

3

6

8

3

6th

P

Yes

3

12

8

6

7th

B

No

4

10

12

5

8th

P

Yes

4

16

12

8

9th

B

No

5

14

16

7

只有参考帧,p_Vid->PrevPicOrderCntLsb和p_Vid->PrevPicOrderCntMsb的才会赋值,所以PrevPicOrderCntLsb如上。

当pSlice->pic_order_cnt_lsb没有发生回绕时,进入condtion 3分支;当 pSlice->pic_order_cnt_lsb发生回绕时,先进入conditon1,发生回绕的时候是P帧,发生回绕后下一帧又会进入condition2分支。

POC赋值的代码如下,delta_pic_order_cnt_bottom 表示一个编码帧的底场和顶场的图像顺序数之间的差

if(pSlice->field_pic_flag==0){ // 不存在场           
    pSlice->toppoc = pSlice->PicOrderCntMsb + pSlice->pic_order_cnt_lsb;
    pSlice->bottompoc = pSlice->toppoc + pSlice->delta_pic_order_cnt_bottom;
    pSlice->ThisPOC = pSlice->framepoc = (pSlice->toppoc < pSlice->bottompoc)? pSlice->toppoc : pSlice->bottompoc; // POC200301
}
else if (pSlice->bottom_field_flag == FALSE){  // 顶场
    pSlice->ThisPOC= pSlice->toppoc = pSlice->PicOrderCntMsb + pSlice->pic_order_cnt_lsb;
}
else{  // 底场
    pSlice->ThisPOC= pSlice->bottompoc = pSlice->PicOrderCntMsb + pSlice->pic_order_cnt_lsb;
}
pSlice->framepoc = pSlice->ThisPOC;
p_Vid->ThisPOC = pSlice->ThisPOC;

pic_order_cnt_type为1

定义在H264协议8.2.1.2中,Log2MaxPOCLsbMinus4=2,MaxPicOrderCntLsb=64,NumberBFrames=2,B帧不作为参考帧,我们编码50帧。然后分析码流和代码。

frame Type的顺序依次为I P B B P B B P B ......。

P B B视为一个cycle,sps中num_ref_frames_in_pic_order_cnt_cycle为1,表示一个循环中的参考帧数目。

Slice

Type

Used for reference

frame_num

POC

Display order

1st

I

Yes

0

0

0

2nd

P

Yes

1

6

3

3rd

B

No

2

2

1

4th

B

No

2

4

2

5th

P

Yes

2

12

6

6th

B

No

3

8

4

7th

B

No

3

10

5

8th

P

Yes

3

18

9

9th

B

No

4

14

7

10th

B

No

4

16

8

11th

P

Yes

4

24

12

12th

B

No

5

20

10

13th

B

No

5

22

11

14th

P

Yes

5

30

15

15th

B

No

6

26

13

16th

B

No

6

28

14

对于pic_order_cnt_type为0和1的情况,最重要的是要明确Display order,所以就要分析下代码中POC的计算方法。先看几个重要的参数,下图是SPS中的部分参数。

  • offset_for_ref_frame

对应代码中的active_sps->offset_for_ref_frame,在本例中一个cycle只有一个参考帧,所以offset_for_ref_frame只有一个元素。它表示的含义是上一个参考帧到下一个参考帧的picture offset。举个例子,从上面表格看,Slice 2 对应的是P帧,Display order为3,下一个P帧对应的是Slice 3,对应的Display order为6,Display order后者减前者为3,这个差值3对应frame的差值,对应的picture为6,即offset_for_ref_frame[0]。

  • offset_for_non_ref_pic

对应代码中的active_sps->offset_for_non_ref_pic,它的含义是上一个参考帧到下一个非参考帧之间的picture offset。还用上面的表格为例,Slice 2对应的是P帧,Display Order为3,下一个非P帧是Slice 3,对应的Display Order为1,后者减前者差值为-2,对应picture offset为-4,即offset_for_non_ref_pic。

  • offset_for_top_to_bottom_field

对应代码中的active_sps->offset_for_top_to_bottom_field,用于计算底场的POC,它含义是bottom filed expected poc和top filed poc的差异

再看Slice header中的字段

  • delta_pic_order_cnt[0]

对应代码中的pSlice->delta_pic_order_cnt[0],它的含义是一个编码帧的top field或一个编码场的POC与ExpectedPicOrderCnt之间的差异。

  • delta_pic_order_cnt[1]

对应代码中的pSlice->delta_pic_order_cnt[1],它的含义是一个编码帧的bottom field POC 与预期的bottom filed POC之间的差异,而预期的bottom field POC为top field poc + offset_for_top_to_bottom_field;

如果码流中没有,则默认为0,本例中码流中不存在delta_pic_order_cnt[1],它恒为0,如下所示句法,在满足一定条件下delta_pic_order_cnt[1]才存在。

先看ExpectedDeltaPerPicOrderCntCycle的计算,它的含义是一个cycle中有多少个picture。

p_Vid->ExpectedDeltaPerPicOrderCntCycle=0;
if(active_sps->num_ref_frames_in_pic_order_cnt_cycle){
  for(i=0;i<(int) active_sps->num_ref_frames_in_pic_order_cnt_cycle;i++){
      p_Vid->ExpectedDeltaPerPicOrderCntCycle += active_sps->offset_for_ref_frame[i]; 
  }
}

由上面的分析易知,p_Vid->ExpectedDeltaPerPicOrderCntCycle为6。

再看p_Vid->ExpectedPicOrderCnt的计算,ExpectedPicOrderCnt为预期的POC,代码比较简单,不再赘述,主要代码如下,pSlice->AbsFrameNum对应P帧的个数,通过AbsFrameNum可以推断出当前经过了多少个cycle。

if(pSlice->AbsFrameNum)
{
  // 计算经过了多少次cycle
  p_Vid->PicOrderCntCycleCnt = (pSlice->AbsFrameNum-1)/active_sps->num_ref_frames_in_pic_order_cnt_cycle;
  // 这个cycle中多余了多少个P帧
  p_Vid->FrameNumInPicOrderCntCycle = (pSlice->AbsFrameNum-1)%active_sps->num_ref_frames_in_pic_order_cnt_cycle;
  // 经过了整cycle的picture 数目
  p_Vid->ExpectedPicOrderCnt = p_Vid->PicOrderCntCycleCnt*p_Vid->ExpectedDeltaPerPicOrderCntCycle;
  
  for(i=0;i<=(int)p_Vid->FrameNumInPicOrderCntCycle;i++){ // 加上多余的P帧的offset
    p_Vid->ExpectedPicOrderCnt += active_sps->offset_for_ref_frame[i];
  }
}
else{
  p_Vid->ExpectedPicOrderCnt=0;
}
if(!pSlice->nal_reference_idc){ // 如果当前是B帧
  p_Vid->ExpectedPicOrderCnt += active_sps->offset_for_non_ref_pic;
}

主要注意的是:如果当前帧是P帧,上面计算的ExpectedPicOrderCnt的很好理解;如果是B帧,因为显示是先出B帧再出P帧,所以要加上active_sps->offset_for_non_ref_pic。但是这里有个问题,就是如果连续多个B帧,这些B帧的ExpectedPicOrderCnt值是一样的,需要再做进一步调整。计算POC的代码如下。

需要注意delta_pic_order_cnt[0]在帧编码的情况下的意义是top field poc和ExpectedPicOrderCnt的差异,如果是场编码,delta_pic_order_cnt[0]的意义是当前picture POC和ExpetedPicOrderCnt的差异,这也是在顶场和底场的情况下,计算POC用的都是pSlice->delta_pic_order_cnt[0]。计算底场的POC还需要加上offset_for_top_to_bottom_field。

如果当前是帧编码,先通过ExpectedPicOrderCnt和delta_pic_order_cnt[0]计算toppoc;然后通过toppoc计算bottompoc,slice poc为toppoc和bottompoc的最小者。

if(pSlice->field_pic_flag==0){ // frame
  pSlice->toppoc = p_Vid->ExpectedPicOrderCnt + pSlice->delta_pic_order_cnt[0];
  pSlice->bottompoc = pSlice->toppoc + active_sps->offset_for_top_to_bottom_field + pSlice->delta_pic_order_cnt[1];
  pSlice->ThisPOC = pSlice->framepoc = (pSlice->toppoc < pSlice->bottompoc)? pSlice->toppoc : pSlice->bottompoc; // POC200301
}
else if (pSlice->bottom_field_flag == FALSE){ // 顶场
  pSlice->ThisPOC = pSlice->toppoc = p_Vid->ExpectedPicOrderCnt + pSlice->delta_pic_order_cnt[0];
}
else{ // 底场
  pSlice->ThisPOC = pSlice->bottompoc = p_Vid->ExpectedPicOrderCnt + active_sps->offset_for_top_to_bottom_field + pSlice->delta_pic_order_cnt[0];
}

总结

  • 如果解码顺序和播放顺序一致(不存在B帧),最好用poc_order_type=2,这样最省码流,推导也方便。
  • 如果解码顺序和播放顺序不一致(存在B帧),poc_order_cnt_type为0最灵活,也好推导,但是码流大些,如果poc_order_cnt_type为1,码流会小些,但是灵活度不够,解码帧必须呈周期性的关系才可以。

Reference

《THE H.264 ADVANCED VIDEO COMPRESSION STANDARD》

《T-REC-H.264-202108》

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neil_baby

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值