H.264参考帧列表的管理主要包括参考帧列表的初始化、参考帧列表的重排序和参考图像的标记这三个步骤,关于它们的具体内容,已经在我转载的一篇博客H.264解码器中参考图像的管理 有了详细的介绍了,这里不再重复,本文主要是结合具体代码对这个过程进行解析。此外,本文只分析P帧(帧方式)下的情况,场方式、B帧讨论起来比较繁琐,大家可以在P帧(帧方式)理解的基础上进一步对更为复杂的情况进行分析。相关函数的实现主要集中在mbuffer.c中。
(1)参考帧列表的初试化
主要在函数void init_lists(int currSliceType, PictureStructure currPicStructure)里实现。在编码端被init_slice(int start_mb_addr)调用,在解码端被read_new_slice()调用。
- /*!
- ************************************************************************
- * \brief
- * Initialize listX[0] and list 1 depending on current picture type
- *
- ************************************************************************
- */
- void init_lists(int currSliceType, PictureStructure currPicStructure)
- {
- int add_top = 0, add_bottom = 0;
- unsigned i;
- int j;
- int MaxFrameNum = 1 << (log2_max_frame_num_minus4 + 4); //!< 定义了frame_num的最大值
- int diff;
- int list0idx = 0;
- int list0idx_1 = 0;
- int listltidx = 0;
- FrameStore **fs_list0;
- FrameStore **fs_list1;
- FrameStore **fs_listlt;
- StorablePicture *tmp_s;
- if (currPicStructure == FRAME) //!< 帧模式
- {
- for (i=0; i<dpb.ref_frames_in_buffer; i++) //!< 遍历dpb中所有的参考帧(包括短期和长期参考帧)
- {
- if (dpb.fs_ref[i]->is_used==3) //!< is_used=3: both fields (or frame)
- {
- if ((dpb.fs_ref[i]->frame->used_for_reference)&&(!dpb.fs_ref[i]->frame->is_long_term)) //!< 处理被用作参考且短期参考帧
- {
- if( dpb.fs_ref[i]->frame_num > img->frame_num )
- {
- dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num - MaxFrameNum;
- }
- else
- {
- dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num;
- }
- dpb.fs_ref[i]->frame->pic_num = dpb.fs_ref[i]->frame_num_wrap;
- }
- }
- }
- // update long_term_pic_num
- for (i=0; i<dpb.ltref_frames_in_buffer; i++) //!< 遍历dpb里的长期参考帧
- {
- 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;
- }
- }
- }
- }
- else //!< 场模式(略过)
- {
- ... ...
- }
- //!< 将dpb中参考帧写入ListX中去
- if ((currSliceType == I_SLICE)||(currSliceType == SI_SLICE)) //!< I帧和SI帧不需要参考帧列表
- {
- listXsize[0] = 0;
- listXsize[1] = 0;
- return;
- }
- if ((currSliceType == P_SLICE)||(currSliceType == SP_SLICE)) //!< P帧和SP帧参考帧列表的初始化
- {
- // 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; //!< 短期参考帧存入参考帧列表listX[0]
- }
- }
- }
- // 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)
- {
- listX[0][list0idx++]=dpb.fs_ltref[i]->frame; //!< 长期参考帧存入参考帧列表listX[0]
- }
- }
- }
- //!< 对长期参考帧进行升序排列
- qsort((void *)&listX[0][listXsize[0]], list0idx-listXsize[0], sizeof(StorablePicture*), compare_pic_by_lt_pic_num_asc);
- listXsize[0] = list0idx; //!< 更新listX[0]的长度,此时包括了短期和长期参考帧的数量
- }
- else //!< 场模式(略过)
- {
- ... ...
- }
- listXsize[1] = 0; //!< listX[1]在P帧和SP帧中不使用,长度置0
- }
- else //!< B帧(略过)
- {
- ... ...
- }
- if ((listXsize[0] == listXsize[1]) && (listXsize[0] > 1))
- {
- // check if lists are identical, if yes swap first two elements of listX[1]
- diff=0;
- for (j = 0; j< listXsize[0]; j++) //!< 检查listX[0]和listX[1]是否相同
- {
- if (listX[0][j]!=listX[1][j])
- diff=1;
- }
- if (!diff) //!< 如果两个列表相同,则交换listX[1]前两个元素的位置
- {
- tmp_s = listX[1][0];
- listX[1][0]=listX[1][1];
- listX[1][1]=tmp_s;
- }
- }
- // set max size
- listXsize[0] = min (listXsize[0], img->num_ref_idx_l0_active);
- listXsize[1] = min (listXsize[1], img->num_ref_idx_l1_active);
- // set the unused list entries to NULL
- for (i=listXsize[0]; i< (MAX_LIST_SIZE) ; i++)
- {
- listX[0][i] = NULL;
- }
- for (i=listXsize[1]; i< (MAX_LIST_SIZE) ; i++)
- {
- listX[1][i] = NULL;
- }
- }
上述代码中,有个函数qsort,关于它的用法,在我的另一篇博客qsort 函数的使用 有简单的介绍。
(2)参考帧列表的重排序
主要在函数reorder_ref_pic_list(StorablePicture **list, int *list_size, int num_ref_idx_lX_active_minus1, int *reordering_of_pic_nums_idc, int *abs_diff_pic_num_minus1, int *long_term_pic_idx)中实现,在编码端被 init_slice (int start_mb_addr)调用,在解码端被read_new_slice()中的子函数reorder_lists(int currSliceType, Slice * currSlice)调用。
- <span style="font-size:12px;">/*!
- ************************************************************************
- * \brief
- * Reordering process for reference picture lists
- *
- ************************************************************************
- */
- void reorder_ref_pic_list(StorablePicture **list, int *list_size, int num_ref_idx_lX_active_minus1, int *reordering_of_pic_nums_idc, int *abs_diff_pic_num_minus1, int *long_term_pic_idx)
- {
- int i;
- int maxPicNum, currPicNum, picNumLXNoWrap, picNumLXPred, picNumLX;
- int refIdxLX = 0;
- if (img->structure==FRAME) //!< 帧模式
- {
- maxPicNum = img->MaxFrameNum; //!< frame_num的最大值
- currPicNum = img->frame_num; //!< 当前帧的frame_num
- }
- else
- {
- maxPicNum = 2 * img->MaxFrameNum;
- currPicNum = 2 * img->frame_num + 1;
- }
- picNumLXPred = currPicNum; //!< 预测值变量
- for (i=0; reordering_of_pic_nums_idc[i]!=3; i++) //!< 重排序操作循环
- {
- if (reordering_of_pic_nums_idc[i]>3) //!< 该句法元素取值范围为0~3,3表示结束循环,退出重排序操作
- error ("Invalid remapping_of_pic_nums_idc command", 500);
- if (reordering_of_pic_nums_idc[i] < 2) //!< 短期参考帧重排序
- {
- if (reordering_of_pic_nums_idc[i] == 0) //!< 当前帧的PicNum减去(<span style="font-size:12px;">abs_diff_pic_num_minus1[i] + 1</span>)指明需要重排序的图像
- {
- if( picNumLXPred - ( abs_diff_pic_num_minus1[i] + 1 ) < 0 ) //!< 预测值小于实际值
- picNumLXNoWrap = picNumLXPred - ( abs_diff_pic_num_minus1[i] + 1 ) + maxPicNum; //!< frame_num循环计数
- else
- picNumLXNoWrap = picNumLXPred - ( abs_diff_pic_num_minus1[i] + 1 );
- }
- else // (remapping_of_pic_nums_idc[i] == 1) //!< <span style="font-size:12px;">当前帧的PicNum加上(<span style="BACKGROUND-COLOR: #f0f0f0">abs_diff_pic_num_minus1[i] + 1</span>)指明需要重排序的图像</span>
- {
- if( picNumLXPred + ( abs_diff_pic_num_minus1[i] + 1 ) >= maxPicNum ) //!< 预测值大于实际值
- picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1[i] + 1 ) - maxPicNum; //!< frame_num循环计数
- else
- picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1[i] + 1 );
- }
- picNumLXPred = picNumLXNoWrap;
- if( picNumLXNoWrap > currPicNum )
- picNumLX = picNumLXNoWrap - maxPicNum;
- else
- picNumLX = picNumLXNoWrap;
- //!< picNumLX存放的是需要移至refIdxLX位置的短期参考图像的PicNum
- reorder_short_term(list, num_ref_idx_lX_active_minus1, picNumLX, &refIdxLX); //!< 调用该函数实现短期参考帧的重排序
- }
- else //(remapping_of_pic_nums_idc[i] == 2) //!< 长期参考帧重排序
- {//!< long_term_pic_idx[i]存放的是需要移至refIdxLX位置的短期参考图像的序号
- reorder_long_term(list, num_ref_idx_lX_active_minus1, long_term_pic_idx[i], &refIdxLX); //!< 调用该函数实现长期参考帧的重排序
- }
- }
- // that's a definition
- *list_size = num_ref_idx_lX_active_minus1 + 1;
- }</span>
(3)参考图像的标记
主要由两种,一种是滑窗标记即FIFO,另一种是自适应标记。
前者由函数sliding_window_memory_management(StorablePicture* p)实现,后者由函数adaptive_memory_management(StorablePicture* p)调用。在编码端,encode_one_frame()的末尾会调用store_picture_in_dpb(StorablePicture* p),从而调用开头的这两个函数进行参考图像的标记;在解码端,主要由exit_picture()通过调用store_picture_in_dpb(StorablePicture* p)来完成这项工作。
- <span style="font-size:12px;">/*!
- ************************************************************************
- * \brief
- * Store a picture in DPB. This includes cheking for space in DPB and
- * flushing frames.
- * If we received a frame, we need to check for a new store, if we
- * got a field, check if it's the second field of an already allocated
- * store.
- *
- * \param p
- * Picture to be stored
- *
- ************************************************************************
- */
- void store_picture_in_dpb(StorablePicture* p)
- {
- unsigned i;
- int poc, pos;
- // diagnostics
- //printf ("Storing (%s) non-ref pic with frame_num #%d\n", (p->type == FRAME)?"FRAME":(p->type == TOP_FIELD)?"TOP_FIELD":"BOTTOM_FIELD", p->pic_num);
- // if frame, check for new store,
- assert (p!=NULL);
- p->used_for_reference = (img->nal_reference_idc != 0);
- img->last_has_mmco_5=0;
- img->last_pic_bottom_field = (img->structure == BOTTOM_FIELD);
- if (img->currentPicture->idr_flag)
- idr_memory_management(p); //!< IDR图像的标记过程
- else
- {
- // adaptive memory management
- if (p->used_for_reference && (img->adaptive_ref_pic_buffering_flag))
- adaptive_memory_management(p); //!< 自适应标记过程
- }
- if ((p->structure==TOP_FIELD)||(p->structure==BOTTOM_FIELD)) //!< 场模式(略过)
- {
- ... ...
- }
- // this is a frame or a field which has no stored complementary field
- // sliding window, if necessary
- if ((!img->currentPicture->idr_flag)&&(p->used_for_reference && (!img->adaptive_ref_pic_buffering_flag)))
- {
- sliding_window_memory_management(p); //!< 滑窗标记过程
- }
- // first try to remove unused frames
- if (dpb.used_size==dpb.size) //!< 当缓冲已满时,删除掉dpb中已经输出到文件且不被参考的帧
- {
- remove_unused_frame_from_dpb();
- }
- // then output frames until one can be removed
- while (dpb.used_size==dpb.size)
- {
- // non-reference frames may be output directly
- if (!p->used_for_reference) //!< 如果当前帧不作为参考帧,可以直接输出到重建文件序列中
- {
- get_smallest_poc(&poc, &pos);
- if ((-1==pos) || (p->poc < poc))
- {
- direct_output(p, p_dec); //!< 直接输出当前帧而不保存到dpb中
- return;
- }
- }
- // flush a frame
- output_one_frame_from_dpb(); //!< 输出一帧到文件
- }
- // check for duplicate frame number in short term reference buffer
- if ((p->used_for_reference)&&(!p->is_long_term))
- {
- for (i=0; i<dpb.ref_frames_in_buffer; i++)
- {
- if (dpb.fs_ref[i]->frame_num == p->frame_num)
- {
- error("duplicate frame_num im short-term reference picture buffer", 500);
- }
- }
- }
- // store at end of buffer
- // printf ("store frame/field at pos %d\n",dpb.used_size);
- insert_picture_in_dpb(dpb.fs[dpb.used_size],p); //!< 将当前解码帧插入到dpb尾部
- if (p->structure != FRAME)
- {
- dpb.last_picture = dpb.fs[dpb.used_size];
- }
- else
- {
- dpb.last_picture = NULL;
- }
- dpb.used_size++; //!< 更新dpb中参考帧的计数
- update_ref_list(); //!< 更新短期参考帧列表
- update_ltref_list(); //!< 更新长期参考帧列表
- check_num_ref();
- dump_dpb();
- }</span>