目录
一、H264 NAL头部
二、H264参数集
2.1 H264 SPS
2.2 H264 PPS
三、H264 Slice
五、H264参考帧管理方法
Elecard StreamEye工具
编码时,一帧图片分为若干slice,编码以slice为单位进行,即slice之间编码相互独立。每个slice分为若干宏块MB,宏块MB是H264编码的基本单位。编码完成后输出宏块MB的亮度色度残差值经预测、变换、量化、熵编码后的数据以及多种语法元素,我们需要将编码输出值按照一定的格式打包成NAL单元用于传输。
NAL单元由NAL头和NAL单元载荷组成。NAL单元分为两类:一类为参数集SPS,PPS NAL单元以及补充增强信息SEI NAL单元等。另一类为slice NAL单元。NAL单元载荷按8字节对齐。
NAL头部共一个字节。第7位为禁止位,编码中默认为0,当网络识别此单元中存在比特错误时可将其设置为1,以便接受方丢弃该单元。第6~5位为优先级,取值范围为0~3,值越高表示当前NAL越重要,需要优先受到保护。第4~0位表示NAL的类型,如非IDR图像中的slice且slice不分区,IDR图像中的slice,补充增强信息SEI,序列参数集SPS,图像参数集PPS等。
每个NAL单元前需添加3字节起始码0x000001或者4字节0x00000001,解码器一旦在码流中检测到起始码,就说明刚刚检测的NAL结束,新的NAL即将开始。为防止NAL数据中存在0x000001编码器会将NAL数据中所有的00000000 00000000 000000XX的数据改写为00000000 00000000 00000011 000000XX,其中XX可为00、01、10、11。解码时若发现00000000 00000000 00000011 000000XX则改写为00000000 00000000 000000XX还原为原来数据。
00 00 00 01 67 SPS NALU头部为0x67,NALU类型为7。
00 00 00 01 68 PPS NALU头部为0x68,NALU类型为8。
00 00 00 01 65 I slice NALU头部为0x65,NALU类型为5。
00 00 00 01 61 P slice NALU头部为0x61,NALU类型为1。
H264参数集包括序列参数集SPS,图像参数集PPS等。H264参数集是将原本属于序列和图像头部的大部分语法元素分离出来,形成序列和图像两级参数集,其余的部分放入条层。
参数集只是在条层语法元素需要的时候被引用,而且一个参数集并不对应某个特定的图像或序列,同一个序列参数集可以被多个序列中的图像参数集引用。同理,一个图像参数集也可以被多幅图像引用。由于参数集是一个独立单元,不依赖于参数集外的其他语法元素,可以被多次重发或采用特殊技术保护。
SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded Video sequence)的全局参数。所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。但在某些特殊情况下,在码流中间也可能出现这两种结构,主要原因可能为:解码器需要在码流中间开始解码、编码器在编码的过程中改变了码流的参数(如图像分辨率等)等;
在做视频播放器时,为了让后续的解码过程可以使用SPS中包含的参数,必须对其中的数据进行解析。
@ profile_idc:标识当前H.264码流的profile。
H.264中定义了三种常用的档次profile:
基准档次:baseline profile,profile_idc = 66;
主要档次:main profile,profile_idc = 77;
扩展档次:extended profile。profile_idc = 88;
@ constraint_set0_flag~constraint_set5_flag:编码的档次方面对码流增加的其他一些额外限制性条件。
@ level_idc:标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的level由level_idc指定。
@ seq_parameter_set_id:表示当前的序列参数集的id。通过该id值,图像参数集pps可以引用其代表的sps中的参数。
@ log2_max_frame_num_minus4:用于计算MaxFrameNum的值。计算公式为MaxFrameNum = 2^(log2_max_frame_num_minus4+4)。MaxFrameNum是frame_num的上限值,frame_num是图像序号的一种表示方法,在帧间编码中常用作一种参考帧标记的手段。
@ pic_order_cnt_type:表示解码picture order count(POC)的方法。POC是另一种计量图像序号的方式,与frame_num有着不同的计算方法。该语法元素的取值为0、1或2。
@ log2_max_pic_order_cnt_lsb_minus4:用于计算MaxPicOrderCntLsb的值,该值表示POC的上限。计算方法为MaxPicOrderCntLsb = 2^(log2_max_pic_order_cnt_lsb_minus4 + 4)。
@ max_num_ref_frames:用于表示参考帧的最大数目。
@ gaps_in_frame_num_value_allowed_flag:标识位,说明frame_num中是否允许不连续的值。
@ pic_width_in_mbs_minus1:用于计算图像的宽度。单位为宏块个数,因此图像的实际宽度为:frame_width = 16 × (pic_width_in_mbs_minus1 + 1);
@ pic_height_in_map_units_minus1:使用PicHeightInMapUnits来度量视频中一帧图像的高度。PicHeightInMapUnits并非图像明确的以像素或宏块为单位的高度,而需要考虑该宏块是帧编码或场编码。PicHeightInMapUnits的计算方式为:PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1;
@ frame_mbs_only_flag:标识位,说明宏块的编码方式。当该标识位为0时,宏块可能为帧编码或场编码;该标识位为1时,所有宏块都采用帧编码。根据该标识位取值不同,PicHeightInMapUnits的含义也不同,为0时表示一场数据按宏块计算的高度,为1时表示一帧数据按宏块计算的高度。
按照宏块计算的图像实际高度FrameHeightInMbs的计算方法为:
FrameHeightInMbs = ( 2 − frame_mbs_only_flag ) * PicHeightInMapUnits
@ mb_adaptive_frame_field_flag:标识位,说明是否采用了宏块级的帧场自适应编码。当该标识位为0时,不存在帧编码和场编码之间的切换;当标识位为1时,宏块可能在帧编码和场编码模式之间进行选择。
@ direct_8x8_inference_flag:标识位,用于B_Skip、B_Direct模式运动矢量的推导计算。
@ frame_cropping_flag:标识位,说明是否需要对输出的图像帧进行裁剪。
@ vui_parameters_present_flag:标识位,说明SPS中是否存在VUI信息。
除了序列参数集SPS之外,H.264中另一重要的参数集合为图像参数集Picture Paramater Set(PPS)。通常情况下,PPS类似于SPS,在H.264的裸码流中单独保存在一个NAL Unit中,只是PPS NAL Unit的nal_unit_type值为8;而在封装格式中,PPS通常与SPS一起,保存在视频文件的文件头中。
@ pic_parameter_set_id:表示当前PPS的id。某个PPS在码流中会被相应的slice引用,slice引用PPS的方式就是在Slice header中保存PPS的id值。该值的取值范围为[0,255]。
@ seq_parameter_set_id:表示当前PPS所引用的激活的SPS的id。通过这种方式,PPS中也可以取到对应SPS中的参数。该值的取值范围为[0,31]。
@ entropy_coding_mode_flag:熵编码模式标识,该标识位表示码流中熵编码/解码选择的算法。对于部分语法元素,在不同的编码配置下,选择的熵编码方式不同。例如在一个宏块语法元素中,宏块类型mb_type的语法元素描述符为“ue(v) | ae(v)”,在baseline profile等设置下采用指数哥伦布编码,在main profile等设置下采用CABAC编码。
标识位entropy_coding_mode_flag的作用就是控制这种算法选择。当该值为0时,选择左边的算法,通常为指数哥伦布编码或者CAVLC;当该值为1时,选择右边的算法,通常为CABAC。
@ bottom_field_pic_order_in_frame_present_flag:标识位,用于表示另外条带头中的两个语法元素delta_pic_order_cnt_bottom和delta_pic_order_cn是否存在的标识。这两个语法元素表示了某一帧的底场的POC的计算方法。
@ num_slice_groups_minus1:表示某一帧中slice group的个数。当该值为0时,一帧中所有的slice都属于一个slice group。slice group是一帧中宏块的组合方式,定义在协议文档的3.141部分。
@ num_ref_idx_l0_default_active_minus1、num_ref_idx_l0_default_active_minus1:表示当Slice Header中的num_ref_idx_active_override_flag标识位为0时,P/SP/B slice的语法元素num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1的默认值。
@ weighted_pred_flag:标识位,表示在P/SP slice中是否开启加权预测。
@ weighted_bipred_idc:表示在B Slice中加权预测的方法,取值范围为[0,2]。0表示默认加权预测,1表示显式加权预测,2表示隐式加权预测。
@ pic_init_qp_minus26和pic_init_qs_minus26:表示初始的量化参数。实际的量化参数由该参数、slice header中的slice_qp_delta/slice_qs_delta计算得到。
@ chroma_qp_index_offset:用于计算色度分量的量化参数,取值范围为[-12,12]。
@ deblocking_filter_control_present_flag:标识位,用于表示Slice header中是否存在用于去块滤波器控制的信息。当该标志位为1时,slice header中包含去块滤波相应的信息;当该标识位为0时,slice header中没有相应的信息。
@ constrained_intra_pred_flag:若该标识为1,表示I宏块在进行帧内预测时只能使用来自I和SI类型宏块的信息;若该标识位0,表示I宏块可以使用来自Inter类型宏块的信息。
@ redundant_pic_cnt_present_flag:标识位,用于表示Slice header中是否存在redundant_pic_cnt语法元素。当该标志位为1时,slice header中包含redundant_pic_cnt;当该标识位为0时,slice header中没有相应的信息。
Slice NAL单元载荷由slice头+slice数据+slice尾部填充组成。slice尾部填充的目的是使得NAL单元载荷8字节对齐。
Slice头信息表示当前slice的编码特性,如PPS的语法元素pic_parameter_set_id,类型(I-slice,P-slice等)语法元素slice_type,第一个宏块在slice中的位置语法元素first_mb_in_slice,去方块滤波操作相关语法元素等。
Slice数据部分由多个(宏块语法元素+宏块编码数据)组成。宏块语法元素如编码模式语法元素mb_type,色度帧内预测语法元素intra_chroma_pred_mode,编码块模式的语法元素coded_block_pattern,量化参数调整量的语法元素mb_gp_delta等。宏块编码数据为宏块的亮度色度残差值经预测、变换、量化、熵编码后的数据。
first_mb_in_slice |
表示本片中第一个宏块的地址。如果 MbaffFrameFlag 等于0,first_mb_in_slice就是该条带中第一个宏块的地址,并且first_mb_in_slice 的值应在0到 PicSizeInMbs– 1 的范围内(包括边界值)。否则,first_mb_in_slice * 2 就是该条带中的第一个宏块地址,该宏块是该条带中第一个宏块对中的顶宏块,并且first_mb_in_slice 的值应该在 0 到 PicSizeInMbs/ 2 – 1 的范围内(包括边界值)。其中,MbaffFrameFlag由序列参数集中的mb_adaptive_frame_field_flag指定,它等于1时表示使用帧场自适应模式,否则不使用;PicSizeInMbs表示图像的大小(以宏块为单位),由序列参数集中的pic_width_in_mbs_minus1、pic_height_in_map_units_minus1以及其他一些元素指定(这里就不详细说明了,因为这涉及到映射单元与宏块的对应关系,这到后面会说到)。 |
slice_type |
片的类型 |
pic_parameter_set_id |
引用的图像集索引 |
frame_num |
对于非参考帧来说,它的frame_num 值在解码过程中是没有意义的,因为frame_num 值是参考帧特有的,它的主要作用是在该图像被其他图像引用作运动补偿的参考时提供一个标识。但 H.264 并没有在非参考帧图像中取消这一句法元素,原因是在 POC 的第二种和第三种解码方法中可以通过非参考帧的frame_num 值计算出他们的POC 值。frame_num是对帧编号的,也就是说如果在场模式下,同属一个场对的顶场和底场两个图像的frame_num 的值是相同的。frame_num 是参考帧的标识,但是在解码器中,并不是直接引用的 frame_num 值,而是由frame_num 进一步计算出来的变量 PicNum。MaxPicNum表征PicNum的最大值,在场模式下MaxPicNum=2*MaxFrameNum,否则MaxPicNum =MaxFrameNum。其中,MaxFrameNum 由序列参数集中的log2_max_frame_num_minus4 确定。PicNum 和frame_num 一样,也是嵌在循环中,当达到这个最大值时,PicNum将从0 开始重新计数。CurrPicNum是当前图像的PicNum 值,在计算PicNum的过程中,当前图像的 PicNum 值是由frame_num 直接算出:如果field_pic_flag= 0 , CurrPicNum = frame_num。否则, CurrPicNum= 2 * frame_num + 1。序列参数集中的gaps_in_frame_num_value_allowed_flag等于0时,参考帧的frame_num都是连续的;如果等于1,这时若网络阻塞,编码器可以将编码后的若干图像丢弃,而不用另行通知解码器。在这种情况下,解码器必须有机制将缺失的frame_num 及所对应的图像填补,否则后续图像若将运动矢量指向缺失的图像将会产生解码错误。 |
field_pic_flag |
指定当前图像是帧编码(0)还是场编码(1)。这个元素在同一图像的所有片中应具有相同值。仅当序列参数集中的frame_mbs_only_flag为0时,这个元素才会存在在码流中。 |
bottom_field_flag |
指定当前的场是顶场还是底场。等于1 时表示当前图像是属于底场;等于 0 时表示当前图像是属于顶场。这个元素仅当field_pic_flag存在且为1时(说明当前片属于一个场图像),才会出现在码流中。 |
idr_pic_id |
IDR 图像的标识。不同的 IDR 图像有不同的idr_pic_id 值。在场模式下,IDR帧的两个场有相同的idr_pic_id值。idr_pic_id 的取值范围是 [0,65535],超出此范围时,以循环的方式重新开始计数。 |
pic_order_cnt_lsb |
当序列参数集中的pic_order_cnt_type等于0时,本元素将出现在码流中。在POC 的第一种算法中,本元素“显式地传递了POC值”,准确的说,是POC值的lsb(具体参见标准8.2.1.1)。序列参数集中的log2_max_pic_order_cnt_lsb_minus4元素指定了编码pic_order_cnt_lsb的最大比特数。 |
delta_pic_order_cnt_bottom |
此元素用于POC的第一种算法。当序列参数集中的frame_mb_only_flag 不为 1时(图像序列中既可以有场图像又可以有帧图像),帧或帧场自适应图像中包含的两个场也必须有各自的 POC 值(供后续场图像作为参考图像)。通过此元素可在已解码的帧或帧场自适应图像的 POC 基础上新映射一个POC 值,并把它赋给底场。本元素存在条件:序列参数集中的pic_order_cnt_type等于0(使用第一种算法计算POC)、图像参数集中的pic_order_present_flag等于1(表示与图像顺序数有关的语法元素将出现于条带头中)、 片头的field_pic_flag存在且为0。 |
delta_pic_order_cnt[0] delta_pic_order_cnt[1] |
这两个语法元素功能与delta_pic_order_cnt_bottom类似,只不过这两个元素用于POC的第二、三中算法(这里有点疑问,因为标准的语法表中,这两个元素仅在pic_order_cnt_type=1,即使用第二种POC算法时才出现,这意味着使用第三种POC算法的话,这俩元素就不存在了,既然不存在,还怎么用于第三种算法呢)。POC 的第二和第三种算法是从frame_num映射得来。delta_pic_order_cnt[0]的存在条件:序列参数集中的delta_pic_order_always_zero_flag等于0(等于1表示视频序列的条带头中没有delta_pic_order_cnt[0]和delta_pic_order_cnt[1 ] 两个字段,它们的值都默认为0)、且pic_order_cnt_type = 1(使用第二种POC算法);delta_pic_order_cnt[1]的存在条件:在delta_pic_order_cnt[0]存在条件的基础上,图像参数集中的pic_order_present_flag等于1(表示与图像顺序数有关的语法元素将出现于条带头中)、 片头的field_pic_flag存在且为0。强调:上面提到图像参数集中的pic_order_present_flag等于1表示“与图像顺序数有关的语法元素将出现于条带头中”,但这个pic_order_present_flag并不是对所有与图像顺序相关的语法元素起作用,他只对条带(片)头中的delta_pic_order_cnt_bottom和delta_pic_order_cnt[ 1 ]起作用,而pic_order_cnt_lsb和delta_pic_order_cnt[ 0]则不受其制约。 |
redundant_pic_cnt |
对于属于基本编码图像的条带和条带数据隔离带应等于0。对于一个冗余编码图像的编码条带或编码条带数据隔离带的redundant_pic_cnt 的值应大于0。当redundant_pic_cnt 在比特流中不存在时,应推定其值为0。redundant_pic_cnt 的值应该在0 到127 范围内;每个冗余编码图像都有一个对应的基本编码图像;对于冗余编码图像的编码条带(或数据分割),其通过pic_parameter_set_id指定的图像参数集,必须与对应的基本编码图像的编码条带指定的图像参数集具有相同的pic_order_present_flag值;标准里在介绍这个元素时用了将近一页的篇幅,内容还挺多,不想过现在关于冗余图像的部分,还是先不要看了,越看脑子越乱,先大概知道有这么回事儿,等以后对H.264熟悉了再回来看也许就容易多了。元素存在条件:图像参数集中的redundant_pic_cnt_present_flag等于1(表示redundant_pic_cnt 语法元素将出现在条带头、图像参数集中指明(直接或与相应的数据分割块 A 关联)的数据分割块B 和数据分割块C 中)。对于数据分割,当这个条件符合时,不仅在A分割的码流中会出现redundant_pic_cnt(并不是直接出现,而是包含在分割A的片头结构中),还会在与之对应的B、C分割中也出现(直接出现)。 |
direct_spatial_mv_pred_flag |
指出在B 图像的直接预测的模式下,用时间预测还是用空间预测。1 :空间预测(标准8.4.1.2节给出的亮度运动矢量B_Skip、B_Direct_16x16 和B_Direct_8x8 将使用空间指引的模式来预期);0:时间预测(亮度运动矢量B_Skip、B_Direct_16x16 和 B_Direct_8x8 将使用临时指引的模式来预期)。存在条件: 片头的slice_type =B ,即当前片是B片。 |
num_ref_idx_active_override_flag |
条带头中的这个元素用来决定是否对这两个元素进行重载,如果重载,那么条带头中将再次出现num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1这两个元素,它们将覆盖在图像参数集中的值。 |
num_ref_idx_l0_active_minus1 num_ref_idx_l1_active_minus1 |
在图像参数集中,已经通过num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1这两个元素指明了参考帧数目,num_ref_idx_l0_active_minus1 表示参考图像列表0 的最大参考索引号,该索引号将用来在一幅图像中num_ref_idx_active_override_flag 等于0 的条带使用列表0 预测时,解码该图像的这些条带。当MbaffFrameFlag 等于1 时(帧场自适应),num_ref_idx_l0_active_minus1 是帧宏块解码的最大索引号值,而2 * num_ref_idx_l0_active_minus1 + 1 是场宏块解码的最大索引号值。(帧场自适应模式下,一个宏块是帧宏块还是场宏块由slice_data语法中的mb_field_decoding_flag元素指定,每个宏块都可以单独指定自己的帧/场模式)。num_ref_idx_l0_active_minus1 的值应该在0 到31的范围内。num_ref_idx_l1_active_minus1具有类似的含义及规则,它表示的是List1中的最大参考索引号。为给某些特定图像更大的灵活度,在条带头中,可以重载这两个元素。 |
ref_pic_list_reordering |
参考帧重排序。这个语法项目嵌套在条带头中,是条带头的一个子项目。 |
pred_weight_table |
预测加权表格。这个语法项目嵌套在条带头中,是条带头的一个子项目。此项目存在条件:1. 如果当前片是P片或SP片,即slice_type = P | | slice_type = SP:如果图像参数集中的weighted_pred_flag为1(表示在P 和SP条带中应使用加权的预测),则pred_weight_table( )存在。 2. 如果当前片是B片,即slice_type= B:如果图像参数集中的weighted_bipred_idc == 1,则pred_weight_table( )存在。关于weighted_bipred_idc ,它等于 0 时表示B 条带应该采用默认的加权预测;等于1 表示 B 条带应该采用具体指明的加权预测,只有这个情况下pred_weight_table( )才存在;等于2 表示B 条带应该采用隐含的加权预测。(疑问:“默认的”和“隐含的”,怎么感觉一个意思呢?)。weighted_bipred_idc的值应该在0 到2 之间(包括0 和2)。 |
dec_ref_pic_marking |
解码的参考图像标识。这个语法项目嵌套在条带头中,是条带头的一个子项目。此项目存在条件:NAL单元中的nal_ref_idc不为0。nal_ref_idc不为0时,表示NAL单元中包含一个序列参数集,或一个图像参数集,或一个参考图像条带,或一个参考图像的条带数据分割。由于本语法项包含在片头中,因此当前的NAL肯定是片或片的数据分割,也就是说,这个语法项存在的条件是:当前的NAL包含的是一个参考图像的条带或条带数据分割。 |
cabac_init_idc |
代表一个表格序号,用于CABAC计算过程,表示用于决定关联变量的初始化过程中使用的初始化表格的序号,范围0 到2。(不太懂没关系,知道有这么个东西即可,等学CABAC时自然会明白)。元素存在条件:图像参数集中的entropy_coding_mode_flag等于1(表示采用cabac编码)、并且slice_type != I && slice_type != SI(表示当前片不是I片或SI片)。 |
slice_qp_delta |
指出在用于当前片的所有宏块的量化参数的初始值,这个元素用于普通帧(非SI和SP的帧),他们量化时都是对预测残差变换后的系数进行量化。QPY。SliceQPY = 26 + pic_init_qp_minus26 +slice_qp_delta 。QPY的范围是 0 to 51 。代表的意义是量化间距。H.264 中的量化参数是分图像参数集、片头、宏块头三层给出的,前两层各自给出一个偏移值,这个句法元素就是片层的偏移。其中,pic_init_qp_minus26位于图像参数集中。 |
slice_qs_delta |
与slice_qp_delta 的与语义相似,用在 SI 和SP中 (这两种片都是直接对预测值和实际值进行变换后对系数进行量化,而不是对残差值变换后的系数进行量化)。QSY = 26 + pic_init_qs_minus26 + slice_qs_delta。QSY 值的范围是0 到51 。其中,pic_init_qs_minus26位于图像参数集中。与普通帧不同的是,在对SI和SP帧进行编解码时,需要两组量化系数(当然这两组系数可以一样):对预测重构块系数进行量化的量化参数 SPQP 与对预测残差系数进行量化的量化参数 PQP。当当前片是SI或SP片时,本元素对应的就是SPQP,而PQP对应的是slice_qp_delta。本元素存在条件:slice_type = = SP | | slice_type = = SI(当前片是SP片或SI片)。 |
sp_for_switch_flag |
指出SP帧中的p 宏块的解码方式是否是switching 模式。什么是switching 模式?好吧,我也不懂,先记录一下,等具体学习SP帧的时候再考虑这个问题,现在没有必要为这个纠结太多。存在条件:slice_type = = SP(当前片是SP片)。 |
disable_deblocking_filter_idc |
表示去块效应滤波器的操作在经过条带的一些块边缘时是否会被废弃,并指定该滤波器针对哪个边缘被废弃。当条带头中不存在disable_deblocking_filter_idc 时,其值默认为0。disable_deblocking_filter_idc 的值应该在0 到2 范围内(包括0 和2)。元素存在条件:图像参数集中的deblocking_filter_control_present_flag等于1(deblocking_filter_control_present_flag equal to 1 specifies that a set of synt axelements controlling the characteristics of the deblocking filter is present inthe slice header.)。 |
slice_alpha_c0_offset_div2 |
给出用于增强 α 和 tC0的偏移值FilterOffsetA = slice_alpha_c0_offset_div2 << 1 。slice_alpha_c0_offset_div2 值的范围是 -6 到+6。 |
slice_beta_offset_div2 |
给出用于增强 β和 tC0的偏移值FilterOffsetB = slice_beta_offset_div2 << 1。slice_beta_offset_div2值的范围是 -6 到+6。 |
slice_group_change_cycle |
这个用于多片组。当片组的类型是3, 4, 5(这三种类型的情况下,每个图像都只包含两个片组),由此句法元素和图像参数集中的slice_group_change_rate_minus1,可获得片组0中映射单元的数目。关于映射单元,其具体涵义将在后面将FMO时提到,现在也可以先跳到该处浏览一下映射单元的定义。片组0中映射单元的数目由下式得到。MapUnitsInSliceGroup0 = Min( slice_group_change_cycle *SliceGroupChangeRate, PicSizeInMapUnits ) 。slice_group_change_cycle 由Ceil( Log2( PicSizeInMapUnits ÷ SliceGroupChangeRate + 1 ) ) 位比特表示。slice_group_change_cycle 值的范围是0 到Ceil( PicSizeInMapUnits÷ SliceGroupChangeRate )。其中,SliceGroupChangeRate可由slice_group_change_rate_minus1得到;而PicSizeInMapUnits = PicWidthInMbs * PicHeightInMapUnits ,等号右边的两个数都由序列参数集中的pic_width_in_mbs_minus1和pic_height_in_map_units_minus1得到,具体参见标准的7.4中关于序列参数集语意的讲解;Ceil(x)函数表示返回大于或者等于x的最小整数。元素存在条件:图像参数集中的num_slice_groups_minus1> 0、图像参数集中的slice_group_map_type是3、4或5。 |
H264中允许从多至15个帧里面选择1帧或者2帧出来作为参考进行预测,所以必须引入一个列表来管理这些参考图像,对于P slice而言,对应 list0,对于 B slice 而言,还需要多一个 list1,因为 B slice 是进行的两次预测!(一个前向一个后向/两个前向/两个后向)
参考帧分为 long term / short term 两种,即所谓的长期参考帧和短期参考帧。其中长期参考帧用 LongTermPicNum 来进行索引,而短期参考帧则利用 frame_num 或者 POC 来进行索引(默认索引顺序即初始化顺序),再具体一点: P slice 的短期参考利用 frame_num 来进行索引,且按照降序排列(即离当前图像最近的前向图像排在第0位)。B slice 的短期参考利用 POC 来进行索引。
对其List0而言,先按照POC降序排列处于其前向的参考帧然后再按照POC升序排列处于其后向的参考帧;
对其List1而言,先按照POC升序排列处于其后向的参考帧然后再按照POC降序排列处于其前向的参考帧。
对于每个MB而言,在mb_pred()中会传输其参考索引,以表明该MB从list0/list1中选择哪一个作为参考,而对于一个 Slice 而言,可能存在该 Slice 内部大多数MB都选择了某一个索引号较大的参考帧,如设定list0中的索引从0~5,而 大多数MB都选择了5,在用哥伦布码进行编码时,将会消耗较多的bit!所以在初始化排序好后,会根据当前 slice 的 具体情况,对列表进行重排序,如将此时排在索引5位置的POC与排在0位置的POC进行交换,那么mb_pred()中传输参考索引所需的bit数就大大减少了!其中参考索引重排的语法在ref_pic_list_reordering()中有详细介绍!
那么当一帧解完后,如何处理该帧呢?需不需要将其放入参考列表中?所以在h264的bit stream中还传输了 。dec_ref_pic_marking(),通过mmco这个玩意告诉我们当前的一帧接完后如何处理参考列表! TBD:剩下的一个问题就是,为什么要分长期参考和短期参考呢? 因为short term参考帧以frame_num做为索引,而frame_num是有最大值的,达到最大值后会进行取模,所以短期参考帧不能长期存在于参考列表中,因为一旦frame_num达到最大值后取模为0,该索引就失去意义了,而长期参考帧则不同!