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

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

  1. /*! 
  2.  ************************************************************************ 
  3.  * \brief 
  4.  *    Initialize listX[0] and list 1 depending on current picture type 
  5.  * 
  6.  ************************************************************************ 
  7.  */  
  8. void init_lists(int currSliceType, PictureStructure currPicStructure)  
  9. {  
  10.   int add_top = 0, add_bottom = 0;  
  11.   unsigned i;  
  12.   int j;  
  13.   int MaxFrameNum = 1 << (log2_max_frame_num_minus4 + 4); //!< 定義了frame_num的最大值  
  14.   int diff;  
  15.   
  16.   int list0idx = 0;  
  17.   int list0idx_1 = 0;  
  18.   int listltidx = 0;  
  19.   
  20.   FrameStore **fs_list0;  
  21.   FrameStore **fs_list1;  
  22.   FrameStore **fs_listlt;  
  23.   
  24.   StorablePicture *tmp_s;  
  25.   
  26.   if (currPicStructure == FRAME) //!< 幀模式   
  27.   {  
  28.     for (i=0; i<dpb.ref_frames_in_buffer; i++) //!< 遍歷dpb中所有的參考幀(包括短期和長期參考幀)  
  29.     {  
  30.       if (dpb.fs_ref[i]->is_used==3) //!< is_used=3: both fields (or frame)  
  31.       {  
  32.         if ((dpb.fs_ref[i]->frame->used_for_reference)&&(!dpb.fs_ref[i]->frame->is_long_term)) //!< 處理被用作參考且短期參考幀  
  33.         {  
  34.           if( dpb.fs_ref[i]->frame_num > img->frame_num )  
  35.           {  
  36.             dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num - MaxFrameNum;  
  37.           }  
  38.           else  
  39.           {  
  40.             dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num;  
  41.           }  
  42.           dpb.fs_ref[i]->frame->pic_num = dpb.fs_ref[i]->frame_num_wrap;  
  43.         }  
  44.       }  
  45.     }  
  46.     // update long_term_pic_num  
  47.     for (i=0; i<dpb.ltref_frames_in_buffer; i++) //!< 遍歷dpb裏的長期參考幀  
  48.     {  
  49.       if (dpb.fs_ltref[i]->is_used==3)  
  50.       {  
  51.         if (dpb.fs_ltref[i]->frame->is_long_term) //!< 處理長期參考幀  
  52.         {  
  53.           dpb.fs_ltref[i]->frame->long_term_pic_num = dpb.fs_ltref[i]->frame->long_term_frame_idx;  
  54.         }  
  55.       }  
  56.     }  
  57.   }  
  58.   else //!< 場模式(略過)  
  59.   {  
  60.     ... ...  
  61.   }  
  62.   
  63.   //!< 將dpb中參考幀寫入ListX中去  
  64.   
  65.   if ((currSliceType == I_SLICE)||(currSliceType == SI_SLICE)) //!< I幀和SI幀不需要參考幀列表  
  66.   {  
  67.     listXsize[0] = 0;  
  68.     listXsize[1] = 0;  
  69.     return;  
  70.   }  
  71.   
  72.   if ((currSliceType == P_SLICE)||(currSliceType == SP_SLICE)) //!< P幀和SP幀參考幀列表的初始化  
  73.   {  
  74.     // Calculate FrameNumWrap and PicNum  
  75.     if (currPicStructure == FRAME)  //!< 幀模式  
  76.     {  
  77.       for (i=0; i<dpb.ref_frames_in_buffer; i++)  
  78.       {  
  79.         if (dpb.fs_ref[i]->is_used==3)  
  80.         {  
  81.           if ((dpb.fs_ref[i]->frame->used_for_reference)&&(!dpb.fs_ref[i]->frame->is_long_term))  
  82.           {  
  83.             listX[0][list0idx++] = dpb.fs_ref[i]->frame; //!< 短期參考幀存入參考幀列表listX[0]  
  84.           }  
  85.         }  
  86.       }  
  87.       // order list 0 by PicNum //!< 對短期參考幀進行降序排列  
  88.       qsort((void *)listX[0], list0idx, sizeof(StorablePicture*), compare_pic_by_pic_num_desc);  
  89.      listXsize[0] = list0idx; //!< 短期參考幀的數量  
  90. //      printf("listX[0] (PicNum): "); for (i=0; i<list0idx; i++){printf ("%d  ", listX[0][i]->pic_num);} printf("\n");  
  91.   
  92.       // long term handling  
  93.       for (i=0; i<dpb.ltref_frames_in_buffer; i++)  
  94.       {  
  95.         if (dpb.fs_ltref[i]->is_used==3)  
  96.         {  
  97.           if (dpb.fs_ltref[i]->frame->is_long_term)  
  98.           {  
  99.             listX[0][list0idx++]=dpb.fs_ltref[i]->frame; //!< 長期參考幀存入參考幀列表listX[0]  
  100.           }  
  101.         }  
  102.       }  
  103.       //!< 對長期參考幀進行升序排列  
  104.       qsort((void *)&listX[0][listXsize[0]], list0idx-listXsize[0], sizeof(StorablePicture*), compare_pic_by_lt_pic_num_asc);  
  105.      listXsize[0] = list0idx; //!< 更新listX[0]的長度,此時包括了短期和長期參考幀的數量  
  106.     }  
  107.     else //!< 場模式(略過)  
  108.     {  
  109.       ... ...   
  110.     }  
  111.     listXsize[1] = 0; //!< listX[1]在P幀和SP幀中不使用,長度置0  
  112.   }  
  113.   else //!< B幀(略過)  
  114.   {  
  115.     ... ...   
  116.   }   
  117.   
  118.   if ((listXsize[0] == listXsize[1]) && (listXsize[0] > 1))  
  119.   {  
  120.     // check if lists are identical, if yes swap first two elements of listX[1]  
  121.     diff=0;  
  122.     for (j = 0; j< listXsize[0]; j++) //!< 檢查listX[0]和listX[1]是否相同  
  123.     {  
  124.       if (listX[0][j]!=listX[1][j])  
  125.         diff=1;  
  126.     }  
  127.     if (!diff) //!< 如果兩個列表相同,則交換listX[1]前兩個元素的位置  
  128.     {  
  129.       tmp_s = listX[1][0];  
  130.       listX[1][0]=listX[1][1];  
  131.       listX[1][1]=tmp_s;  
  132.     }  
  133.   }  
  134.   
  135.   // set max size  
  136.   listXsize[0] = min (listXsize[0], img->num_ref_idx_l0_active);  
  137.   listXsize[1] = min (listXsize[1], img->num_ref_idx_l1_active);  
  138.   
  139.   // set the unused list entries to NULL  
  140.   for (i=listXsize[0]; i< (MAX_LIST_SIZE) ; i++)  
  141.   {  
  142.     listX[0][i] = NULL;  
  143.   }  
  144.   for (i=listXsize[1]; i< (MAX_LIST_SIZE) ; i++)  
  145.   {  
  146.     listX[1][i] = NULL;  
  147.   }  
  148. }  

