程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> H.264參考幀列表管理分析 —— JM中相關函數解析(上)

H.264參考幀列表管理分析 —— JM中相關函數解析(上)

編輯:C++入門知識

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()調用。

[cpp] 
/*!
 ************************************************************************
 * \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)調用。

[cpp] 
<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)來完成這項工作。

[cpp] 
<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> 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved