转:JM8.6之参考图像管理
H.264 参考图像管理综述
总体而言,mbuffer.c文件主要的三个函数是init_lists,reorder_lists和store_picture_in_dpb。
其中init_lists和reorder_lists在read_new_slice函数中,主要是将已经存储在dpb.fs_ref和dpb.fs_ltref,即短期和长期参考队列按照一定的排列规律一起存到listX[0]和listX[1]中
而store_picture_in_dpb在函数exit_picture中调用,即在解码完一帧图像,经过去方块滤波和误码掩盖之后,将图像存储到DPB中。
List_lists()函数
这个函数的功能主要是将已经存储在dpb.fs_ref和dpb.fs_ltref,即短期和长期参考队列按照一定的排列规律一起存到listX[0]和listX[1]中
这里对代码一开始解释两点:
(1). if( dpb.fs_ref[i]->frame_num > img->frame_num )
{
dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num - MaxFrameNum;
}
MaxFrameNum表示frame_num的最大值,frame_num是循环计数的,即当达到MaxFrameNum时再从0开始新一轮的计数。
(2). if (currPicStructure == TOP_FIELD)
{
add_top = 1;
add_bottom = 0;
}
else
{
add_top = 0;
add_bottom = 1;
}
…………
if (dpb.fs_ltref[i]->is_long_term & 1)
{
dpb.fs_ltref[i]->top_field->long_term_pic_num = 2 * dpb.fs_ltref[i]->top_field->long_term_frame_idx + add_top;
}
if (dpb.fs_ltref[i]->is_long_term & 2)
{
dpb.fs_ltref[i]->bottom_field->long_term_pic_num = 2 * dpb.fs_ltref[i]->bottom_field->long_term_frame_idx + add_bottom;
}
这里说明一点,在场模式下,2*frame_num和2*frame_num+1两个值分别赋值给两个场,2*frame_num+1这个值永远赋给当前场。解码到当前场对的下一个场时,刚才被赋为2*frame_num+1的场被重新计算为2*frame_num,而将2*frame_num+1赋给当前场。
接下去就分I_SLICE/SI_SLICE和P_SLICE/SP_SLICE和B_SLICE三类来处理。这里讲一下P_SLICE/SP_SLICE部分。
if ((currSliceType == P_SLICE)||(currSliceType == SP_SLICE)) { // Calculate FrameNumWrap and PicNum if (currPicStructure == FRAME) { for (i=0; i<dpb.ref_frames_in_buffer; i++) { if (dpb.fs_ref[i]->is_used==3) { if ((dpb.fs_ref[i]->frame->used_for_reference)&&(!dpb.fs_ref[i]->frame->is_long_term)) //++ 第二个条件判断说明如果一个帧缓冲中两个场为一个长期参考一短期参考,则该帧缓冲也在短期参考帧缓冲列表中,但其被标记为长期参考 { listX[0][list0idx++] = dpb.fs_ref[i]->frame; } } } // order list 0 by PicNum qsort((void *)listX[0], list0idx, sizeof(StorablePicture*), compare_pic_by_pic_num_desc); listXsize[0] = list0idx; // printf("listX[0] (PicNum): "); for (i=0; i<list0idx; i++){printf ("%d ", listX[0][i]->pic_num);} printf("\n");
// long term handling for (i=0; i<dpb.ltref_frames_in_buffer; i++) { if (dpb.fs_ltref[i]->is_used==3) { if (dpb.fs_ltref[i]->frame->is_long_term) { dpb.fs_ltref[i]->frame->long_term_pic_num = dpb.fs_ltref[i]->frame->long_term_frame_idx; dpb.fs_ltref[i]->frame->order_num=list0idx; listX[0][list0idx++]=dpb.fs_ltref[i]->frame; } } } qsort((void *)&listX[0][listXsize[0]], list0idx-listXsize[0], sizeof(StorablePicture*), compare_pic_by_lt_pic_num_asc); listXsize[0] = list0idx; } else { //场的情况 。。。。。 } listXsize[1] = 0; } |
可以看到,先将dpb.fs_ref[i]短期参考帧赋值给listX[0],接着就是通过qsort函数将listX[0]中的短期参考帧按pic_num值降序排列,接着就是将dpb.fs_ltref[i]长期参考帧中的长期参考帧按照long_term_pic_num升序排列。最后更新listXsize[0] = list0idx;
reorder_lists函数
例如现在的序列是1 2 3 4 5,假设现在4是需要重新排序的,那么经过操作后,就会变为4 1 2 3 5.
在reorder_list中主要是调用了reorder_ref_pic_list,我们来看看这个函数:
if (img->structure==FRAME)
{
maxPicNum = img->MaxFrameNum;
currPicNum = img->frame_num;
}
else
{
maxPicNum = 2 * img->MaxFrameNum;
currPicNum = 2 * img->frame_num + 1;
}
我前面所说,2*img->frame+1永远是赋给当前场的PicNum,在读这段代码时,参考毕书179页的表7.23。同样,PicNum也有循环计数的性质。
接着就是根据短期和长期分别调用函数reorder_short_term和reorder_long_term,接着看reorder_short_term:
picLX = get_short_term_pic(picNumLX);
for( cIdx = num_ref_idx_lX_active_minus1+1; cIdx > *refIdxLX; cIdx-- ) RefPicListX[ cIdx ] = RefPicListX[ cIdx - 1];
RefPicListX[ (*refIdxLX)++ ] = picLX;
nIdx = *refIdxLX;
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 ]; |
一开始通过picLX在短期参考队列中找到需要重新排列的图像。
for( cIdx = num_ref_idx_lX_active_minus1+1; cIdx > *refIdxLX; cIdx-- )
RefPicListX[ cIdx ] = RefPicListX[ cIdx - 1];
假设原本的排列是1 2 3 4 5 Null,其中4是需要重新排列的,那么经过上面的for循环之后就变为4 1 2 3 5
说到这儿,顺便说一下#define MAX_LIST_SIZE 33
为什么最大序列的值是33呢?我们知道在H.264中,规定最多有16个参考帧,即使在场模式下最多也只要32,但程序中却定义了33. 这个多出的一位,我想就是从排列时操作所需要的,通过上面的例子NULL->5
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 ];
经过上面的操作,序列变为4 1 2 3 5 5(最后一个5是多余的,即多出的一位)
为什么要进行重拍序呢?节省码流。当一个序号ref_idx比较大的参考图像被用到概率比较大时,这个时候将该参考图像的序号调小(即排列第一)可以降低码流。
store_picture_in_dpb 函数的大致结构图
参考:http://blog.csdn.net/zhangji1983/article/details/1504778
自己写的东西扫描了下,可能有理解错误的,请大家指出。第三和第四张没有扫描好,请大家谅解。大家可以结合毕书或标准看,这部分还是比较详细的,只是代码长了点。