【从零实现一个H.264码流解析器】(三):解析序列参数集SPS的句法元素

上篇中我们实现了初步解析NALU的步骤,下面我们开始依次解析码流中出现的NALU。最开始出现的就是序列参数集SPS,通过上篇解析nalu_header我们也能看到,第一个NALU的nalu->nal_unit_type等于7,因此我们先解析它。

解析SPS的步骤分为两大类:

  • (1)数据存放:定义与h264文档相匹配的SPS的语法结构,在c语言中我们常用的就是结构体
  • (2)解析实现:从nalu->buf中逐个解析句法元素,存放在结构体元素中

下面我们首先实现第一条,为此我们首先新建文件parset.h和parset.c文件,头文件用于存放定义的结构体,.c文件用于存放函数的实现。

1、数据存放

我们先将SPS的句法元素定义为结构体sps_t,注意到SPS中还包含另一类句法元素,也即vui_parameters(),在sps_t的基础之上,我们将它定义为包含在结构体sps_t中的结构体vui_parameters_t

以此类推,包含在vui_parameters()里的两个hrd_parameters(),也定义为包含在结构体vui_parameters_t中的结构体hrd_parameters_t

下面我们根据h264文档,依次定义这几个结构体,为了便于学习,我们将文档中的if判断、for循环等语法结构也以注释的形式包含在内。而且对于每个句法元素,我们通通定义它们的类型为int,这样就省去了不必要的麻烦。

1.1、sps_t

/**
Sequence Parameter Set
@see 7.3.2.1 Sequence parameter set RBSP syntax
*/
typedef struct
{
   int profile_idc;                                    // u(8)
   /*  —————————— 编码级别的制约条件 Start  —————————— */
   int constraint_set0_flag;                           // u(1)
   int constraint_set1_flag;                           // u(1)
   int constraint_set2_flag;                           // u(1)
   int constraint_set3_flag;                           // u(1)
   int constraint_set4_flag;                           // u(1)
   int constraint_set5_flag;                           // u(1)
   int reserved_zero_2bits;                            // u(2)
   /*  —————————— 编码级别的制约条件 End  —————————— */
   
   int level_idc;                                      // u(8)
   int seq_parameter_set_id;                           // ue(v)
   
   /*  —————————— 几个罕见级别对应的句法元素 Start  —————————— */
   /*
    if( profile_idc = = 100 | | profile_idc = = 110 | |
    profile_idc = = 122 | | profile_idc = = 244 | | 
profile_idc = = 44 | | profile_idc = = 83 | | 
profile_idc = = 86 | | profile_idc = = 118 | | 
profile_idc = = 128 | | profile_idc = = 138 | | 
profile_idc = = 139 | | profile_idc = = 134 | | 
profile_idc = = 135 ) {
    */
       int chroma_format_idc;                                  // ue(v)
//      if( chroma_format_idc = = 3 )
           int separate_colour_plane_flag;                     // u(1)
       int bit_depth_luma_minus8;                              // ue(v)
       int bit_depth_chroma_minus8;                            // ue(v)
       int qpprime_y_zero_transform_bypass_flag;               // u(1)
   
       int seq_scaling_matrix_present_flag;                    // u(1)
//      if( seq_scaling_matrix_present_flag )
//          for( i = 0; i < ( ( chroma_format_idc != 3 ) ? 8 : 12 ); i++ ) {
               int seq_scaling_list_present_flag[12]; // 最大值12数组   // u(1)
//              if( seq_scaling_list_present_flag[ i ] )
//                  if( i < 6 )
                       int ScalingList4x4[6][16]; // 二维数组遍历
                       int UseDefaultScalingMatrix4x4Flag[6];
//                  else
                       int ScalingList8x8[6][64];
                       int UseDefaultScalingMatrix8x8Flag[6];
   /*  —————————— 几个罕见级别对应的句法元素 End  —————————— */
   
   /*  —————————— 用来计算POC的句法元素 Start  —————————— */
   int log2_max_frame_num_minus4;                              // ue(v)
   int pic_order_cnt_type;                                     // ue(v)
   
//  if( pic_order_cnt_type = = 0 )
       int log2_max_pic_order_cnt_lsb_minus4;                  // ue(v)
//  else if( pic_order_cnt_type = = 1 ) {
       int delta_pic_order_always_zero_flag;                   // u(1)
       int offset_for_non_ref_pic;                             // se(v)
       int offset_for_top_to_bottom_field;                     // se(v)
       int num_ref_frames_in_pic_order_cnt_cycle;              // ue(v)
//      for( i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
           int offset_for_ref_frame[256]; // 最大值256数组       // se(v)
   /*  —————————— 用来计算POC的句法元素 End  —————————— */
   
   int max_num_ref_frames;                                     // ue(v)
   int gaps_in_frame_num_value_allowed_flag;                   // u(1)
   
   /*  —————————— 图像宽高相关 Start  —————————— */
   int pic_width_in_mbs_minus1;                                // ue(v)
   int pic_height_in_map_units_minus1;                         // ue(v)
   int frame_mbs_only_flag;                                    // u(1)
//  if( !frame_mbs_only_flag )
       int mb_adaptive_frame_field_flag;                       // u(1)
   /*  —————————— 图像宽高相关 End  —————————— */
   
   int direct_8x8_inference_flag;                              // u(1)
   
   /*  —————————— 解码后图像剪裁的几个句法元素 Start  —————————— */
   int frame_cropping_flag;                                    // u(1)
//  if( frame_cropping_flag ) {
       int frame_crop_left_offset;                             // ue(v)
       int frame_crop_right_offset;                            // ue(v)
       int frame_crop_top_offset;                              // ue(v)
       int frame_crop_bottom_offset;                           // ue(v)
   /*  —————————— 解码后图像剪裁的几个句法元素 End  —————————— */
   
   int vui_parameters_present_flag;                            // u(1)
//  if( vui_parameters_present_flag )
       vui_parameters_t vui_parameters; // Annex E E.1.1
} sps_t;

