H.264分帧-2.结合看码流结构

 因为帧在H.264标准中没有一个具体的概念,而从NALU、slice、宏块等概念来看,它们之中的许多元素实际上是属于把帧这个概念划分细小化了。既然帧是相对属于较大较上层的概念,所以我从NAL层开始往下学习解析。这里学习主要参考了ITU-T H.264标准建议书,所用到的语法结构表,相关标准都取自该建议书。
 建议书链接, H.264标准中文版(不过,这个建议书和我当时看的有微小差别,应该影响不大)

1. NAL头解析

 首先,查看了NAL单元的语法表,见下表,
NAL单元语法表
 该表描述的NAL单元,大致与前一篇文章所描述的一致。
 可看出NALU是由三个重要的语法元素forbidden_zero_bit, nal_ref_idc, nal_unit_type组成及rbsp字节流组成。其中前三个语法元素就组成了nal_unit_header,即nal头。该头由八位组成。
 第1位orbidden_zero_bit为禁止位,值为1表示语法错误,一般情况下应为0,不作详述。
 2~3位nal_ref_idc代表参考级别位,用于表示该NAL是否可以丢弃(有无被其后的NAL参考),“00”表示没有参考作用,可丢弃,如B slice、SEI等,非零——包括“01”、“10”、“11”——表示该NAL不可丢弃,如SPS、PPS、I Slice、P Slice等。
 4~8位指nal_unit_type(nal单元类型),是指包含在NAL 单元中的RBSP 数据结构的类型。nal_unit_type对应的整数所表示的nalu类型如下表。
NAL单元类型码
 其中nal_unit_type为1~5的,称为VCL NAL单元。所有其他的NAL单元称为非VCL NAL单元。
 不难看出,VCL NAL单元都与slice相关,确切的来说,里面装载的RBSP数据就是图像的数据。而非VCL NAL单元大多是用来描述图像数据的。
 实际操作中,可通过将起始码之后的第一个字节&0x1f,对应上表,判断该nalu的类型。如下图是一段码流数据,尝试用以上方法分析下面码流,
码流数据
 矩形标注的为起始码,圆形标注的为该NALU的类型。可以得到第一个NALU类型是27,二进制表示为0010 0111,&0x1f(二进制0001 1111),得到0000 0111,即十进制7,对比NALU类型表可知,该NALU为SPS,是一个序列参数集。紧接着一个NALU类型为8,是PPS,图像参数集。第三个NALU类型为IDR。
 目前为止,根据前面已有知识与结论,结合标准7.4.1.2.3的介绍。可以获得一种分帧的方法。

 标准7.4.1.2.3中介绍,在基本编码图像的最后一个VCL NAL 单元之后的第一个任何下列NAL 单元代表了一个新的访问单元(即一帧图像)的开始。
 — 访问单元分隔NAL单元(存在时)
 — 序列参数集NAL单元(存在时)
 — 图像参数集NAL单元(存在时)
 — SEI NAL单元(存在时)
 — nal_unit_type值在14-18之间(包括)的NAL单元
 — 基本编码图像的第一个VCL NAL单元(总是存在)
 从上述描述中,可以看出新一帧的第一个VCL NAL单元是一定存在的,所以只有找到新一帧的第一个VCL NAL单元时,才能确定的进行分帧。

 而幸运的,该节还介绍了对于基本编码图像的第一个VCL NAL单元的检测的规定在7.4.1.2.4节给出。

 当前访问单元的基本编码图像的任何编码条带NAL单元或编码条带数据分割块A的NAL单元应与前一个访问单元的基本编码图像的任何编码条带NAL单元或编码条带数据分割块A的NAL单元以下列方式中的一种或多种进行区分:
 — frame_num的值不同。不管由于memory_management_control_operation等于5的情况出现而为了在后续解码过程中使用frame_num的值是否已经等于0,该值通常在测试条件中是在条带头的语法中出现的frame_num的值。
注1—上述情况的一个推理是一个包含frame_num值等于1的基本编码图像不能包含一个等于5的memory_management_control_operation,除非跟随其后的下一个基本编码图像(如果有的话)能够满足下面列出的其它条件。
 — pic_parameter_set_id 值不同。
 — field_pic_flag 值不同。
 — bottom_field_flag 在两个访问单元中都出现而且值不同。
 — nal_ref_idc 值不同,而且其中一个的nal_ref_idc 值等于0。
 — 两个访问单元的pic_order_cnt_type 都等于0,并且两个pic_order_cnt_lsb值不同或delta_pic_
order_cnt_bottom 值不同。
 — 两个访问单元的pic_order_cnt_type都等于1,并且两个delta_pic_order_cnt[ 0 ] 值不同或者delta_pic_order_cnt[ 1 ] 值不同。
 — nal_unit_type 值不同,而且其中一个的nal_unit_type 值等于5。
 — 两个访问单元的nal_unit_type都等于5,并且idr_pic_id 值不同。

 追踪至此,发现一下子出现了许多没接触过的参数,而这些参数需要多重判断并且未在NALU层出现。于是,往下进入slice层的解析。

2. slice头解析

 按照前文所述,数据由数据头和数据体组成。数据头即是对数据体的描述。所以进入slice层后,先跟踪slice头的信息,查看其语法结构,果然发现了frame_num这个语法元素。见下图,因slice头语法结构庞大,只给出部分语法结构表。
slice_header部分语法结构表
 进入建议书7.4.3节,查看条带头语义,

7.4.3 条带头语义
 如果存在,条带头语法元素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 的值在一个编码图像的所有条带头中都应一样。
 first_mb_in_slice 表示在条带中第一个宏块的地址。当如附件A 中规定的那样不允许任意的条带顺序时,本条带的first_mb_in_slice 的值应不小于当前图像的任何在该条带之前(按解码顺序)的其他条带的first_mb_in_slice 的值。

 从该小节该段落可知,可以通过段1中的语法元素(若存在)判断是否相等,或者判断段2中first_mb_in_slice是否单调递增(在不允许任意条带顺序时)来进行分帧。
 结合1中nal头的解析,可以分为两种方案,一是对slice头中大量语法元素的判断(存在时),二是对VCL NAL中总是存在的first_mb_in_slice是否单调递增(在不允许任意条带顺序时)进行判断。

小结

 在此,我选择方法的时候,选择了通过判断slice头中的first_mb_in_slice的来进行分帧。因为参考建议书中的附件A并结合实际情况,编写分帧工具,不一定支持ASO,所以我在此不需要对任意条带顺序进行判断。通过进一步了解,可知该语法元素代表slice中首个宏块在图像中的地址。换言之,我无须判断他的单调递增性,只需要判断其值是否为0,就可以知道该NALU是否为一帧新的图像的开始,即分帧方案。

发布了60 篇原创文章 · 获赞 18 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/BadAyase/article/details/103488206