最近在给MP4文件做CENC加密时需要解析H264的slice头部,才发现对于H264的一些基本概念没有搞清楚。小小的记录一下:
1. 如何判断一个H264的帧类型。帧类型包括IDR/I/P/B.
看一下标准的描述:
nal_unit( NumBytesInNALunit )
{
forbidden_zero_bit All f(1)
nal_ref_idc u(2)
nal_unit_type u(5)
NumBytesInRBSP = 0
for( i = 1; i < NumBytesInNALunit; i++ )
{
if( i + 2 < NumBytesInNALunit && next_bits( 24 ) = = 0x000003 )
{
rbsp_byte[ NumBytesInRBSP++ ] b(8)
rbsp_byte[ NumBytesInRBSP++ ] b(8)
i += 2
emulation_prevention_three_byte /* equal to 0x03 */ f(8)
}
else
rbsp_byte[ NumBytesInRBSP++ ] b(8)
}
}
第一个byte中的后5位 NAL_UNIT_TYPE 标志了帧类型。标准里帧类型的描述为:
在这张表里,区分了IDR帧和非IDR帧类型。NAL_UNIT_TYPE=5即IDR帧, 但是非IDR帧中I/P/B帧的类型并没有明显的区分。查看了一些H264文件,发现I/P/B帧的NAL_UNIT_TYPE通常为1,也就是“Coded slice of a non-IDR picture”。看来通过NAL_UNIT_TYPE是无法彻底区分帧类型了,事实上也是。对于H264帧类型,必须解析到Slice层。
NAL_UNIT_TYPE等于1,2,5时是存在 slice_header头的。NAL_UNIT_TYPE为2,3,4的区别见下一节。
slice头的结构如下:
slice_header( )
{
first_mb_in_slice ue(v)
slice_type ue(v)
pic_parameter_set_id ue(v)
frame_num u(v)
if( !frame_mbs_only_flag )
{
field_pic_flag u(1)
if( field_pic_flag )
bottom_field_flag u(1)
}
if( nal_unit_type = = 5 )
idr_pic_id ue(v)
....
}
Slice的类型,见下图:
1、I宏块是指每个块或宏块是通过其所在的Slice中的之前的已经编码过的数据进行预测的;
2、P宏块是指宏快或宏块分割是通过List0中的一个参考图像来进行预测的;
3、B宏块是指宏快或宏块分割是通过List0和/或List1中的参考图像来进行预测的;
4、SI和SP:即Switch I和Switch P,是一种特殊的编解码条带,可以保证在视频流之间进行有效的切换,并且解码器可以任意的访问。比如,同一个视频源被编码成各种码率的码流,在传输的过程中可以根据网络环境进行实时的切换;
5、SI宏块是一种特殊类型的内部编码宏块,按Intra_4x4预测宏块编码。
Slice类型对应的slice_type值,见下图:
在上图中,I/SI条带的值包括2,4,7,9。 P条带包括0,3,5,8。B条带包括1,6。
可能会感觉有些奇怪,0到4与5到9居然是重复的。答案是这样的,slice_type的值在5到9的范围内表示,除了当前条带的编码类型,所有当前编码图像的其他条带的slice_type的值应与当前条带的slice_type的值一样,或者等于当前条带的slice_type的值减5。
回到问题的原点,如何判断I/P/B。
首先判断NALU类型是否是5,如果是,那么以后连续出现的NALU类型为5的NALU就属于 IDR 帧(一种特殊的 I 帧);
如果NALU不是5,则要进一步判断 slice_type 是否是 7, 如果是, 那么连续出现的 slice_type=7的slice 就属于 I 帧; 如果 slice_type=2,那么就要判断与当前 slice 同属一帧的 slice 是否都是 I slice, 如果都是, 那么这些 slice 就属于一个 I 帧。
码流中一般不会出现复杂的情况,粗略的判断标准就是 slice_type 是否等于2或7。
2. 编码条带数据分割块A/B/C区别
我查了几个h264文件,没有在实际中发现这几种类型。
以下直接引用自
H264标准句法表中C的含义理解
编码条带数据分割块A slice_data_partition_a_layer_rbsp( )
编码条带数据分割块B slice_data_partition_b_layer_rbsp( )
编码条带数据分割块C slice_data_partition_c_layer_rbsp( )
这是3种对于片数据的处理方式,其中2类型时,只传递片中最重要的信息,如片头,片中宏块的预测模式等,3类型是只传输残差,而4时则只可以传输残差中的AC系数。对照句法表可以看到通过C中指定的数字值,限定了在各个句法元素在特定NAL类型中的使用,以达到在特定NAL中使用不同的句法元素,如不在4中传输残差的DC值,见毕书---表7.17中DC系数语法后面为3,而AC系数后面为3|4,这就达到了在 编码条带数据分割块B 中可以传输所有残差,而在编码条带数据分割块C中仅可以传输AC残差。
据此可以得到下面的结论:
C是语法元素可以出现在哪种NAL中的指示,NAL的类型由nal_type_unit指定
参考:
1. h264 NAL头解析
2. h264 图像、帧、片、NALU
3. 从Slice_Header学习H.264(一)--片头语法元素介绍