要注意里面有些句法元素,需要结合它前后的语法结构,和它的语义推敲得来。比如scaling_list相关几个数组,它们的数组大小是根据for循环的遍历次数得来。而且ScalingList4x4和ScalingList8x8被定义为了二维数组,因为在调用scaling_list()时,传进去的ScalingList4x4[i]本身是个一维数组,因此ScalingList4x4是二维数组。

而其中数组offset_for_ref_frame[]的大小,是由for循环的次数num_ref_frames_in_pic_order_cnt_cycle决定的,我们已经知道num_ref_frames_in_pic_order_cnt_cycle的取值为[0,255],因此数组offset_for_ref_frame[]的大小就是256。

1.2、vui_parameters_t

/**
vui_parameters()
@see Annex E E.1.1 VUI parameters syntax
*/
typedef struct {
   int aspect_ratio_info_present_flag;                 // u(1)
//  if( aspect_ratio_info_present_flag ) {
       int aspect_ratio_idc;                           // u(8)
//      if( aspect_ratio_idc = = Extended_SAR ) {
           int sar_width;                              // u(16)
           int sar_height;                             // u(16)
   
   int overscan_info_present_flag;                     // u(1)
//  if( overscan_info_present_flag )
       int overscan_appropriate_flag;                  // u(1)
   int video_signal_type_present_flag;                 // u(1)
   
//  if( video_signal_type_present_flag ) {
       int video_format;                               // u(3)
       int video_full_range_flag;                      // u(1)
       int colour_description_present_flag;            // u(1)
//      if( colour_description_present_flag ) {
           int colour_primaries;                       // u(8)
           int transfer_characteristics;               // u(8)
           int matrix_coefficients;                    // u(8)
   
   int chroma_loc_info_present_flag;                   // u(1)
//  if( chroma_loc_info_present_flag ) {
       int chroma_sample_loc_type_top_field;           // ue(v)
       int chroma_sample_loc_type_bottom_field;        // ue(v)
   
   int timing_info_present_flag;                       // u(1)
//  if( timing_info_present_flag ) {
       int num_units_in_tick;                          // u(32)
       int time_scale;                                 // u(32)
       int fixed_frame_rate_flag;                      // u(1)
   
   int nal_hrd_parameters_present_flag;                // u(1)
//  if( nal_hrd_parameters_present_flag )
       hrd_parameters_t nal_hrd_parameters;
   
   int vcl_hrd_parameters_present_flag;                // u(1)
//  if( vcl_hrd_parameters_present_flag )
       hrd_parameters_t vcl_hrd_parameters;
//  if( nal_hrd_parameters_present_flag | | vcl_hrd_parameters_present_flag )
       int low_delay_hrd_flag;                         // u(1)
   
   int pic_struct_present_flag;                        // u(1)
   int bitstream_restriction_flag;                     // u(1)
//  if( bitstream_restriction_flag ) {
       int motion_vectors_over_pic_boundaries_flag;    // u(1)
       int max_bytes_per_pic_denom;                    // ue(v)
       int max_bits_per_mb_denom;                      // ue(v)
       int log2_max_mv_length_horizontal;              // ue(v)
       int log2_max_mv_length_vertical;                // ue(v)
       int max_num_reorder_frames;                     // ue(v)
       int max_dec_frame_buffering;                    // ue(v)
}vui_parameters_t;

