- 由 zhongluqiang创建, 最后修改于2月 18, 2023
H264压缩编码流程
H264码流分层
H264视频数据分为VCL层和NAL层。
VCL层
Video Coding Layer,视频数据编码层,负责产生H264视频原始数据,以上的SODB, RBSP, EBSP都属于VCL层。
NAL层
Network Abstraction Layer,视频数据网络抽象层,用于对VCL的数据进行封包,增加纠错能力,以便于在网络上传输。
码流基本概念
SODB
String of Data Bits,二进制数据流,代表H264编码器输出的原始比特流数据。每个Slice经H264编码后都会产生一个SODB。SODB是按bit紧密排列的,长度不一定是8的整数倍,也就是不保证按字节对齐。
RBSP
Raw Byte Sequence Payload,原始字节流载荷,将SODB进行字节对齐后得到RBSP,对齐方法是在尾部添加一bit的'1',然后补齐若干个'0',直到字节对齐。特殊地,即使SODB本身就是字节对齐的,也会先补'1'再补'0'再对齐,导致多出一个字节的'10000000'。这种设计有助于统一H264解码器的设计,减少分支预测。
NAL单元的最后一字节不可能是0x00。
EBSP
EBSP不是H264标准中定义的语法,而是在H264的参考软件JM中定义的,这个软件的主页是https://iphome.hhi.de/suehring/tml/。JM是用标准C语言实现的一个H264的编码器,它并没有针对不同的架构进行优化,所以性能比较差,但它的代码是最容易读懂的,所以可以作为学习H264编解码的参考。
Extend Byte Sequence Payload,扩展字节流载荷,对RBSP添加防竞争字节0x03后得到的码流。由于AnnexB格式的码流要通过00 00 01 或 00 00 00 01来作为NALU之间的分隔符,所以RBSP内部的数据不能出现这两个序列,H264编码器在每编码完一个NAL之后,会检查码流中是否有以下四种序列:
- 0x000000
- 0x000001
- 0x000002
- 0x000003
如果遇到以上的序列,那么就在最后一个字节前面插入一个字节的0x3,使其变成下面的序列:
- 0x00000300
- 0x00000301
- 0x00000302
- 0x00000303
上面的这四种形式,前两种是用来与Start Code防止竞争的,0x00000302是保留的,0x00000303是为了区分0x000003序列本身的。
参考《T-REC-H.264-202108-I!!PDF-E.pdf》第7.4.1小节:
- 在一个NAL单元内,绝无可能出现0x000000、0x000001、0x000002三种序列。
- 在一个NAL单元内,如果一个4字节序列的前3字节是0x000003,那么它只能是0x00000300、0x00000301、0x00000302、0x00000303中的一种,不可能是其他组合。
- H264解码器在解码时,会自动剔除位于两个连续的00后面的03。
H264码流结构(AnnexB格式)
整体结构

