從Slice_Header學習H.264(三.2)--相關細節之 參考圖像列表

轉:http://blog.csdn.net/newthinker_wei/article/details/8784742



2.參考圖像列表

 

解碼器每解碼完一幅圖像,都會判斷該圖像是否用於參考,並標記相應的參考圖像,而且會在解碼下一幅圖像前,將參考圖像列表初始化好;解碼下一幅圖像時,先根據圖像的片頭信息判斷是否需要對參考列表重排序,如果需要,就根據片頭的附加信息重新排序,之後開始對圖像解碼,解碼完成後,重新進行參考圖像的標記……如此循環。(IDR幀不需要參考幀,它解碼完後標記產生第一個參考圖像,供之後的圖像解碼時使用)

 

a.參考圖像列表的初始化

 

       前面我們已經知道,參考圖像有短期參考和長期參考,這兩種參考幀分別以不同的方式表示,其中短期參考圖像使用 FrameNum(最終用的是PicNum) 的值作爲標誌,而長期參考圖像則被分配一個長期參考幀索引LongTermFrameIdx(最終用的是LongTermPicNum)。參考圖像列是一個由變量PicNum 和LongTermPicNum構成的數組,當解碼一個宏塊時,要指定參考圖像,只需指定所需要的參考圖像在這個數組中對應的索引值。關於LongTermFrameIdx的相關說明在下面介紹“參考圖像的標記-幾個重要的結構體”中說到,不妨先跳到那裏去看看,這樣繼續往下看時也就少了些疑惑。

另外,當產生的RefPicList0 或 RefPicList1 中的分量個數分別大於num_ref_idx_l0_active_minus1 + 1 或 num_ref_idx_l1_active_minus1+ 1 時,多餘的分量將從列表中丟棄;分量個數分別小於 num_ref_idx_l0_active_minus1 + 1 或num_ref_idx_l1_active_minus1 + 1 時,列表中剩餘的分量設置爲"no reference picture"。

在初始化圖像列表之前,需要先計算出PicNum,這個在前面已經提及。




       上圖的計算過程用到了FrameNumWrap(每個參考圖像都對應一個FrameNum和FrameNumWrap),這個變量是這樣取值的:如果FrameNum(參考幀的frame_num) 不大於當前圖像片頭中的frame_num ,則FrameNumWrap 等於FrameNum;否則,FrameNumWrap 等於FrameNum 減去MaxFrameNum。使用FrameNumWrap的原因是:若直接使用frame_num來計算PicNum,由於frame_num是在0到MaxFrameNum之間循環變化的,所以如果當前圖像的frame_num正好處於臨界值,比如0時,那前幾幀(按解碼順序)參考圖像的frame_num就會比當前圖像的frame_num(此處假設是0)大,由此計算出來的PicNum也就會比CurrPicNum大,這在處理上會帶來一些麻煩,而對於FrameNumWrap,當前圖像前幾個參考幀對應的FrameNumWrap始終會比當前圖像的frame_num小(如果當前圖像處於臨界位置0,那前幾幀參考圖像的FrameNumWrap就變成了負值),因此使用FrameNumWrap計算PicNum的話,只要某個參考圖像在解碼順序上比當前圖像靠前,那它對應的PicNum就比CurrPicNum小,不需要再分情況討論。這個特點爲場圖像內P和SP片的參考幀列表中短期參考圖像的排序提供了方便。

 

下面開始詳細討論圖像列表的初始化。根據參考圖像是幀還是場、當前片是P(或SP)片還是B片,可將這一節分爲四部分討論。

(1)      “幀”中P和SP片的參考幀列表的初始化

排序規則:短期參考幀(或短期補充參考場對)位於長期參考幀(或長期參考場對)之前;短期參考幀和短期補充參考場對根據PicNum 值進行降序排列;長期參考幀和長期參考場對根據LongTermPicNum 值進行升序排列。由此可直接得到RefPicList0列表。

       可見,對於P片和SP片,其參考圖像列表中的短期參考幀都是基於PicNum排序的

 

