从Slice_Header学习H.264(三.1)--相关细节之 POC的计算

本文详细解析H.264标准中图像序列号(POC)的三种计算方法,包括pic_order_cnt_type=0、1、2的具体实现细节与应用场景,帮助读者深入理解视频解码中的关键概念。

三、slice头相关的一些细节

 

1.关于POC的计算

      

       图像序列号(POC)主要用于标识图象的播放顺序,同时还用于在对帧间预测片解码时,标记参考图像的初始图像序号

       对于每个编码帧有两个图像序列号,分别称为顶场序列号(TopFieldOrderCnt)和底场序列号(BottomFieldOrderCnt );对于每个编码场有一个图像序列号,对于一个编码顶场其称为TopFieldOrderCnt,对于编码底场,其称为 BottomFieldOrderCnt ;对于每个编码场对有两个图像序列号,TopFieldOrderCnt 和BottomFieldOrderCnt 分别用于标记该场对的顶场和底场。

opFieldOrderCnt 和BottomFieldOrderCnt 分别指明了相应的顶场/ 底场相对于前一个IDR 图像,或解码顺序中前一个包含memory_management_control_operation=5的参考图像(此值为5表示清空参考帧队列,因此有着跟IDR同样的效果),的第一个输出场的相对位置。这也意味着,只要遇到IDR图像或memory_management_control_operation=5的参考图像,相应的POC就等于零。

在H.264中,由于B帧可以进行双向预测,因此图像的解码顺序可以不同于播放顺序。

前面已经提到,计算POC(也就是计算TopFieldOrderCnt和BottomFieldOrderCnt),有三种方法,具体使用哪种由序列参数集中的pic_order_cnt_type元素指定。下面一次介绍三种方法。

(1)      pic_order_cnt_type=0

本过程的输入是在本节规定的解码顺序中前一图像的PicOrderCntMsb,也即prevOrderCntMsb 。 关于Msb:POC由高位Msb和低位Lsb两部分组成,当Lsb发生溢出时,会向Msb进位,Msb+Lsb=POC。

本过程的输出是TopFieldOrderCnt  和BottomFieldOrderCnt ,或者其中之一。

 

大致流程:首先计算变量 prevPicOrderCntMsb,然后计算当前图像的PicOrderCntMsb (刚刚提到POC=Msb+Lsb,Lsb已经在码流的slice_header中由pic_order_cnt_lsb指定,因此主要任务其实就是计算Msb),最后计算当前图像的TopFieldOrderCnt  和(或) BottomFieldOrderCnt。

 

流程图:


              其中,MaxPicOrderCntLsb由序列参数集中的log2_max_pic_order_cnt_lsb_minus4元素确定;delta_pic_order_cnt_bottom在片头中指定,上文介绍POC时已介绍过。

              图中所提到的“乱序”,指的是,解码顺序是否与播放顺序不一致,准确的说,如果当前图像的播放顺序POC比上一个解码图像的播放顺序prePOC小,就是发生了乱序。那如何判断是否发生乱序了呢?一般情况下,Msb在解码顺序相邻的两个图片间不发生变化时(即Lsb不发生溢出或借位),只要判断当前解码的Lsb是否比之前解码的Lsb小即可,如果比之前的小,说明乱序发生;但是如果Lsb发生溢出或借位时,就不能这样简单的判断了(前后两帧的Msb将不再相同),那如何判断Lsb是否发生了溢出或借位呢?这就要用到一个规则:解码顺序相邻的两个图像,他们的播放顺序POC之差(的绝对值)不会超过MaxPicOrderCntLsb / 2,根据这个规则,计算解码顺序相邻的两幅图像的Lsb之差,并与MaxPicOrderCntLsb/2进行比较,就可判断Lsb是否发生了溢出或借位。在标准中,关于Msb的计算是这样描述的:

 

if( ( pic_order_cnt_lsb  < prevPicOrderCntLsb ) &&  

         ( ( prevPicOrderCntLsb − pic_order_cnt_lsb )  >= ( MaxPicOrderCntLsb / 2 ) ) )

         PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb 

else if( ( pic_order_cnt_lsb  > prevPicOrderCntLsb )  &&

           ( ( pic_order_cnt_lsb − prevPicOrderCntLsb )  >  (MaxPicOrderCntLsb / 2 ) ) )

         PicOrderCntMsb = prevPicOrderCntMsb − MaxPicOrderCntLsb (这一步乱序发生)

