LV05-02-H264-11-01-SPS帧简介
本文主要是H264的SPS帧简介的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.6的64位版本 | |
VMware® Workstation 17 Pro | 17.0.0 build-20800274 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 |
点击查看本文参考资料
点击查看相关文件下载
--- | --- |
一、SPS帧
1. SPS是什么
在H.264标准协议中规定了多种不同的NAL Unit类型,其中类型7表示该NAL Unit内保存的数据为Sequence Paramater Set(SPS)。一个SPS帧的数据如下:
1 | uint8_t data_buffer[SPS_DATA_SIZE] = {0x00,0x00,0x00,0x01,0x67,0x64,0x00,0x33,0xac,0x15,0x14,0xa0,0x50,0x05,0xba,0x68,0x80,0x00,0x01,0xf4,0x00,0x00,0x75,0x30,0x02 }; |
在H.264的各种语法元素中,SPS中的信息至关重要。如果其中的数据丢失或出现错误,那么解码过程很可能会失败。SPS及后续将要讲述的图像参数集PPS在某些平台的视频处理框架(比如iOS的VideoToolBox等)还通常作为解码器实例的初始化信息使用。
SPS 即 Sequence Paramater Set,又称作序列参数集。SPS 中保存了一组编码视频序列 (Coded video sequence) 的全局参数。 所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。但在某些特殊情况下,在码流中间也可能出现这两种结构,主要原因可能为:
- 解码器需要在码流中间开始解码;
- 编码器在编码的过程中改变了码流的参数(如图像分辨率等);
2. SPS基本格式
H.264标准协议中规定的SPS格式位于 ITU-T Rec. H.264 (08/2024) Advanced video coding for generic audiovisual services 文档的7.3.2.1.1 Sequence parameter set data syntax 。
1 | seq_parameter_set_data() |
vui_parameters结构如下,它在ITU-T Rec. H.264 (08/2024) Advanced video coding for generic audiovisual services的E.1.1 VUI parameters syntax:
1 | vui_parameters() |
二、SPS格式分析
1.ue(v)与se(v)
可以看到在 ITU-T Rec. H.264 (08/2024) Advanced video coding for generic audiovisual services 文档的7.3.2.1.1 Sequence parameter set data syntax 这里有说明每个成员是什么格式,例如:
u(1)、u(8)这些里面是数字的表示这个成员占了多少位,1就是1位,8就是8位。但是还有一些ue(v)、se(v),这些是什么?
ue(v)
是一种用于编码和解码语法元素的编码方式,称为“无符号指数哥伦布编码”(Exponential-Golomb Coding)。这种编码方式主要用于表示非负整数,广泛应用于H.264的语法元素中,如宏块类型、运动矢量差值等。se(v)
是一种用于编码和解码有符号整数的编码方式,称为“有符号指数哥伦布编码”(Signed Exponential-Golomb Coding)。这种编码方式主要用于表示有符号整数,广泛应用于H.264的语法元素中,如运动矢量差值等。
Tips:
具体编解码的原理可以看这里后面指数哥伦布编码的相关笔记。
2. 格式说明
2.1 profile_idc
标识当前H.264码流的profile。H.264中定义了四种常用的档次profile:
Baseline:(最低Profile)级别支持I/P 帧,只支持无交错(Progressive)和CAVLC,一般用于低阶或需要额外容错的应用,比如视频通话、手机视频等;
Extended:EP-Extended profile:进阶画质,是后来才加入的。支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC
Main:(主要Profile)级别提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),同样提供对于CAVLC 和CABAC 的支持,用于主流消费类电子产品规格如低解码(相对而言)的mp4、便携的视频播放器、PSP和Ipod等;
High:(高端Profile,也叫FRExt)级别在Main的基础上增加了8x8 内部预测、自定义量化、无损视频编码和更多的YUV 格式(如4:4:4),用于广播及视频碟片存储(蓝光影片),高清电视的应用。
在H.264的SPS中,第一个字节表示profile_idc,根据profile_idc的值可以确定码流符合哪一种档次。判断规律为:
1 | profile_idc = 66 —— baseline 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表示。
2.2 constraint_set0~5_flag
constraint_set0_flag ~ constraint_set5_flag是在编码的档次方面对码流增加的其他一些额外限制性条件。具体参考h264标准文档(可以看这个ITU-T Rec. H.264 (08/2024) Advanced video coding for generic audiovisual services ),在附录A中列出了各种profile对应的情况。
2.3 reserved_zero_2bits
保留位,始终等于0.
2.4 level_idc
标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的level由level_idc指定。
2.5 seq_parameter_set_id
表示当前的序列参数集的id。通过该id值,图像参数集pps可以引用其代表的sps中的参数。
三、实例分析
这是我保存出来的一个SPS帧的信息:
1 | uint8_t data_buffer[SPS_DATA_SIZE] = {0x00,0x00,0x00,0x01,0x67,0x64,0x00,0x33,0xac,0x15,0x14,0xa0,0x50,0x05,0xba,0x68,0x80,0x00,0x01,0xf4,0x00,0x00,0x75,0x30,0x02 }; |
现在就来分析一下。
1. 0x00,0x00,0x00,0x01
这4个字节是start code,就是起始码,表示这是一个NALU的开始。
2. 0x67
这个字节是nalutype,它与上0x1f就是7,表示这是一个SPS。
3. 第5、6、7字节
接下来就是SPS帧的相关信息了,先来分析第一个哥伦布编码前的这三个字节:
1 | seq_parameter_set_data() |
对应到上面的数据就是:
可以得到:
1 | 64 : profile_idc=0x64(100 —— high profile) |
4. 第8字节
接下来分析ue(v)这个seq_parameter_set_id,由于前面profile_idc=100,所以这里if循环里面的也会有。
1 | seq_parameter_set_data() |
由于哥伦布编码要解出来后才知道数据有多长,所以先看0xac这个字节:
1 | ac : 1010 1100 |
(1)先看第一位,如下图:
根据哥伦布编码的原理,第一个bit是1,前面没有0,所以这里不需要向后读取,这里就是2^0-1+0=0,所以seq_parameter_set_id=0。
(2)profile_idc=100,所以if成立。再看后面1位:
接下来分析if里面的chroma_format_idc,他也是一个ue(v)类型,继续向后分析看0的个数,如上图所示,这里有1个0,所以从1向后读1位,就是0
,所以这里就是2^1-1+0=1,即chroma_format_idc=1。
(3)继续往后分析的起始位置如下:
由于chroma_format_idc不等于3,这里我们继续往后分析bit_depth_luma_minus8,继续向后找发现是一个1,如上图所示,所以这里bit_depth_luma_minus8=0.
(4)如下图:
再后面是 bit_depth_chroma_minus8,也是一个ue(v)数据,所以继续分析,如上图所示,后面是一个1,所以这里 bit_depth_chroma_minus8 也是0。分析到这里,0xac这个字节还剩下2位。
(5)如下图
继续往下是 qpprime_y_zero_transform_bypass_flag,它是个u(1),占1位,如上图,在这里就是0。
(6)如下图
再往下是seq_scaling_matrix_present_flag,这也是一个u(1),如上图,这里就是0了。所以后面的if不成立,这一段就分析结束了。到这里为止,分析了一个完整的字节0xac。
(7)最终结果:
最后这个0xac字节刚好分析完,指针指向下一个数据的第一位。
5. 第9字节
接下来是log2_max_frame_num_minus4后面这部分,发现连续两个都是ue(v),这里只能解码后才知道长度了。
1 | seq_parameter_set_data() |
先看0x15这个数据:
1 | 15 : 0001 0101 |
(1)如下图位置:
这里是3个0,所以要从1后面读3个位,就是010
,这里就是2^3-1+2=9。所以 log2_max_frame_num_minus4 = 9。
(2)上面读取完毕,0x15这个字节还剩余1位,如下图:
(3)最终结果:
分析到这里,0x15这个字节还剩余1位。
6. 第9 - 10字节
接下来是pic_order_cnt_type后面这部分,发现pic_order_cnt_type是ue(v):
1 | seq_parameter_set_data() |
前面一个字节分析完还剩余1位,加上0x14,数据情况如下:
1 | 15 : 1 |
(1)如下图:
往后的pic_order_cnt_type是一个ue(v)类型,我们继续找0,会发现这里只有一个1,所以这里pic_order_cnt_type = 2^0-1+0=0。到此0x15这个数据分析完毕。后面该下一个字节的第一位。
(2)如下图,上面0x15字节全部分析完毕,从0x14的第一个字节开始:
由于上面的pic_order_cnt_type=0,这里log2_max_pic_order_cnt_lsb_minus4存在,它是一个ue(v)类型,我们开始从0x14的第一位分析,这里有3个0,所以向后读3位010
,这里就是2^3-1+2=9。所以 log2_max_pic_order_cnt_lsb_minus4 = 9。
(3)最终结果
这一段就分析到这里,还剩下1个位。
7. 第11 - 14字节
接下来继续往下,下面这个四个不涉及if之类的,放在一起分析
1 | seq_parameter_set_data() |
前面分析完0x14,还剩余1位,再继续看下面的0xa0(其实这里有预留了,因为后面会发现,这几个成员并不是一个字节就能容纳的):
1 | 14 : 0 |
(1)先看这个max_num_ref_frames,是一个ue(v)类型,我们还是按指数哥伦布编码解如下图:
发现有1个0,这里向后读1位,这里就是2^1-1+0=1。所以max_num_ref_frames=1。
(2)继续向后看gaps_in_frame_num_value_allowed_flag,如下图:
gaps_in_frame_num_value_allowed_flag是个u(1)类型,只占1位,所以它的值为1。
(3)再来看pic_width_in_mbs_minus1,如下图:
这个pic_width_in_mbs_minus1是ue(v)类型,所以要向后找0的个数,发现0xa0剩余几位全为0,所以要读到下一个字节0x50,这里向后一共是6个0,所以向后数6位010 000
,也就是16(注意这里是二进制的10000
),这里就是2^6-1+16=79。
(4)再来看pic_height_in_map_units_minus1的值:
pic_height_in_map_units_minus1是一个ue(v)类型,这里就是5个0,往后读5位就是01101
,就是13,这里就是2^5-1+13=44。
(5)最终结果
最后第14个字节还剩余5位没有分析。
8. 第14字节
继续往下分析这一部分:
1 | seq_parameter_set_data() |
之前分析完后,数据剩余情况如下:
(1)frame_mbs_only_flag是一个u(1)类型,只占1位:
这里就是1,所以frame_mbs_only_flag=1,后面的if不成立。
(2)direct_8x8_inference_flag是一个u(1)类型:
读取1位就是1,所以direct_8x8_inference_flag=1。
(3)frame_cropping_flag是一个u(1)类型,只占1位,如下图:
所以frame_cropping_flag=0。这样的话后面的if不成立。这一段就分析完毕了。
(4)最终结果
上面分析完数据情况如上图。
9. 第14字节
接下来分析最后一部分:
1 | seq_parameter_set_data() |
上面分析完的数据情况如下:
(1)vui_parameters_present_flag是一个u(1)类型,只需要读取1位:
所以有vui_parameters_present_flag=1。后面的if成立。
(2)由于后面是另一个结构,分析到这里结束,最后数据情况如下:
10. 第14 - 15字节
接下来就是vui_parameters()里面的内容了,先来分析这一部分:
1 | vui_parameters() |
前面执行完毕数据情况如下:
第14字节还剩余1位。
(1)aspect_ratio_info_present_flag是u(1)类型,读取1位:
所以aspect_ratio_info_present_flag=0。所以后面的if不成立。到这里第14字节就分析完毕了。
(2)再来分析overscan_info_present_flag,它是u(1)类型:
读取1位就是overscan_info_present_flag=0。所以它后面的if也不成立。
(3)继续看video_signal_type_present_flag,它是u(1)类型:
读取1位就是video_signal_type_present_flag=1,所以后面的if成立。
(4)接下来分析if (video_signal_type_present_flag)中的内容:
video_format是u(3),往后读3位是101,所以video_format=5。video_full_range_flag是u(1),往后再读1位就是0,所以video_full_range_flag=0。后面是colour_description_present_flag,也是u(1),继续读1位就是0,所以colour_description_present_flag=0。后面的if不成立。这里一共分析了5个位。
(5)最终结果
上面分析完,数据如上图。
11. 第15 - 24字节
继续往下看后面的这部分:
1 | vui_parameters() |
前面分析完数据情况如下:
(1)chroma_loc_info_present_flag是u(1)类型,读取1位:
有chroma_loc_info_present_flag=0,所以后面的if不成立。到这里第15字节分析完毕。
(2)timing_info_present_flag是u(1),继续读取1位:
有timing_info_present_flag=1。所以if (timing_info_present_flag)成立。
(3)if (timing_info_present_flag)中是两个u(32),一个u(1):
所以有num_units_in_tick=11 1110 1000,也就是1000,所以num_units_in_tick=1000。time_scale=1110 1010 0110 0000=60000。fixed_frame_rate_flag=0。
(4)最终结果
上面分析完,就到了最后一个字节,最后还剩余6位。
12. 第25字节
再来看最后这一部分:
1 | vui_parameters() |
前面分析完后,数据剩余情况如下:
(1)nal_hrd_parameters_present_flag占1位:
所以nal_hrd_parameters_present_flag=0,后面的if (nal_hrd_parameters_present_flag)不成立。
(2)vcl_hrd_parameters_present_flag占1位:
所以vcl_hrd_parameters_present_flag=0,后面的if (vcl_hrd_parameters_present_flag)不成立。
(3)由于 nal_hrd_parameters_present_flag 和 vcl_hrd_parameters_present_flag 都是0,所以if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) 不成立。
(4)pic_struct_present_flag占1位:
pic_struct_present_flag=0。
(5)bitstream_restriction_flag占1位:
bitstream_restriction_flag=0。所以后面的if (bitstream_restriction_flag)不成立。
13. 总结
最后数据还剩余2位。其他关键信息已经全部解析完毕。