(2)      “場”中P和SP片的參考幀列表的初始化

       與(1)中的情況相比,參考圖像列表中的每個場都有單獨的列表索引,而且對一個場解碼時,可用的參考圖像數將是解碼一個幀時的兩倍。在對場中P和SP片參考圖像列表初始化時,先要將短期參考圖像和長期參考圖像分別生成一個refFrameList0ShortTerm和refFrameList0LongTerm,這兩個列表用於確定最後的RefPicList0。

       refFrameList0ShortTerm的生成:所有包含一個或多個標記爲“用於短期參考”場的幀,均包含在短期參考幀列表refFrameList0ShortTerm中。若當前場是互補參考場對中的第二場(解碼順序),並且第一場被標記爲“用於短期參考”時,則該“第一場”包含在短期參考幀列表refFrameList0ShortTerm中。refFrameList0ShortTerm的順序以具有最大FrameNumWrap 值的幀開始,以遞減順序直到具有最小FrameNumWrap 值的幀爲止。(P幀是單向參考, FrameNumWrap越大,說明越靠近當前圖像)。

       refFrameListLongTerm的生成:所有包含一個或多個幀被標記爲“用於長期參考”場的幀,均包含在長期參考幀列表LongTermFrameIdx中。噹噹前場是互補參考場對中的第二場(解碼順序),並且第一場被標記爲“用於長期參考”時,則該第一場包含在長期參考幀列表refFrameList0LongTerm中。refFrameList0LongTerm 的順序以具有最小LongTermFrameIdx值的幀開始,以遞增順序直到具有最大LongTermFrameIdx值的幀爲止。

       至於如何由refFrameList0ShortTerm 和refFrameList0LongTerm得到RefPicList0,將在後面提到。

 

(3)      “幀”中B片的參考幀列表的初始化

B片需要雙向參考。與P片不同的是,B片的RefPicList0 和RefPicList1 中短期參考圖像的排列順序取決於輸出次序(而不再是FrameNum),即由PicOrderCnt( )給定。

函數PicOrderCnt( picX )如下:

if( picX 是一幀或互補場對 )

  PicOrderCnt(picX)= picX 互補場對的Min(TopFieldOrderCnt,BottomFieldOrderCnt )

else if( picX 是頂場)

  PicOrderCnt( picX ) = 場picX 的TopFieldOrderCnt

else if( picX 是底場) 

  PicOrderCnt( picX ) = 場picX 的BottomFieldOrderCnt

排序規則:短期參考幀和短期參考場對均排在長期參考幀和長期參考場對之前。

具體如下:

對於RefPicList0:

可將列表分爲三段,頭一段中所有的圖像的POC都比當前圖像的小,且按POC降序排列;中間一段,所有圖像的POC都比當前圖像的大,且按POC升序排列;前兩段都是短期參考幀,最後一段是長期參考幀,各圖像按其LongTermPicNum升序排列。

對於RefPicList1:

與RefPicList0類似,也可將列表分爲三段:頭一段中所有的圖像的POC都比當前圖像的大,且按POC升序排列;中間一段,所有圖像的POC都比當前圖像的小,且按POC降序排列;最後一段長期參考幀,與RefPicList0一樣,也是按LongTermPicNum升序排列。

 

(4)      “場”中B片的參考幀列表的初始化

需要先生成refFrameList0ShortTerm, refFrameList1ShortTerm 和 refFrameListLongTerm三個參考列表。然後用refFrameList0ShortTerm和refFrameListLongTerm可得到RefPicList0,用refFrameList1ShortTerm和refFrameListLongTerm可得到RefPicList1。

 

       refFrameList0ShortTerm的生成:

              分成兩段:頭一段中所有的圖像的POC都比當前圖像的小或相等(在幀模式下沒有相等的POC,但是場模式下就可能有,比如一幀中的兩場就可以有相等的POC),且按POC降序排列;第二段中,將剩下的POC比當前圖像大的參考圖像按POC升序排列。

       refFrameList1ShortTerm的生成:

              也分兩段:頭一段中所有的圖像的POC都比當前圖像的大(此處不包含相等),按POC升序排列;剩下的POC比當前圖像小的參考圖像按POC降序排列。

       refFrameListLongTerm的生成:

              按LongTermFrameIdx升序排列。

 

       至於如何用這三個列表最終生成RefPicList0和RefPicList1,將在下面講述。

 

