天天看點

H264源碼分析(二)

(四)圖像參數集語義

pic_parameter_set_rbsp( ) {  

    // pic_parameter_set_id 用以指定本參數集的序号,該序号在各片的片頭被引用。

   pic_parameter_set_id  

    // seq_parameter_set_id  指明本圖像參數集所引用的序列參數集的序号。

    seq_parameter_set_id  

   // entropy_coding_mode_flag  指明熵編碼的選擇,本句法元素為0時,表示熵編碼使用 CAVLC,本句法元素為1時表示熵編碼使用 CABAC 

    entropy_coding_mode_flag  

    // pic_order_present_flag        POC 的三種計算方法在片層還各需要用一些句法元素作為參數,本句法元素等于1時表示在片頭會有句法元素指明這些參數;本句法元素等于0時,表示片頭不會給出這些參數,這些參數使用預設值

    pic_order_present_flag  

    // num_slice_groups_minus1  本句法元素加1後指明圖像中片組的個數。H.264  中沒有專門的句法元素用于指明是否使用片組模式,當本句法元素等于0(即隻有一個片組),表示不使用片組模式,後面也不會跟有用于計算片組映射的句法元素。 

    num_slice_groups_minus1  

    if( num_slice_groups_minus1 > 0 ) {   

        /* slice_group_map_type  用以指明片組分割類型。 

           map_units 的定義:

          -  當 frame_mbs_only_flag 等于1時,map_units 指的就是宏塊

          -  當 frame_mbs_only_flag 等于0時 

              -  幀場自适應模式時,map_units 指的是宏塊對

              -  場模式時,map_units 指的是宏塊

              -  幀模式時,map_units 指的是與宏塊對相類似的,上下兩個連續宏塊的組合體。      */

        slice_group_map_type       

        if( slice_group_map_type    = =    0 )        

            for( iGroup = 0; iGroup <= num_slice_groups_minus1; iGroup++ )        

                // run_length_minus1[i]     用以指明當片組類型等于0時,每個片組連續的 map_units 個數

                run_length_minus1[ iGroup ]   

        else if( slice_group_map_type    = =    2 )        

            for( iGroup = 0; iGroup < num_slice_groups_minus1; iGroup++ ) {        

                // top_left[i],bottom_right[i]   用以指明當片組類型等于2時,矩形區域的左上及右下位置。

                top_left[ iGroup ]  

                bottom_right[ iGroup ]  

            }        

        else if(    slice_group_map_type    = =    3    | |    

                    slice_group_map_type    = =    4    | |    

                    slice_group_map_type    = =    5 ) {

            // slice_group_change_direction_flag 與下一個句法元素一起指明确切的片組分割方法。 

           slice_group_change_direction_flag   

           // slice_group_change_rate_minus1       用以指明變量 SliceGroupChangeRAte

            slice_group_change_rate_minus1            } else if( slice_group_map_type    = =    6 ) {   

           // pic_size_in_map_units_minus1   在片組類型等于6時,用以指明圖像以 map_units 為機關的大小。     

            pic_size_in_map_units_minus1   

            for( i = 0; i <= pic_size_in_map_units_minus1; i++ )        

                // slice_group_id[i]           在片組類型等于6時,用以指明某個 map_units 屬于哪個片組。

                slice_group_id[ i ]   

        }        

    }        

   // num_ref_idx_l0_active_minus1  加1後指明目前參考幀隊列的長度,即有多少個參考幀(包括短期和長期)。值得注意的是,當目前解碼圖像是場模式下,參考幀隊列的長度應該是本句法元素再乘以2,因為場模式下各幀必須被分解以場對形式存在。(這裡所說的場模式包括圖像的場及幀場自适應下的處于場模式的宏塊對)  本句法元素的值有可能在片頭被重載。 

      在序列參數集中有句法元素 num_ref_frames 也是跟參考幀隊列有關,它們的差別是num_ref_frames指明參考幀隊列的最大值,解碼 器用它的值來配置設定記憶體空 間;num_ref_idx_l0_active_minus1 指明在這個隊列中目前實際的、已存在的參考幀數目,這從它的名字“active”中也可以看出來。圖像時,并不是直接傳送該圖像的編号,而是傳送該圖像在參考幀隊列中的序号。這個序号并不是在碼流中傳送的,這個句法元素是 H.264 中最重要的句法元素之一,編碼器要通知解碼器某個運動矢量所指向的是哪個參考而是編碼器和解碼器同步地、用相同的方法将參考圖像放入隊列,進而獲得一個序号。這個隊列在每解一個圖像,甚至是每個片後都會動态地更新。維護參考幀隊列是編解碼器十分重要的工作,而本句法元素是維護參考幀隊列的重要依據。參考幀隊列的複雜的維護機制是 H.264 重要也是很有特色的組成部分 

    num_ref_idx_l0_active_minus1  

    num_ref_idx_l1_active_minus1 

    // weighted_pred_flag  用以指明是否允許P和SP片的權重預測,如果允許,在片頭會出現用以計算權重預測的句法元素。

   weighted_pred_flag   

    // weighted_bipred_flag   用以指明是否允許 B 片的權重預測,本句法元素等于 0 時表示使用預設權重預測模式,等于 1 時表示使用顯式權重預測模式,等于 2 時表示使用隐式權重預測模式。 

    weighted_bipred_idc   

    // pic_init_qp_minus26  加 26 後用以指明亮度分量的量化參數的初始值。在 H.264 中,量化參數分三個級别給出:圖像參數集、片頭、宏塊。在圖像參數集給出的是一個初始值。

    pic_init_qp_minus26    /* relative to 26 */   

    pic_init_qs_minus26    /* relative to 26 */  

    // chroma_qp_index_offset   色度分量的量化參數是根據亮度分量的量化參數計算出來的,本句法元素用以指明計算時用到的參數。

    chroma_qp_index_offset   

   // deblocking_filter_control_present_flag  編碼器可以通過句法元素顯式地控制去塊濾波的強度,本句法元素指明是在片頭是否會有句法元素傳遞這個控制資訊。如果本句法元素等于 0,那些用于傳遞濾波強度的句法元素不會出現,解碼器将獨立地計算出濾波強度。

    deblocking_filter_control_present_flag  

    // constrained_intra_pred_flag  在 P 和 B 片中,幀内編碼的宏塊的鄰近宏塊可能是采用的幀間編碼。當本句法元素等于 1 時,表示幀内編碼的宏塊不能用幀間編碼的宏塊的像素作為自己的預測,即幀内編碼的宏塊隻能用鄰近幀内編碼的宏塊的像素作為自己的預測;而本句法元素等于 0 時,表示不存在這種限制。

    constrained_intra_pred_flag  

    // redundant_pic_cnt_present_flag 指明是否會出現 redundant_pic_cnt  句法元素。

    redundant_pic_cnt_present_flag  

    rbsp_trailing_bits( )       

}

