应用角度理解H264码流


前言

在开发媒体的时候,需要对媒体编解码的一些参数有了解,这篇文章记录一些H264码流开发中,码流的剖析


1、NAL

NAL全称Network Abstract Layer, 即网络抽象层。在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 0100 00 01分隔符,称起始码,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……。由于NAL的语法中没有给出长度信息,实际的传输、存储系统需要增加额外的头实现各个NAL单元的定界。IDR帧和I帧区别:新的序列开始编码的第一针,清空DecodedPictureBuffer参考帧列表

编码器编出的首帧

如图所示,首帧图像一般包含着三个NAL Unit(NALU),三个NALU分别是SPS、PPS、IDR。每一个NALU都分为三部分,第一部分
NALU start code (NALU头),一般是00 00 00 01 或者 00 00 01,代表一个NALU开头;第二部分是NALU type
(NALU类型),其中常见的0x27代表SPS,0x28代表PPS,0x25代表IDR Picture。详细的NALU type计算:

0x27 对应二进制是 0010 0111,后五位00111 对应 十进制是 7,对应的NALU type = SPS

0x28 对应二进制是 0010 1000,后五位01000 对应 十进制是 8,对应的NALU type = PPS

0x25 对应二进制是 0010 0101,后五位00101 对应 十进制是 5,对应的NALU type = IDR

PS:使用FFMPEG,sps和pps是保存在AVCodecContext的extradata.data中,在解码提取sps和pps时,判断NALU type可以用extradata.data[ 4 ]&0x1f,即将NALU第五个字节(即NALU type)与0x1f进行按位与,结果是7是SPS,8是PPS,5是IDR,计算方式是先转成二进制,前三位001 中,第1位是forbidden_zero_bit,都是0,如果不为1则表示语法错误,第2~3位表示参考级别nal_ref_idc,指示这个NALU的重要性,如00的NALU解码器可以丢弃它而不影响图像的回放,0~3,取值越大,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需大于0。

NALU num NALU type
1 NALU_TYPE_SLICE 【不分区,非IDR图像的片】
2 NALU_TYPE_DPA【片分区A】
3 NALU_TYPE_DPB【片分区B】
4 NALU_TYPE_DPB【片分区C】
5 NALU_TYPE_IDR【IDR图像中的片】
6 NALU_TYPE_SEI【补充增强信息单元(SEI)】
7 NALU_TYPE_SPS【SPS】
8 NALU_TYPE_PPS【PPS】
9 NALU_TYPE_AUD【序列结束】
10 NALU_TYPE_EOSEQ【序列结束】
11 NALU_TYPE_EOSTREAM【码流借宿】
12 NALU_TYPE_FILL【填充】
13~23 【保留】
24 STAP-A 【 单一时间的组合包】
25 STAP-B 【单一时间的组合包】
26 MTAP16 【多个时间的组合包】
27 MTAP24 【多个时间的组合包】
28 FU-A 【分片的单元】
29 FU-B 【分片的单元】
30~31 【没有定义】

PS:IDR帧和I帧的关系。在H.264中,图像以序列为单位进行组织。一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。在一个完整的视频流单元中第一个图像帧是IDR帧,IDR帧是强制刷新帧,在解码过程中,当出现了IDR帧时,要更新sps、pps,原因是防止前面I帧错误,导致sps,pps参考I帧导致无法纠正。IDR会导致DPB(DecodedPictureBuffer参考帧列表——这是关键所在)清空,而I不会。GOP的全称是Group of picture图像组,也就是两个I帧之间的距离,GOP值越大,那么I帧之间P帧和B帧数量越多,图像画质越精细,如果GOP是120,如果分辨率是720P,帧率是60,那么两I帧的时间就是120/60=2s。

扫描二维码关注公众号,回复: 14743425 查看本文章

