H.264參考幀列表、解碼圖像緩存

1.參考圖像列表(reference picture list)

一般來說,h.264會把需要編碼的圖像分爲三種類型:I、P、B,其中的B、P類型的圖像由於採用了幀間編碼的這種編碼方式,而幀間編碼又是以參考圖像爲基礎進行的,因此需要有個參考圖像列表來管理之前生成的參考圖像,方便用於對當前圖像進行編碼。

2.解碼圖像緩存(decoded picture buffer)

隨着圖像編碼的進行,(解碼階段)會不斷有新的圖像生成(重建圖像),已解碼圖像會被放到解碼圖像緩存區中(或直接輸出,這個在下面DPB講)。但是由於內存大小與效率的限制,不可能一直保存參考圖像而不丟棄,所以會有個對解碼圖像緩存這塊內存區的管理策略。

 

 

參考圖像列表

1. 來源

參考圖像列表中的參考圖像來自於解碼圖像緩存(DPB)中的已解碼參考圖像,DPB中可能包含參考以及非參考圖像,參考圖像列表把DPB中的參考圖像整合成列表(數組)的形式,便於後續的排序等操作

 

2. 結構

組成

如標準8.4.1.2.3的注1所說:

  • 如果當前宏塊是場宏塊(mb_field),則refIdxL0和refIdxL1指的是一系列的場;
  • 如果當前宏塊是幀宏塊(!mb_field),則refIdxL0和refIdxL1指的是一系列的幀或者互補場對

 

但是參考圖像列表的排列方式是不可能根據每個宏塊的幀場時時刻刻做出改變,參考圖像列表當前圖像編碼前就通過圖像的幀場屬性確定下來了:

  • 如果當前圖像爲幀(structure == Frame),參考圖像列表中存放的是一系列的幀或者互補場對
  • 如果當前圖像爲場(structure == Field),參考圖像列表中存放的是一系列的場

這就造成了當採用的是mbaff,且當前宏塊爲mb_field的時候參考圖像列表的幀場不匹配,標準中的解決方法是:先得到參考幀RefPicListX[ refIdxLX / 2 ],然後選擇該參考幀中對應的場進行編碼(8.4.2.1),而JM則採用了一種巧妙的方法:如果當前圖像是mbaff,則專門構造另外存放場參考圖像列表RefPicListX(X = 2,3,4,5)方便進行提取參考場(JM中的list_offset就是用於決定X的值)。

 

長度

參考圖像列表的最大長度取決於當前宏塊是以何種方式進行編碼

  • 如果當前宏塊爲幀宏塊(純幀圖像 或 mbaff && !mb_field),默認情況下參考圖像列表的長度爲16,也可以通過num_ref_idx_l0_active_minus1指定最大長度爲(num_ref_idx_l0_active_minus1 + 1)
  • 如果當前宏塊爲場宏塊(場圖像 或 mbaff && mb_field),默認情況下參考圖像列表長度爲32,也可以通過num_ref_idx_l0_active_minus1指定長度爲(2 * num_ref_idx_l0_active_minus1 + 2)

——標準7.4.5.2

參考圖像列表中的條目數目可能存在大於,或小於等於最大長度的情況:

  • 如果條目數大於最大長度,將刪除最大長度之外位置的條目
  • 如果條目數小於最大長度,將把空餘的條目設置爲“非參考圖像”

——標準8.2.4.2

 

3. 排序

參考圖像分爲短期參考圖像(short-term reference)與長期參考圖像(long-term reference),在某一個時間點上,參考圖像只能是這兩種的其中一種(非短即長)。參考圖像列表分爲兩個部分:短期參考部分,長期參考部分。短期參考部分排在列表前頭,長期排在後面。

P幀排序

一般來說,距離當前圖像最近的參考圖像會被當前圖像用作最多的參考,距離越遠則參考得越少,短期參考圖像列表就是依據這種規律來進行排序的。

短期參考圖像的序號由FrameNumWrap進行標記,以GOP爲一個週期,GOP的第一幀的FrameNumWrap爲0,後面持續遞增,到當前幀是FrameNumWrap爲最大,因此FrameNumWrap越大代表距離當前圖像越近,因此將參考幀以FrameNumWrap降序方式放在refPicList0的起始位置