1.3、hrd_parameters_t

/**
hrd_parameters()
@see Annex E E.1.2 HRD parameters syntax
*/
typedef struct {
   int cpb_cnt_minus1;                                 // ue(v)
   int bit_rate_scale;                                 // u(4)
   int cpb_size_scale;                                 // u(4)
   
//  for( SchedSelIdx = 0; SchedSelIdx <= cpb_cnt_minus1; SchedSelIdx++ ) {
       int bit_rate_value_minus1[MAX_CPB_CNT];         // ue(v)
       int cpb_size_value_minus1[MAX_CPB_CNT];         // ue(v)
       int cbr_flag[MAX_CPB_CNT];                      // u(1)
   
   int initial_cpb_removal_delay_length_minus1;        // u(5)
   int cpb_removal_delay_length_minus1;                // u(5)
   int dpb_output_delay_length_minus1;                 // u(5)
   int time_offset_length;                             // u(5)
}hrd_parameters_t;

这样用到的结构体就已经定义完成,当然为了方便使用,我们还写了两个初始化结构体allocSPS()和释放的函数freeSPS(),就不贴代码了。下面我们开始实现解析部分。

2、解析实现

SPS的句法元素解析分为两步:

  • (1)解析句法元素,存入结构体实例sps
  • (2)保存解析后的结构体实例sps,以便后续使用

为此实现一个概括性函数:processSPS()

/**
处理SPS,包含两步:
先解析、后保存
*/
void processSPS(bs_t *b)
{
   sps_t *sps = allocSPS();
   // 0.解析
   parse_sps_syntax_element(sps, b);
   // 1.保存
   save_sps_as_available(sps);
   freeSPS(sps);
}

它接收码流读取工具句柄bs_t,并负责控制解析和保存流程。

2.1 parse_sps_syntax_element()

解析的步骤很简单,我们只需对照h264文档,逐元素的解析即可。下面为parse_sps_syntax_element()的实现:

/**
解析sps句法元素
[h264协议文档位置]:7.3.2.1.1 Sequence parameter set data syntax
*/
void parse_sps_syntax_element(sps_t *sps, bs_t *b)
{
   sps->profile_idc = bs_read_u(b, 8);
   sps->constraint_set0_flag = bs_read_u(b, 1);
   sps->constraint_set1_flag = bs_read_u(b, 1);
   sps->constraint_set2_flag = bs_read_u(b, 1);
   sps->constraint_set3_flag = bs_read_u(b, 1);
   sps->constraint_set4_flag = bs_read_u(b, 1);
   sps->constraint_set5_flag = bs_read_u(b, 1);
   sps->reserved_zero_2bits = bs_read_u(b, 2);
   sps->level_idc = bs_read_u(b, 8);
   
   sps->seq_parameter_set_id = bs_read_ue(b);
   
   if (sps->profile_idc == 100 || sps->profile_idc == 
		110 || sps->profile_idc == 122 || sps->profile_idc == 
		244 || sps->profile_idc == 44 || sps->profile_idc == 83 
		|| sps->profile_idc == 86 || sps->profile_idc == 118 || 
		sps->profile_idc == 128 || sps->profile_idc == 138 || 
		sps->profile_idc == 139 || sps->profile_idc == 134 || 
		sps->profile_idc == 135) {
       
       sps->chroma_format_idc = bs_read_ue(b);
       if (sps->chroma_format_idc == YUV_4_4_4) {
           sps->separate_colour_plane_flag = bs_read_u(b, 1);
       }
       sps->bit_depth_luma_minus8 = bs_read_ue(b);
       sps->bit_depth_chroma_minus8 = bs_read_ue(b);
       sps->qpprime_y_zero_transform_bypass_flag = bs_read_u(b, 1);
       sps->seq_scaling_matrix_present_flag = bs_read_u(b, 1);
       
       if (sps->seq_scaling_matrix_present_flag) {
           int scalingListCycle = (sps->chroma_format_idc != YUV_4_4_4) ? 8 : 12;
           for (int i = 0; i < scalingListCycle; i++) {
               sps->seq_scaling_list_present_flag[i] = bs_read_u(b, 1);
               if (sps->seq_scaling_list_present_flag[i]) {
                   if (i < 6) {
                       scaling_list(sps->ScalingList4x4[i], 16, &sps->UseDefaultScalingMatrix4x4Flag[i], b);
                   }else {
                       scaling_list(sps->ScalingList8x8[i-6], 64, &sps->UseDefaultScalingMatrix8x8Flag[i-6], b);
                   }
               }
           }
       }
   }
   
   sps->log2_max_frame_num_minus4 = bs_read_ue(b);
   sps->pic_order_cnt_type = bs_read_ue(b);
   if (sps->pic_order_cnt_type == 0) {
       sps->log2_max_pic_order_cnt_lsb_minus4 = bs_read_ue(b);
   }else if (sps->pic_order_cnt_type == 1) {
       sps->delta_pic_order_always_zero_flag = bs_read_u(b, 1);
       sps->offset_for_non_ref_pic = bs_read_se(b);
       sps->offset_for_top_to_bottom_field = bs_read_se(b);
       sps->num_ref_frames_in_pic_order_cnt_cycle = bs_read_ue(b);
       for (int i = 0; i < sps->num_ref_frames_in_pic_order_cnt_cycle; i++) {
           sps->offset_for_ref_frame[i] = bs_read_se(b);
       }
   }
   
   sps->max_num_ref_frames = bs_read_ue(b);
   sps->gaps_in_frame_num_value_allowed_flag = bs_read_u(b, 1);
   
   sps->pic_width_in_mbs_minus1 = bs_read_ue(b);
   sps->pic_height_in_map_units_minus1 = bs_read_ue(b);
   sps->frame_mbs_only_flag = bs_read_u(b, 1);
   if (!sps->frame_mbs_only_flag) {
       sps->mb_adaptive_frame_field_flag = bs_read_u(b, 1);
   }
   
   sps->direct_8x8_inference_flag = bs_read_u(b, 1);
   
   sps->frame_cropping_flag = bs_read_u(b, 1);
   if (sps->frame_cropping_flag) {
       sps->frame_crop_left_offset = bs_read_ue(b);
       sps->frame_crop_right_offset = bs_read_ue(b);
       sps->frame_crop_top_offset = bs_read_ue(b);
       sps->frame_crop_bottom_offset = bs_read_ue(b);
   }
   
   sps->vui_parameters_present_flag = bs_read_u(b, 1);
   if (sps->vui_parameters_present_flag) {
       parse_vui_parameters(sps, b);
   }
}

注意到我们在讲语义时,曾说过句法元素chroma_format_idc代表了颜色空间的子采样格式,因此我们仿造JM,定义了几个枚举值,存放在frame.h文件中。

typedef enum {
   COLOR_SPACE_FORMAT_UNKNOWN = -1,     //!< Unknown color space format
   YUV_4_0_0     =  0,     //!< 没有色度chrome,只采样亮度luma
   YUV_4_2_0     =  1,     //!< 4:2:0
   YUV_4_2_2     =  2,     //!< 4:2:2
   YUV_4_4_4     =  3      //!< 4:4:4
} Color_Space_Format;

另外解析sps时,与sps_t结构体一致,依次会调用scaling_list()、parse_vui_parameters()、parse_vui_hrd_parameters()三个函数:

void scaling_list(int *scalingList, int sizeOfScalingList, int *useDefaultScalingMatrixFlag, bs_t *b);
void parse_vui_parameters(sps_t *sps, bs_t *b);
void parse_vui_hrd_parameters(hrd_parameters_t *hrd, bs_t *b);

函数实现就不贴代码了。

2.2 save_sps_as_available()

此时sps已解析完毕,我们只需将它保存即可。为了便于后续pps根据它引用的sps_id,找出对应的sps,我们需要将它按照索引sps_id保存到全局变量的结构体数组中,也即:Sequence_Parameters_Set_Array[]中。

而数组的大小,也即sps_id的取值范围,根据h264文档7.4.2.1节我们知道,seq_parameter_set_id的取值为[0, 31],因此数组最大容量即为32。

save_sps_as_available()实现如下:

void save_sps_as_available(sps_t *sps)
{
   memcpy (&Sequence_Parameters_Set_Array[sps->seq_parameter_set_id], sps, sizeof (sps_t));
}

Sequence_Parameters_Set_Array[]定义如下:

// 因为sps_id的取值范围为[0,31],因此数组容量最大为32,详见7.4.2.1
sps_t Sequence_Parameters_Set_Array[32];

本文源码地址如下(H264Analysis_03中):

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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值