(剩餘的解釋)    從refFrameListXShortTerm (X 可爲 0 或1) 和refFrameListLongTerm得到RefPicListX的步驟:(本次排序的主要特點就是儘可能使奇偶場交叉出現)

       從與當前場具有相同奇偶性的場(如果存在)開始,對短期參考場進行排序,其方法爲從有序的幀列表refFrameListXShortTerm中通過交替選擇具有不同奇偶性的場作爲參考場。當一個參考幀的一個場還沒有解碼或被標記爲“用於短期參考”時,忽略該場,並且把refFrameListXShortTerm幀列表中與該場具有相同奇偶性的下一個可用已存參考場插入到RefPicListX中。當已經排好序的refFrameListXShortTerm幀列表中沒有更多的具有交替奇偶性的短期參考場時,把接下來的奇偶性可用、還沒有被索引的場按照它們在已經排好序的refFrameListXShortTerm幀列表中具有的順序插入到RefPicListX中。

從與當前場具有相同奇偶性的場(如果存在)開始,對長期參考場進行排序,其方法爲從有序的幀列表refFrameListLongTerm 中通過交替選擇具有不同奇偶性的場作爲參考場。當一個參考幀的一個場還沒有解碼或被標記爲“用於長期參考”時,忽略該場,並且把refFrameListLongTerm 幀列表中與該場具有相同奇偶性的下一個可用已存參考場插入到RefPicListX中。當已經排好序的refFrameListLongTerm 幀列表中沒有更多的 具有交替奇偶性的長期參考場時,把剩下的奇偶性可用、還沒有被索引的場按照它們在已經排好序的幀refFrameListLongTerm 幀列表中具有的順序插入到RefPicListX中。

 

 

b.參考圖像列表的重排序

 

從上面的參考圖像列表的初始化過程中,我們可以看到,不論是四種情況中的哪一種,最後的參考圖像列表總是可以看成兩部分,第一部分是短期參考圖像列表,剩下部分是長期參考列表。下面將分這兩種情況講述參考幀的重排序過程,重排序的目的是爲了將需要的參考幀放在參考列表的前面,這樣指定參考圖像索引號時就可以用更小的數(更少的比特數)。

 

1)短期參考幀的重排序

 

首先,根據片頭ref_pic_list_reordering( ) 中出現的所有abs_diff_pic_num_minus1元素計算出它們對應的參考圖像的實際序號(picNum),並根據這些序號找到對應參考圖像。然後按照各個“abs_diff_pic_num_minus1”在碼流中出現的順序,將它們對應的參考圖像,依次移動到短期參考列表的開頭位置(這些參考圖像原來就在短期參考列表中,但重排序前他們在這個列表中的位置可能較靠後,重排序之後就會移動到列表前面)。

 

由abs_diff_pic_num_minus1計算picNum一般分爲兩步:

首先,根據片頭ref_pic_list_reordering( ) 中出現的所有abs_diff_pic_num_minus1元素計算出它們對應的參考圖像的序號picNumLXNoWrap,計算這個序號時考慮了picNumLXNoWrap的循環計數特性,它的值總在0到MaxPicNum之間(MaxPicNum在前面介紹frame_num元素時提到過):

       — 如果reordering_of_pic_nums_idc等於0

if( picNumLXPred − ( abs_diff_pic_num_minus1 + 1 ) < 0 ) 

picNumLXNoWrap=picNumLXPred−(abs_diff_pic_num_minus1+1)+MaxPicNum

       else

picNumLXNoWrap = picNumLXPred − ( abs_diff_pic_num_minus1 + 1 )

—  否則(reordering_of_pic_nums_idc 等於1)

if( picNumLXPred + ( abs_diff_pic_num_minus1 + 1 )  >= MaxPicNum )

picNumLXNoWrap=picNumLXPred+(abs_diff_pic_num_minus1+1)−MaxPicNum

       else

picNumLXNoWrap = picNumLXPred + ( abs_diff_pic_num_minus1 + 1 )

              其中picNumLXPred代表前一個picNumLXNoWrap值;而當計算第一個picNumLXNoWrap時,picNumLXPred=CurrPicNum。

 

然後,根據picNumLXNoWrap計算對應的picNumLX(類似於之前由frame_num計算FrameNumWrap):如果某個圖像的picNumLXNoWrap比CurrPicNum大,則picNumLX=picNumLXNoWrap-MaxPicNum ;否則,picNumLX=picNumLXNoWrap。picNumLX就是某個參考圖像的picNum值。

 

 