else

         PicOrderCntMsb = prevPicOrderCntMsb

      

 

(1)      pic_order_cnt_type=1

本过程的输入是在本节规定的解码顺序中前一图像的FrameNumOffset。

本过程的输出是TopFieldOrderCnt  和BottomFieldOrderCnt ,或者其中之一。

计算过程中涉及到两个变量prevFrameNum  和prevFrameNumOffset ,其中prevFrameNum 是前一图像的frame_num  ,而对于prevFrameNumOffset ,如当前图像不是IDR ,而前一图像的memory_management_control_operation等于5 ,prevFrameNumOffset 设为0;否则,prevFrameNumOffset 设置等于前一图像的FrameNumOffset;注意,当序列参数及中的gaps_in_frame_num_value_allowed_flag 等于1 时(表示相邻解码图像的frame_num可以出现间隔),通过frame_num 间隔的解码过程可能会推断出解码顺序中的前一幅图像为“不存在”帧。

大致流程:


       毕厚杰书中的插图并没有完全解释清楚,有很多细节没有说明,看了这个图还是感觉什么都没看懂,所以这部分还是结合标准中的说明来看比较好,但标准中只是说了每个值该如何计算,而没有详细讲解,因此要想搞懂每一步的意义也要费点脑细胞才行。

a.关于absFrameNum:可以理解成“绝对帧序号”,而原来的frame_num则应理解为相对帧序号。前面讲frame_num时提到,当一个序列中的参考帧数量超过MaxFramenum时,frame_num在达到MaxFramenum后会重新从0开始循环计数,这样的话,一个序列中可能会存在两个或多个参考图像拥有相同的“相对帧序号(即frame_num)”的情况 。因此,如果需要一个符号来唯一地标识一个序列中的所有参考帧,用相对帧序号是不行的,于是就需要为每个参考帧分配一个绝对帧序号:absFrameNum=FrameNumOffset + frame_num,FrameNumOffset代表当前序列中frame_num已经循环的次数与MaxFrameNum的积。absFrameNum的具体计算过程如下:

       if(num_ref_frames_in_pic_order_cnt_cycle !=  0 )

            absFrameNum = FrameNumOffset +frame_num

    else 

            absFrameNum = 0

    if( nal_ref_idc  = = 0  &&  absFrameNum >  0 )

            absFrameNum = absFrameNum − 1

 

       其中,num_ref_frames_in_pic_order_cnt_cycle在序列参数集中指定,其取值范围[0,255],它是数组offset_for_ref_frame[]的变化周期(或者说数组长度),这个数组也是在序列参数集中指定的,这个周期和这个数组到底什么意思呢?在用第二种POC计算方法时,一个参考帧跟下一个参考帧,他们POC的差值不能是任意的,必须是周期变化的,即每隔num_ref_frames_in_pic_order_cnt_cycle个参考帧,相邻参考帧之间POC的差值循环一次,而每个周期中,第i个差值即为offset_for_ref_frame[i],正是这个规律,才使得第二种POC算法变得可行,在步骤c、d中将会看到这个数组的作用。

nal_ref_idc  = =  0 表示当前图像不是参考图像。当当前图像不是参考图像时,absFrameNum要额外减1。

 

b. picOrderCntCycleCnt 和 frameNumInPicOrderCntCycle 这俩分别代表absFrameNum对num_ref_frames_in_pic_order_cnt_cycle(前面说到的周期值)取模和取余。当absFrameNum 大于0 时,二者的值由如下方法得到:

if(absFrameNum  >  0 ) {

picOrderCntCycleCnt=(absFrameNum− 1 ) / num_ref_frames_in_pic_order_cnt_cycle ;

       //得到完整周期数。

frameNumInPicOrderCntCycle=(absFrameNum−1)%num_ref_frames_in_pic_order_cnt_cycle;

       //得到最后一个不完整的周期中参考帧的数量

}

 

c.关于expectedDeltaPerPicOrderCntCycle:代表每隔一个完整周期,POC总的变化量。求这个值只需将上面说到的数组中各个元素相加即可。

expectedDeltaPerPicOrderCntCycle = 0

for( i = 0;  i <num_ref_frames_in_pic_order_cnt_cycle; i++ )

