从Slice_Header学习H.264
写在前面:
$ H.264我是结合标准和毕厚杰的书一块学的。看句法语义时最是头疼,一大堆的元素,很需要耐心。标准中在介绍某个元素的语义时,经常会突然冒出与之相关的另一个变量,这个变量一般都在前文中讲过,但那么多变量怎么可能看一遍就记住?这时我只能去前面重新找这个变量再看一遍。没办法,H.264这个庞大的结构内部肯定是环环相扣的,各个部分联系紧密,所以刚开始看时要搞明白H.264的主要细节以及相互间的关系不是特别容易,尤其看到一大堆不认识的变量时,头大是难免的。所以做这个笔记时,介绍语法元素的过程中我特意将所有被牵扯到的其他变量都顺带解释一下,并记录下来他们是在哪个结构中出现的,这样方便大家也方便以后我自己翻阅。
$ 对于序列参数集和图像参数集,初看时会发现这两个参数集中的很多元素不能立刻搞明白,这是因为很多细节性的东西你还没有了解,这些搞不明白的元素大都会在下面介绍片头语法元素的过程中被提到并解释,我们逐渐进入细节。不过在这之前,也要简单看一下两个参数集,最起码要了解他们的作用,以及把其中不涉及细节的那部分元素尽可能搞明白。
$ 要了解H.264的全局结构以及一些重要细节,个人感觉slice_header是一个很好的切入点。这一系列笔记都是结合片头结构叙述的,前后会逐渐涉及到很多内容。标准中在介绍一些细节操作时,通常是只把计算方法用一大堆伪代码写出来,却没有直白的描述,要看明白很费劲;毕厚杰书中的插图可以帮大忙,结合图来看会容易一下,但毕厚杰的书中有些细节内容没有出现(估计是让读者自己看标准),而且有些不易搞懂的地方毕书中也是直接照搬的标准。本系列笔记中,我把我刚开始看时容易疑惑的部分按照我现在的理解重新描述了一遍,有些必要的地方也会照搬标准的伪代码,不过在照搬之前我会先做尽可能直白的描述,说明伪代码要干什么。我尽可能地将涉及到的所有细节都叙述到。
$ 个人感觉初学时可以先不深究冗余片、滤波等,大概有个概念就行,否则由于搞不懂可能会越看越烦躁,降低学习效率,数据分割如果暂时不是特别清楚也可以先隔过去,这些都可以等对h.264比较熟悉后再回来看,那时也许就感觉容易多了。多片组将在本系列笔记的最后说到,所以中间遇到多片组时可以先把疑惑放一边。
一、slice头的主要元素介绍
首先做一下说明,slice_header()如果存在,那片头中的语法元素 pic_parameter_set_id 、 frame_num、 field_pic_flag、bottom_field_flag、 idr_pic_id、 pic_order_cnt_lsb 、 delta_pic_order_cnt_bottom 、delta_pic_order_cnt[ 0 ] 、delta_pic_order_cnt[ 1 ] 、 sp_for_switch_flag 和 slice_group_change_cycle在同一个图像的所有条带头(条带=片=slice,条带头=片头=slice_header)中都应有相同的值。下面开始介绍各个元素。
$ slice_type so easy,略过
$ pic_parameter_set_id so easy,略过
$ 片头的field_pic_flag ,指定当前图像是帧编码(0)还是场编码(1)。这个元素在同一图像的所有片中应具有相同值。仅当序列参数集中的frame_mbs_only_flag为0时,这个元素才会存在在码流中。
序列参数集中的句法元素frame_mbs_only_flag 和mb_adaptive_frame_field_flag再加上本句法元素共同决定图像的编码模式。
frame_mbs_only_flag |
mb_adaptive_frame_field_flag |
field_pic_flag |
模式 |
1 |
不存在于码流中 |
不存在于码流中 |
帧编码 |
0 |
0 |
0 |
帧编码 |
0 |
0 |
1 |
场编码 |
0 |
1 |
0 |
帧场自适应(仅在此情形下,MbaffFrameFlag=1, 其他几种情况下MbaffFrameFlag都为0) |
0 |
1 |
1 |
场编码 |
$ first_mb_in_slice 表示本片中第一个宏块的地址。
(MbaffFrameFlag的取值参考上面的表格)
如果 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以及其他一些元素指定(这里就不详细说明了,因为这涉及到映射单元与宏块的对应关系,这到后面会说到)。
$ bottom_field_flag 指定当前的场是顶场还是底场。等于1 时表示当前图像是属于底场;等于 0 时表示当前图像是属于顶场。这个元素仅当field_pic_flag存在且为1时(说明当前片属于一个场图像),才会出现在码流中。
$ frame_num和PicNum(picnum不是slice头的元素)
对于非参考帧来说,它的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 及所对应的图像填补,否则后续图像若将运动矢量指向缺失的图像将会产生解码错误。
$ idr_pic_id IDR 图像的标识。不同的 IDR 图像有不同的idr_pic_id 值.在场模式下,IDR帧的两个场有相同的idr_pic_id值。idr_pic_id 的取值范围是 [0,65535],超出此范围时,以循环的方式重新开始计数。
$ POC相关:
POC是指pic_order_cnt ,表示图像的播放顺序。POC的有三种计算方法,具体使用哪一种算法来计算POC,是由序列参数集中的pic_order_cnt_type指定的。在 POC 的第一种算法中是显式地传递POC 的值,而其他两种算法是通过frame_num 来映射POC 的值。三种算法下POC具体如何计算是在 标准2005/03的“8.2.1 图像顺序号的解码过程”章节中讲述的。
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片。
$ 关于List0和List1中的参考帧数目。
在图像参数集中,已经通过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中的最大参考索引号。
为给某些特定图像更大的灵活度,在条带头中,可以重载这两个元素。
num_ref_idx_active_override_flag:条带头中的这个元素用来决定是否对这两个元素进行重载,如果重载,那么条带头中将再次出现num_ref_idx_l0_active_minus1和num_ref_idx_l1_active_minus1这两个元素,它们将覆盖在图像参数集中的值。
$ 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片)。
$ 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。
$ 滤波相关:
H.264指定了一套算法可以在解码器端独立地计算图像中各边界的滤波强度进行滤波。除了解码器独立计算之外,编码器也可以传递句法元素来干涉滤波强度。
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。
这两个元素的存在条件: disable_deblocking_filter_idc != 1。对于这几个元素,知道他们是跟方块滤波相关的就行了,等学习了方块滤波,这些元素的含义自然会明白。
http://dl.dbank.com/c0fry4rukk 毕厚杰-新一代视频压缩编码标准h264
http://dl.dbank.com/c0ng2niprl# h264官方中文版
http://dl.dbank.com/c0dnp0ne0j# Elecard StreamEye Tools 2.9.1.70328.zip,视频分析工具