上述代碼中,有個函數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)調用。

  1. <span style="font-size:12px;">/*! 
  2.  ************************************************************************ 
  3.  * \brief 
  4.  *    Reordering process for reference picture lists 
  5.  * 
  6.  ************************************************************************ 
  7.  */  
  8. 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)  
  9. {  
  10.   int i;  
  11.   
  12.   int maxPicNum, currPicNum, picNumLXNoWrap, picNumLXPred, picNumLX;  
  13.   int refIdxLX = 0;  
  14.   
  15.   if (img->structure==FRAME) //!< 幀模式  
  16.   {  
  17.     maxPicNum  = img->MaxFrameNum; //!< frame_num的最大值  
  18.     currPicNum = img->frame_num; //!< 當前幀的frame_num  
  19.   }  
  20.   else  
  21.   {  
  22.     maxPicNum  = 2 * img->MaxFrameNum;  
  23.     currPicNum = 2 * img->frame_num + 1;  
  24.   }  
  25.   
  26.   picNumLXPred = currPicNum; //!< 預測值變量  
  27.   
  28.   for (i=0; reordering_of_pic_nums_idc[i]!=3; i++) //!< 重排序操作循環  
  29.   {  
  30.     if (reordering_of_pic_nums_idc[i]>3) //!< 該句法元素取值範圍爲0~3,3表示結束循環,退出重排序操作  
  31.       error ("Invalid remapping_of_pic_nums_idc command", 500);  
  32.   
  33.     if (reordering_of_pic_nums_idc[i] < 2)   //!< 短期參考幀重排序  
  34.     {  
  35.       if (reordering_of_pic_nums_idc[i] == 0) //!< 當前幀的PicNum減去(<span style="font-size:12px;">abs_diff_pic_num_minus1[i] + 1</span>)指明需要重排序的圖像  
  36.       {  
  37.         if( picNumLXPred - ( abs_diff_pic_num_minus1[i] + 1 ) < 0 )  //!< 預測值小於實際值  
  38.           picNumLXNoWrap = picNumLXPred - ( abs_diff_pic_num_minus1[i] + 1 ) + maxPicNum; //!< frame_num循環計數  
  39.         else  
  40.           picNumLXNoWrap = picNumLXPred - ( abs_diff_pic_num_minus1[i] + 1 );  
  41.       }  
  42.       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>  
  43.       {  
  44.         if( picNumLXPred + ( abs_diff_pic_num_minus1[i] + 1 )  >=  maxPicNum )   //!< 預測值大於實際值  
  45.           picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1[i] + 1 ) - maxPicNum; //!< frame_num循環計數  
  46.         else  
  47.           picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1[i] + 1 );  
  48.       }  
  49.       picNumLXPred = picNumLXNoWrap;  
  50.   
  51.       if( picNumLXNoWrap > currPicNum )  
  52.         picNumLX = picNumLXNoWrap - maxPicNum;  
  53.       else  
  54.         picNumLX = picNumLXNoWrap;  
  55.       //!< picNumLX存放的是需要移至refIdxLX位置的短期參考圖像的PicNum  
  56.       reorder_short_term(list, num_ref_idx_lX_active_minus1, picNumLX, &refIdxLX); //!< 調用該函數實現短期參考幀的重排序  
  57.     }  
  58.     else //(remapping_of_pic_nums_idc[i] == 2)  //!< 長期參考幀重排序  
  59.     {//!< long_term_pic_idx[i]存放的是需要移至refIdxLX位置的短期參考圖像的序號  
  60.       reorder_long_term(list, num_ref_idx_lX_active_minus1, long_term_pic_idx[i], &refIdxLX); //!< 調用該函數實現長期參考幀的重排序  
  61.     }      
  62.   }  
  63.   // that's a definition  
  64.   *list_size = num_ref_idx_lX_active_minus1 + 1;  
  65. }</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)來完成這項工作。

  1. <span style="font-size:12px;">/*! 
  2.  ************************************************************************ 
  3.  * \brief 
  4.  *    Store a picture in DPB. This includes cheking for space in DPB and  
  5.  *    flushing frames. 
  6.  *    If we received a frame, we need to check for a new store, if we 
  7.  *    got a field, check if it's the second field of an already allocated 
  8.  *    store. 
  9.  * 
  10.  * \param p 
  11.  *    Picture to be stored 
  12.  * 
  13.  ************************************************************************ 
  14.  */  
  15. void store_picture_in_dpb(StorablePicture* p)  
  16. {  
  17.   unsigned i;  
  18.   int poc, pos;  
  19.   // diagnostics  
  20.   //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);  
  21.   // if frame, check for new store,   
  22.   assert (p!=NULL);  
  23.   
  24.   p->used_for_reference = (img->nal_reference_idc != 0);  
  25.     
  26.   img->last_has_mmco_5=0;  
  27.   img->last_pic_bottom_field = (img->structure == BOTTOM_FIELD);  
  28.   
  29.   if (img->currentPicture->idr_flag)  
  30.     idr_memory_management(p); //!< IDR圖像的標記過程  
  31.   else  
  32.   {  
  33.     // adaptive memory management  
  34.     if (p->used_for_reference && (img->adaptive_ref_pic_buffering_flag))  
  35.       adaptive_memory_management(p); //!< 自適應標記過程  
  36.   }  
  37.   
  38.   if ((p->structure==TOP_FIELD)||(p->structure==BOTTOM_FIELD)) //!< 場模式(略過)  
  39.   {  
  40.     ... ...  
  41.   }  
  42.     
  43.   // this is a frame or a field which has no stored complementary field  
  44.   
  45.   // sliding window, if necessary  
  46.   if ((!img->currentPicture->idr_flag)&&(p->used_for_reference && (!img->adaptive_ref_pic_buffering_flag)))  
  47.   {  
  48.     sliding_window_memory_management(p); //!< 滑窗標記過程  
  49.   }   
  50.   
  51.   // first try to remove unused frames  
  52.   if (dpb.used_size==dpb.size) //!< 當緩衝已滿時,刪除掉dpb中已經輸出到文件且不被參考的幀  
  53.   {  
  54.     remove_unused_frame_from_dpb();  
  55.   }  
  56.     
  57.   // then output frames until one can be removed  
  58.   while (dpb.used_size==dpb.size)  
  59.   {  
  60.     // non-reference frames may be output directly  
  61.     if (!p->used_for_reference) //!< 如果當前幀不作爲參考幀,可以直接輸出到重建文件序列中  
  62.     {  
  63.       get_smallest_poc(&poc, &pos);  
  64.       if ((-1==pos) || (p->poc < poc))  
  65.       {  
  66.         direct_output(p, p_dec); //!< 直接輸出當前幀而不保存到dpb中  
  67.         return;  
  68.       }  
  69.     }  
  70.     // flush a frame  
  71.     output_one_frame_from_dpb(); //!< 輸出一幀到文件  
  72.   }  
  73.     
  74.   // check for duplicate frame number in short term reference buffer  
  75.   if ((p->used_for_reference)&&(!p->is_long_term))  
  76.   {  
  77.     for (i=0; i<dpb.ref_frames_in_buffer; i++)  
  78.     {  
  79.       if (dpb.fs_ref[i]->frame_num == p->frame_num)  
  80.       {  
  81.         error("duplicate frame_num im short-term reference picture buffer", 500);  
  82.       }  
  83.     }  
  84.   
  85.   }  
  86.   // store at end of buffer  
  87. //  printf ("store frame/field at pos %d\n",dpb.used_size);  
  88.   insert_picture_in_dpb(dpb.fs[dpb.used_size],p); //!< 將當前解碼幀插入到dpb尾部  
  89.     
  90.   if (p->structure != FRAME)  
  91.   {  
  92.     dpb.last_picture = dpb.fs[dpb.used_size];  
  93.   }  
  94.   else  
  95.   {  
  96.     dpb.last_picture = NULL;  
  97.   }  
  98.   
  99.   dpb.used_size++; //!< 更新dpb中參考幀的計數  
  100.   
  101.   update_ref_list(); //!< 更新短期參考幀列表  
  102.   update_ltref_list(); //!< 更新長期參考幀列表  
  103.   
  104.   check_num_ref();  
  105.   
  106.   dump_dpb();  
  107. }</span>  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章