(五)片頭句法

slice_header( ) { 

    // first_mb_in_slice  片中的第一個宏塊的位址,  片通過這個句法元素來标定它自己的位址。 要注意的是在幀場自适應模式下,宏塊都是成對出現,這時本句法元素表示的是第幾個宏塊對,對應的第一個宏塊的真實位址應該是2 * first_mb_in_slice 

    first_mb_in_slice   

    /* slice_type    指明片的類型

       slice_type          Name of slice_type

       0                        P (P slice)

       1                        B (B slice)

       2                        I (I slice)

       3                        SP (SP slice)

       4                        SI (SI slice)

       5                        P (P slice)

       6                        B (B slice)

       7                        I (I slice)

       8                        SP (SP slice)

       9                        SI (SI slice) */

    slice_type   

    // pic_parameter_set_id  圖像參數集的索引号.  範圍 0  到 255。 

    pic_parameter_set_id   

    // frame_num  每個參考幀都有一個依次連續的 frame_num 作為它們的辨別,這指明了各圖像的解碼順序。但事實上我們可以看到,frame_num 的出現沒有 if 語句限定條件,這表明非參考幀的片頭也會出現 frame_num。隻是當該個圖像是參考幀時,它所攜帶的這個句法元素在解碼時才有意義。

    H.264 對 frame_num的值作了如下規定:當參數集中的句法元素gaps_in_frame_num_value_allowed_flag 不為1 時,每個圖像的 frame_num  值是它前一個參考幀的frame_num 值增加 1。這句話包含有兩層意思:

    1)  當 gaps_in_frame_num_value_allowed_flag  不為 1,即 frame_num  連續的情況下,每個圖像的frame_num 由前一個參考幀圖像對應的值加 1,着重點是“前一個參考幀”。

           前面我們曾經提到,對于非參考幀來說,它的 frame_num  值在解碼過程中是沒有意義的,因為frame_num  值是參考幀特有的,它的主要作用是在該圖像被其他圖像引用作運動補償的參考時提供一個辨別。但 H.264 并沒有在非參考幀圖像中取消這一句法元素,原因是在 POC 的第二種和第三種解碼方法中可以通過非參考幀的 frame_num 值計算出他們的 POC 值。

    2)  當 gaps_in_frame_num_value_allowed_flag 等于 1,前文已經提到,這時若網絡阻塞,編碼器可以将編碼後的若幹圖像丢棄,而不用另行通知解碼器。在這種情況下,解碼器必須有機制将缺失的frame_num 及所對應的圖像填補,否則後續圖像若将運動矢量指向缺失的圖像将會産生解碼錯誤。 

    frame_num   

    if( !frame_mbs_only_flag ) { 

        // field_pic_flag     這是在片層辨別圖像編碼模式的唯一一個句法元素。所謂的編碼模式是指的幀編碼、場編碼、幀場自适應編碼。當這個句法元素取值為 1 時  屬于場編碼; 0 時為非場編碼。        

       field_pic_flag   

        if( field_pic_flag )          

            // bottom_field_flag   等于 1 時表示目前圖像是屬于底場;等于 0 時表示目前圖像是屬于頂場。

            bottom_field_flag   

    }         

    if( nal_unit_type    ==    5 )         

        // idr_pic_id      IDR  圖像的辨別。不同的 IDR 圖像有不同的 idr_pic_id 值。值得注意的是,IDR 圖像有不等價于 I 圖像,隻有在作為 IDR 圖像的 I 幀才有這個句法元素,在場模式下,IDR 幀的兩個場有相同的 idr_pic_id 值。idr_pic_id 的取值範圍是  [0,65535],和 frame_num 類似,當它的值超出這個範圍時,它會以循環的方式重新開始計數。  

        idr_pic_id  

    if( pic_order_cnt_type    ==    0 ) {    

        // pic_order_cnt_lsb    在 POC 的第一種算法中本句法元素來計算 POC 值,在 POC 的第一種算法中是顯式地傳遞 POC 的值,而其他兩種算法是通過 frame_num 來映射 POC 的值。

        pic_order_cnt_lsb        

        if( pic_order_present_flag &&    !field_pic_flag )         

            // delta_pic_order_cnt_bottom    如果是在場模式下,場對中的兩個場都各自被構造為一個圖像,它們有各自的 POC 算法來分别計算兩個場的 POC 值,也就是一個場對擁有一對 POC 值;而在是幀模式或是幀場自适應模式下,一個圖像隻能根據片頭的句法元素計算出一個 POC 值。根據 H.264 的規定,在序列中有可能出現場的情況,即 frame_mbs_only_flag 不為 1 時,每個幀或幀場自适應的圖像在解碼完後必須分解為兩個場,以供後續圖像中的場作為參考圖像。是以當 frame_mb_only_flag  不為 1時,幀或幀場自适應中包含的兩個場也必須有各自的 POC 值。通過本句法元素,可以在已經解開的幀或幀場自适應圖像的 POC 基礎上新映射一個 POC 值,并把它賦給底場。當然,象句法表指出的那樣,這個句法元素隻用在 POC 的第一個算法中。

            delta_pic_order_cnt_bottom        }         

    if( pic_order_cnt_type = = 1 && !delta_pic_order_always_zero_flag ) {      

       // delta_pic_order_cnt[0], delta_pic_order_cnt[1]:POC 的第二和第三種算法是從 frame_num 映射得來,這兩個句法元素用于映射算法。delta_pic_order_cnt[0]用于幀編碼方式下的底場和場編碼方式的場,delta_pic_order_cnt[1] 用于幀編碼方式下的頂場。    

        delta_pic_order_cnt[ 0 ] 

        if( pic_order_present_flag    &&    !field_pic_flag )         

            delta_pic_order_cnt[ 1 ]  

    if( redundant_pic_cnt_present_flag )        

        // redundant_pic_cnt    備援片的 id 号。 

        redundant_pic_cnt  

    if( slice_type    ==    B )         

        // direct_spatial_mv_pred_flag  指出在B圖像的直接預測的模式下,用時間預測還是用空間預測。1:空間預測;0:時間預測。 

        direct_spatial_mv_pred_flag  

    if( slice_type = = P | | slice_type = = SP | | slice_type = = B ) {         

        // num_ref_idx_active_override_flag    在圖像參數集中我們看到已經出現句法元素num_ref_idx_l0_active_minus1 和num_ref_idx_l1_active_minus1 指定目前參考幀隊列中實際可用的參考幀的數目。在片頭可以重載這對句法元素,以給某特定圖像更大的靈活度。這個句法元素就是指明片頭是否會重載,如果該句法元素等于 1,下面會出現新的 num_ref_idx_l0_active_minus1  和num_ref_idx_l1_active_minus1 值。 

        num_ref_idx_active_override_flag          if( num_ref_idx_active_override_flag ) {         

            num_ref_idx_l0_active_minus1              if( slice_type    ==    B )         

                num_ref_idx_l1_active_minus1  

        }         

    }     

    // 參考幀隊列重排序(reordering)句法

    ref_pic_list_reordering( )          

    if( ( weighted_pred_flag    &&    ( slice_type == P    | |    slice_type == SP ) )   | |

        ( weighted_bipred_idc    ==    1    &&    slice_type    ==    B ) )

        // 權重預測句法 

        pred_weight_table( )      

    if( nal_ref_idc != 0 )         

        // 參考幀隊列标記(marking)句法 

       dec_ref_pic_marking( )        

    if( entropy_coding_mode_flag    &&    slice_type    !=    I    &&   slice_type    !=    SI )

        // cabac_init_idc  給出 cabac 初始化時表格的選擇,範圍 0 到 2。

        cabac_init_idc  

    // slice_qp_delta  指出在用于目前片的所有宏塊的量化參數的初始值。SliceQPY = 26+ pic_init_qp_minus26 + slice_qp_delta   範圍是  0 to 51。 H.264  中量化參數是分圖像參數集、片頭、宏塊頭三層給出的,前兩層各自給出一個偏移值,這個句法元素就是片層的偏移。

    slice_qp_delta   

    if( slice_type    = =    SP    | |    slice_type    = =    SI ) {         

        if( slice_type    = =    SP )         

            // sp_for_switch_flag  指出SP 幀中的p 宏塊的解碼方式是否是switching 模式 

            sp_for_switch_flag 

            // slice_qs_delta  與 slice_qp_delta 的與語義相似,用在 SI 和 SP 中的 

            slice_qs_delta  

    if( deblocking_filter_control_present_flag ) {     

        // disable_deblocking_filter_idc  H.264 指定了一套算法可以在解碼器端獨立地計算圖像中各邊界的濾波強度進行濾波。除了解碼器獨立計算之外,編碼器也可以傳遞句法元素來幹涉濾波強度,當這個句法元素指定了在塊的邊界是否要用濾波,同時指明那個塊的邊界不用塊濾波     

        disable_deblocking_filter_idc  

        if( disable_deblocking_filter_idc    !=    1 ) {   

            // slice_alpha/beta_c0_offset_div2  給出用于增強  α/beta 和  t C0 的偏移值        

            slice_alpha_c0_offset_div2  

            slice_beta_offset_div2   

    if( num_slice_groups_minus1 > 0    &&

        slice_group_map_type >= 3    &&    slice_group_map_type <= 5)

        // slice_group_change_cycle  當片組的類型是 3, 4,    5,由句法元素可獲得片組中  映射單元的數目:

        slice_group_change_cycle 

}

(六)參考幀隊列重排序(reordering)句法

ref_pic_list_reordering( ) {  

    if( slice_type    !=    I    &&    slice_type    !=    SI ) {             

        // ref_pic_list_reordering_flag_l0   指明是否進行重排序操作,這個句法元素等于1 時表明緊跟着會有一系列句法元素用于參考幀隊列的重排序。

       ref_pic_list_reordering_flag_l0  

        if( ref_pic_list_reordering_flag_l0 )          

            do {          

                // reordering_of_pic_nums_idc      指明執行哪種重排序操作

                   reordering_of_pic_nums_idc       操作

                   0                                                  短期參考幀重排序,abs_diff_pic_num_minus1會出現在碼流中,從當

                                                                       前圖像的PicNum減去  (abs_diff_pic_num_minus1  +  1)  後指明需要重

                                                                       排序的圖像。

                   1                                                  短期參考幀重排序,abs_diff_pic_num_minus1會出現在碼流中,從當

                                                                       前圖像的PicNum加上  (abs_diff_pic_num_minus1  +  1)  後指明需要重

                   2                                                  長期參考幀重排序,long_term_pic_num會出現在碼流中,指明需要重

                   3                                                  結束循環,退出重排序操作。 

                reordering_of_pic_nums_idc 

                if( reordering_of_pic_nums_idc    ==    0    | |

                    reordering_of_pic_nums_idc    ==    1 ) 

                    // abs_diff_pic_num_minus1   在對短期參考幀重排序時指明重排序圖像與目前的差

                    abs_diff_pic_num_minus1  

                else if( reordering_of_pic_nums_idc    = =    2 )        

                    // long_term_pic_num     在對長期參考幀重排序時指明重排序圖像  

                   long_term_pic_num 

            } while( reordering_of_pic_nums_idc    !=    3 )          

    }          

    if( slice_type    ==    B ) {          

       ref_pic_list_reordering_flag_l1  

        if( ref_pic_list_reordering_flag_l1 )          

                reordering_of_pic_nums_idc                   if( reordering_of_pic_nums_idc    = =    0    | |

                    reordering_of_pic_nums_idc    = =    1 )

                   abs_diff_pic_num_minus1                   else if( reordering_of_pic_nums_idc    = =    2 )          

                    long_term_pic_num   

繼續閱讀