2)長期參考幀的重排序

 

大致過程與短期參考幀類似,不過這時不再需要計算picNumLXNoWrap和picNumLX這些過程了,因爲對長期參考幀重排序時,片頭ref_pic_list_reordering( ) 中的“long_term_pic_num”元素直接指定了參考圖像的LongTermPicNum。

 

剩下的主要任務就是:按照各個“long_term_pic_num”在碼流中出現的順序,將它們對應的長期參考圖像,依次移動到長期參考列表的開頭位置(這些參考圖像原來就在長期參考列表中,但重排序前他們在這個列表中的位置可能較靠後,重排序之後就會移動到列表前面)。需要注意的是,雖然是“開頭位置”,但這個開頭位置是針對參考列表的第二部分,即“長期參考列表”來說的,所有的長期參考圖像依然都在短期參考列表之後。

 

 

 

 

c.參考圖像的標記

 

爲了方便後面的理解,這裏要先簡單介紹幾個數據結構

 

DPB(DecodedPicture Buffer),它保存了編、解碼過程中所有的重建圖像(在JM的參考代碼中,DecodedPictureBuffer這個結構體是在mbuffer.h中定義的),這裏說的“所有重建圖像”,並不是真的指所有解碼過的圖像,那樣內存開銷太大了,dpb中的圖像每隔一段時間會清空一次,“所有”指的是,自上一次清空之後,所有新解碼的圖像。什麼時候dpb會清空一次呢?一般當IDR幀到來時,解碼器會根據該IDR幀片頭的dec_ref_pic_marking( )中的no_output_of_prior_pics_flag,來決定是否清空dpb,這個在前面已經說過。

一個dpb結構中,包含了三個FrameStore數組,準確的說是三個FrameStore類型的指針(一個FrameStore結構描述一個幀),爲什麼需要三個呢?其中,一個數組包含了所有重建幀(這個數組叫fs),而另外兩個,一個僅包含了所有短期參考幀(數組名fs_ref),一個僅包含了所有長期參考幀(fs_ltref)。FrameStore是個很關鍵的結構體,它記錄了一幀圖像的很多有用信息:在幀模式下,它的成員變量標記了當前幀是否是參考幀,以及是否是長期參考;在場模式下,它的成員變量記錄了本幀中的每個場的特性,包括某個場是否是參考場、以及是否是長期參考、是否已解碼等。如果某幀(或幀中某場)用於長期參考,那此幀對應的LongTermFrameIdx也在這個結構中給出,LongTermFrameIdx表示當前幀在數組fs_ltref中的索引。另外,這個結構體的“is_non_existent”成員用來標記當前幀是否是“不存在”幀。

一個FrameStore結構中,包含三個StorablePicture結構,一個StorablePicture結構描述一幅圖像,爲什麼需要三個呢?一個用於描述整幀圖像,另外兩個分別用於描述當前幀中頂、底兩場圖像。StorablePicture也是一個很重要的結構體,它記錄了本圖像的POC、頂場POC、底場POC等信息。另外,當本圖像用於短期參考時,圖像對應的PicNum就記錄在這個結構體中;當本圖像用長期參考時,圖像對應的long_term_pic_num也記錄在這個結構體中。

 

有了這些簡單的認識後,來看一個序列中參考圖像的標記過程。

在每一個序列的開始,會有一個IDR幀,對於IDR幀,不需要參考圖像,這時會將之前所有標記爲“短期參考”或“長期參考”的圖像,都重新標記爲“不用於參考”,即清空參考圖像列表(此外,當一個片的片頭出現memory_management_control_operation等於5的情況時,也會清空參考圖像列表);並且會根據片頭dec_ref_pic_marking( )中的no_output_of_prior_pics_flag,來決定是否清空dpb中的圖像;然後根據long_term_reference_flag將當前的IDR幀標記爲短期或長期參考:

如果long_term_reference_flag 等於0 ,則該IDR 圖像將被標記爲"短期參考" ,並將MaxLongTermFrameIdx 設爲“非長期幀索引”(即標記爲 -1 )。

