前面我們解析了序列參數集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中):