• JM86中多参考帧相关问题


    JM86中多参考帧相关问题

    1. 关于jm86中MAX_LIST_SIZE值的选取

    我们知道在参考图像队列中listX[ i ][ j ], 这边的i的取值范围是0~5,而且0~5所代表的含义论坛上已经有人说过。

    即如下:

    istXsize[6];  
    奇数为参考帧列表 list0 中参考帧的个数;偶数为参考帧列表 list1 中参考帧的个数。0、1用于帧图像或者场图像,2、3用于MBAFF帧图像中顶宏块,4、5用于MBAFF帧图像中底宏块  
    所有涉及帧间参考的大小为6的数组都可以这样类推。例如listX[6],它的解释就是:奇数为参考帧列表 list0;偶数为参考帧列表 list1。0、1用于帧图像或者场图像,2、3用于MBAFF帧图像中顶宏块,4、5用于MBAFF帧图像中底宏块。还有 storable_picture结构体中的ref_pic_num变量第二维的那个6也是同样道理。
    JM 代码中的 list_offset 的作用就是根据当前图像类型在这 6 种列表变量中选择对应的列表变量。以上是 JM86 中的情况。其他版本可能相同。

    现在我们要讨论j的大小:

    这个j的取值范围是0~MAX_LIST_SIZE-1 即0~32, 为什么会有33个元素呢?
    我们知道264规定最多可用16个参考帧,如果是场模式,那么也就32个,但是这边还是多了一个,为什么呢?
    现在我告诉你,这是程序上需要多留了一个的,跟264本身应该没有关系的。

    在参考队列重排序的代码中, 我们可以看到原因。

    static void reorder_short_term(StorablePicture **RefPicListX, int num_ref_idx_lX_active_minus1, int picNumLX, int *refIdxLX)
    {
      int cIdx, nIdx;
      StorablePicture *picLX;
      picLX = get_short_term_pic(picNumLX);
      for( cIdx = num_ref_idx_lX_active_minus1+1; cIdx > *refIdxLX; cIdx-- )
        RefPicListX[ cIdx ] = RefPicListX[ cIdx - 1];
      //假设原本的排序是 1,2,3,4,5,NULL(假设3是要重排序的)
      
    //那么现在的排序是 3,1,2,3,4,5  
      RefPicListX[ (*refIdxLX)++ ] = picLX;
      nIdx = *refIdxLX;//这边nIdx=1
      for( cIdx = *refIdxLX; cIdx <= num_ref_idx_lX_active_minus1+1; cIdx++ )
        if (RefPicListX[ cIdx ])
          if( (RefPicListX[ cIdx ]->is_long_term ) ||  (RefPicListX[ cIdx ]->pic_num != picNumLX ))
            RefPicListX[ nIdx++ ] = RefPicListX[ cIdx ];
      //那么现在的排序是 3,1,2,4,5,5(最后一个5是多余的)
    }

    我在注释中写了,大家自己理解下。大家可以运行下面我的简化版程序

    void main()
    {
    int test[6]={1,2,3,4,5,0};//这边的最后一个0相当于多出的那一位数据
    int i=0,j=1;
    for(i=5;i>0;i--)
    {
      test[ i ]=test[i-1];
    }
    test[0]=3;
    for(i=0;i<6;i++)
    {
      printf("test[%d]=%d\n",i,test[ i ]);
    }
    printf("\n");
    for(i=1;i<=5;i++)
    {
      if(test[ i ]!=3)
      {
       test[j++]=test[ i ];
      }
    }
    for(i=0;i<6;i++)
    {
      printf("test[%d]=%d\n",i,test[ i ]);
    }
    }
    1. JM代码中ref_id, ref_idx, ref_pic_id, ref_pic_num解析

    本文参考了网上的一篇文章<<ref_id, ref_idx, ref_pic_id, ref_pic_num猜想>>

    此次我是参考的JM8.6

    1. 变量的声明

    这几个变量属于结构体StorablePicture中,其声明在文件mbuffer.h中:

    //! definition a picture (field or frame)

    typedef struct storable_picture{

    ...

    int64 ref_pic_num[MAX_NUM_SLICES][6][MAX_LIST_SIZE]; MAX_NUM_SLICES=150, MAX_LIST_SIZE=33

    ...

    int *** ref_idx; //!< reference picture [list][subblock_y][subblock_x]

    int64 *** ref_pic_id; //!< reference picture identifier [list][subblock_y][subblock_x]

    // (not simply index)

    int64 *** ref_id; //!< reference picture identifier [list][subblock_y][subblock_x]

    // (not simply index)

    ...

    } StorablePicture;

    根据这个几个变量的声明可以得到如下信息:

    1. 【数据类型】数据类型为int64的,表征的是图像序号,该图像序号的计算方式与POC有关。数据类型为int的,表征的是参考队列的下标值(索引)。
    2. 【维数】根据各变量的维数信息,可以猜测ref_pic_num保存的是当前picture中的每一个slice所使用的全部list中的所有参考图像序号。ref_idref_idxref_pic_id则表示某个4x4块在某个list里的参考图像序号和索引。
    3. 【数组指针】ref_pic_num声明为数组,ref_idref_idxref_pic_id为指针。猜测当解码至某一4x4块时,ref_pic_id会指向ref_pic_num数组中的某一项。
    1. 实例

    这里给出一个编解码实例,全frame编码,码流结构为IPBPB…

    frame_num 

    0 

    1 

    2 

    2 

    3 

    3 

    4 

    4 

    5 

    POC 

    0 

    4 

    2 

    8 

    6 

    12 

    10  

    16  

    14 

    slice_type 

    I 

    P 

    B 

    P 

    B 

    P 

    B 

    P 

    B 

    ref_id[0][0][0] 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    ref_id[1][0][0] 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    0

    0 

    ref_idx[0][0][0] 

    -1 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    ref_idx[1][0][0] 

    -1 

    -1 

    0 

    -1 

    0 

    -1 

    0 

    -1 

    0 

    ref_pic_id[0][0][0] 

    NA 

    0 

    0 

    8 

    8 

    16 

    16 

    24 

    24 

    ref_pic_id[1][0][0] 

    NA 

    NA 

    8 

    NA 

    16 

    NA 

    24 

    NA 

    32 

    ref_pic_num[0][0][0] 

    0  

    0 

    0 

    8 

    8 

    16 

    16 

    24 

    24 

    ref_pic_num[0][1][0] 

    0 

    0 

    8 

    0 

    16 

    0  

    24 

    0 

    32 

    List[0] and List[1]

    Red is List[0]

    Blue is List[1]

    (括号中是对应的frame_num)

     

    0 

    0 4

    4 0

    4(1)

    0(0) 

    4 8

    0 4

    8 0

    8(2)

    4(1)

    0(0) 

    8 12

    4 8

    0 4

    12 0

    12(3)

    8(2)

    4(1)

    0(0) 

    12 16

    8 12

    4 8

    0 4

    16 0

    说明:NA: int64类型的无意义数。

    ref_pic_num只给出了list[0]list[1]的首个参考图像序号

    List竖着看。例如POC6B帧,list[0]4 0 8list[1] 8 4 0

     

    首先需要说明, 为了方便起见,本例子都取用了图像的左上角4x4block

    1. ref_pic_num正如前面所说,是一个记录当前picture所有参考列表信息(以POC为依据)的表。例如POC12P slice,因为List0210frame_num),各参考图像对应的POC840,分别乘2后得到ref_pic_num[0][0][0]…[0][0][2]1680。再例如POC6B sliceref_pic_num[0][0][0]…[0][0][2]8016ref_pic_num[0][1][0]…[0][1][2]1680,也都是列表中参考帧的图像POC2倍(下面有讨论)
    2. ref_idx的确为参考队列的索引。在例子中,ref_idx0,表明使用的都是相应list中的首个参考图像,通过对码流的检查,图像的左上角4x4block的确如此。且在非零处同样也做了相应检查。而ref_idx-1,都是不使用的相应list的情况。
    3. 结合上述两点分析,ref_pic_id的确指向ref_pic_num中的相应元素。
    4. ref_id始终为0。因为例子中所有的数据都是采集自函数decode_one_slice内。ref_id的作用在后面有详细说明。

    这是另一个全field编码的例子,码流结构仍为IPBPB…

    frame_num 

    0 

    0 

    1 

    1 

    2 

    2 

    2 

    2 

    3 

    3 

    POC 

    0 

    1 

    4 

    5 

    2 

    3 

    8 

    9  

    6 

    7 

    slice_type 

    I 

    P 

    P 

    P 

    B 

    B 

    P 

    P 

    B 

    B 

    ref_idx[0][0][0] 

    -1 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    0 

    ref_idx[1][0][0] 

    -1 

    -1 

    -1 

    -1 

    0 

    0

    -1 

    -1 

    0 

    0 

    ref_pic_id[0][0][0] 

    NA 

    0 

    0 

    3 

    0 

    3 

    8 

    11 

    8 

    11 

    ref_pic_id[1][0][0] 

    NA 

    NA 

    NA 

    NA 

    8 

    11 

    NA 

    NA 

    16 

    19 

    ref_pic_num[0][0][0] 

    0  

    0 

    0 

    3 

    0 

    3 

    8 

    11 

    8 

    11 

    ref_pic_num[0][1][0] 

    0 

    0 

    0 

    0 

    9 

    11  

    0 

    0 

    16 

    19 

    List[0] and List[1]

    Red is List[0]

    Blue is List[1]

    (括号中是对应的frame_num)

     

    0 

    1

    0 

    1

    2

    0 

    0 4

    1 5

    4 0

    5 1

    1 5

    0 4

    5 0

    4 1

    3

    2

    1

    0

    3

    4

    1

    2

    0 

    4 8

    5 9

    0 4

    1 5

    8 0

    9 1

    5 9

    4 8

    1 5

    0 4

    9 1

    8 0

    注意:P帧使用的是pic_num, 而B帧使用的是POC

    1. ref_pic_num

    函数decode_one_slice()在一开始就调用了函数set_ref_pic_num()计算ref_pic_num。函数set_ref_pic_num()的定义在image.c里:

    void set_ref_pic_num()
    {
    ...
    for (i=0;i<listXsize[LIST_0];i++)
    {
    dec_picture->ref_pic_num[slice_id][LIST_0][i] = listX[LIST_0][i]->poc * 2 + ((listX[LIST_0][i]->structure==BOTTOM_FIELD)?1:0) ;
    ...
    }
    ...
    }

    这里首先讨论一个问题,jm里为什么需要ref_pic_num。我们知道H.264P sliceB slice的参考列表计数的序号是不同的:P slice采用的是由frame_num推导得到的PicNumB slice采用的是POC。而如上代码所示,这里ref_pic_num则给出了一种统一的计数方式,无论是P slice还是B slice,都基于POC来得到参考图像的序号值。以上是我个人的理解。

    为什么要采用poc * 2 + BOTTOM_FIELD ? 1 : 0,这是一个没能完全确定的问题。原因应当是与Picture AFF有关,但目前还没彻底想通。

    1. ref_idx和ref_pic_id

    macroblock.c中,init_macroblock()函数将当前宏块中每一个4x4块对应的ref_idxref_pic_id都置为-1NA值。

    void init_macroblock(struct img_par *img)
    {
    ...
    for(j=img->block_y; j < img->block_y + BLOCK_SIZE; j++)
    // reset vectors and pred. modes
    ...
    memset(&dec_picture->ref_idx[LIST_0][j][img->block_x], -1, BLOCK_SIZE * sizeof(char));
    memset(&dec_picture->ref_idx[LIST_1][j][img->block_x], -1, BLOCK_SIZE * sizeof(char));
    for (i=img->block_x;i<img->block_x+BLOCK_SIZE;i++)
    {
    dec_picture->ref_pic_id[LIST_0][j][i] = INT64_MIN;
    dec_picture->ref_pic_id[LIST_1][j][i] = INT64_MIN;
    }
    }
    ...
    }

    之后在函数read_one_macroblock()decode_one_macroblock()ref_idx被赋值和使用(这个比较简单,不再详述)。

    在函数decode_one_macroblock()中可以找到ref_pic_id指向ref_pic_num的代码:

    ...
    dec_picture->ref_pic_id[LIST_0][j4][i4] = dec_picture->ref_pic_num[img->current_slice_nr][LIST_0 + list_offset][(short)dec_picture->ref_idx[LIST_0][j4][i4]];

    dec_picture->ref_pic_id[LIST_1][j4][i4] = dec_picture->ref_pic_num[img->current_slice_nr][LIST_1 + list_offset][(short)dec_picture->ref_idx[LIST_1][j4][i4]];
    ...
    1. ref_id

    本节说明ref_id的作用。在整个工程中搜索ref_id,发现搜到的结果全部都在mbuffer.cmbuffer.h两个文件中,因此可以变量ref_id在条带的解码计算过程中不起作用(在上面的实例中ref_id始终为0),它只是一个中间变量。

    在文件mbuffer.c中与相关的主要ref_id是两部分:一部分是在函数gen_field_ref_ids() dpb_split_field() dpb_combine_field()中赋值,代码类似于如下:


    p->ref_id[LIST_0][j][i] = (dummylist0>=0)? p->ref_pic_num[p->slice_id[j>>2][i>>2]][LIST_0][dummylist0] : 0;
    p->ref_id[LIST_0][j][i] = (dummylist0>=0)? p->ref_pic_num[p->slice_id[j>>2][i>>2]][LIST_0][dummylist0] : 0;

    这三个函数都是与frame拆分成fieldfiled合并成frame相关。因此推测ref_id在这里起到中间作用。

    另一部分在函数compute_colocated()中,例如:


    p->ref_pic_id[LIST_0][j][i] = fs->ref_id[LIST_0][j][i];
    p->ref_pic_id[LIST_1][j][i] = fs->ref_id[LIST_1][j][i];

    因此推测在Direct预测时,当前的ref_pic_id是从参考图像的ref_id复制而来。

    1. 总结

    结论1ref_idx是参考队列索引,是H.264标准规定的语法元素

    结论2ref_pic_num是保存了当前所有的参考列表信息。无论是P slice还是B sliceref_pic_num其中的参考图像序号都是基于POC计算出来的。

    结论3ref_pic_id是指针,对于当前解(编)码的4x4块,ref_pic_id指向ref_pic_num中相应的数值。

    结论4ref_id是一个中间变量。

    问题1ref_pic_num采用计算公式poc * 2 + BOTTOM_FIELD ? 1 : 0的原因。

    问题2ref_id的准确用途有待确证。

    对JM86中参考列表相关问题的研究

    2011年9月7日 14:25:27

    多参考帧技术是H.264中一项非常重要的技术, 这项技术大大提高了H.264的在帧间匹配间的精确度, 从而使得H.264高效压缩成为可能.

    多参考帧牵扯到的问题很多, 比如多参考帧的管理, DPB缓冲区的管理, 参考帧列表的初始化, 参考帧列表重排序等等.

    在JM8.6的代码中, 相关的数据结构主要有StorablePicture **listX[6];int listXsize[6];

    img(ImageParameters):

    int number; //!< current image number to be encoded

    int pn; //!< picture number

    signed int framepoc; //!< min (toppoc, bottompoc)

    signed int ThisPOC; //!< current picture POC

    unsigned int frame_num; //!< frame_num for this frame

    StorablePicture:

    int poc;

    int frame_poc;

    int pic_num;

    为了查看上述比较相近的变量的关系, 我在JM8.6中添加了一些代码, 将编码完每一帧之前listX表中的参考帧的相关信息输出到了文本文档中了. 具体如下:

    1. 帧编码, 编码序列为IPBPBPB.....

    最左边图像中,显示的list列表中的数据是frame_poc, 中间的是poc, 最右边的是pic_num

    注意:[PB](number,pn,framepoc,ThisPOC,frame_num)

    从图中我们可以看到frame_poc与poc的值完全一样, 都是pic_num值的4倍

    1. 场编码, 编码序列为IPBPB...

    三个图的顺序和上面的帧编码是一样的

    我们从这儿可以看到frame_poc和poc之间的差别, 应该就类似与frame_poc是min (toppoc, bottompoc)

    xkfz007

    2011年9月7日 15:39:08

  • 相关阅读:
    团队作业八——第二次团队冲刺(Beta版本)第6天
    团队作业八——第二次团队冲刺(Beta版本)第5天
    团队作业八——第二次团队冲刺(Beta版本)第4天
    个人作业5——软工个人总结
    个人作业4——alpha阶段个人总结
    个人作业3-案例分析
    软件工程网络15结对编程作业(201521123062)
    软件工程网络15个人阅读作业2(201521123062 杨钧宇)
    软件工程网络15个人阅读作业1(201521123062 杨钧宇)
    Java课程设计-随机密码生成器
  • 原文地址:https://www.cnblogs.com/xkfz007/p/2616639.html
Copyright © 2020-2023  润新知