expectedDeltaPerPicOrderCntCycle += offset_for_ref_frame[ i ] 

      

       d.关于expectedPicOrderCnt:期望的POC值。要得到这个值,只需用POC在一个完整周期内的总变化量乘以周期数,再加上最后一个不完整周期跨越的POC数量即可。

              if(absFrameNum > 0 ){

                     expectedPicOrderCnt=picOrderCntCycleCnt* expectedDeltaPerPicOrderCntCycle

           for( i = 0; i <= frameNumInPicOrderCntCycle; i++ )

 //循环计算最后一个不完整周期所跨越的POC数量。

expectedPicOrderCnt = expectedPicOrderCnt + offset_for_ref_frame[ i]

        } else

           expectedPicOrderCnt = 0

      if( nal_ref_idc  = =  0 )      //对于非参考图像

           expectedPicOrderCnt = expectedPicOrderCnt + offset_for_non_ref_pic

              其中,offset_for_ref_frame[ i ](前面已经说过)和offset_for_non_ref_pic都是在序列参数集中指定,他们的取值范围都是[-2^31,2^31-1]

 

e.变量TopFieldOrderCnt  或 BottomFieldOrderCnt 的值由如下方法得到:

if( !field_pic_flag ) {    //当前图像不是场图像

           TopFieldOrderCnt = expectedPicOrderCnt + delta_pic_order_cnt[ 0 ]

           BottomFieldOrderCnt = TopFieldOrderCnt +

                          offset_for_top_to_bottom_field + delta_pic_order_cnt[ 1 ]  

        } else if( !bottom_field_flag )

            TopFieldOrderCnt = expectedPicOrderCnt + delta_pic_order_cnt[ 0 ]

      else

           BottomFieldOrderCnt =  expectedPicOrderCnt + offset_for_top_to_bottom_field +delta_pic_order_cnt[ 0 ]

      

       其中,offset_for_top_to_bottom_field在序列参数集中指定,delta_pic_order_cnt[ 0或1 ]在片头指定。

       补充:从上面的过程中可以看到,当图像序列中出现两个或多个连续的非参考帧时,这些非参考帧将具有相同的POC期望值(即expectedPicOrderCnt),但是他们的顶场序号和底场序号可以通过各自的delta_pic_order_cnt[0和1 ]加以区别。因此第二种POC计算方法也是支持图像序列中出现连续非参考帧的。而下面将介绍的第三种POC计算方法则不支持连续的非参考帧。

 

 

(1)      pic_order_cnt_type=2

第三种POC计算方法所依赖的额外参数最少(可以节省片头的比特数),它只根据frame_num就可以得到顶、底场的序列号。但是缺点是,用这种方法时,不允许图像序列中出现连续的非参考帧。

大致流程:



这个方法不像第二种那么麻烦,从图示中已可以看出完整的过程。唯一需要说的一点是,最后计算顶、底场序号时的规则(这个规则在三种方法中各不相同):

       if(!field_pic_flag ) {

           TopFieldOrderCnt = tempPicOrderCnt

           BottomFieldOrderCnt = tempPicOrderCnt 

} else if(bottom_field_flag )

           BottomFieldOrderCnt = tempPicOrderCnt

else

            TopFieldOrderCnt = tempPicOrderCnt

 

       在这种方法中,如果解码顺序相邻的两个帧具有相同的frame_num,那么其中必有一个是参考帧,而另一个则是非参考帧,而且参考帧总是在非参考帧之前进行编解码(和传输),但非参考帧比参考帧先采样和播放(即非参考帧的POC更靠前)。

 

 


