同序列参数集SPS一样,图像参数集PPS在解码的过程中,也将起到至关重要的作用。我们已经知道,在h264的句法元素分层结构上,由片引用PPS,PPS再引用SPS,从而得到解码所需的全部句法元素。
PPS同SPS一样,也是从一段视频序列的基础上,抽取出大部分公共编解码信息,从而形成的SPS和PPS。因此呢,我们接下来就会感受到,PPS里的句法元素的语义,也是作用在视频序列上的。
因为SPS和PPS是h264码流中最重要的两个NAL单元,因此它们通常位于码流的最前面,也即码流开头的两个NALU,第一个是SPS,第二个就是PPS。所以由前面可知,当从码流中解析到第二个NALU时,它的nal_unit_type的值应为8。
同样根据h264协议文档7.4.1节可知,PPS的RBSP数据的元素构成,可参考pic_parameter_set_rbsp()部分,也即7.3.2.2节。

1、PPS的句法结构
在7.3.2.2节规定了PPS的RBSP句法,不过与SPS不同,PPS在文档里,并没有显式的分为pic_parameter_set_data()和rbsp_trailing_bits() 。不过我们方便起见,可以在心里默默的将它划分为两大类:
pic_parameter_set_rbsp() {
pic_parameter_set_data();
rbsp_trailing_bits();
}
其中的pic_parameter_set_data()也即包含在RBSP中的SODB部分,所有编解码所需的句法元素都在里面。下面我们忽略rbsp_trailing_bits(),来简单看一下pic_parameter_set_rbsp()中句法元素所具有的功能。
1.1 pic_parameter_set_rbsp()结构
如7.3.2.2节所示的一样,里面的句法元素很多,它们的功能如下:
pic_parameter_set_rbsp() {
// 1. pps_id(唯一标识这个pps)和sps_id(这个pps引用的sps的id),
// 2. 熵编码flag,采用的是CAVLC还是CABAC
// 3. 计算poc所需句法元素
// 4. FMO相关
// 5. 参考图像列表相关
// 6. 加权预测
// 7. 量化相关
// 8. DCT相关
// 9. scaling_list相关
}
因为更接近片层,因此PPS所表现出的功能,相对SPS来说也更具体。像熵编码模式、FMO等这些呢,PPS里都能描述到。不过也正因如此呢,要了解PPS里的句法元素的语义,也需要更宽广的知识面,需要对h264的编解码流程较为熟悉。
但是呢,即使我们现在对CAVLC、CABAC、POC、FMO、参考图像列表、预测、量化、DCT等这些重要的知识点不太了解,我们也不用灰心。因为这些以后都会详细讲到,在这里呢,我们只需知道,这几项的编解码所用到的句法元素,在PPS里都有涉及。在后面深入学习各知识点的时候,再对照PPS里的句法元素前来查阅即可。
我们先将上述的几个功能,对照pic_parameter_set_rbsp()的句法元素进行划分如下:
pic_parameter_set_rbsp( ) {
/* —————————— pps_id、sps_id Start —————————— */
pic_parameter_set_id
seq_parameter_set_id
/* —————————— pps_id、sps_id End —————————— */
entropy_coding_mode_flag // 熵编码
bottom_field_pic_order_in_frame_present_flag // POC
/* —————————— FMO相关 Start —————————— */
num_slice_groups_minus1
if( num_slice_groups_minus1 > 0 ) {
slice_group_map_type
if( slice_group_map_type = = 0 )
for( iGroup = 0; iGroup <= num_slice_groups_minus1; iGroup++ )
run_length_minus1[ iGroup ]
else if( slice_group_map_type = = 2 )
for( iGroup = 0; iGroup < num_slice_groups_minus1; iGroup++ ) {
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_rate_minus1
} else if( slice_group_map_type = = 6 ) {
pic_size_in_map_units_minus1
for( i = 0; i <= pic_size_in_map_units_minus1; i++ )
slice_group_id[ i ]
}
}
/* —————————— FMO相关 End —————————— */
/* —————————— 参考图像列表 Start —————————— */
num_ref_idx_l0_default_active_minus1
num_ref_idx_l1_default_active_minus1
/* —————————— 参考图像列表 End —————————— */
/* —————————— 加权预测 Start —————————— */
weighted_pred_flag
weighted_bipred_idc
/* —————————— 加权预测 End —————————— */
/* —————————— 量化 Start —————————— */
pic_init_qp_minus26 /* relative to 26 */
pic_init_qs_minus26 /* relative to 26 */
chroma_qp_index_offset
/* —————————— 量化 End —————————— */
deblocking_filter_control_present_flag
constrained_intra_pred_flag
redundant_pic_cnt_present_flag
if( more_rbsp_data( ) ) {
// DCT变换
transform_8x8_mode_flag
/* —————————— Scaling_list Start —————————— */
pic_scaling_matrix_present_flag
if( pic_scaling_matrix_present_flag )
for( i = 0; i < 6 +
( ( chroma_format_idc != 3 ) ? 2 : 6 ) * transform_8x8_mode_flag;
i++ ) {
pic_scaling_list_present_flag[ i ]
if( pic_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 ] )
}
/* —————————— Scaling_list End —————————— */
second_chroma_qp_index_offset
}
rbsp_trailing_bits( )
}
2、PPS语义
详细语义如下:
pic_parameter_set_rbsp( ) {
/* —————————— pps_id、sps_id Start —————————— */
// 唯一标识这个pps,供片头引用使用
pic_parameter_set_id
// 本图像参数集所引用的序列参数集的id
seq_parameter_set_id
/* —————————— pps_id、sps_id End —————————— */
/*
在讲描述子时,曾提到这个语法元素。它用于指定语法元素的熵编码方式,
当entropy_coding_mode_flag为0时,表示熵编码使用CAVLC,解析语法元素时使用左边的描述子
当entropy_coding_mode_flag为1时,表示熵编码使用CABAC,解析语法元素时使用右边的描述子
*/
entropy_coding_mode_flag
/*
PPS里唯一一个与计算POC相关的句法元素,用于控制在slice_header中,是否有用于计算底场POC的句法元素出现
*/
bottom_field_pic_order_in_frame_present_flag
/* —————————— FMO相关 Start —————————— */
/*
这个句法元素加1,用于表明图像中的片组个数。h264没有专门的句法元素表明是否使用FMO。因此
当num_slice_groups_minus1 等于 0时,表示只有1个片组,也即不使用FMO
当num_slice_groups_minus1大于0时,即表示使用FMO,这时后面会有用于计算片组映射的其他句法元素
*/
num_slice_groups_minus1
if( num_slice_groups_minus1 > 0 ) {
/*
指定FMO的映射模式,取值为[0, 6],0~6的每个值都对应一种映射模式,我们曾在【H.264/AVC 的分层结构与画面划分】里讲过。不过
联系到句法元素,更具体的还需要另行介绍,后面视情形可能会单独开一篇再次介绍FMO
*/
slice_group_map_type
if( slice_group_map_type = = 0 )
for( iGroup = 0; iGroup <= num_slice_groups_minus1; iGroup++ )
/*
当映射模式为0时,需要参数的句法元素,表示每个片组连续的map_units数目。取值范围为[0, PicSizeInMapUnits-1]
*/
run_length_minus1[ iGroup ]
else if( slice_group_map_type = = 2 )
/*
当映射模式为0时,需要参数的句法元素,top_left[ i ] 和 bottom_right[ i ] 分别表示一个矩形的左上角和右下角。
*/
for( iGroup = 0; iGroup < num_slice_groups_minus1; iGroup++ ) {
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_rate_minus1一起,用来表示当slice_group_map_type为3、4、5时精确的映射类型。
*/
slice_group_change_direction_flag
/*
slice_group_change_rate_minus1用来指定
SliceGroupChangeRate的值,它表示一个片组的大小,
从一个图像到下一个图像的改变的倍数,以map_units为
单位。取值范围为[0, PicSizeInMapUnits-1]
*/
slice_group_change_rate_minus1
} else if( slice_group_map_type = = 6 ) {
/*
用于指明当slice_group_map_type等于6时,图像以map_units为单位的大小
*/
pic_size_in_map_units_minus1
for( i = 0; i <= pic_size_in_map_units_minus1; i++ )
/*
用于指明当slice_group_map_type等于6时,某个map_units属于哪个片组
*/
slice_group_id[ i ]
}
}
/* —————————— FMO相关 End —————————— */
/* —————————— 参考图像列表 Start —————————— */
/*
加1后表明参考图像列表0的最大参考索引号,这个句法元素是针对帧宏块
来说的,比如假设编码模式为宏块级别的帧场自适应,那么帧场自适应下
的帧宏块解码的最大索引值就是
num_ref_idx_l0_default_active_minus1,而
2*num_ref_idx_l0_default_active_minus1+1是场宏块解码的最
大索引值。num_ref_idx_l0_default_active_minus1的取值范围
为:[0, 31]
*/
num_ref_idx_l0_default_active_minus1
/*
和num_ref_idx_l0_default_active_minus1具有同样的定义,
只不过这个是用来描述列表1的
*/
num_ref_idx_l1_default_active_minus1
/* —————————— 参考图像列表 End —————————— */
/* —————————— 加权预测 Start —————————— */
/*
表示P和SP片是否应该使用加权预测,等于0表示不应使用,等于1表
示应使用。
*/
weighted_pred_flag
/*
取值[0, 2]
等于0表示B片应该采取默认的加权预测
等于1表示B片应使用显式的加权预测
等于2表示B片应使用隐式的加权预测
*/
weighted_bipred_idc
/* —————————— 加权预测 End —————————— */
/* —————————— 量化 Start —————————— */
/*
取值[-26, 25]
加26后表示每个片的亮度分量的量化参数的初始值,在片头和宏块
中,进一步会有额外的量化参数给出,几个量化参数一起决定真正的
量化值
*/
pic_init_qp_minus26 /* relative to 26 */
/*
与pic_init_qp_minus26的语义一致,只不过作用于SI和SP片。
*/
pic_init_qs_minus26 /* relative to 26 */
/*
取值[-12, 12]
用于计算色度分量的量化参数,需要注意的时,计算时需要结合亮度
分量的量化参数
*/
chroma_qp_index_offset
/* —————————— 量化 End —————————— */
/*
用于表示,是否有控制去块滤波强度的语法元素值,在片头出现。
如果deblocking_filter_control_present_flag等于1, 表
示编码器已经设置了去块滤波强度,也即片头中句法元素的值
如果deblocking_filter_control_present_flag等于0,表示
编码器没有设置,那么解码器就要独立计算出去块滤波强度
*/
deblocking_filter_control_present_flag
/*
在P和B片中,当宏块进行帧内编码时,它的邻近宏块可能采用的是帧
间编码。
那么当constrained_intra_pred_flag等于1时,表示帧内编码的
宏块,不能使用帧间编码的宏块的像素值作为自己的预测值,也就是
它只能使用邻近帧内编码的宏块的像素值作为自己的预测
当constrained_intra_pred_flag等于0时,表示不存在这种限制
*/
constrained_intra_pred_flag
/*
用于控制是否显示redundant_pic_cnt
*/
redundant_pic_cnt_present_flag
if( more_rbsp_data( ) ) {
/*
等于1表示当前亮度宏块使用的是8x8的DCT变换
等于0表示当前亮度宏块没有使用8x8的DCT变换
*/
transform_8x8_mode_flag
/* —————————— Scaling_list Start —————————— */
/*
用于表示是否存在缩放比例列表Scaling_list
*/
pic_scaling_matrix_present_flag
if( pic_scaling_matrix_present_flag )
for( i = 0; i < 6 +
( ( chroma_format_idc != 3 ) ? 2 : 6 ) * transform_8x8_mode_flag;
i++ ) {
pic_scaling_list_present_flag[ i ]
if( pic_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 ] )
}
/* —————————— Scaling_list End —————————— */
/*
色度分量Cr与亮度分量Y的量化差值,取值范围为[-12, 12]
当second_chroma_qp_index_offset不存在时,默认值为
chroma_qp_index_offset
*/
second_chroma_qp_index_offset
}
rbsp_trailing_bits( )
}
准确的来说,以上语义看不懂也没关系,因为关系到每个知识点,我们都可以单开一篇文章来详细介绍。而且在图像参数集里面,我们并没有获取到用于讲解某一知识点的全部句法元素。因此在后面讲到slice_header以后,一些知识点就可以详细介绍,比如如何计算POC、编码模式、参考图像列表等。