前面我们根据SPS和PPS的句法和语义,已经能够解析出h264码流文件中的前两个NALU。现在我们就开始解析第三个NALU,它同码流中的后面几个NALU一样,它们的nal_unit_type不是等于5,就是等于1。
因此它们不是IDR的slice,就是非IDR的slice,总之后面的几个NALU,都代表了片层的数据。而它们的句法元素,都包含在slice_layer_without_partitioning_rbsp()中,由表7-1可以查到。
在h264协议文档7.3.2.8节,我们可以看到slice_layer()的结构如下:
/*
7.3.2.8 Slice layer without partitioning RBSP syntax
*/
slice_layer_without_partitioning_rbsp( ) {
slice_header( )
slice_data( ) /* all categories of slice_data( ) syntax */
rbsp_slice_trailing_bits( )
}
它主要包含了slice_header()
和slice_data()
两部分,slice_header()保存了这个slice解码所需的一些全局性信息,slice_data()则真正保存了这个slice的宏块数据。
在解析完slice_header()之后,我们很多解码准备性的工作就可以开展了。比如激活这个slice所指向的PPS和SPS,确定编码的帧场模式,计算视频宽高,计算POC等。
因此我们接下来几篇的重点,就围绕这几个话题来开展。一旦这些准备性工作完成,我们就可以从slice_data()中解析宏块数据,然后就可以嗨皮的去解码了。
slice_header()的结构定义在7.3.3 Slice header syntax一节中,其中的句法元素所表达的功能,主要分为以下几点:
slice_header() {
// 1. 指定当前slice类型
// 2. 当前slice所引用的pps_id
// 3. 帧场编码模式
// 4. 用于计算POC的几个句法元素
// 5. B Slice和P Slice所需的参考图像列表和加权预测表
// 6. 量化初始值
// 7. SP和SI 条带相关的句法元素
// 8. 去块效应滤波器相关句法元素
// 9. FMO相关
}
联系上篇最简单的H264编解码器,可知其中5、7、9都是暂时不会涉及到的,剩下的几点功能中,1、2、3、4是马上就能用到的,而6、8要再后面一点才会涉及到。
因此在下面的语义解析中,我们要把精力先主要放在前四点中。为此我们先来浏览一下,这八点功能对应的句法元素的划分:
slice_header( ) {
first_mb_in_slice
// 1. 指定当前slice类型
slice_type
// 2. 当前slice所引用的pps_id
pic_parameter_set_id
if( separate_colour_plane_flag = = 1 )
colour_plane_id
/* —————————— 4. 用于计算POC的几个句法元素 Start —————————— */
frame_num
/* —————————— 3. 帧场编码模式 Start —————————— */
if( !frame_mbs_only_flag ) {
field_pic_flag
if( field_pic_flag )
bottom_field_flag
}
/* —————————— 3. 帧场编码模式 End —————————— */
if( IdrPicFlag )
idr_pic_id
if( pic_order_cnt_type = = 0 ) {
pic_order_cnt_lsb
if( bottom_field_pic_order_in_frame_present_flag && !field_pic_flag )
delta_pic_order_cnt_bottom
}
if( pic_order_cnt_type = = 1 && !delta_pic_order_always_zero_flag ) {
delta_pic_order_cnt[ 0 ]
if( bottom_field_pic_order_in_frame_present_flag && !field_pic_flag )
delta_pic_order_cnt[ 1 ]
}
/* —————————— 4. 用于计算POC的几个句法元素 End —————————— */
if( redundant_pic_cnt_present_flag )
redundant_pic_cnt
if( slice_type = = B )
direct_spatial_mv_pred_flag
/* ———— 5. B Slice和P Slice所需的参考图像列表和加权预测表 Start ————— */
if(slice_type == P || slice_type == SP || slice_type == B){
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
}
}
if( nal_unit_type = = 20 | | nal_unit_type = = 21 )
ref_pic_list_mvc_modification( ) /* specified in Annex H */
else
ref_pic_list_modification( )
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 )
dec_ref_pic_marking( )
/* ———— 5. B Slice和P Slice所需的参考图像列表和加权预测表 End ————— */
if( entropy_coding_mode_flag && slice_type != I && slice_type != SI )
cabac_init_idc
// 6. 量化初始值
slice_qp_delta
// 7. SP和SI 条带相关的句法元素
if(slice_type == SP || slice_type == SI){
if( slice_type = = SP )
sp_for_switch_flag
slice_qs_delta
}
// 8. 去块效应滤波器相关句法元素
if( deblocking_filter_control_present_flag ) {
disable_deblocking_filter_idc
if( disable_deblocking_filter_idc != 1 ) {
slice_alpha_c0_offset_div2
slice_beta_offset_div2
}
}
// 9. FMO相关
if( num_slice_groups_minus1 > 0 &&
slice_group_map_type >= 3 && slice_group_map_type <= 5)
slice_group_change_cycle
}
其中第5点,B片、P片的参考图像列表和加权预测表,使用了额外的三个语法结构。
slice_header()的语义在h264协议文档的7.4.3节介绍,这里详细解释如下:
slice_header( ) {
/*
表示在Slice中第一个宏块的地址,在目前我们不使用FMO时,一幅图像只有一个Slice,因此这个值通常为0。
要注意的是,first_mb_in_slice并不是直接表示为第一个宏块的
地址的,具体如下:
当MbaffFrameFlag为0时,first_mb_in_slice就是该Slice中
第一个宏块的地址,并且first_mb_in_slice的取值范围为[0, PicSizeInMbs-1]
否则(MbaffFrameFlag为1时),first_mb_in_slice表示的是
Slice中的第几个宏块对,因此第一个宏块的地址为first_mb_in_slice * 2,
并且此时first_mb_in_slice的取值范围为[0, PicSizeInMbs/2-1]
其中判断的依据MbaffFrameFlag,表示是否为宏块级的帧场自适应,它的值需要经另外两个句法元素推算得来。
*/
first_mb_in_slice
// 1. 指定当前slice类型
/*
表示当前Slice的编码类型,它的取值范围为[0, 9],可参见表7-6
我们已经知道Slice的类型总共只有五种,因此slice_type值为0~4
时,依次表示当前Slice为P、B、I、SP、SI, 而slice_type值为
5~9时,依然顺序表示这五种slice类型。因此在解码器中,如果
slice_type大于4,则做减5处理。
*/
slice_type
// 2. 当前slice所引用的pps_id
/*
表示当前Slice引用的PPS的pps_id,同PPS语义一篇一样,它的取值范围为[0, 255]
*/
pic_parameter_set_id
if( separate_colour_plane_flag = = 1 )
/*
这一句法元素需要结合SPS中的separate_colour_plane_flag的语义来理解,
separate_colour_plane_flag等于1表示三个颜色分量Y、Cb、Cr分别进行编码,
而colour_plane_id就表示当前Slice是对哪一个颜色分量进
行编码的。
因此colour_plane_id的取值范围为[0, 2],0、1、2分别表示Y、Cb、Cr。
*/
colour_plane_id
/* —————————— 4. 用于计算POC的几个句法元素 Start —————————— */
/*
表示一个图像的标识符,在后面我们会单开几篇,用于讲解POC的计
算,
那时会对这个句法元素有非常深刻的理解。此时我们只需知道,
它的最大值是由SPS中的log2_max_frame_num_minus4决定,
并且frame_num的解析方式为u(v),其中的v即是指(log2_max_frame_num_minus4 + 4)
*/
frame_num
/* —————————— 3. 帧场编码模式 Start —————————— */
/*
以下两个句法元素field_pic_flag和bottom_field_flag,
和SPS中的frame_mbs_only_flag、
mb_adaptive_frame_field_flag一起,决定了当前Slice的编码模式。
而所谓编码模式,即指当前Slice用的是帧编码、还是场编码,如果是场编码,
那么当前Slice位于顶场,还是底场。关于这点,我们会单开一小篇来介绍。
在编码模式确定后,视频的宽高、当前图像(当前图像可能是帧、可能是场)的宽高,就可以计算出来了。
*/
if( !frame_mbs_only_flag ) {
field_pic_flag
if( field_pic_flag )
bottom_field_flag
}
/* —————————— 3. 帧场编码模式 End —————————— */
if( IdrPicFlag )
/*
IDR图像的标识,意思是只有作为IDR图像的I帧才有这个句法元素,
并且不同的IDR图像有不同的idr_pic_id值,如果编码模式为场模式,那么IDR帧的两个场的idr_pic_id值相同。
idr_pic_id的取值范围为[0, 65535],如果它的值超出65535,它会以循环的方式重新开始计数。
*/
idr_pic_id
if( pic_order_cnt_type = = 0 ) {
/*
在poc_type为0时,用于计算poc。它的值为编码帧的顶场,或
一个编码场的poc值对MaxPicOrderCntLsb取模得出,因此它直接传递了POC的值。
pic_order_cnt_lsb的解析方式为u(v),其中的v即为SPS中的(log2_max_pic_order_cnt_lsb_minus4 + 4)
而pic_order_cnt_lsb的取值范围为:[0, MaxPicOrderCntLsb-1]
*/
pic_order_cnt_lsb
if( bottom_field_pic_order_in_frame_present_flag && !field_pic_flag )
/*
表示一个编码帧的底场和顶场之间的poc的差值,它的取值范围为:
如果当前图像包含一个等于5的memory_management_control_operation,
那么它的取值范围为:[1 – MaxPicOrderCntLsb, 2^31 – 1]
否则它的取值范围为:[-2^31, 2^31 - 1]
当此句法元素不存在时,默认为0
*/
delta_pic_order_cnt_bottom
}
if( pic_order_cnt_type = = 1 && !delta_pic_order_always_zero_flag ) {
/*
表示POC值与经计算得出的,编码帧的顶场或编码场的,预期POC值之间的差值,
它的取值范围为[-2^31, 2^31 - 1],当此句法元素不存在时,默认为0
*/
delta_pic_order_cnt[ 0 ]
if( bottom_field_pic_order_in_frame_present_flag && !field_pic_flag )
/*
与delta_pic_order_cnt[ 0 ]类似,只不过delta_pic_order_cnt[ 1 ]表示的是POC值与
经计算得出的,编码帧的底场的,预期POC值之间的差值,它的取值范围同样为[-2^31, 2^31 - 1],
当此句法元素不存在时,默认为0
*/
delta_pic_order_cnt[ 1 ]
}
/* —————————— 4. 用于计算POC的几个句法元素 End —————————— */
if( redundant_pic_cnt_present_flag )
// 冗余片的id号,取值范围为[0, 127]
redundant_pic_cnt
if( slice_type = = B )
// 表示在B片下,决定为得到帧间预测的运动矢量和参考序号而使用的方法,当然暂时也用不到
direct_spatial_mv_pred_flag
/* ———— 5. B Slice和P Slice所需的参考图像列表和加权预测表 Start ————— */
if(slice_type == P || slice_type == SP || slice_type == B){
/*
此句法元素用于控制,num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1是否要覆盖在PPS中的默认值:
num_ref_idx_l0_default_active_minus1和num_ref_idx_l1_default_active_minus1。
因此如果num_ref_idx_active_override_flag为1,则覆盖,
否则则使用num_ref_idx_l0_default_active_minus1和num_ref_idx_l1_default_active_minus1
在以下两种情况下,num_ref_idx_active_override_flag的值为1
当当前Slice为P、SP或B Slice,且field_pic_flag等于0,
且num_ref_idx_l0_default_active_minus1的值大于15时,num_ref_idx_active_override_flag的值将为1
当当前Slice为B Slice,且field_pic_flag等于0,且
num_ref_idx_l1_default_active_minus1的值大于15时,num_ref_idx_active_override_flag的值将为1
*/
num_ref_idx_active_override_flag
if( num_ref_idx_active_override_flag ) {
/*
表示用于解码该Slice的参考图像列表 0 的最大参考序号。
它的取值范围为:如果field_pic_flag等于0,取值范围为:[0, 15],否则为[0, 31]
*/
num_ref_idx_l0_active_minus1
if( slice_type = = B )
// 与num_ref_idx_l0_active_minus1语义一致,只不过表示的是参考图像列表1
num_ref_idx_l1_active_minus1
}
}
if( nal_unit_type = = 20 | | nal_unit_type = = 21 )
// 一般用不到
ref_pic_list_mvc_modification( ) /* specified in Annex H */
else
/*
用于在编解码端维护参考图像列表时,
指定是否要对参考图像列表进行重排序操作,
这需要我们使用到B Slice或P Slice时,
用到帧间预测时才会出现,因此暂时可以忽略
*/
ref_pic_list_modification( )
if( ( weighted_pred_flag && ( slice_type = = P | | slice_type = = SP ) ) | |
( weighted_bipred_idc = = 1 && slice_type = = B ) )
/*
加权预测表,当使用到加权预测,并且用到P、SP、B 条带时才会用到,暂时可以忽略
*/
pred_weight_table( )
if( nal_ref_idc != 0 )
/*
解码参考图像标记,同重排序操作一样,它同样用于维护参考图
像列表,主要功能为将参考图像移入或移出参考图像列表
*/
dec_ref_pic_marking( )
/* ———— 5. B Slice和P Slice所需的参考图像列表和加权预测表 End ————— */
if( entropy_coding_mode_flag && slice_type != I && slice_type != SI )
/*
CABAC中用于决定关联变量的初始化过程中使用的初始化表格的序号,取值范围[0, 2]。
当然暂时也用不到,由上篇【最简单的h264编解码器】可知,只有在main级别时才可能用到
*/
cabac_init_idc
// 6. 量化初始值
/*
这个值在不久的将来会用到,结合PPS中的
pic_init_qp_minus26,表示该Slice内所有宏块的初始量化值:
SliceQP(Y) = 26 + pic_init_qp_minus26 + slice_qp_delta
要注意的是,在宏块层会进一步有句法元素mb_qp_delta,对这个初始值进行修改
*/
slice_qp_delta
// 7. SP和SI 条带相关的句法元素
if(slice_type == SP || slice_type == SI){
if( slice_type = = SP )
// 用于解码SP Slice中P宏块的解码过程,当然暂时用不到
sp_for_switch_flag
/*
与slice_qp_delta类似,只不过用于SP和SI Slice,因此QS(Y)量化参数为:
QS(Y) = 26 + pic_init_qs_minus26 + slice_qs_delta
QS(Y)取值范围为[0, 51]
*/
slice_qs_delta
}
// 8. 去块效应滤波器相关句法元素
// 去块滤波器相关,暂时也不会用到
if( deblocking_filter_control_present_flag ) {
disable_deblocking_filter_idc
if( disable_deblocking_filter_idc != 1 ) {
slice_alpha_c0_offset_div2
slice_beta_offset_div2
}
}
// 9. FMO相关
if( num_slice_groups_minus1 > 0 &&
slice_group_map_type >= 3 && slice_group_map_type <= 5)
/*
slice_group_map_type为3、4、5时才会用到,因此暂时可
以忽略,不过它的解析方式有点特别,这点我们接下来会在【解
析器】中解析Slice_Header()句法元素时看到
*/
slice_group_change_cycle
}