Start Code
每个NALU前面都有一个起始码,用于分隔各个NALU。起始码是三字节的0x000001或四字节的0x00000001,当NALU类型是PPS/SPS/SEI或是该NALU是一帧的第一个片时,起始码是四字节的0x00000001,其他情况是0x000001。
NALU
NALU由NAL Header和NAL Body组成。NAL头占用1个字节,其各个位的定义如下:
- forbidden_zero_bit
默认为0,为1时表示该NALU存在错误。 - nal_ref_idc
用于标记当前NAL的重要性,值为0时表示这个NAL没有用于预测,抛弃不会导致错误扩散,值越高表示此NAL单元越重要,越不能抛弃。SPS/PPS/I-Slice的这个值不为0。 - nal_unit_type
表示当前NAL的类型,占5bit,范围0~31,其代码的值含义如下:
nal_unit_type | NAL类型 |
---|---|
0 | 未使用 |
1 | 不分片、非IDR图像的片 |
2 | 片分区A |
3 | 片分区B |
4 | 片分区C |
5 | IDR图像中的片 |
6 | 补充增强信息单元(SEI) |
7 | 序列参数集(SPS) |
8 | 图像参数集(PPS) |
9 | 分界符 |
10 | 序列结束 |
11 | 码流结束 |
12 | 填充 |
13~23 | 保留 |
24~31 | 用于H264以外的,RTP负荷规范使用这其中的一些值来定义包聚合和分裂 |
常用的NAL头取值如下:
0x65 | IDR |
0x06 | SEI |
0x67 | SPS |
0x68 | PPS |
0x61 | 非IDR Slice |
0x01 | B Slice |
SPS
Sequence Parameter Set,序列参数集。
SPS本身是一种NALU,所以也包括一字节的NAL头,但是头之后的内容是经过指数哥伦布熵编码的,需要先解码才能得到各个成员参数的信息。
SPS用于指定一组编码视频序列(Coded Video Sequence)的全局参数,如seq_parameter_set_id、帧数及POC(picture order count)的约束、参考帧数目、解码图像尺寸和帧场编码模式选择标识等。
SPS具体语法参考《T-REC-H.264-202108-I!!PDF-E.pdf》第7.3.2.1小节,重要参数如下(注意,这些参数都需要先经过指数哥伦布熵编码的解码才能获得):
档次与级别:
profile_idc | 标识当前H264码流的档次:
另外constraint_set0_flag~constraint_set5_flag还会对档次增加一些其他的限制条件 |
level_idc | 标识当前码流的level |
seq_parameter_se_id | 表示当前SPS的id,后续PPS可以通过这个id来引用PPS。 |
帧相关:
log2_max_frame_num_minus4 | 用于计算一个GOP内frame_num的上限,方法是2^(log2_max_fram_num_minus4+4)。frame_num在Slice header中,决定了这个Slice在这个GOP中的显示顺序。 |
pic_order_cnt_type | 表示解码picture order count(POC)的方法。POC是另一种计量图像序号的方式,与frame_num有着不同的计算方法。 |
log2_max_pic_order_cnt_lsb_minux4 | 用于计算MaxPicOrderCntLsb的值,该值表示POC的上限。 计算方法为MaxPicOrderCntLsb = 2^(log2_max_pic_order_cnt_lsb_minus4 + 4)。 |
max_num_ref_frams | 用于表示参考帧的最大数目。 |
gaps_in_frame_num_value_alloed_flag | 标识位,说明frame_num中是否允许不连续的值。 |
分辨率相关:
pic_width_in_mbs_minus1 | 图像宽度包含的宏块数减1 |
pic_height_in_mbs_minus1 | 图像高度包含的宏块数减1 |
frame_mbs_only_flag | 帧编码还是场编码(场是隔行扫描,产生两张图片) |
frame_cropping_flag | 图像是否需要裁剪(当宽高不是16的整数倍时需要裁剪) |
frame_crop_left_offset | 减去左侧的偏移量 |
frame_crop_right_offset | 减去右侧的偏移量 |
frame_crop_top_offset | 减去顶部的偏移量 |
frame_crop_bottom_offset | 减去底部的偏移量 |
通过SPS计算分辨率,参考:H264之sps解析分辨率_g0415shenw的博客-CSDN博客_sps 分辨率
通过SPS计算帧率,参考:H264(代码)---序列参数集(SPS)---解析并获取帧率_SXM19940913sxm的博客-CSDN博客_sps 哪个是帧率
PPS
Picture Parameter Set,图像参数集。
用于指定视频序列中图像的参数,如pic_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等。
PPS具体语法参考《T-REC-H.264-202108-I!!PDF-E.pdf》第7.3.2.2小节,重要参数如下(PPS的内容也需要先解码):
pic_parameter_set_id | 当前PPS的id。 某个PPS在码流中会被相应的slice引用,slice引用PPS的方式就是在Slice header中保存PPS的id值。该值的取值范围为[0,255]。 |
seq_parameter_set_id | 当前PPS所引用的激活的SPS的id。 |
entropy_coding_mode_flag | 熵编码模式标识:
|
num_slice_groups_minus1 | 分片数量 |
weighted_pred_flag | 在P/SP Slice中是否开启权重预测 |
weighted_bipred_idc | 在B Slice中加权预测的方法 |
pic_init_qp_minus26/ | 初始化量化参数,实际参数在Slice Header |
chroma_qp_index_offset | 用于计算色度分量的量化参数 |
deblocking_filter_control_present_flag | 表示Slice header中是否存在用于去块滤波器控制的信息 |
constrained_intra_pred_flag | 若该标识为1,表示I宏块在进行帧内预测时只能使用来自I和SI类型宏块的信息 |
redundant_pic_cnt_present_flag | 用于表示Slice header中是否存在redundant_pic_cnt语法元素 |
SPS修饰GOP,PPS修改单帧图像。
因为SPS和PPS存放了码流的参数信息,后续解码要依赖SPS和PPS提供的参数,所以解码器解码时首先要读入SPS和PPS。
H264编码器在打包码流时,会在每个IDR帧前面放置SPS和PPS。
SEI
Supplemental Enhancement Information,自定义信息或叫增强信息,用于在码流中放入一些额外信息。SEI优先级不高,有时候在转码转封装过程中会被忽略掉。
Slice & Slice Header
当一个NALU的类型是Slice时,表示这个NALU对应一个分片,此时,这个NALU可以再分成Slice header和Slice data。
Slice Header具体语法参考《T-REC-H.264-202108-I!!PDF-E.pdf》第7.4.3小节,重要参数如下:
first_mb_in_slice | 片中第一个宏块的地址 |
slice_type | 片类型,取值如下: |
pic_parameter_set_id | 指定PPS的id |
colour_plane_id | 当标识位separate_colour_plane_flag为true时,colour_plane_id表示当前的颜色分量,0、1、2分别表示Y、U、V分量 |
frame_num | 对参考帧的编号,用于指示各个参考帧的解码顺序,这个值最大为2^(log2_max_fram_num_minus4+4)-1。 |
field_pic_flag | 场编码标识位。当该标识位为1时表示当前slice按照场进行编码;该标识位为0时表示当前slice按照帧进行编码。 |
bottom_filed_flag | 底场标识位。该标志位为1表示当前slice是某一帧的底场;为0表示当前slice为某一帧的顶场。 |
idr_pic_id | 表示IDR帧的序号,同一个IDR帧的所有slice这的idc_pic_id应保持一致。 |
pic_order_cnt_lsb | H264中frame_poc与frame_num详解_nut_coffee的博客-CSDN博客_h264 poc和frame num |
参考链接
- 无标签