前面我们解析了序列参数集SPS,它也是第一个出现的NALU中包含的内容。下面我们开始解析第二个NALU,由上篇生成的trace文件也可以看到,它的nalu->nal_unit_type等于8,为图像参数集PPS。
有了解析SPS的铺垫,解析PPS就轻松自然多了。解析PPS同SPS一样,同样分为两大步:
- (1)数据存放:定义与h264文档相匹配的PPS的数据结构,在这里我们依然选用结构体
- (2)解析实现:从nalu->buf中逐个解析句法元素,存放在结构体实例中
下面首先实现第一步,我们将PPS的结构体定义放在parset.h中,解析实现放在parset.c中。
1、数据存放
我们参照h264协议文档,将pps的句法元素定义为结构体pps_t
。相对sps,pps的结构体较为简洁,它并没有引用其他的结构体。
1.1 pps_t
/**
Picture Parameter Set
@see 7.3.2.2 Picture parameter set RBSP syntax
*/
typedef struct
{
int pic_parameter_set_id; // ue(v)
int seq_parameter_set_id; // ue(v)
int entropy_coding_mode_flag; // u(1)
int bottom_field_pic_order_in_frame_present_flag; // u(1)
/* —————————— FMO相关 Start —————————— */
int num_slice_groups_minus1; // ue(v)
// if( num_slice_groups_minus1 > 0 ) {
int slice_group_map_type; // ue(v)
// if( slice_group_map_type = = 0 )
// for( iGroup = 0; iGroup <= num_slice_groups_minus1; iGroup++ )
// num_slice_groups_minus1取值范围[0, 7],见附录A
int run_length_minus1[8]; // ue(v)
// else if( slice_group_map_type = = 2 )
// for( iGroup = 0; iGroup < num_slice_groups_minus1; iGroup++ ) {
int top_left[8]; // ue(v)
int bottom_right[8]; // ue(v)
// else if( slice_group_map_type = = 3 | | slice_group_map_type = = 4 | | slice_group_map_type = = 5 ) {
int slice_group_change_direction_flag; // u(1)
int slice_group_change_rate_minus1; // ue(v)
// } else if( slice_group_map_type = = 6 ) {
int pic_size_in_map_units_minus1; // ue(v)
// for( i = 0; i <= pic_size_in_map_units_minus1; i++ )
int *slice_group_id; // u(v)
/* —————————— FMO相关 End —————————— */
int num_ref_idx_l0_default_active_minus1; // ue(v)
int num_ref_idx_l1_default_active_minus1; // ue(v)
int weighted_pred_flag; // u(1)
int weighted_bipred_idc; // u(2)
int pic_init_qp_minus26; // se(v)
int pic_init_qs_minus26; // se(v)
int chroma_qp_index_offset; // se(v)
int deblocking_filter_control_present_flag; // u(1)
int constrained_intra_pred_flag; // u(1)
int redundant_pic_cnt_present_flag; // u(1)
// if( more_rbsp_data( ) ) {
int transform_8x8_mode_flag; // u(1)
int pic_scaling_matrix_present_flag; // u(1)
// if( pic_scaling_matrix_present_flag )
/*
for( i = 0; i < 6 +
( ( chroma_format_idc != 3 ) ? 2 : 6 ) * transform_8x8_mode_flag; i++ ) {
*/
int pic_scaling_list_present_flag[12]; // u(1)
// if( pic_scaling_list_present_flag[ i ] )
// if( i < 6 )
int ScalingList4x4[6][16]; // 二维数组遍历
int UseDefaultScalingMatrix4x4Flag[6];
// else
int ScalingList8x8[6][64];
int UseDefaultScalingMatrix8x8Flag[6];
int second_chroma_qp_index_offset; // se(v)
} pps_t;
pps_t中有FMO相关的几个数组:run_length_minus1[]、top_left[]、bottom_right[],它们的数组大小都由num_slice_groups_minus1的取值范围决定。而num_slice_groups_minus1的取值范围,根据其语义查询附录A可得为[0, 7],因此这几个数组容量为8。
后面scaling_list相关的几个数组大小,推理方法和sps中的scaling_list一致,因此不再赘述。
值得一提的是,pps中有一个数组,我们并没有直接写明它的数组大小,而是写成了指针的形式,那就是(int *)slice_group_id
。这里我们使用c语言的特性,将指针当做数组使用。而这里没有指定数组大小的原因是,它的大小与解码的图像尺寸有关。
当然我们可以一开始就使用一个较大值,但是我们选择在解析时,根据解析到的以映射单元为单位的图像尺寸,动态初始化数组大小,具体到这里也即指针(int *)slice_group_id的大小
。
1.2 allocPPS()
而在pps的初始化函数中,我们只是将指针pps->slice_group_id置为NULL。
// 初始化pps结构体
pps_t *allocPPS(void)
{
pps_t *pps = calloc(1, sizeof(pps_t));
if (pps == NULL) {
fprintf(stderr, "%s\n", "Alloc PPS Error");
exit(-1);
}
pps->slice_group_id = NULL;
return pps;
}
1.3 freePPS()
在释放pps时,会先判断pps->slice_group_id是否为NULL,进而再去释放。这一步的判断操作,需要结合后面保存pps的操作来理解。
// 释放pps
void freePPS(pps_t *pps)
{
if (pps->slice_group_id != NULL) {
free (pps->slice_group_id);
}
free(pps);
}
2、解析实现
PPS的句法元素解析同样分为两步:
- (1)解析句法元素,存入结构体实例pps
- (2)保存解析后的结构体实例pps,以便后续使用
为此实现函数:processPPS()
/**
处理PPS,包含两步:
先解析、后保存
*/
void processPPS(bs_t *b)
{
pps_t *pps = allocPPS();
// 0.解析
parse_pps_syntax_element(pps, b);
// 1.保存
save_pps_as_available(pps);
freePPS(pps);
}
它接收从read_nal_unit()传进来的,码流读取工具句柄bs_t,并负责控制解析和保存流程。
2.1 parse_pps_syntax_element()
解析的过程,同样参照h264协议中句法元素的语法结构即可,不过有些小点需要注意的,我们先看代码再说。
/**
解析pps句法元素
[h264协议文档位置]:7.3.2.2 Picture parameter set RBSP syntax
*/
void parse_pps_syntax_element(pps_t *pps, bs_t *b)
{
// 解析slice_group_id[]需用的比特个数
int bitsNumberOfEachSliceGroupID;
pps->pic_parameter_set_id = bs_read_ue(b, "PPS: pic_parameter_set_id");
pps->seq_parameter_set_id = bs_read_ue(b, "PPS: seq_parameter_set_id");
pps->entropy_coding_mode_flag = bs_read_u(b, 1, "PPS: entropy_coding_mode_flag");
pps->bottom_field_pic_order_in_frame_present_flag = bs_read_u(b, 1, "PPS: bottom_field_pic_order_in_frame_present_flag");
/* —————————— FMO相关 Start —————————— */
pps->num_slice_groups_minus1 = bs_read_ue(b, "PPS: num_slice_groups_minus1");
if (pps->num_slice_groups_minus1 > 0) {
pps->slice_group_map_type = bs_read_ue(b, "PPS: slice_group_map_type");
if (pps->slice_group_map_type == 0)
{
for (int i = 0; i <= pps->num_slice_groups_minus1; i++) {
pps->run_length_minus1[i] = bs_read_ue(b, "PPS: run_length_minus1[]");
}
}
else if (pps->slice_group_map_type == 2)
{
for (int i = 0; i < pps->num_slice_groups_minus1; i++) {
pps->top_left[i] = bs_read_ue(b, "PPS: top_left[]");
pps->bottom_right[i] = bs_read_ue(b, "PPS: bottom_right[]");
}
}
else if (pps->slice_group_map_type == 3 ||
pps->slice_group_map_type == 4 ||
pps->slice_group_map_type == 5)
{
pps->slice_group_change_direction_flag = bs_read_u(b, 1, "PPS: slice_group_change_direction_flag");
pps->slice_group_change_rate_minus1 = bs_read_ue(b, "PPS: slice_group_change_rate_minus1");
}
else if (pps->slice_group_map_type == 6)
{
// 1.计算解析slice_group_id[]需用的比特个数,Ceil( Log2( num_slice_groups_minus1 + 1 ) )
if (pps->num_slice_groups_minus1+1 >4)
bitsNumberOfEachSliceGroupID = 3;
else if (pps->num_slice_groups_minus1+1 > 2)
bitsNumberOfEachSliceGroupID = 2;
else
bitsNumberOfEachSliceGroupID = 1;
// 2.动态初始化指针pps->slice_group_id
pps->pic_size_in_map_units_minus1 = bs_read_ue(b, "PPS: pic_size_in_map_units_minus1");
pps->slice_group_id = calloc(pps->pic_size_in_map_units_minus1+1, 1);
if (pps->slice_group_id == NULL) {
fprintf(stderr, "%s\n", "parse_pps_syntax_element slice_group_id Error");
exit(-1);
}
for (int i = 0; i <= pps->pic_size_in_map_units_minus1; i++) {
pps->slice_group_id[i] = bs_read_u(b, bitsNumberOfEachSliceGroupID, "PPS: slice_group_id[]");
}
}
}
/* —————————— FMO相关 End —————————— */
pps->num_ref_idx_l0_default_active_minus1 = bs_read_ue(b, "PPS: num_ref_idx_l0_default_active_minus1");
pps->num_ref_idx_l1_default_active_minus1 = bs_read_ue(b, "PPS: num_ref_idx_l1_default_active_minus1");
pps->weighted_pred_flag = bs_read_u(b, 1, "PPS: weighted_pred_flag");
pps->weighted_bipred_idc = bs_read_u(b, 2, "PPS: weighted_bipred_idc");
pps->pic_init_qp_minus26 = bs_read_se(b, "PPS: pic_init_qp_minus26");
pps->pic_init_qs_minus26 = bs_read_se(b, "PPS: pic_init_qs_minus26");
pps->chroma_qp_index_offset = bs_read_se(b, "PPS: chroma_qp_index_offset");
pps->deblocking_filter_control_present_flag = bs_read_u(b, 1, "PPS: deblocking_filter_control_present_flag");
pps->constrained_intra_pred_flag = bs_read_u(b, 1, "PPS: constrained_intra_pred_flag");
pps->redundant_pic_cnt_present_flag = bs_read_u(b, 1, "PPS: redundant_pic_cnt_present_flag");
// 如果有更多rbsp数据
if (more_rbsp_data(b)) {
pps->transform_8x8_mode_flag = bs_read_u(b, 1, "PPS: transform_8x8_mode_flag");
pps->pic_scaling_matrix_present_flag = bs_read_u(b, 1, "PPS: pic_scaling_matrix_present_flag");
if (pps->pic_scaling_matrix_present_flag) {
int chroma_format_idc = Sequence_Parameters_Set_Array[pps->seq_parameter_set_id].chroma_format_idc;
int scalingListCycle = 6 + ((chroma_format_idc != YUV_4_4_4) ? 2 : 6) * pps->transform_8x8_mode_flag;
for (int i = 0; i < scalingListCycle; i++) {
pps->pic_scaling_list_present_flag[i] = bs_read_u(b, 1, "PPS: pic_scaling_list_present_flag[]");
if (pps->pic_scaling_list_present_flag[i]) {
if (i < 6) {
scaling_list(pps->ScalingList4x4[i], 16, &pps->UseDefaultScalingMatrix4x4Flag[i], b);
}else {
scaling_list(pps->ScalingList8x8[i-6], 64, &pps->UseDefaultScalingMatrix8x8Flag[i], b);
}
}
}
}
pps->second_chroma_qp_index_offset = bs_read_se(b, "PPS: second_chroma_qp_index_offset");
}else {
pps->second_chroma_qp_index_offset = pps->chroma_qp_index_offset;
}
}
注意到其中大部分句法元素的解析,都和协议文档一模一样。只是其中pps->slice_group_id[]的解析,我们需要注意两点:
(1)pps->slice_group_id[I]的解析方式为u(v),其中的v需要动态计算,根据slice_group_id的语义可知v的计算方式为:Ceil( Log2( num_slice_groups_minus1 + 1 ) )
,也即对num_slice_groups取以2为底的指数,然后对其指数向上取整。
开始我们已经说过num_slice_groups_minus1的取值范围为[0, 7],因此Ceil( Log2( num_slice_groups_minus1 + 1 ) )的结果无非三种情况:1、2、3。因此我们不需要专门写一个函数调用,在这里直接手撸一遍即可。
(2)第二点就是开始说过的指针pps->slice_group_id的初始化问题了,这里我们直接指定其大小,为刚刚解析到的pic_size_in_map_units
。
另外,最后一个句法元素second_chroma_qp_index_offset的解析也需注意,因为在h264语法结构表中,只指定了句法元素的解析过程,并没有指定它们的默认值。因此根据其语义,需要在没有解析到的时候,赋予其默认值pps->chroma_qp_index_offset。这种赋默认值的方式,我们在后面解析slice_header时也会很常见。
2.2 save_pps_as_available
此时pps已经解析完毕,我们只需将它保存即可。同样,我们定义一个全局变量Picture_Parameters_Set_Array[]
,它的大小256,即为pps_id的取值范围[0, 255]。
// 因为pps_id的取值范围为[0,255],因此数组容量最大为256,详见7.4.2.2
pps_t Picture_Parameters_Set_Array[256];
类似sps,我们也需要将pps以pps_id为索引,存入Picture_Parameters_Set_Array[],以便后续slice引用。
void save_pps_as_available(pps_t *pps)
{
// 2.更新同一个pps_id对应的pps时再释放
if (Picture_Parameters_Set_Array[pps->pic_parameter_set_id].slice_group_id != NULL) {
free(Picture_Parameters_Set_Array[pps->pic_parameter_set_id].slice_group_id);
}
// 0.保存sps
memcpy (&Picture_Parameters_Set_Array[pps->pic_parameter_set_id], pps, sizeof (pps_t));
// 1.不释放由pps->slice_group_id指向的内存,交由Picture_Parameters_Set_Array[pps->pps_id]使用
Picture_Parameters_Set_Array[pps->pic_parameter_set_id].slice_group_id = pps->slice_group_id;
pps->slice_group_id = NULL;
}
注意到相比sps的保存,这里多了两步,也即注释标注的1、2步。
其中第1步很好理解,这里我们将pps保存到内存,当然指针pps->slice_group_id指向的内存,也应移交给Picture_Parameters_Set_Array[pps->pps_id]使用,因此我们只需将指针pps->slice_group_id置NULL,却并不释放其指向的内存。而在freePPS()中释放slice_group_id时,也需判断slice_group_id是否为NULL。
第2步则是出于长远考虑,如果将来更新了相同pps_id的pps,则那时需要将slice_group_id指向的内存释放。
3、trace文件
解析完成后生成的trace文件如下(只贴pps部分):
Annex B NALU len 5, forbidden_bit 0, nal_reference_idc 3, nal_unit_type 8
@ PPS: pic_parameter_set_id ( 0)
@ PPS: seq_parameter_set_id ( 0)
@ PPS: entropy_coding_mode_flag ( 0)
@ PPS: bottom_field_pic_order_in_frame_present_flag ( 0)
@ PPS: num_slice_groups_minus1 ( 0)
@ PPS: num_ref_idx_l0_default_active_minus1 ( 9)
@ PPS: num_ref_idx_l1_default_active_minus1 ( 9)
@ PPS: weighted_pred_flag ( 0)
@ PPS: weighted_bipred_idc ( 0)
@ PPS: pic_init_qp_minus26 ( 0)
@ PPS: pic_init_qs_minus26 ( 0)
@ PPS: chroma_qp_index_offset ( 0)
@ PPS: deblocking_filter_control_present_flag ( 0)
@ PPS: constrained_intra_pred_flag ( 0)
@ PPS: redundant_pic_cnt_present_flag ( 0)
本文源码地址如下(H264Analysis_05中):