/*---------------------------------------------------------------------- | AP4_HevcVuiParameterSet::AP4_HevcVuiParameterSet +---------------------------------------------------------------------*/ AP4_Result AP4_HevcVuiParameterSet::Parse(AP4_BitReader& bits) { aspect_ratio_info_present_flag = bits.ReadBit(); if (aspect_ratio_info_present_flag) { aspect_ratio_idc = bits.ReadBits(8); // Appendix E. Table E-1 Meaning of sample aspect ratio indicator if (aspect_ratio_idc == 255/*Extended_SAR*/) { sar_width = bits.ReadBits(16); sar_height = bits.ReadBits(16); } } overscan_info_present_flag = bits.ReadBit(); if (overscan_info_present_flag) { overscan_appropriate_flag = bits.ReadBit(); } video_signal_type_present_flag = bits.ReadBit(); if (video_signal_type_present_flag) { video_format = bits.ReadBits(3); video_full_range_flag = bits.ReadBit(); colour_description_present_flag = bits.ReadBit(); if (colour_description_present_flag) { colour_primaries = bits.ReadBits(8); transfer_characteristics = bits.ReadBits(8); matrix_coefficients = bits.ReadBits(8); } } chroma_loc_info_present_flag = bits.ReadBit(); if (chroma_loc_info_present_flag) { chroma_sample_loc_type_top_field = ReadGolomb(bits); chroma_sample_loc_type_bottom_field = ReadGolomb(bits); } neutral_chroma_indication_flag = bits.ReadBit(); field_seq_flag = bits.ReadBit(); frame_field_info_present_flag = bits.ReadBit(); default_display_window_flag = bits.ReadBit(); if (default_display_window_flag) { def_disp_win_left_offset = ReadGolomb(bits); def_disp_win_right_offset = ReadGolomb(bits); def_disp_win_top_offset = ReadGolomb(bits); def_disp_win_bottom_offset = ReadGolomb(bits); } timing_info_present_flag = bits.ReadBit(); if (timing_info_present_flag) { num_units_in_tick = bits.ReadBits(32); time_scale = bits.ReadBits(32); vui_poc_proportional_to_timing_flag = bits.ReadBit(); if (vui_poc_proportional_to_timing_flag) { vui_num_ticks_poc_diff_one_minus1 = ReadGolomb(bits); } } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | AP4_HevcPictureParameterSet::AP4_HevcPictureParameterSet +---------------------------------------------------------------------*/ AP4_HevcPictureParameterSet::AP4_HevcPictureParameterSet() : pps_pic_parameter_set_id(0), pps_seq_parameter_set_id(0), dependent_slice_segments_enabled_flag(0), output_flag_present_flag(0), num_extra_slice_header_bits(0), sign_data_hiding_enabled_flag(0), cabac_init_present_flag(0), num_ref_idx_l0_default_active_minus1(0), num_ref_idx_l1_default_active_minus1(0), init_qp_minus26(0), constrained_intra_pred_flag(0), transform_skip_enabled_flag(0), cu_qp_delta_enabled_flag(0), diff_cu_qp_delta_depth(0), pps_cb_qp_offset(0), pps_cr_qp_offset(0), pps_slice_chroma_qp_offsets_present_flag(0), weighted_pred_flag(0), weighted_bipred_flag(0), transquant_bypass_enabled_flag(0), tiles_enabled_flag(0), entropy_coding_sync_enabled_flag(0), num_tile_columns_minus1(0), num_tile_rows_minus1(0), uniform_spacing_flag(1), loop_filter_across_tiles_enabled_flag(0), pps_loop_filter_across_slices_enabled_flag(0), deblocking_filter_control_present_flag(0), deblocking_filter_override_enabled_flag(0), pps_deblocking_filter_disabled_flag(0), pps_beta_offset_div2(0), pps_tc_offset_div2(0), pps_scaling_list_data_present_flag(0), lists_modification_present_flag(0), log2_parallel_merge_level_minus2(0), slice_segment_header_extension_present_flag(0) { } /*---------------------------------------------------------------------- | AP4_HevcPictureParameterSet::Parse +---------------------------------------------------------------------*/ AP4_Result AP4_HevcPictureParameterSet::Parse(const unsigned char* data, unsigned int data_size) { raw_bytes.SetData(data, data_size); AP4_DataBuffer unescaped(data, data_size); AP4_NalParser::Unescape(unescaped); AP4_BitReader bits(unescaped.GetData(), unescaped.GetDataSize()); bits.SkipBits(16); // NAL Unit Header pps_pic_parameter_set_id = ReadGolomb(bits); if (pps_pic_parameter_set_id > AP4_HEVC_PPS_MAX_ID) { DBG_PRINTF_2("pps_pic_parameter_set_id[%d] > AP4_HEVC_PPS_MAX_ID[%d]\n", pps_pic_parameter_set_id, AP4_HEVC_PPS_MAX_ID); return AP4_ERROR_INVALID_FORMAT; } pps_seq_parameter_set_id = ReadGolomb(bits); if (pps_seq_parameter_set_id > AP4_HEVC_SPS_MAX_ID) { DBG_PRINTF_2("pps_seq_parameter_set_id[%d] > AP4_HEVC_SPS_MAX_ID[%d]\n", pps_seq_parameter_set_id, AP4_HEVC_SPS_MAX_ID); return AP4_ERROR_INVALID_FORMAT; } dependent_slice_segments_enabled_flag = bits.ReadBit(); output_flag_present_flag = bits.ReadBit(); num_extra_slice_header_bits = bits.ReadBits(3); sign_data_hiding_enabled_flag = bits.ReadBit(); cabac_init_present_flag = bits.ReadBit(); num_ref_idx_l0_default_active_minus1 = ReadGolomb(bits); num_ref_idx_l1_default_active_minus1 = ReadGolomb(bits); init_qp_minus26 = SignedGolomb(ReadGolomb(bits)); constrained_intra_pred_flag = bits.ReadBit(); transform_skip_enabled_flag = bits.ReadBit(); cu_qp_delta_enabled_flag = bits.ReadBit(); if (cu_qp_delta_enabled_flag) { diff_cu_qp_delta_depth = ReadGolomb(bits); } pps_cb_qp_offset = SignedGolomb(ReadGolomb(bits)); pps_cr_qp_offset = SignedGolomb(ReadGolomb(bits)); pps_slice_chroma_qp_offsets_present_flag = bits.ReadBit(); weighted_pred_flag = bits.ReadBit(); weighted_bipred_flag = bits.ReadBit(); transquant_bypass_enabled_flag = bits.ReadBit(); tiles_enabled_flag = bits.ReadBit(); entropy_coding_sync_enabled_flag = bits.ReadBit(); if (tiles_enabled_flag) { num_tile_columns_minus1 = ReadGolomb(bits); num_tile_rows_minus1 = ReadGolomb(bits); uniform_spacing_flag = bits.ReadBit(); if (!uniform_spacing_flag) { for (unsigned int i=0; i<num_tile_columns_minus1; i++) { ReadGolomb(bits); // column_width_minus1[i] } for (unsigned int i = 0; i < num_tile_rows_minus1; i++) { ReadGolomb(bits); // row_height_minus1[i] } } loop_filter_across_tiles_enabled_flag = bits.ReadBit(); } pps_loop_filter_across_slices_enabled_flag = bits.ReadBit(); deblocking_filter_control_present_flag = bits.ReadBit(); if (deblocking_filter_control_present_flag) { deblocking_filter_override_enabled_flag = bits.ReadBit(); pps_deblocking_filter_disabled_flag = bits.ReadBit(); if (!pps_deblocking_filter_disabled_flag) { pps_beta_offset_div2 = SignedGolomb(ReadGolomb(bits)); pps_tc_offset_div2 = SignedGolomb(ReadGolomb(bits)); } } pps_scaling_list_data_present_flag = bits.ReadBit(); if (pps_scaling_list_data_present_flag) { scaling_list_data(bits); } lists_modification_present_flag = bits.ReadBit(); log2_parallel_merge_level_minus2 = ReadGolomb(bits); slice_segment_header_extension_present_flag = bits.ReadBit(); return AP4_SUCCESS; } 详细注释上述代码
09-25
### 明:本段代码实现H.265/HEVC标准中视频可用性信息(VUI)和图像参数(PPS)的解析逻辑,包含关键语法元素的读取和处理流程。 #### 1. VUI参数解析(AP4_HevcVuiParameterSet::Parse) ```cpp /*---------------------------------------------------------------------- | AP4_HevcVuiParameterSet::Parse +---------------------------------------------------------------------*/ AP4_Result AP4_HevcVuiParameterSet::Parse(AP4_BitReader& bits) { // [1] 宽高比信息解析 aspect_ratio_info_present_flag = bits.ReadBit(); if (aspect_ratio_info_present_flag) { aspect_ratio_idc = bits.ReadBits(8); // 标准预定义ID(255表示自定义) if (aspect_ratio_idc == 255) { // Extended_SAR sar_width = bits.ReadBits(16); // 自定义宽高比分子 sar_height = bits.ReadBits(16); // 自定义宽高比分母 } } // [2] 过扫描信息(适用于电视信号) overscan_info_present_flag = bits.ReadBit(); if (overscan_info_present_flag) { overscan_appropriate_flag = bits.ReadBit(); // 1=内容适配过扫描显示 } // [3] 视频信号类型(色彩空间定义) video_signal_type_present_flag = bits.ReadBit(); if (video_signal_type_present_flag) { video_format = bits.ReadBits(3); // 0-7对应NTSC/PAL等制式 video_full_range_flag = bits.ReadBit(); // 0=窄范围(16-235),1=全范围(0-255) colour_description_present_flag = bits.ReadBit(); if (colour_description_present_flag) { colour_primaries = bits.ReadBits(8); // 颜色原色(如BT.709=1) transfer_characteristics = bits.ReadBits(8); // 传输函数(如HLG=18) matrix_coefficients = bits.ReadBits(8); // 矩阵系数(如BT.2020=9) } } // [4] 色度采样位置(影响YUV420等格式的色度对齐) chroma_loc_info_present_flag = bits.ReadBit(); if (chroma_loc_info_present_flag) { chroma_sample_loc_type_top_field = ReadGolomb(bits); // 顶场位置 chroma_sample_loc_type_bottom_field = ReadGolomb(bits); // 底场位置 } // [5] 时序信息(关键计算参数) timing_info_present_flag = bits.ReadBit(); if (timing_info_present_flag) { num_units_in_tick = bits.ReadBits(32); // 时间刻度分子 time_scale = bits.ReadBits(32); // 时间刻度分母 // 计算公式:fps = time_scale / (2 * num_units_in_tick) vui_poc_proportional_to_timing_flag = bits.ReadBit(); if (vui_poc_proportional_to_timing_flag) { vui_num_ticks_poc_diff_one_minus1 = ReadGolomb(bits); // POC时序比例因子 } } return AP4_SUCCESS; } ``` #### 2. 图像参数集解析(AP4_HevcPictureParameterSet) ```cpp /*---------------------------------------------------------------------- | AP4_HevcPictureParameterSet::Parse +---------------------------------------------------------------------*/ AP4_Result AP4_HevcPictureParameterSet::Parse(const unsigned char* data, unsigned int data_size) { // [1] 初始化比特流读取器 AP4_DataBuffer unescaped(data, data_size); AP4_NalParser::Unescape(unescaped); // 去除防竞争字节(0x03) AP4_BitReader bits(unescaped.GetData(), unescaped.GetDataSize()); bits.SkipBits(16); // 跳过NAL单元头 // [2] 基础参数读取 pps_pic_parameter_set_id = ReadGolomb(bits); // PPS ID(0-63) pps_seq_parameter_set_id = ReadGolomb(bits); // 关联的SPS ID(0-15) // [3] 切片控制参数 dependent_slice_segments_enabled_flag = bits.ReadBit(); // 是否启用依赖切片 num_extra_slice_header_bits = bits.ReadBits(3); // 切片头扩展位数 // [4] 量化参数控制 init_qp_minus26 = SignedGolomb(ReadGolomb(bits)); // 初始QP偏移(-26~+25) pps_cb_qp_offset = SignedGolomb(ReadGolomb(bits)); // CB分量QP偏移(-12~+12) pps_cr_qp_offset = SignedGolomb(ReadGolomb(bits)); // CR分量QP偏移 // [5] 瓦片(tile)划分参数 tiles_enabled_flag = bits.ReadBit(); if (tiles_enabled_flag) { num_tile_columns_minus1 = ReadGolomb(bits); // 瓦片列数-1(0~19) num_tile_rows_minus1 = ReadGolomb(bits); // 瓦片行数-1(0~21) uniform_spacing_flag = bits.ReadBit(); // 是否均匀划分 if (!uniform_spacing_flag) { for (unsigned int i=0; i<num_tile_columns_minus1; i++) ReadGolomb(bits); // 每列宽度-1 for (unsigned int i=0; i<num_tile_rows_minus1; i++) ReadGolomb(bits); // 每行高度-1 } } // [6] 去块滤波参数 deblocking_filter_control_present_flag = bits.ReadBit(); if (deblocking_filter_control_present_flag) { pps_beta_offset_div2 = SignedGolomb(ReadGolomb(bits)); // β偏移/2(-6~+6) pps_tc_offset_div2 = SignedGolomb(ReadGolomb(bits)); // tc偏移/2(-6~+6) } // [7] 高级特性标志 scaling_list_data_present_flag = bits.ReadBit(); // 是否启用自定义量化矩阵 log2_parallel_merge_level_minus2 = ReadGolomb(bits); // 并行合并深度(0~3) return AP4_SUCCESS; } ``` ### 关键数据结构说明 | 参数类别 | 典型参数 | 作用说明 | |---------|---------|---------| | **VUI参数** | `time_scale/num_units_in_tick` | 计算真实率,例如`time_scale=180000, num_units_in_tick=1500`对应60fps | | **量化控制** | `init_qp_minus26` | 初始QP值=26+该值,控制整体码率与质量 | | **瓦片划分** | `num_tile_columns_minus1` | 实际瓦片列数=该值+1,支持并行解码 | | **色度控制** | `pps_cb_qp_offset` | 色度分量QP偏移,影响色彩质量 |
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值