——標準8.2.4.1

也可以參考h.264的POC計算

長期參考圖像的序號由LongTermPicNum進行標記,以升序的方式進行排序,(LongTermPicNum由MMCO分配)

 

 

 

 

B幀排序

B幀的參考幀排列方式與P幀並不完全相同,B幀的短期參考幀是以POC進行排序,其中有前向參考列表refPicList0與後向參考列表refPicList1

  • 當參考幀的POC小於當前圖像的POC時,將參考幀以POC降序方式放在refPicList0的起始位置上。然後剩餘的短期參考圖像按照POC升序的方式附加到refPicList0
  • 當參考幀的POC大於當前圖像的POC時,將參考幀以POC升序方式放在refPicList1的起始位置上。然後剩餘的短期參考圖像按照POC降序的方式附加到refPicList1

——標準8.2.4.2.3

長期參考圖像的序號由LongTermPicNum進行標記,以升序的方式進行排序,分別放進refPicList0與refPicList1中(LongTermPicNum由MMCO分配)

注:一個未配對的場不能用於幀間預測,即表明如果當前爲幀,那麼其參考幀不可能爲一個單一的場

 

 

 

場的處理

無論是P還是B,如果當前圖像是場編碼時,需要對上述的步驟做進一步處理,即把列表內的幀分爲兩個場,從與當前場具有相同奇偶性的場(如果存在)開始,對參考場進行排序

  • 如果當前場爲Top_field,則列表中爲|top|bottom|top|bottom..
  • 如果當前場爲Bottom_field,則列表中爲|bottom|top|bottom|top..
  • 如果其中某一幀缺少了某個場作爲參考場,那麼將忽略該缺少的場

——標準8.2.4.2.5

 

4. 參考圖像列表重排序

在某些特殊情況下會需要把已經得到的參考圖像列表重新排序,可以參考JM18.6的HierarchicalCoding,下面主要演示重排序過程:

 

 

 

  • 當中的紫灰色格子爲重排序操作,對於短期參考圖像,重拍序有符號與操作數,對於長期參考圖像,直接通過操作數指定圖像;
  • 後面還沒進行到的列表項會被後移;
  • 劃掉的格子爲重複的圖像,會被消除,如果被消除項後面還有其他項,後面的項會被前移填補前面被消除項的空格;
  • 重排序的初始操作是以當前圖像的FrameNumWrap爲基礎。

——標準8.2.4.3

 

解碼圖像緩存

1.參考圖像標記過程

在當前圖像解碼過後、插入DPB之前,會進行標記過程Decoded reference picture marking process,對先前已經解碼並存在於DPB中的圖像進行標記。該過程會對DPB中已經存在的圖像(當然不包括當前圖像)重新標記。標記過程有兩種情況:

1.當前圖像爲IDR

如果指定了no_output_of_prior_pics_flag,表示DPB中所有的圖像不進行輸出,但是需要從DPB中移除

否則,從DPB中移除所有圖像並輸出到磁盤

2.當前圖像不爲IDR,且指定了adaptive_ref_pic_buffering_flag

表明需要對當前DPB進行重新標記,其中有6種標記方法,即內存管理控制操作(memory management control operation)

 

mmco 參數1 參數2 操作描述
0 - - 結束mmco
1 difference_of_pic_nums_minus1 - 求得短期參考圖像picNumX後,把picNumX標記爲非參考圖像
2 - long_term_pic_num 求得長期參考圖像LongTermPicNum後,把該長期參考圖像標記爲非參考圖像
3 difference_of_pic_nums_minus1 long_term_pic_num 把短期參考圖像picNumX,標記爲長期參考圖像,並賦值當前圖像的LongTermPicNum = long_term_pic_num
4 MaxLongTermFrameIdx - 把LongTermPicNum > MaxLongTermFrameIdx 的長期參考圖像都從DPB移除
5 - - 把DPB中所有的參考圖像移除
6 - long_term_pic_num 把當前參考圖像標記爲長期參考圖像,並賦值當前圖像的LongTermPicNum = long_term_pic_num

 

