【從零實現一個H.264碼流解析器】(六):解析片頭部Slice_Header的句法元素

原文鏈接:https://mp.weixin.qq.com/s/01-JTlph0mduW76N-zubaA

在之前我們已經解析出碼流文件中的前兩個NALU,分別爲SPS和PPS,下面我們就開始解析第三個NALU。在【最簡單的H264編解碼器】裏已經說過,我們接下來會首先按照最簡的方式來考慮問題,因此接下來我們遇到的都是I_Slice。

解析Slice總的來說分成兩大塊,第一步先解析Slice_Header,然後解析Slice_Data。解析Slice_Header較爲簡單,而解析Slice_Data則至少需要學習CAVLC,以深入去解析宏塊數據。

本篇我們先來解析Slice_Header,爲此我們先建立四個文件,slice.h、slice.c、header.h、header.c。其中slice用於處理Slice_Header和Slice_Data的解析工作,而header就是我們今天的主角,是解析Slice_Header的實現文件。

1、Slice結構體

爲了後續解析Slice_Data方便,我們參照h264協議文檔7.3.2.8節,先給出Slice的結構體:

typedef struct
{
   int idr_flag; // 是否爲IDR幀
   int nal_ref_idc; // nalu->nal_ref_idc
   slice_header_t slice_header;
} slice_t;

其中前兩個元素idr_flagnal_ref_idc,在解析Slice_Header時會用到,它們是從nalu_header中傳遞過來的值,即:

case H264_NAL_SLICE:
case H264_NAL_IDR_SLICE:
    currentSlice->idr_flag = (nalu->nal_unit_type == H264_NAL_IDR_SLICE);
    currentSlice->nal_ref_idc = nalu->nal_ref_idc;
    nalu->len = rbsp_to_sodb(nalu);
    processSlice(bs);
    break;

2、解析Slice_Header

解析Slice_Header的操作,和解析SPS和PPS的步驟一樣,總共分爲兩步:

  • (1)數據存放:定義與h264文檔相匹配的Slice_Header的數據結構,在這裏我們依然選用結構體
  • (2)解析實現:從nalu->buf中逐個解析句法元素,存放在結構體實例中

其中第二步又分爲三步:

  • (1)解析Slice_Header的前三個句法元素
  • (2)激活Slice引用的PPS,和PPS引用的SPS
  • (3)解析Slice_Header剩餘的句法元素

2.1 數據存放

參照協議文檔 7.3.3 Slice header syntax,定義結構體slice_header_t。對於其中涉及的ref_pic_list_modification()、pred_weight_table()和dec_ref_pic_marking(),我們另外定義三個結構體rplm_tpred_weight_table_tdec_ref_pic_marking_t

slice_header_t:

/**
slice_header( )
[h264協議文檔位置]:7.3.3 Slice header syntax
*/
typedef struct
{
   int first_mb_in_slice;                                // ue(v)
   int slice_type;                                       // ue(v)
   int pic_parameter_set_id;                             // ue(v)
//  if( separate_colour_plane_flag = = 1 )
       int colour_plane_id;                              // u(2)
   int frame_num;                                        // u(v)
//  if( !frame_mbs_only_flag ) {
       int field_pic_flag;                               // u(1)
//      if( field_pic_flag )
           int bottom_field_flag;                        // u(1)
//  if( IdrPicFlag )
       int idr_pic_id;                                   // ue(v)
   
//  if( pic_order_cnt_type = = 0 ) {
       int pic_order_cnt_lsb;                            // u(v)
//      if( bottom_field_pic_order_in_frame_present_flag && !field_pic_flag )
           int delta_pic_order_cnt_bottom;               // se(v)
//  if( pic_order_cnt_type = = 1 && !delta_pic_order_always_zero_flag ) {
       int delta_pic_order_cnt[2];                       // se(v)
   
//  if( redundant_pic_cnt_present_flag )
       int redundant_pic_cnt;                            // ue(v)
   
//  if( slice_type = = B )
       int direct_spatial_mv_pred_flag;                  // u(1)
//  if(slice_type == P || slice_type == SP || slice_type == B){
       int num_ref_idx_active_override_flag;             // u(1)
//      if( num_ref_idx_active_override_flag ) {
           int num_ref_idx_l0_active_minus1;             // ue(v)
//          if( slice_type = = B )
               int num_ref_idx_l1_active_minus1;         // ue(v)
   
//  7.3.3.1 Reference picture list modification syntax
   rplm_t ref_pic_list_modification;
   
//  if( ( weighted_pred_flag && ( slice_type = = P | | slice_type = = SP ) ) | | ( weighted_bipred_idc = = 1 && slice_type = = B ) )
       pred_weight_table_t pred_weight_table;
   
//  if( nal_ref_idc != 0 )
       dec_ref_pic_marking_t dec_ref_pic_marking;
   
//  if( entropy_coding_mode_flag && slice_type != I && slice_type != SI )
       int cabac_init_idc;                               // ue(v)
   int slice_qp_delta;                                   // se(v)
//  if(slice_type == SP || slice_type == SI){
//      if( slice_type = = SP )
           int sp_for_switch_flag;                       // u(1)
       int slice_qs_delta;                               // se(v)
   
//  if( deblocking_filter_control_present_flag ) {
       int disable_deblocking_filter_idc;                // ue(v)
//      if( disable_deblocking_filter_idc != 1 ) {
           int slice_alpha_c0_offset_div2;               // se(v)
           int slice_beta_offset_div2;                   // se(v)
   
//  if( num_slice_groups_minus1 > 0 &&
//      slice_group_map_type >= 3 && slice_group_map_type <= 5)
       int slice_group_change_cycle;                     // u(v)
   
} slice_header_t;

rplm_t(即ref_pic_list_modification()):

/**
2005.03版h264:ref_pic_list_reordering()
2017.04版h264:ref_pic_list_modification()
下面以最新版也即2017.04版進行定義
[h264協議文檔位置]:7.3.3.1 Reference picture list modification syntax
*/
typedef struct
{
//  if( slice_type % 5 != 2 && slice_type % 5 != 4 ) {
       int ref_pic_list_modification_flag_l0;        // u(1)
//      if( ref_pic_list_modification_flag_l0 )
//      do {
           int *modification_of_pic_nums_idc_l0;         // ue(v)
//          if( modification_of_pic_nums_idc = = 0 | | modification_of_pic_nums_idc = = 1 )
               int *abs_diff_pic_num_minus1_l0;          // ue(v)
//          else if( modification_of_pic_nums_idc = = 2 )
               int *long_term_pic_num_l0;                // ue(v)
//      } while( modification_of_pic_nums_idc != 3 )
   
//  if( slice_type % 5 = = 1 ) {
       int ref_pic_list_modification_flag_l1;         // u(1)
//      if( ref_pic_list_modification_flag_l1 )
//      do {
           int *modification_of_pic_nums_idc_l1;         // ue(v)
//          if( modification_of_pic_nums_idc = = 0 | | modification_of_pic_nums_idc = = 1 )
               int *abs_diff_pic_num_minus1_l1;          // ue(v)
//          else if( modification_of_pic_nums_idc = = 2 )
               int *long_term_pic_num_l1;                // ue(v)
//      } while( modification_of_pic_nums_idc != 3 )
   
} rplm_t; // ref_pic_list_modification()

rplm_t裏的句法元素結構爲對稱結構,上下分別代表了參考列表0和參考列表1,因此分別是嵌套在do-while循環裏的數組。它們的數組大小分別由slice_header->num_ref_idx_l0_active_minus1和slice_header->num_ref_idx_l1_active_minus1給出,需要在解析Slice_Header時動態初始化,因此這裏給出的是指針。

pred_weight_table_t:

