H264
H264是视频编码规范,在这个规范下H264码流有两种方式:AnnexB格式 && AVCC格式
AnnexB
AnnexB 的原理是在每个 NALU 前面写上一个特殊的起始码,通过这个起始码来当做 NALU 的分隔符,从而分割每个 NALU
[start code] NALU | [start code] NALU | [start code] NALU |
---|---|---|
AVCC
avcC 则采用了另外一种方式。那就是在 NALU 前面写上几个字节,这几个字节组成一个整数(大端字节序)这个整数表示了整个 NALU 的长度
[extra data] | [length] NALU | [length] NALU | [length] NALU |
---|---|---|---|
NALU
NALU (Network Abstraction Layer Unit) 翻译过来就是网络抽象层单元。在 H.264/AVC 视频编码标准中,最外面一层叫做 NAL(Network Abstract Layer) 网络抽象层。所有的码流数据,最终都被封装成了一个一个的 NALU(Network Abstract Layer Unit)就是网络抽象层单元。
对应的NAL 还有一个视频编码层VCL(video code layer),视频编码后的原始数据 SODB (String Of Data Bits)
- 视频编码层(VCL),是对视频编码核心算法过程、子宏块、宏块、片等概念的定义。这层主要是为了尽可能的独立于网络来高效的对视频内容进行编码。编码完成后,输出的数据是 SODB(String Of Data Bits)。
- 网络适配层(NAL),是对图像序列、图像等片级别以上的概念的定义。这层负责将 VCL 产生的比特字符串适配到各种各样的网络和多元环境中。该层将 VCL 层输出的 SODB 数据打包成 RBSP(Raw Byte Sequence Payload)。SODB 是编码后的原始数据,RBSP 是在原始编码数据后面添加了结尾比特,一个比特 1 和若干个比特 0,用于字节对齐。然后再在 RBSP 头部加上 NAL Header 来组成一个一个的 NAL 单元。
sps & pps
SPS(Sequence Paramater Set) 序列参数集
PPS(Picture Paramater Set) 图像参数集
SPS 和 PPS 存放的的信息是码流中的参数信息,后续的解码流程是要依赖着里面的参数的,所以解码器在解码一路码流的时候,总是要首先读入 SPS 和 PPS
AnnexB 格式的码流,sps 跟 pps 是作为nalu存在的,一般都最前面
AVCC格式的码流,sps 跟 pps 是放在extra data中。
NALU header
nalu 的第一个字节,其实就是nalu header
forbidden_zero_bit | nal_ref_idc | nal_unit_type |
---|---|---|
forbidden_zero_bit 占用 1 位,nal_ref_idc 占用 2 位,nal_unit_type 占用 5 位。三个元素一共占用 8 位,也就是一个字节
forbidden_zero_bit
禁止位,正常情况下为 0。在某些情况下,如果 NALU 发生丢失数据的情况,可以将这一位置为 1,以便接收方纠错或丢掉该单元
nal_ref_idc
该元素表示这个 NALU 的重要性。可能的值有 4 个,越重要的 NALU 越不能丢弃
nal_ref_idc | 重要性 |
---|---|
3 | HIGHEST |
2 | HIGH |
1 | LOW |
0 | DISPOSABLE |
nal_unit_type
nal_unit_type | NALU 类型 | |
---|---|---|
0 | 未定义 | |
1 | 非 IDR SLICE | slice_layer_without_partitioning_rbsp( ) |
2 | 非 IDR SLICE,采用 A 类数据划分片段 | slice_data_partition_a_layer_rbsp( ) |
3 | 非 IDR SLICE,采用 B 类数据划分片段 | slice_data_partition_b_layer_rbsp( ) |
4 | 非 IDR SLICE,采用 C 类数据划分片段 | slice_data_partition_c_layer_rbsp( ) |
5 | IDR SLICE | slice_layer_without_partitioning_rbsp( ) |
6 | 补充增强信息 SEI | sei_rbsp( ) |
7 | 序列参数集 SPS | seq_parameter_set_rbsp( ) |
8 | 图像参数集 PPS | pic_parameter_set_rbsp( ) |
9 | 分隔符 | access_unit_delimiter_rbsp( ) |
10 | 序列结束符 | end_of_seq_rbsp( ) |
11 | 码流结束符 | end_of_stream_rbsp( ) |
12 | 填充数据 | filler_data_rbsp( ) |
13 | 序列参数扩展集 | seq_parameter_set_extension_rbsp( ) |
14~18 | 保留 | |
19 | 未分割的辅助编码图像的编码条带 | slice_layer_without_partitioning_rbsp( ) |
20~23 | 保留 | |
24~31 | 未指定 |
slice
我们之前介绍过 SPS 和 PPS,SPS 和 PPS 中储存的信息是一些参数项,例如,图像的长宽,图像的 profile 信息等。那么在 Slice 中,存放的信息就是编码后的图像信息了,也就是说,解码 Slice,我们就能还原出来图像了。
一个 Slice 通常被分为两个部分,Slice Header 和 Slice Body
Slice Header | Slice Body |
---|---|
slice_type
值 | 含义 |
---|---|
0 | P(P Slice) |
1 | B(B Slice) |
2 | I(I Slice) |
3 | SP(SP Slice) |
4 | SI(SI Slice) |
5 | P(P Slice) |
6 | B(B Slice) |
7 | I(I Slice) |
8 | SP(SP Slice) |
9 | SI(SI Slice) |
可以看到 slice_type 规定了 slice 的类型,这也解释了之前的一些误区。有人说根据 NALU 的类型就能判读是 I Slice 还是 P Slice 还是 B Slice。其实是错误的,要判断这些必须要读到 slice_type 才行
pic_parameter_set_id
slice_type 之后,是 pic_parameter_set_id,这个属性表明该 Slice 要依赖的 PPS 的 id。我们在解析 PPS 的时候,PPS 里面有一个 pic_parameter_set_id 的属性。当你解析出 Slice 中的 pic_parameter_set_id 之后,拿着这个 id 找到与之对应的 PPS 就可以了。然后 PPS 里还有个 seq_parameter_set_id,用这个 id 就可以找到依赖的 SPS 的 id。这样,你就可以为这个 Slice 查找到合适的 SPS 和 PPS 了。
MP4
h264编码之后的码流,最终还是会封装到各种媒体文件中,这里就只了解下最常用的mp4.
Mdat Box这个Box是存储音视频数据的Box,要从这个Box解封装出真实的媒体数据。当然这个Box一般都会存在,但是不是必须的。
但是在MP4格式文件中,H264 slice并不是以00 00 00 01 Start Code来进行分割,而是存储在Mdat Box的Data中。
Mdat Box的格式:
Box header + Box Data
Box length + Box Type + NALU length + NALU header + Nalu Data……. NALU length + NALU header + Nalu Data
Mdat里的NALU一般不再包含SPS PPS等数据,这些数据已经放到Moov Box里面了
mp4里的NALU其实就是AVCC格式的
references
深入浅出理解视频编码 H264 结构
最详尽的 H.264 编码相关概念介绍
自己动手写 H.264 解码器
H.264 媒体流 AnnexB 和 AVCC 格式分析 及 FFmpeg 解析mp4的H.264码流方法