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

posted @ 2012-07-31 12:39  Mr.Rico  阅读(...)  评论(...编辑  收藏