/**
pred_weight_table()
[h264協議文檔位置]:7.3.3.2 Prediction weight table syntax
*/
typedef struct
{
   int luma_log2_weight_denom;                         // ue(v)
//  if( ChromaArrayType != 0 )
       int chroma_log2_weight_denom;                   // ue(v)
   
//  for( i = 0; i <= num_ref_idx_l0_active_minus1; i++ ) {
       int luma_weight_l0_flag;                        // u(1)
//      if( luma_weight_l0_flag ) {
           int luma_weight_l0[32];                     // se(v)
           int luma_offset_l0[32];                     // se(v)
   
//      if( ChromaArrayType != 0 ) {
           int chroma_weight_l0_flag;                  // u(1)
//          if( chroma_weight_l0_flag )
//              for( j =0; j < 2; j++ ) {
                   int chroma_weight_l0[32][2];        // se(v)
                   int chroma_offset_l0[32][2];        // se(v)
   
//  if( slice_type % 5 = = 1 )
//      for( i = 0; i <= num_ref_idx_l1_active_minus1; i++ ) {
           int luma_weight_l1_flag;                    // u(1)
//          if( luma_weight_l1_flag ) {
               int luma_weight_l1[32];                 // se(v)
               int luma_offset_l1[32];                 // se(v)
   
//          if( ChromaArrayType != 0 ) {
               int chroma_weight_l1_flag;              // u(1)
//              if( chroma_weight_l1_flag )
//                  for( j = 0; j < 2; j++ ) {
                       int chroma_weight_l1[32][2];    // se(v)
                       int chroma_offset_l1[32][2];    // se(v)
   
} pred_weight_table_t;

其中luma_weight_l0、luma_offset_l0等幾個數組的大小,是由num_ref_idx_l0_active_minus1的取值範圍決定的,而num_ref_idx_l0_active_minus1的取值範圍爲[0, 31],因此它們的大小爲32。

dec_ref_pic_marking_t:

/**
dec_ref_pic_marking()
[h264協議文檔位置]:7.3.3.3 Decoded reference picture marking syntax
*/
typedef struct
{
//  if( IdrPicFlag ) {
       int no_output_of_prior_pics_flag;                   // u(1)
       int long_term_reference_flag;                       // u(1)
//  } else {
       int adaptive_ref_pic_marking_mode_flag;             // u(1)
//      if( adaptive_ref_pic_marking_mode_flag )
//          do{
               int memory_management_control_operation[64];    // ue(v)
//              if( memory_management_control_operation = = 1 | | memory_management_control_operation = = 3 )
                   int difference_of_pic_nums_minus1[64];      // ue(v)
//              if(memory_management_control_operation = = 2 )
                   int long_term_pic_num[64];                  // ue(v)
//              if( memory_management_control_operation = = 3 | | memory_management_control_operation = = 6 )
                   int long_term_frame_idx[64];                // ue(v)
//              if( memory_management_control_operation = = 4 )
                   int max_long_term_frame_idx_plus1[64];      // ue(v)
//          } while( memory_management_control_operation != 0 )
   
} dec_ref_pic_marking_t;

其中的幾個數組如memory_management_control_operation,在JM裏定義的是指針,這裏我圖省事,參照H264Bitstream直接定義爲64,一般情況下夠用了。

2.2 解析實現

如上述所說,解析分爲三步:

/**
處理slice_header,包含三步:
1.先解析頭三個元素
2.去激活參數集
3.解析剩餘的句法元素
*/
void processSliceHeader(bs_t *b)
{
   // 0.解析前三個句法元素
   parse_first_three_element(b);
   // 1.激活參數集
   activeParameterSet(currentSlice->slice_header.pic_parameter_set_id);
   // 2.解析剩餘的句法元素
   parse_rest_elememt_of_sliceHeader(b);
}

2.2.1 解析前三個句法元素