这里贴一个首帧图像:
一个完整首帧
其中右下角三种颜色红、绿、蓝分别对应的就是对应的SPS、PPS、IDR。前面介绍了NALU分为三个部分,第一部分是NALU start code,第二部分是NALU type,第三部分就是具体的SPS DataSequence Parameter Set Data)。如果需要涉及到编解码算法,则每个NALU都可以分为NALU start code,和RBSP(区别EBSP,因为防竞争码)。标识NAL单元中的RBSP数据类型,其中,nal_unit_type为1, 2, 3, 4, 5的NAL单元称为VCL的NAL单元,其他类型的NAL单元为非VCL的NAL单元,如果去掉RBSP数据中的RBSP尾部,就得到了原始编码数据SODB(String Of Data Bits)。所以在H264 Annex B封装(区别于avcC在NALU前面写上几个字节,这几个字节组成一个整数(大端字节序)来表示整个NALU的长度)、MP4、MKV),其数据一层层解析为:
H264 = 起始码 + NALU
NALU = NALU header + EBSP
EBSP = RBSP + 防竞争码
RBSP = RBSP尾 + SODB
数据传输流结构
Annex B和avcC区别有两点:一个是参数集(SPS, PPS)组织格式;一个是分隔。
Annex-B:使用start code分隔NAL(start code为三字节或四字节,0x000001或0x00000001,一般是四字节);SPS和PPS按流的方式写在头部。avcC:使用NALU长度(固定字节,通常为4字节)分隔NAL;在头部包含extradata(或sequence header)的结构体。(extradata包含分隔的字节数、SPS和PPS,具体结构见https://blog.csdn.net/yue_huang/article/details/75126155)
上图NAL如下:

nal_unit
	index				2(0x2)
	nal_unit_type		slice IDR
	NumBytesInNALUnit	125183
	nal_ref_idc			1(0x1)
	StartAddress(bits)	424(0x1a8)
	Length(bits)		1101464(0xf47f8)
	leading_zero_8bits	0(0x0)
	zero_byte			1(0x1)
	trailing_zero_8bits	0(0x0)

2、SPS、PPS

2.1 SPS

一段视频包含一个或多个编码视频序列CVS,每个CVS都有一个SPS,这些SPS都引用同一个VPS。序列参数集SPS包含了一个CVS中所有
编码图像的共享编码参数,一个CVS里的所有PPS都必须引用同一个SPS。当一个SPS被引用时,该SPS处于激活状态直到整个CVS结束。
SPS所包含的语法元素大致分为以下几个方面:

1、图像格式信息。包括采样格式、图像分辨率、量化深度、解码图像是否需要裁剪输出以及相关裁剪参数。

2、编码参数信息。包括编码块、变换块的最小和最大尺寸,帧内帧间预测时变换块最大划分深度,对4:4:4采样格式的三个通道是否单独编码,是否需要帧内强滤
波,帧间预测过程的某些限制条件(如非对称模式AMP的使用,时域MV预测的使用),是否使用量化矩阵,是否需要样点自适应补偿SAO,是否采用PCM模式及
在该模式下的相关编码参数。

3、与参考图像相关的信息。包括短期参考图像的设置,长期参考图像的使用和数目,长期参考图像的POC和其能否作为当前图像的参考图像。

4、profile,tier和level相关参数。

5、时域分级信息。包括时域子层最大数目,控制传输POC进位的参数,时域子层顺序标识开关,与子层相关的参数(如DPB的最大需求)。

6、可视化可用信息(Video Usability Information,VUI),用于表征视频格式等额外信息。

7、其他信息。包括当前SPS引用的VPS编号、SPS标识号和SPS扩展信息。

在H.264标准协议中规定了多种不同的NAL Unit类型,其中类型7表示该NAL Unit内保存的数据为Sequence Paramater Set。在H.264的各种
语法元素中,SPS中的信息至关重要。如果其中的数据丢失或出现错误,那么解码过程很可能会失败。SPS及后续将要讲述的图像参数集
PPS在某些平台的视频处理框架(比如iOS的VideoToolBox等)还通常作为解码器实例的初始化信息使用。SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。但在某些特殊情况下,在码流中间也可能出现这两种结构,主要原因可能为:

①·解码器需要在码流中间开始解码;

②·编码器在编码的过程中改变了码流的参数(如图像分辨率等);

下表是SPS的语法结构:
其中的每一个语法元素及其含义如下:

(1) profile_idc:

标识当前H.264码流的profile。我们知道,H.264中定义了三种常用的档次profile:

基准档次:baseline profile;

主要档次:main profile;

扩展档次:extended profile;

在H.264的SPS中,第一个字节表示profile_idc,根据profile_idc的值可以确定码流符合哪一种档次。判断规律为:

profile_idc = 66 → baseline profile;

profile_idc = 77 → main profile;

profile_idc = 88 → extended profile;

在新版的标准中,还包括了High、High 10、High 4:2:2、High 4:4:4、High 10 Intra、High 4:2:2 Intra、High 4:4:4 Intra、CAVLC 4:4:4 Intra

等,每一种都由不同的profile_idc表示。

另外,constraint_set0_flag ~ constraint_set5_flag是在编码的档次方面对码流增加的其他一些额外限制性条件。

在我们实验码流中,profile_idc = 0x42 = 66,因此码流的档次为baseline profile。

(2) level_idc

标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的level由level_idc指定。

当前码流中,level_idc = 0x1e = 30,因此码流的级别为3。

(3) seq_parameter_set_id

表示当前的序列参数集的id。通过该id值,图像参数集pps可以引用其代表的sps中的参数。

(4) log2_max_frame_num_minus4

用于计算MaxFrameNum的值。计算公式为MaxFrameNum = 2^(log2_max_frame_num_minus4 + 4)。MaxFrameNum是frame_num的上限

值,frame_num是图像序号的一种表示方法,在帧间编码中常用作一种参考帧标记的手段。

(5) pic_order_cnt_type

表示解码picture order count(POC)的方法。POC是另一种计量图像序号的方式,与frame_num有着不同的计算方法。该语法元素的取值为

0、1或2。

(6) log2_max_pic_order_cnt_lsb_minus4

用于计算MaxPicOrderCntLsb的值,该值表示POC的上限。计算方法为MaxPicOrderCntLsb = 2^(log2_max_pic_order_cnt_lsb_minus4 +

4)。

(7) max_num_ref_frames

用于表示参考帧的最大数目。

(8) gaps_in_frame_num_value_allowed_flag

标识位,说明frame_num中是否允许不连续的值。

(9) pic_width_in_mbs_minus1

用于计算图像的宽度。单位为宏块个数,因此图像的实际宽度为:

frame_width = 16 × (pic_width_in_mbs_minus1 + 1);

(10) pic_height_in_map_units_minus1

使用PicHeightInMapUnits来度量视频中一帧图像的高度。PicHeightInMapUnits并非图像明确的以像素或宏块为单位的高度,而需要考虑该

宏块是帧编码或场编码。PicHeightInMapUnits的计算方式为:

PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1;

(11) frame_mbs_only_flag

标识位,说明宏块的编码方式。当该标识位为0时,宏块可能为帧编码或场编码;该标识位为1时,所有宏块都采用帧编码。根据该标识位取

值不同,PicHeightInMapUnits的含义也不同,为0时表示一场数据按宏块计算的高度,为1时表示一帧数据按宏块计算的高度。

按照宏块计算的图像实际高度FrameHeightInMbs的计算方法为:

FrameHeightInMbs = ( 2 − frame_mbs_only_flag ) * PicHeightInMapUnits

(12) mb_adaptive_frame_field_flag

标识位,说明是否采用了宏块级的帧场自适应编码。当该标识位为0时,不存在帧编码和场编码之间的切换;当标识位为1时,宏块可能在帧

编码和场编码模式之间进行选择。

(13) direct_8x8_inference_flag

标识位,用于B_Skip、B_Direct模式运动矢量的推导计算。

(14) frame_cropping_flag

标识位,说明是否需要对输出的图像帧进行裁剪。

(15) vui_parameters_present_flag

标识位,说明SPS中是否存在VUI信息。

上图的SPS参数如下

seq_parameter_set_rbsp()
{
    
    
	profile_idc					100(0x64)			//解码配置
	constraint_set0_flag		false
	constraint_set1_flag		false
	constraint_set2_flag		false
	constraint_set3_flag		false
	level_idc					41(0x29)			//解码级别
	seq_parameter_set_id		0(0x0)
	if(profile_idc == 100||110||122||144)
	{
    
    
		chroma_format_idc		1(0x1)
		if(chroma_format_idc == 3)
		bit_depth_luma_minus8	0(0x0)
		bit_depth_chroma_minus8	0(0x0)
		qpprime_y_zero_transform_bypass_flag		false
		seq_scaling_matrix_present_flag				false
		if(seq_scaling_matrix_present_flag)
	}
	log2_max_frame_num_minus4	12(0xc)				//标识所属图像的解码顺序
	pic_order_cnt_type			2(0x2)				//解码图像顺序的计数方法
	if(pic_order_cnt_type == 0)
	else if(pic_order_cnt_type == 1)
	num_ref_frames				1(0x1)				//短期参考帧和长期参考帧、互补参考场对以及不成对的参考场的最大数量
	gaps_in_frame_num_value_allowed_flag			false
	pic_width_in_mbs_minus				119(0x77)	//加1是指以宏块为单元的每个解码图像的宽度
	pic_height_in_mps_units_minus1		67(0x43)	//语义依赖于变量frame_mbs_only_flag,否则+1就表示以宏块为单位的一场的高度
	frame_mbs_only_flag					true
	if(!frame_mbs_only_flag)
	direct_8x8_inference_flag			true
	frame_cropping_flag					true
	if(frame_cropping_flag)
	{
    
    
		frame_cropping_left_offset		0(0x0)
		frame_cropping_right_offset		0(0x0)
		frame_cropping_top_offset		0(0x0)
		frame_cropping_bottom_offset	4(0x4)
	}
	vui_parameters_present_flag			true
	if(vui_parameters_present_flag)
	{
    
    
		vui_parameters()
		{
    
    
			aspect_ratio_info_present_flag			false
			if(aspect_ratio_info_present_flag)
			overscan_info_present_flag				false
			if(overscan_info_present_flag)
			video_signal_type_present_flag			false
			if(video_signal_type_present_flag)
			chroma_loc_info_present_flag			false
			if(chroma_loc_info_present_flag)
			timing_info_present_flag				true
			if(timing_info_present_flag)
			{
    
    
				num_units_in_tick					2(0x2)
				time_scale							60(0x3c)
				fixed_frame_rate_flag				false
			}
			nal_hrd_parameters_present_flag			true
			if(nal_hrd_parameters_present_flag)
			{
    
    
				hrd_parameters()
				{
    
    
					cpb_cnt_minus1					0(0x0)
					bit_rate_scale					5(0x5)
					cpb_size_scale					7(0x7)
					bit_rate_value_minus1
						bit_rate_value_minus1[0]	1850
					cpb_size_value_minus1
						cpb_size_value_minus1[0]	1850
					cbr_flag
						cbr_flag[0]					false
					initial_cpb_removal_delay_length_minusl		23(0x17)
					cpb_removal_delay_length_minusl				23(0x17)
					dpb_output_delay_length_minusl				23(0x17)
					time_offset_length							1(0x1)
				}
			}
			vcl_hrd_parameters_present_flag			false
			if(vcl_hrd_parameters_present_flag)
			if(nal_hrd_parameters_present_flag||vcl_hrd_parameters_present_flag)
			{
    
    
				low_delay_hrd_flag					false
			}
			pic_struct_present_flag					false
			bitstream_restriction_flag				true
			if(bitstream_restriction_flag)
			{
    
    
				motion_vactors_over_pic_boundaries_flag			true
				max_bytes_per_pic_denom							0(0x0)
				max_bits_per_mb_denom							0(0x0)
				log2_max_mv_length_horizontal					9(0x9)
				log2_max_mv_length_vertical						7(0x7)
				num_reorder_frames								0(0x0)
				max_dec_frame_buffering							1(0x0)
			}
		}
	}
}

PS:如何在不解码视频帧的前提下获取视频宽高、帧率。
如上图,其参数在软件解析如下

Profile			Hight
Level			4.1
Chroma Format	[4:2:0]
MBAFF Mode		FALSE
Resolution		1920*1088	(实际解码为1080,上下有偏移)
Max DPB Size	1
Coding Type		CABAC
BitDepth Luma	8
BitDepth Chroma	8
Frame Rate		30
AUs in Seq# 1	423
AUs in Seq# 2	1757

实际计算宽高,参照上面的SPS参数

width = (int)(pic_width_in_mbs_minus1 + 1) * 16
//pic_width_in_mbs_minus1 = 119
//(119+1)*16=1920
height = (int)(2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16
//frame_mbs_only_flag = 1
//pic_height_in_map_units_minus1 = 67
//(67+1)*16=1088
int crop_unit_x = 1;
int crop_unit_y = 2 - frame_mbs_only_flag;      //monochrome or 4:4:4
    if (chroma_format_idc == 1) {
    
       //4:2:0
        crop_unit_x = 2;
        crop_unit_y = 2 * (2 - frame_mbs_only_flag);
    } else if (chroma_format_idc == 2) {
    
        //4:2:2
        crop_unit_x = 2;
        crop_unit_y = 2 - frame_mbs_only_flag;
    }
    
width -= crop_unit_x * (frame_crop_left_offset + frame_crop_right_offset)
//1920-2*0=1920
height -= crop_unit_y * (frame_crop_top_offset + frame_crop_bottom_offset)
//1088-2*4=1080

帧率

fps = (unsigned int)((float)time_scale / (float)num_units_in_tick);
//time_scale = 60
//num_units_in_tick = 2
//fixed_frame_rate_flag = 0
    if (fixed_frame_rate_flag) {
    
    
        fps = fps/2;
    }

PS:软解的时候,pps及sps不能从packet获得,而是保存在AVCodecContext的extradata数据域中;一般情况下,extradata中包含一个
sps、一个pps 的nalu, 从h264_mp4toannexb_bsf.c代码中容易看出extradata的数据格式;分析后的sps及pps依然储存在extradata域中,并
添加了起始符。

2.2 PPS

除了序列参数集SPS之外,H.264中另一重要的参数集合为图像参数集Picture Paramater Set(PPS)。通常情况下,PPS类似于SPS,在H.264的裸码流中单独保存在一个NAL Unit中,只是PPS NAL Unit的nal_unit_type值为8;而在封装格式中,PPS通常与SPS一起,保存在视
频文件的文件头中。其中的每一个语法元素及其含义如下:

(1) pic_parameter_set_id

表示当前PPS的id。某个PPS在码流中会被相应的slice引用,slice引用PPS的方式就是在Slice header中保存PPS的id值。该值的取值范围为

[0,255]。

(2) seq_parameter_set_id

表示当前PPS所引用的激活的SPS的id。通过这种方式,PPS中也可以取到对应SPS中的参数。该值的取值范围为[0,31]。

(3) entropy_coding_mode_flag

熵编码模式标识,该标识位表示码流中熵编码/解码选择的算法。对于部分语法元素,在不同的编码配置下,选择的熵编码方式不同。例如在

一个宏块语法元素中,宏块类型mb_type的语法元素描述符为“ue(v) | ae(v)”,在baseline profile等设置下采用指数哥伦布编码,在main

profile等设置下采用CABAC编码。

标识位entropy_coding_mode_flag的作用就是控制这种算法选择。当该值为0时,选择左边的算法,通常为指数哥伦布编码或者CAVLC;当

该值为1时,选择右边的算法,通常为CABAC。

(4) bottom_field_pic_order_in_frame_present_flag

标识位,用于表示另外条带头中的两个语法元素delta_pic_order_cnt_bottom和delta_pic_order_cn是否存在的标识。这两个语法元素表示了

某一帧的底场的POC的计算方法。

(5) num_slice_groups_minus1

表示某一帧中slice group的个数。当该值为0时,一帧中所有的slice都属于一个slice group。slice group是一帧中宏块的组合方式,定义在协

议文档的3.141部分。

(6) 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的默认值。

(7) weighted_pred_flag

标识位,表示在P/SP slice中是否开启加权预测。

(8) weighted_bipred_idc

表示在B Slice中加权预测的方法,取值范围为[0,2]。0表示默认加权预测,1表示显式加权预测,2表示隐式加权预测。

(9) pic_init_qp_minus26和pic_init_qs_minus26

表示初始的量化参数。实际的量化参数由该参数、slice header中的slice_qp_delta/slice_qs_delta计算得到。

(10) chroma_qp_index_offset

用于计算色度分量的量化参数,取值范围为[-12,12]。

(11) deblocking_filter_control_present_flag

标识位,用于表示Slice header中是否存在用于去块滤波器控制的信息。当该标志位为1时,slice header中包含去块滤波相应的信息;当该

标识位为0时,slice header中没有相应的信息。

(12) constrained_intra_pred_flag

若该标识为1,表示I宏块在进行帧内预测时只能使用来自I和SI类型宏块的信息;若该标识位0,表示I宏块可以使用来自Inter类型宏块的信息。

(13) redundant_pic_cnt_present_flag

标识位,用于表示Slice header中是否存在redundant_pic_cnt语法元素。当该标志位为1时,slice header中包含redundant_pic_cnt;当该标

识位为0时,slice header中没有相应的信息。

上图的PPS参数如下

pic_parameter_set_rbsp()
{
    
    
	pic_parameter_set_id			0(0x0)
	seq_parameter_set_id			0(0x0)
	entropy_coding_mode_flag		true
	pic_order_present_flag			false
	num_slice_groups_minus1			0(0x0)
	if(num_slice_groups_minus1>0)
	num_ref_idx_10_active_minus1	0(0x0)
	num_ref_idx_11_active_minus1	0(0x0)
	weighted_pred_flag				false
	weighted_bipred_idc				0(0x0)
	pic_init_qp_minus26				11(0xb)
	pic_init_qs_minus26				0(0x0)
	chrmoa_qp_index_offset			2(0x2)
	deblocking_filter_control_present_flag		true
	constrained_intra_pred_flag					false
	redundant_pic_cnt_present_flag				false
	if(more_rbsp_data())
	{
    
    
		transform_8x8_mode_flag					true
		pic_scaling_matrix_present_flag			false
		if(pic_scaling_matrix_present_flag)
		second_chroma_qp_index_offset			2(0x2)
	}
}

3、Slice&MB

H264结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成。
H264编码过程中的三种不同的数据形式:

SODB:数据比特串 ---->最原始的编码数据,即VCL数据;

RBSP:原始字节序列载荷 ---->在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若干比特“0”,以便字节对齐;

EBSP:扩展字节序列载荷 ---- > 在RBSP基础上填加了仿校验字节(0X03)它的原因是:在NALU加到Annexb上时,需要添加每组NALU之

前的开始码StartCodePrefix,如果该NALU对应的slice为一帧的开始则用4位字节表示,ox00000001,否则用3位字节表示ox000001(是一帧的
一部分)。另外,为了使NALU主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将
0x03去掉。也称为脱壳操作。
一个完整序列图像细分到slice

3.1、Slice

Slice:一帧视频图像可编码成一个或者多个片,每片包含整数个宏块,即每片至少一个宏块,最多时包含整个图像的宏块。片的目的:为了
限制误码的扩散和传输,使编码片相互间保持独立。片共有5种类型:I片(只包含I宏块)、P片(P和I宏块)、B片(B和I宏块)、SP片
(用于不同编码流之间的切换)和SI片(特殊类型的编码宏块)。上图Slice如下

Slice
{
    
    
	index					0(0x0)
	type					I_slice
	macroblocks				8160(0x1fe0)
	IsPartitioned			false
	slice_qp				27(0x1b)
	slice_qs				26(0x1a)
	StartAddress(bits)		432(0x1b0)
	Length(bits)			1001448(0xf47e8)
	slice_header()						
	{
    
    						
		first_mb_in_slice	0(0x0)
		slice_type			I_slice
		pic_parameter_set_id			0(0x0)
		frame_num						0(0x0)
		if(!frame_mbs_only_flag)					
		if(nal_unit_type==5)					
		{
    
    					
			idr_pic_id	0(0x0)
		}
		if(pic_order_cnt_type==0)
		if(pic_order_cnt_type==1 && !delta_pic_order_always_sero_flag)
		if(reduntant)pic_cnt_present_flag)
		if(slice_type==B)
		if(slice_type==P | slice_type==SP | slice_type==B)
		ref_pic_list_reordering
		{
    
    
			if(slice_type!=I && slice_type!=SI)
			if(slice_type==B)
		}
		if(weighted_pred_flag&&(slice_type==P||slice_type==SP)||(weighted_bipred_idc==1 && slice_type==B))
		if(nal_ref_idc!=0)
		{
    
    
			dec_ref_pic_marking()
			{
    
    
				if(nal_unit_type==5)
				{
    
    
					no_output_of_prior_pics_flag			false
					long_term_reference_flag				false
				}
				else
			}
		}
		if(entropy_coding_mode_flag && slice_type!=I && slice_type!=SI)
		slice_qp_delta										-10(0xfffffff6)
		if(slice_type==SP || slice_type==SI)
		if(deblocking_filter_control_present_flag)
		{
    
    
			disable_deblocking_filter_idc					0(0x0)
			if(disable_deblocking_filter_idc!=1)
			{
    
    
				slice_alpha_c0_offset_div2					-2(0xfffffffe)
				slice_deta_offset_div2						3(0x3)
			}
		}
		if(num_slice_groups_minus1>0 && slice_group_map_type>=3 && slice_group_map_type<=5)
	}
}

3.2、MB

MB(Macro Block):是H.264编码的基本单位,一个编码图像首先要划分成多个块(4x4 像素)才能进行处理,显然宏块应该是整数个块
组成,通常宏块大小为16x16个像素。宏块分为I、P、B宏块:

I宏块只能利用当前片中已解码的像素作为参考进行帧内预测;

P宏块可以利用前面已解码的图像作为参考图像进行帧内预测;

B宏块则是利用前后向的参考图形进行帧内预测

上图MB参数如下:

macroblock
	index				0(0x0)
	StartAddress(bits)	476(0x1dc)
	Length(bits)		113(0x71)
	frame/field			frame/field
	sliceNo				0(0x0)
	slice_type			I_slice
	bm_type				I_4x4
	mb_qp_delta			0(0x0)
	qp					27(0x1b)
	coded_block_pattern		31::011111
	CodedBlockPatternLuma	15::1111
	CodedBlockPatternChroma	1::01
	transform_size_8x8_flag	false
	intra_mb_pred
	{
    
    
		if(Intra_4x4 MB)
		else if(Intra_8x8 MB)
		else
		intra_chroma_pred_mode		DC
	}
	inter_mb_pred

4、RTP负载

H264 over RTP基本上分三种类型:

(1)Single NAL unit packet 也就是实际的NAL类型,可以理解为一个包就是一帧H264数据,这个在实际中是比较多的。

(2)Aggregation packet 一包数据中含有多个H264帧。

STAP-A 包内的帧含有相同的NALU-Time,没有DON

STAP-B 包内的帧含有相同的NALU-Time,有DON

MTAP16 包内的帧含有不同的NALU-Time,timestamp offset = 16

MTAP24 包内的帧含有不同的NALU-Time,timestamp offset = 24

封装在Aggregation packet中的 NAL单元大小为65535字节

(3) Fragmentation unit 一帧数据被分为多个RTP包,这也是很常见的,特别是对于关键帧。现存两个版本FU-A,FU-B。h264包在传输的时候,如果包太大,会被分成多个片。NALU头会被如下的2个自己代替。

The FU indicator octet has the following format:

  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |F|NRI|  Type   |
  +---------------+

别被名字吓到这个格式就是上面提到的RTP h264负载类型,Type为FU-A

The FU header has the following format:

  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |S|E|R|  Type   |
  +---------------+

S bit为1表示分片的NAL开始,当它为1时,E不能为1

E bit为1表示结束,当它为1,S不能为1

R bit保留位

Type就是NALU头中的Type,取1-23的那个值

5、AUD

一般文档没有对AUD进行描叙,其实这是一个帧开始的标志,字节顺序为:00 00 00 01 09 f0从结构上看,有start code, 所以确实是一个
NALU,类型09在H264定义里就是AUD(分割器)。大部分播放器可以在没有AUD的情况下正常播放。紧随AUD,一般SPS/PPS/SEI/IDR
的组合或者简单就是一个SLICE,也就是一个帧的开始。像Flash这样的播放器,每次需要一个完整的帧数据,那么把2个AUD之间的数据按
照格式打包给播放器就可以了。
H.264编码时,在每个NAL前添加起始码 0x000001,解码器在码流中检测到起始码,当前NAL结束。为了防止NAL内部出现0x000001的数
据,h.264又提出’防止竞争 emulation prevention"机制,在编码完一个NAL时,如果检测出有连续两个0x00字节,就在后面插入一个0x03。
当解码器在NAL内部检测到0x000003的数据,就把0x03抛弃,恢复原始数据。

0x000000  >>>>>>  0x00000300
0x000001  >>>>>>  0x00000301
0x000002  >>>>>>  0x00000302
0x000003  >>>>>>  0x00000303

总的来说H264的码流的打包方式有两种,一种为annex-b byte stream format 的格式,这个是绝大部分编码器的默认输出格式,就是每个帧的
开头的3~4个字节是H264的start_code,0x00000001或者0x000001。另一种是原始的NAL打包格式,就是开始的若干字节(1,2,4字节)
是NAL的长度,而不是start_code,此时必须借助某个全局的数据来获得编 码器的profile,level,PPS,SPS等信息才可以解码。

6、文献

												H.264先进视讯编译码标准
												  郭其昌/工研院电通所

1. 前言
在2001年12月,ITU-T VCEG与ISO MPEG共同组成联合视讯小组(Joint Video Term,JVT)来研订新的视频压缩格式,此新格式在ITU-T组织中称为H.264,在
ISO组织中则纳入MPEG-4 Part-10 (ISO/IEC 14496-10)并命名为Advanced VideoCoding (AVC),通常合并称为H.264/AVC [1],其国际标准的第一版于2003年
公布,而增修的第二版也于2005年3月定案。相关研究显示H.264/AVC与MPEG-2及MPEG-4相较之下,无论是压缩率或视讯质量皆有大幅的提升[2],而且
H.264/AVC也首次将视讯编码层(Video Coding Layer,VCL)与网络提取层(Network Abstraction Layer,NAL)的概念涵盖进来,以往视讯标准着重的是压缩效能
部分,而H.264/AVC包含一个内建的NAL网络协议适应层,藉由NAL来提供网络的状态,可以让VCL有更好的编译码弹性与纠错能力,使得H.264/AVC非常适用
于多媒体串流(multimedia streaming)及行动电视(mobile TV)的相关应用。在第一版的标准规范中,H.264/AVC根据使用的编码工具种类来提供三种编码规模
(Profile),如表1所示分别为Baseline Profile、Main Profile、Extension Profile,而相对应的影片尺寸与比特率等级由Level 1至Level 5.1,涵盖小画面与高分辨
率画面的应用范围。Baseline Profile主要是着眼于低比特率的应用(例如:影像通讯),而且其运算复杂度低,所以也适合应用于个人随身的多媒体拨放机;Main 
Profile因为有支持交错式影片(interlaced content)的编码,所以适合应用于HDTV数字电视广播,而且非常容易整合在传统的MPEG-2 Transport/Program
 Stream上来传送H.264/AVC比特流;对于IP-TV或是MOD(Multimedia On Demand)等应用,使用包含高抗错性编码工具(error resilient tools)的Extension Profile
 即可以满足这些需求。然而,微软公司在2003年将其视频压缩技术向美国的电影电视工程师协会(Society of Motion Picture and Television Engineers,
 SMPTE)提出公开标准化的申请,并以VC-1(Video Codec 1)为此新标准的命名[3],由于VC-1在高分辨率影片上的表现出色,导致H.264/AVC在DVD Forum与
 Blu-ray Disc Association的高分辨率DVD影片测试中败阵下来,其主要原因是H.264/AVC使用较小尺寸的转换公式与无法调整的量化矩阵,造成不能完整保留影
 像的高频细节信息,因此H.264/AVC于2004年展开标准增修的讨论,来纳入称之为Fidelity Range Extensions (FRExt) [4]的新编码工具,并以先前MainProfile
 为基础来扩充增加4个新的等级(Table 1),期望能够在高分辨率影片的应用上扳回劣势,目前增修的H.264/AVC第二版标准已于2005年3月发表。本文后段将探
 讨网络提取层的相关特性,接着来说明视讯编码层的原理,最后并讨论H.264/AVC的应用现况。

2. 网络提取层 (Network Abstraction Layer,NAL)
H.264/AVC标准的特色是将网络提取层的概念涵盖进来,亦即以NAL封包为单位的方式来做为VCL编译码的运算单位,这样传输层拿到NAL封包之后不需要再进行
切割,只需附加该传输协议的文件头信息(adding header only)就可以交由底层传送出去,如图1所示,可以将NAL当成是一个专作封装(packaging)的模块,用来将
VCL压缩过的bitstream封装成适当大小的封包单位(NAL-unit),并在NAL-unit Header中的NAL-unit Type字段记载此封包的型式,每种型式分别对应到VCL中不同
的编解碼工具。NAL另外一个重要的功能为当网络发生壅塞而导致封包错误或接收次序错乱(out-of-order)的状况时,传输层协议会在Reference Flag作设定的动
作,接收端的VCL在收到这种NAL封包时,就知道要进行所谓的纠错运算(error concealment),在解压缩的同时也会尝试将错误修正回来。如图2所示,一个完整
的H.264/AVC bitstream是由多个NAL-units所组成的,所以此bitstream也称之为NAL unit stream,一个NAL unit stream内可以包含多个压缩视讯序列(coded video 
sequence),一个单独的压缩视讯序列代表一部视讯影片,而压缩视讯序列又是由多个access units所组成,当接收端收到一个access unit后,可以完整地译码成
单张的画面,而每个压缩视讯序列的第一个access unit必须为Instantaneous Decoding Refresh (IDR) access unit,IDRaccess unit的内容全是采用intra-prediction
编码,所以自己本身即可完全译码,不用参考其他access unit的数据。access unit亦是由多个NAL-units所组成,标准中总共规范12种的NAL-unit型式,这些可以
进一步分类成VCL NAL-unit及non-VCL NAL-unit,所谓的VCL NAL-unit纯粹是压缩影像的内容,而所谓的non-VCL NAL-unit则有两种:Parameter Sets与
Supplemental Enhancement Information (SEI),SEI可以存放影片简介、版权宣告、用户自行定义的数据…等;Parameter Sets主要是描述整个压缩视讯序列的参
数,例如:长宽比例、影像显现的时间点(timestamp)、相关译码所需的参数…等,这些信息非常重要,万一在传送的过程中发生错误,会导致整段影片无法译
码,以往像MPEG-2/-4都把这些信息放在一般的packet header,所以很容易随着packet loss而消失,现在H.264/AVC将这些信息独立出来成为特殊的parameter 
set,可以采用所谓的out-of-band的方式来传送,以便将out-of-band channel用最高层级的信道编码(channel coding)保护机制,来保证传输的正确性。

3. 视讯编码层 (Video Coding Layer,VCL)
视频压缩的原理是利用影像在时间与空间上存有相似性,这些相似的数据经过压缩算法处理之后,可以将人眼无法感知的部分抽离出来,这些称为视觉冗余(visual 
redundancy)的部分在去除之后,就可以达到视频压缩的目的。如图1所示,H.264/AVC的视讯编码机制是以图块(block-based)为基础单元,也就是说先将整张影像
分割成许多矩形的小区域,称之为巨图块(macroblock,MB),再将这些巨图块进行编码,先使用画面内预测(intra-prediction)与画面间预测(inter-prediction)技术,
以去除影像之间的相似性来得到所谓的差余影像(residual),再将差余影像施以空间转换(transform)与量化(quantize)来去除视觉冗余,最后视讯编码层会输出编码
过的比特流(bitstream),之后再包装成网络提取层的单元封包(NAL-unit),经由网络传送到远程或储存在储存媒体中。H.264/AVC允许视讯影片以frame或是以filed
的方式来进行编码,两者可以共存,而frame可以是progress或是interlace形式,对同一段影片来说也可使用两者来混合编码,这个特性与MPEG-2相同。而在影像
色彩格式的支持上,H.264/AVC第一版的标准只支持YCrCb 4:2:0取样的方式,而在增修的第二版标准中增加4:2:2与4:4:4取样格式,通常这些格式会被数字电影或
HDTV影片所采用。

3.1 H.264/AVC影像格式阶层架构
H.264/AVC的阶层架构由小到大依序是sub-block、block、macroblock、slice、slicegroup、frame/field-picture、sequence。对一个采用4:2:0取样的MB而言,它
是由16x16点的Luma与相对应的2个8x8点Chroma来组成,而在H.264/AVC的规范中,MB可再分割成多个16x8、8x16、8x8、8x4、4x8、4x4格式的sub-blocks。
所谓的slice是许多MB的集合,而一张影像是由许多slice所组成(图3),slice为H.264/AVC格式中的最小可译码单位(self-decodable unit),也就是说一个slice单靠本
身的压缩数据就能译码,而不必依靠其他slice,这样的好处是当传送到远程时,每接收完一笔slice的压缩数据就能马上译码,不用等待整张的数据接收完后才能开
始,而且万一传送的过程中发生数据遗失或错误,也只是影响该笔slice,不会对其他slice有所影响,但跟MPEG-2的slice不同处在于它允许slice的范围可以超过一
行MB,也就是说H.264/AVC允许整张影像只由单一个slice组成。H.264/AVC的slice架构还有一项特性称为Flexible Macroblock Ordering (FMO),也就是说组成
slice的MB可以不必局限于循序扫描(rasterscan)的排列方式,例如:图3最右侧的排法就非常适用于多个前景(foreground) slice groups与一个独自的背景
(background) slice group,好处是对不同的slice group可以用不同质量的压缩参数,例如:对于前景物件通常是人眼较感兴趣的区域,可以用较小的压缩率来维持
较好的质量。

3.2 Slice的编码模式
H.264/AVC的slice依照编码的类型可以分成下列种类:(1)I-slice: slice的全部MB都采用intra-prediction的方式来编码;(2) P-slice: slice中的MB使用intra-prediction
和inter-prediction的方式来编码,但每一个inter-prediction block最多只能使用一个移动向量;(3) B-slice:与P-slice类似,但每一个inter-prediction block可以使用二
个移动向量。比较特别的是B-slice的‘B’是指Bi-predictive,与MPEG-2/-4 B-frame的Bi-directional概念有很大的不同,MPEG-2/-4 B-frame被限定只能由前一张和后
一张的I(或P)-frame来做inter- prediction,但是H.264/AVC B-slice除了可由前一张和后一张影像的I(或P、B)-slice外,也能从前二张不同影像的I(或P、B)-slice来做
inter- prediction,而H.264/AVC另外增加两种特殊slice类型:(1) SP-slice:即所谓的Switching P slice,为P-slice的一种特殊类型,用来串接两个不同bitrate的
bitstream;(2) SI-slice: 即所谓的Switching I slice,为I-slice的一种特殊类型,除了用来串接两个不同content的bitstream外,也可用来执行随机存取(random 
access)来达到网络VCR的功能。这两种特殊的slice主要是考虑当进行Video-On-Demand streaming的应用时,对同一个视讯内容的影片来说,server会预先存放
不同bitrate的压缩影片,而当带宽改变时,server就会送出适合当时带宽比特率的影片,传统的做法是需要等到适当的时间点来传送新的I-slice (容量较P-slice大上
许多),但因为带宽变小导致需要较多的时间来传送I-slice,如此会让client端的影像有所延迟,为了让相同content但不同bitrate的bitstream可以较平顺地串接,使
用SP-slice会很容易来达成(图4),不仅可以直接送出新的bitstream,也因为传送的P-slice的容量较小,所以不会有时间延迟的情形出现。当client端的使用者要切
换到新的接收频道(channel)时,因为与目前传送的bitstream不但内容不同连比特率也不同,传统的做法需让client重新缓冲(buffering)一段新频道的内容(图5),此
时是为了要接收新频道bitstream的I-slice,然后再开始传送新频道bitstream后续的P-slice,如此client也会发生延迟接收的现象,而且当client要进行所谓的快转、
倒转、随机存取(random access)的动作时,传统的做法无法达到实时的反应,H.264/AVC利用SI-slice就可以轻易地达到目的。

3.3画面内预测技术(Intra-frame Prediction)
以往的压缩标准在进行intra-prediction时,多半只是将转换系数做差值编码,而H.264/AVC在空间领域(spatial domain)来进行像点之间的预测,而不是用转换过的
系数,它提供两种intra-prediction的型式:intra_4x4及intra_16x16,所谓的intra_4x4是以Luma 4x4 sub-block为单位,找出它的参考对象(predictor)后,再将其与
参考对象相减后所产生的差余影像(residual)送入转换算法,而寻找参考对象的模式共有9种预测的方向(图6),以mode 0 (vertical)为例,{a,e,i,m}、{b,f,j,n}、
{c,g,k,o}、{d,h,l,p}的参考对象分别为A、B、C、D;Luma intra_16x16与Chroma的模式跟Luma intra_4x4类似,详细的运算公式可以参考[1]。

3.4画面间预测技术(Inter-frame Prediction)
至于横跨每张画面之间的预测技术,H.264/AVC提供了更丰富的编码模式,计有下述几种区块分割(partition)的方法:16x16、16x8、8x16、8x8、8x4、4x8、
4x4,多样的分割方式可以让移动向量的预测更准确,如图7所示,画面中有些移动的区域并不是正方形,使用长方形或较小的4x4分割来做预测的区域,可以大幅
降低差余影像的数值来增加了压缩比,但也因此P-slice中的MB最多可有16个移动向量(motion vector),而B-slice中的MB最多可拥有32个移动向量,虽然这些会增
加移动向量档头(header)的容量,但整体来说对压缩比仍有正面的益处。再者,以往的压缩标准所使用的动态估测(motion estimation),只有使用前一张图像来作
为预测的对象,H.264/AVC提供了多重参考图像(multiple reference frames)的概念,使得移动向量不再只限于前后相邻的影像,而是可以跨过多张影像,如图8所
示,在时间点t的图块,可以使用t-1到t-2图像中的图块来作为预测的对象,当影片有周期重复性的内容时,例如:背景影像周期性的出现或被遮盖、对象有来回跳
动的行为、形状忽大忽小,或是摄影机在拍摄时,因为有多处的取景点,并且摄影画面在取景点之间来回移动,这种情形在球类比赛转播时常出现,这些状况都能
得到较好的动态预测结果,因而提高了压缩的效能。

3.5 转换、量化与熵编码算法 (Transform, Quantization, and EntropyCoding)
H.264/AVC的转换算法采用所谓的4x4与8x8整数转换,跟MPEG-2/-4的8x8 DCT(Discrete Cosine Transform)有很大的不同,因为是整数运算的缘故,不像小数运
算的DCT有系数还原后无法匹配的问题,而且以4x4的区块大小来进行转换也可减低区块效应的程度。在量化技术方面,H.264/AVC只使用加法与乘法而没有除法
运算,有利于集成电路的实现。跟以往MPEG-2/-4的熵编码技术(entropy coding)不同的是,H.264/AVC针对量化过的转换系数与非转换系数数据(文件头数据、移
动向量…等),分别使用二个不同的编码法则。非转换系数数据使用单一个的编码表,好处是可以节省编码表所占用的内存空间;针对量化过的转换系数数据来
说,不像MPEG-2/-4对每种影像都使用固定的编码表,H.264/AVC使用所谓的内容适应性编码技术(context-adaptive),也就是会根据编码的内容来统计某些代码
(code-word)的出现机率,而产生一个最适合于目前影像的编码表,好处是能够提高压缩比,但要使用额外的带宽来传送这些编码表。H.264/AVC内容适应性编码
技术有两种:Context Adaptive Variable Length Coding (CAVLC)以及ContextAdaptive Arithmetic Binary Coding (CABAC),CAVLC的基本原理跟MPEG-2/-4的
VLC相同,而CABAC的复杂度比CAVLC高,但却可以提供较高的压缩比,尤其是用在压缩交错式的数字电视影片。

3.6 内嵌式去区块效应滤波器(In-Loop De-blocking Filter)
先前有提到H.264/AVC也是一种block-based的压缩方法,所以会有区块效应(blocking-effect)的现象,虽然它采用4x4转换可以稍减区块效应的程度,但是在影像较
平滑的区域,仍需依靠去区块效应滤波器来做影像质量的修补。通常去区块效应滤波器分成两种:post filter及in-loop filter,所谓post filter就是在译码的流程之后
再进行的,而不在解压缩标准的规范中,好处是厂商可以依应用的复杂度,有弹性地决定滤波器的实现方式,而所谓的in-loop filter就是直接规范在编译码的流程
中,虽然会增加复杂度,但由于经过滤波器处理后的影像质量较好,若以此作为画面间预测的参考图像,其预测精确度会大幅的提升,因而增加了压缩比。

4. 结论 
由于H.264/AVC在视讯编码算法上的改进,其压缩比及视讯质量与MPEG-2/-4相较下有大幅度的提升,而其NAL概念有助于在有限带宽的传输通道上来传送高质量
的视讯内容,此外,对于高画质数字电视或高画质DVD,以H.264/AVC的编码技术都可以很轻易地满足应用需求,但就市场面来看,VC-1标准凭借着微软在PC
平台的优势与低价授权的策略,今后将成为H.264/AVC最强大的挑战者。

猜你喜欢

转载自blog.csdn.net/qq_38750519/article/details/122296213