【H264/AVC 句法和语义详解】(八):序列参数集SPS句法和语义

在本系列第一篇我们就知道,在h264码流的句法元素中,序列参数集SPS(seq_parameter_set)可由多个图像参数集PPS(pic_parameter_set)引用,然后PPS再由多个片引用。在形式上,SPS统领了一段视频序列的编码信息,如果SPS在传输中丢失或损坏,可以说这段视频序列,也就无法进行接下来的解码操作。

因此类型为SPS的NALU,在h264码流的众多NALU中,一般位于第一个。也即我们打开h264码流文件,读取到的第一个NALU的nal_unit_type 值为7。而根据h264协议文档7.4.1节可知,SPS的RBSP数据的元素构成,可参考seq_parameter_set_rbsp()部分,也即7.3.2.1节。

在这里插入图片描述
nal_unit_type = 7的NALU

1、SPS的句法结构

在7.3.2.1节规定的SPS的RBSP句法,我们可以按照RBSP的结构,简单直接的分为两大类:
【H264/AVC 句法和语义详解】(八):序列参数集SPS句法和语义_第1张图片
其中的seq_parameter_set_ data()也即包含在RBSP中的SODB部分,rbsp_trailing_bits( ) 即RBSP尾部。这里我们的重点,当然是seq_parameter_set_ data(),rbsp_trailing_bits()在之前介绍过就不再赘述。

1.1 seq_parameter_set_ data()结构

所以接下来我们就来到7.3.2.1.1节,这里记录了seq_parameter_set_ data()的元素构成,第一次看一定都不清楚它们都是干什么用的,因此我们先将所有的句法元素做一个粗归类,以表明seq_parameter_set_ data()里面的句法元素都具备哪些功能。总结如下:

seq_parameter_set_ data() {
   // 1. 遵从的h264编码profile(即baseline、Main、Extended或其他罕见级别)
   // 2. sps_id(唯一标识这个sps)
   // 3. 几个罕见级别对应的句法元素
   // 4. 几个用来计算POC的句法元素
   // 5. 图像宽高
   // 6. 用于解码后图像剪裁的几个句法元素
   // 7. vui_parameters( ) (vui的参数)
}

由上可见,sps里面所含的句法元素,能干的事情是简单而清晰的。毕竟sps是属于统领视频序列的参数集,里面的句法元素的作用范围也比较宽泛。因此像标记这段视频序列的编码级别、计算图像宽高、解码后的图像剪裁、计算POC这些功能,针对点都在图像和视频序列级别上,这是理解sps的关键。

当然上述有些功能,单靠sps中的几个句法元素并不能完成,还需要结合pps以及片层的句法元素,才能实现,比如计算POC和计算图像的高度,这点我们讲到pps和slice时会详细分析。

下面我们将sps中的句法元素与上述功能对号入座:

seq_parameter_set_data( ) { 
    profile_idc   // 遵从的h264编码profile

/*  —————————— 编码级别的制约条件 Start  —————————— */
    constraint_set0_flag  
    constraint_set1_flag  
    constraint_set2_flag  
    constraint_set3_flag 
    constraint_set4_flag 
    constraint_set5_flag 
/*  —————————— 编码级别的制约条件 End  —————————— */

    reserved_zero_2bits 
    level_idc // 编码级别

   // 唯一标识这个sps,供pps引用使用
    seq_parameter_set_id 

/*  —————————— 几个罕见级别对应的句法元素 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 ) {
        chroma_format_idc  
        if( chroma_format_idc = = 3 )
            separate_colour_plane_flag 
        bit_depth_luma_minus8 
        bit_depth_chroma_minus8 
        qpprime_y_zero_transform_bypass_flag 
        seq_scaling_matrix_present_flag 
        if( seq_scaling_matrix_present_flag )
            for( i = 0; i < ( ( chroma_format_idc != 3 ) ? 8 : 12 ); i++ ) {
                seq_scaling_list_present_flag[ i ]
                if( seq_scaling_list_present_flag[ i ] )
                if( i < 6 )
                    scaling_list( ScalingList4x4[ i ], 16,
                    UseDefaultScalingMatrix4x4Flag[ i ]) 
                else
                    scaling_list( ScalingList8x8[ i − 6 ], 64,
                    UseDefaultScalingMatrix8x8Flag[ i − 6 ] ) 
            }
    }
/*  —————————— 几个罕见级别对应的句法元素 End  —————————— */

/*  —————————— 用来计算POC的句法元素 Start  —————————— */
    log2_max_frame_num_minus4 
    pic_order_cnt_type 
    if( pic_order_cnt_type = = 0 )
        log2_max_pic_order_cnt_lsb_minus4 
    else if( pic_order_cnt_type = = 1 ) {
        delta_pic_order_always_zero_flag 
        offset_for_non_ref_pic 
        offset_for_top_to_bottom_field 
        num_ref_frames_in_pic_order_cnt_cycle 
        for( i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
            offset_for_ref_frame[ i ] 
    }
/*  —————————— 用来计算POC的句法元素 End  —————————— */

    max_num_ref_frames
    gaps_in_frame_num_value_allowed_flag 

/*  —————————— 图像宽高相关 Start  —————————— */
    pic_width_in_mbs_minus1 
    pic_height_in_map_units_minus1 
    frame_mbs_only_flag 
    if( !frame_mbs_only_flag )
        mb_adaptive_frame_field_flag 
/*  —————————— 图像宽高相关 End  —————————— */

    direct_8x8_inference_flag 

/*  —————————— 解码后图像剪裁的几个句法元素 Start  —————————— */
    frame_cropping_flag 
    if( frame_cropping_flag ) {
        frame_crop_left_offset
        frame_crop_right_offset 
        frame_crop_top_offset 
        frame_crop_bottom_offset 
    }
/* —————————— 解码后图像剪裁的几个句法元素 End  —————————— */

   // vui参数
    vui_parameters_present_flag
    if( vui_parameters_present_flag )
        vui_parameters( ) 
}

这样我们就对sps能干的事情,心里大概有了个底。有个别句法元素不属于上述大的功能,我们在下面统一详细介绍。

2、SPS语义

SPS的语义在h264协议文档的7.4.2.1节也有介绍,遇到任何不确定性问题请先参考文档,另外毕厚杰的sps语义一节有多处错误,不确定的还是请看文档。

详细如下:

seq_parameter_set_data( ) { 
   /*
   这段视频序列编码时遵从的编码级别,它的值在Annex A中给出。比 
   如baseline、Main、Extended三个profile对应的profile_idc 
   分别为:66、77、88
   */
    profile_idc

/*  —————————— 编码级别的制约条件 Start  —————————— */
   /*
   这个值用来约束h264比特流,是否必须遵从baseline级别的所有制 
   约条件。1表示必须遵从,0表示不必遵从所有条件。关于baseline 
   级别规定的制约条件,在h264文档的A.2.1节
   */
    constraint_set0_flag
   /*
   这个值用来约束h264比特流,是否必须遵从Main级别的所有制约条 
   件。1表示必须遵从,0表示不必遵从所有条件。关于Main级别规定的 
   制约条件,在h264文档的A.2.2节
   */
    constraint_set1_flag
   /*
   这个值用来约束h264比特流,是否必须遵从Extended级别的所有制 
   约条件。1表示必须遵从,0表示不必遵从所有条件。关于Extended级 
   别规定的制约条件,在h264文档的A.2.3节
   */
    constraint_set2_flag
   /* 
   需要注意的是,当constraint_set0_flag、 
   constraint_set1_flag、constraint_set2_flag中的一个或 
   多于一个等于1时,该比特流必须遵从A.2节下面的所有规定。而且当 
   profile_idc等于44、100、110、122或244时, 
   constraint_set0_flag、constraint_set1_flag 和 
   constraint_set2_flag都必须等于0 
   */

    constraint_set3_flag  // 一般用不到,详细可以参考h264文档
    constraint_set4_flag  // 一般用不到,详细可以参考h264文档
    constraint_set5_flag  // 一般用不到,详细可以参考h264文档
/* —————————— 编码级别的制约条件 End  —————————— */

    reserved_zero_2bits // 两个0比特的保留位,解码器会忽略reserved_zero_2bits的值

   /*
   综合profile_idc和level_idc,两个元素一起决定了编解码器的 
   级别。什么意思呢?简单来说,上面的profile_idc指定的是编码所 
   采用的方法,例如采用I、P条带还是I、P、B,是采用CAVLC还是采 
   用CABAC,是否采用加权预测,以及是否采用数据分割。而 
   level_idc,则是对编码的参数进行了限定。比如level_idc值越 
   大,最大宏块处理速率、最大帧长、最大解码图像缓存、最大视频比 
   特率、最大CPB大小,这些编码参数的取值范围也越大。
   */
    level_idc // 

   // 唯一标识这个sps,供pps引用使用
    seq_parameter_set_id 

/*  —————————— 几个罕见级别对应的句法元素 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 ) {

       /*
       表示色度取样格式,取值范围0~3,0代表不对色度进行采样,也 
       即只采样亮度。1代表采样格式为:4:2:0,2代表:4:2: 
       2,3代表:4:4:4。当chroma_format_idc不存在时,默认 
       为1。详细见6.2节
       */
        chroma_format_idc
        if( chroma_format_idc = = 3 )
           /*
           当采样格式为4:4:4时,指定颜色分量Y、Cr、Cb的编码模 
           式。如果separate_colour_plane_flag为1,表示对三个分 
           量分别进行编码,也即将一幅彩色图像分成三个单色图像,单独 
           进行编码。如果separate_colour_plane_flag为0则不会分 
           别编码。如果separate_colour_plane_flag不存在,默认 
           为0。
           */
            separate_colour_plane_flag

       /*
       指亮度采样点的比特深度和亮度量化参数范围的取值偏移 
       QpBdOffsetY 。
       计算时, BitDepth(Y) = bit_depth_luma_minus8 + 8
       QpBdOffsetY = bit_depth_luma_minus8 * 6
       当bit_depth_luma_minus8不存在时,默认为0,它的取值范 
       围为0~4,即亮度的比特深度取值范围为:8~12
       */
        bit_depth_luma_minus8 

       /*
       指色度采样点的比特深度和色度量化参数范围的取值偏移 QpBdOffsetC 。
       计算时, BitDepth(C) = bit_depth_chroma_minus8 + 8
       QpBdOffsetC = bit_depth_chroma_minus8 * 6
       当bit_depth_chroma_minus8不存在时,默认为0,它的取值范围为 
       0~4,即亮度的比特深度取值范围为:8~12

       由BitDepth(Y)和BitDepth(C)可以进一步算出,16x16的宏块所需的比特数:
       RawMbBits = 256 * BitDepthY + 2 * MbWidthC * MbHeightC 
       * BitDepthC 
       */
        bit_depth_chroma_minus8 

        qpprime_y_zero_transform_bypass_flag  // 默认为0,暂时用不到,详细可以参考h264文档
        seq_scaling_matrix_present_flag // 等于1表示存在i=0..7或0..11的seq_scaling_list_present_flag[I]

        if( seq_scaling_matrix_present_flag )
            for( i = 0; i < ( ( chroma_format_idc != 3 ) ? 8 : 12 ); i++ ) {
                seq_scaling_list_present_flag[ i ] // 描述子为u(1),顺序读取存入
                if( seq_scaling_list_present_flag[ i ] ) // 等于1表示sps中存在缩放比例列表I的语法结构
                if( i < 6 )
                    scaling_list( ScalingList4x4[ i ], 16,
                    UseDefaultScalingMatrix4x4Flag[ i ])  // 如果I小于6,则去解析scaling_list()这个语法元素结构,这里注意ScalingList4x4是个二维数组
                else
                    scaling_list( ScalingList8x8[ i − 6 ], 64,
                    UseDefaultScalingMatrix8x8Flag[ i − 6 ] ) // 如果I不小于6,则传入的是二维数组:ScalingList8x8
            }
    }
/*  —————————— 几个罕见级别对应的句法元素 End  —————————— */

/*  —————————— 用来计算POC的句法元素 Start  —————————— */

   /*
   1. 这个句法元素是为了读取另一个句法元素frame_num服务的,可 
   以说,frame_num是最重要的句法元素之一,我们在后面还会经常碰到 
   它,因为它标识了所属图像的解码顺序。所以它的第一个作用,就是 
   用来解析frame_num,frame_num是slice_header中的元素,我 
   们后面将会看到。frame_num的解析函数u(v),里面的变量v就由这 
   个句法元素指定:v = log2_max_frame_num_minus4 + 4
   2. 同时这个句法元素本身,指定了frame_num的最大值: 
   MaxFrameNum = 2^(log2_max_frame_num_minus4+4)
   */
    log2_max_frame_num_minus4 

   /*
   H264定义了3中poc的编码方法,pic_order_cnt_type就是告诉解 
   码器该用哪种方法来计算poc,它的取值范围为:0~2
   由于计算poc,还需要结合slice_header中的句法元素,所以我们 
   在介绍到slice_header时,再详细介绍poc,包括它的算法
   */
    pic_order_cnt_type 
    if( pic_order_cnt_type = = 0 )
       /*
       指明mac_pic_order_cnt_lsb的值,即 
       mac_pic_order_cnt_lsb = 
       2^(log2_max_pic_order_cnt_lsb_minus4+4)
       */
        log2_max_pic_order_cnt_lsb_minus4
    else if( pic_order_cnt_type = = 1 ) {
       /*
       这个句法元素将结合slice_header中的其他句法元素,来决定 
       delta_pic_order_cnt[0]和delta_pic_order_cnt[1] 
       是否应该在slice_header中出现
       */
        delta_pic_order_always_zero_flag
        offset_for_non_ref_pic // 用于计算非参考图像的poc,它的取值范围为[-2^31, 2^31-1]
        offset_for_top_to_bottom_field // 用来计算图像帧中的底场的poc,它的取值范围为[-2^31, 2^31-1]
        num_ref_frames_in_pic_order_cnt_cycle // 用于poc的解码,它的取值范围为[0,255]
        for( i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
            offset_for_ref_frame[ i ]  // 当poc为1时,用于解码poc,它指定了参考帧I的偏移量,取值范围为:[-2^31, 2^31-1]
    }

/*  —————————— 用来计算POC的句法元素 End  —————————— */

   /*
   指明参考帧队列可能达到的最大长度
    gaps_in_frame_num_value_allowed_flag // 值为1时,表示 
   允许句法元素frame_num可以不连续,解码器如果检测到frame_num 
   不连续,这时解码器必须启动错误掩藏的机制来恢复这些图像,因为 
   这些图像有可能被后续图像作为参考帧。值为0时,表示不允许 
   frame_num不连续,即编码器任何情况下,都不能丢弃图像。
   */
    max_num_ref_frames

/*  —————————— 图像宽高相关 Start  —————————— */
    pic_width_in_mbs_minus1  // 指定以宏块为单位的图像宽度,即PicWidthInMbs = pic_width_in_mbs_minus1 + 1
   /*
   指定以映射单位为单位的图像高度,即PicHeightInMapUnits = 
   pic_height_in_map_units_minus1 + 1。注意这里得到的并不是图 
   像的高度,而是映射高度,因为我们不知道图像的编码模式是帧、场、还是 
   帧场自适应,所以无法计算。而图像的编码模式,需要结合slice_header 
   中的句法元素,才可以得知。所以我们会在后面介绍slice_header时,再 
   来计算图像高度,并且那时会再详细讨论帧、场和帧场自适应的区别。
   */
    pic_height_in_map_units_minus1
    frame_mbs_only_flag // 是否只含帧宏块,如果为1,说明这个视频序列只含帧宏块,也即所有图像的编码模式都为帧编码,如果为0,则编码模式不确定,需要结合slice_header中的句法元素来确定。
    if( !frame_mbs_only_flag )
        mb_adaptive_frame_field_flag // 指定是否本序列为宏块级的帧场自适应,同样是为了指定本序列图像的编码模式,到slice_header时再详细介绍
/*  —————————— 图像宽高相关 End  —————————— */

   /*
   用于指定B片的B_Skip、B_Direct_16x16 和 B_Direct_8x8模 
   式下,亮度运动矢量的计算过程。当frame_mbs_only_flag为0 
   时,direct_8x8_inference_flag应为1
   */
    direct_8x8_inference_flag

/*  —————————— 解码后图像剪裁的几个句法元素 Start  —————————— */
    frame_cropping_flag // 是否要将图像剪裁后输出,如果为1,则下面if判断中的四个句法元素,分别指定了左、右、上、下剪裁的宽度
    if( frame_cropping_flag ) {
        frame_crop_left_offset
        frame_crop_right_offset 
        frame_crop_top_offset 
        frame_crop_bottom_offset 
    }
/*  —————————— 解码后图像剪裁的几个句法元素 End  —————————— */

   // vui参数
    vui_parameters_present_flag // 是否显示vui_parameters()语法结构
    if( vui_parameters_present_flag )
        vui_parameters( ) // 去解析vui的语法结构,它定义在Annex E中,vui表征了额外的一些视频的可用性信息。
}

你可能感兴趣的:(视频编解码,H264,SPS)