在之前我们已经解析出码流文件中的前两个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_flag
和nal_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_t
、pred_weight_table_t
和dec_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 = ¤tSlice->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中):