JM86中多参考帧相关问题
关于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本身应该没有关系的。
在参考队列重排序的代码中, 我们可以看到原因。
{
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是多余的)
}
我在注释中写了,大家自己理解下。大家可以运行下面我的简化版程序
{
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 ]);
}
}
JM代码中ref_id, ref_idx, ref_pic_id, ref_pic_num解析
本文参考了网上的一篇文章<<ref_id, ref_idx, ref_pic_id, ref_pic_num猜想>>
此次我是参考的JM8.6
- 变量的声明
这几个变量属于结构体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;
根据这个几个变量的声明可以得到如下信息:
- 【数据类型】数据类型为int64的,表征的是图像序号,该图像序号的计算方式与POC有关。数据类型为int的,表征的是参考队列的下标值(索引)。
- 【维数】根据各变量的维数信息,可以猜测ref_pic_num保存的是当前picture中的每一个slice所使用的全部list中的所有参考图像序号。ref_id,ref_idx,ref_pic_id则表示某个4x4块在某个list里的参考图像序号和索引。
- 【数组指针】ref_pic_num声明为数组,ref_id,ref_idx,ref_pic_id为指针。猜测当解码至某一4x4块时,ref_pic_id会指向ref_pic_num数组中的某一项。
- 实例
这里给出一个编解码实例,全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竖着看。例如POC为6的B帧,list[0]为4 0 8;list[1] 为8 4 0 |
首先需要说明, 为了方便起见,本例子都取用了图像的左上角4x4block。
- ref_pic_num正如前面所说,是一个记录当前picture所有参考列表信息(以POC为依据)的表。例如POC为12的P slice,因为List0为2,1,0(frame_num),各参考图像对应的POC为8,4,0,分别乘2后得到ref_pic_num[0][0][0]…[0][0][2]为16,8,0。再例如POC为6的B slice,ref_pic_num[0][0][0]…[0][0][2]为8,0,16,ref_pic_num[0][1][0]…[0][1][2]为16,8,0,也都是列表中参考帧的图像POC的2倍(下面有讨论)。
- ref_idx的确为参考队列的索引。在例子中,ref_idx为0,表明使用的都是相应list中的首个参考图像,通过对码流的检查,图像的左上角4x4block的确如此。且在非零处同样也做了相应检查。而ref_idx为-1,都是不使用的相应list的情况。
- 结合上述两点分析,ref_pic_id的确指向ref_pic_num中的相应元素。
- 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
- ref_pic_num
函数decode_one_slice()在一开始就调用了函数set_ref_pic_num()计算ref_pic_num。函数set_ref_pic_num()的定义在image.c里:
{
...
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.264里P slice和B slice的参考列表计数的序号是不同的:P slice采用的是由frame_num推导得到的PicNum;B slice采用的是POC。而如上代码所示,这里ref_pic_num则给出了一种统一的计数方式,无论是P slice还是B slice,都基于POC来得到参考图像的序号值。以上是我个人的理解。
为什么要采用poc * 2 + BOTTOM_FIELD ? 1 : 0,这是一个没能完全确定的问题。原因应当是与Picture AFF有关,但目前还没彻底想通。
- ref_idx和ref_pic_id
在macroblock.c中,init_macroblock()函数将当前宏块中每一个4x4块对应的ref_idx和ref_pic_id都置为-1和NA值。
{
...
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]];
...
- ref_id
本节说明ref_id的作用。在整个工程中搜索ref_id,发现搜到的结果全部都在mbuffer.c和mbuffer.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拆分成field、filed合并成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:ref_idx是参考队列索引,是H.264标准规定的语法元素。
结论2:ref_pic_num是保存了当前所有的参考列表信息。无论是P slice还是B slice,ref_pic_num其中的参考图像序号都是基于POC计算出来的。
结论3:ref_pic_id是指针,对于当前解(编)码的4x4块,ref_pic_id指向ref_pic_num中相应的数值。
结论4:ref_id是一个中间变量。
问题1:ref_pic_num采用计算公式poc * 2 + BOTTOM_FIELD ? 1 : 0的原因。
问题2:ref_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倍
- 场编码, 编码序列为IPBPB...
三个图的顺序和上面的帧编码是一样的
我们从这儿可以看到frame_poc和poc之间的差别, 应该就类似与frame_poc是min (toppoc, bottompoc)
xkfz007
2011年9月7日 15:39:08