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_typeNAL类型
0未使用
1不分片、非IDR图像的片
2片分区A
3片分区B
4片分区C
5IDR图像中的片
6补充增强信息单元(SEI)
7序列参数集(SPS)
8图像参数集(PPS)
9分界符
10序列结束
11码流结束
12填充
13~23保留
24~31用于H264以外的,RTP负荷规范使用这其中的一些值来定义包聚合和分裂

常用的NAL头取值如下:

0x65IDR
0x06SEI
0x67SPS
0x68PPS
0x61非IDR Slice
0x01B 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码流的档次:

  • 基准档次:baseline profile, 值66
  • 主要档次:main profile,值77
  • 扩展档次:extended profile,值88

另外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。
通过这种方式,PPS中也可以取到对应SPS中的参数。该值的取值范围为[0,31]。

entropy_coding_mode_flag

熵编码模式标识:

  • 0:指数哥伦布编码或CAVLC
  • 1:表示使用CABAC
num_slice_groups_minus1分片数量
weighted_pred_flag在P/SP Slice中是否开启权重预测
weighted_bipred_idc在B Slice中加权预测的方法

pic_init_qp_minus26/
pic_init_qs_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_lsbH264中frame_poc与frame_num详解_nut_coffee的博客-CSDN博客_h264 poc和frame num

参考链接





  • 无标签