JM86中多参考帧相关问题
我们知道在参考图像队列中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本身应该没有关系的。
在参考队列重排序的代码中, 我们可以看到原因。
我在注释中写了,大家自己理解下。大家可以运行下面我的简化版程序
本文参考了网上的一篇文章<<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;
根据这个几个变量的声明可以得到如下信息:
这里给出一个编解码实例,全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。
这是另一个全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
函数decode_one_slice()在一开始就调用了函数set_ref_pic_num()计算ref_pic_num。函数set_ref_pic_num()的定义在image.c里:
这里首先讨论一个问题,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有关,但目前还没彻底想通。
在macroblock.c中,init_macroblock()函数将当前宏块中每一个4x4块对应的ref_idx和ref_pic_id都置为-1和NA值。
之后在函数read_one_macroblock()和decode_one_macroblock()中ref_idx被赋值和使用(这个比较简单,不再详述)。
在函数decode_one_macroblock()中可以找到ref_pic_id指向ref_pic_num的代码:
本节说明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()中赋值,代码类似于如下:
这三个函数都是与frame拆分成field、filed合并成frame相关。因此推测ref_id在这里起到中间作用。
另一部分在函数compute_colocated()中,例如:
因此推测在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倍
三个图的顺序和上面的帧编码是一样的
我们从这儿可以看到frame_poc和poc之间的差别, 应该就类似与frame_poc是min (toppoc, bottompoc)
xkfz007
2011年9月7日 15:39:08