/**
解析slice_header頭三個句法元素
[h264協議文檔位置]:7.3.3 Slice header syntax
*/
void parse_first_three_element(bs_t *b)
{
   currentSlice->slice_header.first_mb_in_slice = bs_read_ue(b, "SH: first_mb_in_slice");
   
   // 因爲slice_type值爲0~9,0~4和5~9重合
   int slice_type = bs_read_ue(b, "SH: slice_type");
   if (slice_type > 4) {slice_type -= 5;}
   currentSlice->slice_header.slice_type = slice_type;
   
   currentSlice->slice_header.pic_parameter_set_id = bs_read_ue(b, "SH: pic_parameter_set_id");
}

注意解析slice_type時,如果元素值大於4,我們做了減5處理,詳見[Slice_Header的句法和語義]

2.2.2 激活參數集

#pragma mark - 激活參數集
void activeParameterSet(int pps_id)
{
   active_pps = &Picture_Parameters_Set_Array[pps_id];
   active_sps = &Sequence_Parameters_Set_Array[active_pps->seq_parameter_set_id];
}

這裏我們處理的比較簡單,後續會加入限制條件。

2.2.3 解析剩餘的句法元素

/**
解析slice_header剩餘句法元素
[h264協議文檔位置]:7.3.3 Slice header syntax
*/
void parse_rest_elememt_of_sliceHeader(bs_t *b)
{
   slice_header_t *slice_header = &currentSlice->slice_header;
   
   if (active_sps->separate_colour_plane_flag == 1) {
       slice_header->colour_plane_id = bs_read_u(b, 2, "SH: colour_plane_id");
   }else {
       slice_header->colour_plane_id = COLOR_PLANE_Y;
   }
   
   slice_header->frame_num = bs_read_u(b, active_sps->log2_max_frame_num_minus4 + 4, "SH: frame_num");
   
   // FIXME: frame_num gap processing
   
   if (active_sps->frame_mbs_only_flag) {
       slice_header->field_pic_flag = 0;
   } else {
       slice_header->field_pic_flag = bs_read_u(b, 1, "SH: field_pic_flag");
       if (slice_header->field_pic_flag) {
           slice_header->bottom_field_flag = bs_read_u(b, 1, "SH: bottom_field_flag");
       }else {
           slice_header->bottom_field_flag = 0;
       }
   }
   
   if (currentSlice->idr_flag) {
       slice_header->idr_pic_id = bs_read_ue(b, "SH: idr_pic_id");
   }
   
   if (active_sps->pic_order_cnt_type == 0)
   {
       slice_header->pic_order_cnt_lsb = bs_read_u(b, active_sps->log2_max_pic_order_cnt_lsb_minus4 + 4, "SH: pic_order_cnt_lsb");
       if (active_pps->bottom_field_pic_order_in_frame_present_flag && !slice_header->field_pic_flag) {
           slice_header->delta_pic_order_cnt_bottom = bs_read_se(b, "SH: delta_pic_order_cnt_bottom");
       }else {
           slice_header->delta_pic_order_cnt_bottom = 0;
       }
   }
   
   if (active_sps->pic_order_cnt_type == 1 &&
       !active_sps->delta_pic_order_always_zero_flag)
   {
       slice_header->delta_pic_order_cnt[0] = bs_read_se(b, "SH: delta_pic_order_cnt[0]");
       if (active_pps->bottom_field_pic_order_in_frame_present_flag &&
           !slice_header->field_pic_flag) {
           slice_header->delta_pic_order_cnt[1] = bs_read_se(b, "SH: delta_pic_order_cnt[1]");
       }else {
           slice_header->delta_pic_order_cnt[1] = 0;
       }
   }else if (active_sps->pic_order_cnt_type == 1) {
       slice_header->delta_pic_order_cnt[0] = 0;
       slice_header->delta_pic_order_cnt[1] = 0;
   }
   
   if (active_pps->redundant_pic_cnt_present_flag) {
       slice_header->redundant_pic_cnt = bs_read_ue(b, "SH: redundant_pic_cnt");
   }
   
   if (slice_header->slice_type == Slice_Type_B) {
       slice_header->direct_spatial_mv_pred_flag = bs_read_u(b, 1, "SH: direct_spatial_mv_pred_flag");
   }
   
   if (slice_header->slice_type == Slice_Type_P ||
            slice_header->slice_type == Slice_Type_SP ||
            slice_header->slice_type == Slice_Type_B)
   {
       slice_header->num_ref_idx_active_override_flag = bs_read_u(b, 1, "SH: num_ref_idx_active_override_flag");
       if (slice_header->num_ref_idx_active_override_flag)
       {
           slice_header->num_ref_idx_l0_active_minus1 = bs_read_ue(b, "SH: num_ref_idx_l0_active_minus1");
           if (slice_header->slice_type == Slice_Type_B)
           {
               slice_header->num_ref_idx_l1_active_minus1 = bs_read_ue(b, "SH: num_ref_idx_l1_active_minus1");
           }
       }
   }
   
   // 1.解析參考圖像列表修正句法元素
   parse_ref_pic_list_modification(b);
   
   if ((active_pps->weighted_pred_flag && (slice_header->slice_type == Slice_Type_P || slice_header->slice_type == Slice_Type_SP)) ||
       (active_pps->weighted_bipred_idc == 1 && slice_header->slice_type == Slice_Type_B)) {
       // 2.解析預測加權表格句法元素
       parse_pred_weight_table(b);
   }
   
   if (currentSlice->nal_ref_idc != 0) {
       // 3.解析解碼參考圖像標識句法元素
       parse_dec_ref_pic_marking(b);
   }
   
   if (active_pps->entropy_coding_mode_flag &&
       slice_header->slice_type != Slice_Type_I &&
       slice_header->slice_type != Slice_Type_SI) {
       slice_header->cabac_init_idc = bs_read_ue(b, "SH: cabac_init_idc");
   }else {
       slice_header->cabac_init_idc = 0;
   }
   
   slice_header->slice_qp_delta = bs_read_se(b, "SH: slice_qp_delta");
   if (slice_header->slice_type == Slice_Type_SP ||
       slice_header->slice_type == Slice_Type_SI) {
       if (slice_header->slice_type == Slice_Type_SP) {
           slice_header->sp_for_switch_flag = bs_read_u(b, 1, "SH: sp_for_switch_flag");
       }
       slice_header->slice_qs_delta = bs_read_se(b, "SH: slice_qs_delta");
   }
   
   if (active_pps->deblocking_filter_control_present_flag) {
       slice_header->disable_deblocking_filter_idc = bs_read_ue(b, "SH: disable_deblocking_filter_idc");
       if (slice_header->disable_deblocking_filter_idc != 1) {
           slice_header->slice_alpha_c0_offset_div2 = bs_read_se(b, "SH: slice_alpha_c0_offset_div2");
           slice_header->slice_beta_offset_div2 = bs_read_se(b, "SH: slice_beta_offset_div2");
       }else {
           // 設置默認值
           slice_header->slice_alpha_c0_offset_div2 = 0;
           slice_header->slice_beta_offset_div2 = 0;
       }
   }else {
       // 設置默認值
       slice_header->disable_deblocking_filter_idc = 0;
       slice_header->slice_alpha_c0_offset_div2 = 0;
       slice_header->slice_beta_offset_div2 = 0;
   }
   
   if (active_pps->num_slice_groups_minus1 > 0 &&
       active_pps->slice_group_map_type >= 3 &&
       active_pps->slice_group_map_type <= 5) {
       // 見7.4.3 slice_header語義
       // 不能直接用active_pps->pic_size_in_map_units_minus1,因爲它可能沒值
       int bit_len = ((active_sps->pic_width_in_mbs_minus1 + 1) * (active_sps->pic_height_in_map_units_minus1 + 1)) / (active_pps->slice_group_change_rate_minus1 + 1);
       // 計算Ceil(bit_len)
       if (((active_sps->pic_width_in_mbs_minus1 + 1) * (active_sps->pic_height_in_map_units_minus1 + 1)) % (active_pps->slice_group_change_rate_minus1 + 1)) {
           bit_len++;
       }
       
       // 去計算Ceil( Log2( PicSizeInMapUnits ÷ SliceGroupChangeRate + 1 ) )
       bit_len = calculateCeilLog2(bit_len + 1);
       
       slice_header->slice_group_change_cycle = bs_read_u(b, bit_len, "SH: slice_group_change_cycle");
   }
}