否則,該IDR 圖像需要被標記爲"長期參考" 。 LongTermFrameIdx應被置爲0,並將MaxLongTermFrameIdx設爲0。(每標記一個長期參考的圖像,MaxLongTermFrameIdx會加1,它始終是長期參考圖像數組中最後一個長期參考圖像的索引)。

      

       上面介紹的IDR圖像的標記過程,對於之後的非IDR圖像,情況就比較多,看標準的話內容較多,但畢厚傑書中的插圖比較直觀。

 

當frame_num允許間隔並且間隔出現時,應爲屬於“不存在”圖像的frame_num 的每一個值產生和標記一個幀,並將生成的這些幀也可標記爲“不存在non-existing”或“用於短期參考”(注意,標準中只是允許將這些不存在幀“標記”爲用於短期參考,但實際解碼時不能真的以他們做參考,否則會引起錯誤,因此最好都標記成不存在)。

 

當片頭dec_ref_pic_marking( )結構中的adaptive_ref_pic_marking_mode_flag 元素等於0 時,解碼完當前片後,要用自動劃窗法來標記參考幀。

自動滑窗法:即FIFO的方法,當DPB中參考幀總數量(包括短期和長期參考幀) 等於序列參數集中的num_ref_frames 時,將DPB中PicNum最小的短期參考圖像,即最早的參考圖像,移出DPB。注意,調用這個過程時,只是將最早的參考圖像標記爲非參考,而不負責將當前正解碼的圖像標記爲短期參考,因爲每個這樣的片解碼完後(這時當前的圖像不一定解碼完),都會調用一次這個過程,而如果解碼完此片後當前圖像還沒有解碼完,當然就不能標記當前圖像。從JM提供的代碼中可以看出,每解碼完一幀圖像,都會調用一次store_picture_in_dpb函數,在這個函數的最後會自動根據剛剛解碼的圖像的相應參數(參考幀標誌位)來決定是否將當前圖像標記爲短期參考。

 

當片頭dec_ref_pic_marking( )結構中的adaptive_ref_pic_marking_mode_flag 元素等於1 時,解碼完當前片後,會用自適應內存控制標記過程。

自適應內存控制標記過程:



 

 

 

 

 

 

 

(附)

注:

下面介紹的幾個函數的定義都在mbuffer.c文件中;

相關變量(dpb和listX數組)也都在mbuffer.c文件中定義,listX數組的定義爲“StorablePicture **listX[6]”,但實際上我們只用了listX[0]和listX[1];

相關的結構體如DecodedPictureBuffer、FrameStore、StorablePicture等在mbuffer.h中定義;

最好先回頭熟悉一下我上面介紹的三個結構體再往下看;

 

函數介紹:

store_picture_in_dpb函數,是在解碼完一幅圖像時調用,它負責將重建圖像存儲;其內部會進行參考圖像的標記工作,在“標記”這一步,一般就是修改圖像結構體(dpb.fs[i]->frame)中相應的標識變量的值,這些標識變量負責標記當前圖像是否用於參考,以及是長期還是短期參考;修改完這些變量後,調用update_ref_list函數即可。

update_ref_list 函數,根據DPB中包含的所有圖像結構體(dpb中的fs數組)中相關的標識變量,判斷哪些是短期參考圖像,然後將短期參考圖像填充到dpb.ref_fs數組中,這之後ref_fs中就存在有用的信息了。與此對應的,update_ltref_list函數則用於對長期參考圖像數組dpb.ltref_fs[]進行操作。(這一段中我提到了很多“數組”,但實際上在JM代碼中它們只是指針)。

上面的三個函數都是用於參考圖像的標記過程,但它們都不會操作參考圖像列表(ListX數組),而只是在dpb內部進行操作。

init_lists()       初始化參考圖像列表。根據dpb.ref_fs和dpb.ltref_fs數組中的內容,對ListX[0]和ListX[1]進行初始化。實際上就是將先將短期參考數組dpb.ref_fs[]的內容複製到ListX[0](或ListX[1],下面不再重複提示)中,然後對ListX[0]按PicNum排序;接着再將長期參考數組dpb.ltref_fs[]的內容複製到ListX[0]後半部分,然後對ListX[0]後半部分按LontTermPicNum排序。

reorder_ref_pic_list函數以及該函數內部調用的reorder_short_term、reorder_long_term,都用於參考圖像列表重排序。他們都是根據片頭ref_pic_list_reordering()中相應元素的值對ListX[0]和ListX[1]進行操作。


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