其中

  • 短期參考圖像序號爲picNumX = CurrPicNum − ( difference_of_pic_nums_minus1 + 1 )
  • 長期參考圖像序號爲LongTermPicNum = long_term_pic_num(也就是說長期參考圖像可以從上表3,6項指定)

注:把圖像作爲參考或者非參考,對於參考場對來說,兩個場必須共同作爲參考圖像或者非參考圖像,也就是說當你想爲一個場標記的時候,其實標準需要你對整個幀(兩場)都進行標記

——標準8.2.5

 

2.DPB

DPB標記完成後,需要把當前圖像插入DPB(如果需要)。

DPB結構

解碼圖像緩存(DPB)是真正管理與存儲圖像數據的地方,凡是已經解碼的圖像,如果需要存儲在內存中,都需要通過DPB進行管理,基本操作包括對解碼圖像的插入與刪除。在JM中有個dpb結構體表示解碼圖像緩存。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//! Decoded Picture Buffer

typedef struct decoded_picture_buffer

{

  FrameStore  **fs;     //參考圖像列表,該指針主要用於管理一幀圖像,其中包括一個幀與兩個場,其成員frame管理幀,top_field與bottom_field會用於管理場

  FrameStore  **fs_ref;  //短期參考圖像列表(當然,在這裏是未經排序的)

  FrameStore  **fs_ltref; //長期參考圖像列表

  unsigned      size;  //dpb最大容量,能容得下多少幀,最大不超過16

  unsigned      used_size; //dpb實際存了多少幀,以幀爲單位,如果只有其中一場,也被看做一幀

  unsigned      ref_frames_in_buffer;//chj fs中用做short-term reference的幀的個數,短期參考幀,遇到下個I(IDR)會清空緩存

  unsigned      ltref_frames_in_buffer; //長期參考幀個數

  int           last_output_poc;  //上一個從dpb輸出的poc序號

  int           max_long_term_pic_idx; //最大長期參考幀個數

 

  int           init_done;//是否已經初始化dpb

 

  FrameStore   *last_picture;//如果當前幀爲場編碼,則用於存其中一場,等待下一場編碼完成後合併

} DecodedPictureBuffer;

 

DPB最大長度    

當然DPB的大小也是有限制的,標準附件A就對DPB大小做出了限定:MaxDpbSize  = Min( 1024 * MaxDPB / ( PicWidthInMbs * FrameHeightInMbs * 384 ), 16 )

——標準附件A.3.2

 

DPB管理策略

另外DPB對解碼圖像的存儲有個策略:更傾向於存儲對後面編碼有用的圖像(也就是參考圖像)。

雖然說DPB中也可以存儲非參考圖像,在DPB沒滿的時候,會無差別地把參考圖像與非參考圖像一併插入DPB中;

但是一旦DPB滿了之後:

如果新重建的圖像爲參考圖像,該參考圖像需要插入DPB

  • 如果DPB中沒有非參考圖像,會按照滑動窗口模式把DPB序號最小的參考圖像移除; (1)
  • 如果DPB中存在非參考圖像,會把DPB中已經輸出到磁盤的非參考圖像移除;  (2)

如果新重建的圖像爲非參考圖像

  • 如果DPB中不存在比當前圖像POC更小的非參考圖像,當前非參考圖像會被直接輸出到磁盤,而不插入DPB; (3)
  • 如果DPB中存在比當前圖像POC更小的非參考圖像,會把DPB中POC最小的參考圖像移除,插入當前非參考圖像;(4)

可以對應下面代碼中的四段:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

void store_picture_in_dpb(StorablePicture* p)

{

  ...

    // sliding window, if necessary  (1)

  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 (2)

  if (dpb.used_size==dpb.size)

  {

    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(3)

    if (!p->used_for_reference)

    {

      get_smallest_poc(&poc, &pos);

      if ((-1==pos) || (p->poc < poc))

      {

        direct_output(p, p_dec);

        return;

      }

    }

    // flush a frame(4)

    output_one_frame_from_dpb();

  }<br>insert_picture_in_dpb(dpb.fs[dpb.used_size],p);

  ...

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章