全程參考H264協議文檔即可,唯一值得注意的是,最後解析slice_header->slice_group_change_cycle時,使用的是u(v)。所需的比特數v的計算公式爲:

Ceil( Log2( PicSizeInMapUnits ÷ SliceGroupChangeRate + 1 ) )

在處理的時候,我們將PicSizeInMapUnits ÷ SliceGroupChangeRate做了向上取整的操作,因此我們判斷了如果取模運算的結果大於0,就對bit_len加1。

對於引用的另外三個解析函數:parse_ref_pic_list_modification()、parse_pred_weight_table()、parse_dec_ref_pic_marking(),因爲篇幅有限我們就不貼代碼了,如果有疑問的歡迎留言。

3、trace文件

解析完成後,可以查看trace文件如下,已和JM對照完全一致(以下只截取了Slice_Header部分):

Annex B NALU len 11074, forbidden_bit 0, nal_reference_idc 3, nal_unit_type 5

@ SH: first_mb_in_slice                                   (  0)
@ SH: slice_type                                          (  7)
@ SH: pic_parameter_set_id                                (  0)
@ SH: frame_num                                           (  0)
@ SH: idr_pic_id                                          (  0)
@ SH: pic_order_cnt_lsb                                   (  0)
@ SH: no_output_of_prior_pics_flag                        (  0)
@ SH: long_term_reference_flag                            (  0)
@ SH: slice_qp_delta                                      (  2)


Annex B NALU len 11057, forbidden_bit 0, nal_reference_idc 2, nal_unit_type 1

@ SH: first_mb_in_slice                                   (  0)
@ SH: slice_type                                          (  7)
@ SH: pic_parameter_set_id                                (  0)
@ SH: frame_num                                           (  1)
@ SH: pic_order_cnt_lsb                                   (  2)
@ SH: adaptive_ref_pic_marking_mode_flag                  (  0)
@ SH: slice_qp_delta                                      (  2)


Annex B NALU len 11112, forbidden_bit 0, nal_reference_idc 2, nal_unit_type 1

@ SH: first_mb_in_slice                                   (  0)
@ SH: slice_type                                          (  7)
@ SH: pic_parameter_set_id                                (  0)
@ SH: frame_num                                           (  2)
@ SH: pic_order_cnt_lsb                                   (  4)
@ SH: adaptive_ref_pic_marking_mode_flag                  (  0)
@ SH: slice_qp_delta                                      (  2)


Annex B NALU len 11074, forbidden_bit 0, nal_reference_idc 2, nal_unit_type 1

@ SH: first_mb_in_slice                                   (  0)
@ SH: slice_type                                          (  7)
@ SH: pic_parameter_set_id                                (  0)
@ SH: frame_num                                           (  3)
@ SH: pic_order_cnt_lsb                                   (  6)
@ SH: adaptive_ref_pic_marking_mode_flag                  (  0)
@ SH: slice_qp_delta                                      (  2)


Annex B NALU len 11048, forbidden_bit 0, nal_reference_idc 2, nal_unit_type 1

@ SH: first_mb_in_slice                                   (  0)
@ SH: slice_type                                          (  7)
@ SH: pic_parameter_set_id                                (  0)
@ SH: frame_num                                           (  4)
@ SH: pic_order_cnt_lsb                                   (  8)
@ SH: adaptive_ref_pic_marking_mode_flag                  (  0)
@ SH: slice_qp_delta                                      (  2)

本文源碼地址如下(H264Analysis_06中):

1、GitHub:https://github.com/Gosivn/H264Analysis

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