第一章 前言
第三章 有关 SPS 和 PPS 的一切
第四章 有关 Slice 的一切
第七章 帧间编码
第八章 残差的熵编码: CAVLC 和 CABAC
目前视频中的 H.264 流行的包装方式有两种,一种叫做 AnnexB,一种叫做 avcC。对于这两种格式,各家的支持程度也不太一样,例如,Android 硬解码 MediaCodec 只接受 AnnexB 格式的数据,而 Apple 的 VideoToolBox,只支持 avcC 的格式。所以这就需要我们从业者对两种格式都有一个了解。本章,我们先来介绍 AnnexB
假如我们把多个 NALU 写到一个文件里面去,多个 NALU 首位相连穿成一串,因为 NALU 本身长度不一,也没有具体的标识符用来表明自己是一个独立的 NALU,那么我们在读取这个文件的时候其实并没有办法将写到一起 NALU 有效得进行区分。为了解决这个问题,我们必须给 NALU 添加上一些数据,将各个 NALU 进行分割。 AnnexB 就是用来对 NALU 层进行包装的一种格式。
AnnexB 格式的原理非常简单,就是在一个 NALU 前面加上三个或者四个字节,这些字节的内容是 0 0 0 1 或者 0 0 1。当我们读取一个 H264 流的时候,一旦遇到 0 0 0 1 或者 0 0 1,我们就认为一个新的 NALU 开始了,因此,这些用来做分隔符的字节,一般也被称为 start code, 起始码。
对于程序员来说,不确定的事情是非常难受的,我们刚说过,AnnexB 格式的数据的 start code 可能会有两种情况,0 0 0 1 的四字节模式和 0 0 1 的三字节模式。那么,到底什么时候用四字节模式,什么时候用三字节模式呢?
答案是,不一定。
有的人会误以为,在一路码流里只会出现一种情况,例如,头一个 NALU 是 0 0 0 1 的四字节模式,剩下的 NALU 就都是四字节模式。这是错误的,一路码流可能都是 0 0 0 1 也可能都是 0 0 1 ,同样也可能既有 0 0 0 1 也有 0 0 1 。而且这种交错使用的场景也并不少见。
所以在编程过程中,你要手动去判断是 0 0 0 1 还是 0 0 1,不能撞大运,否则分分钟 core dump 给你看,别问我怎么知道的。
但是只在 NALU 前面加上起始码是会产生问题了,因为原始码流中,是有可能出现 0 0 0 1 或者 0 0 1 的,这样就会导致读取程序将一个 NALU 误分割成多个 NALU。为了防止这种情况发生,AnnexB 引入了防竞争字节(Emulation Prevention Bytes)的概念。
所谓防竞争字节(Emulation Prevention Bytes),就是在给 NALU 添加起始码之前,先对码流进行一次遍历,查找码流里面的存在的 0 0 0,0 0 1,0 0 2,0 0 3 的字节,然后对其进行如下修改
0 0 0 => 0 0 3 0
0 0 1 => 0 0 3 1
0 0 2 => 0 0 3 2
0 0 3 => 0 0 3 3
即在上面的 4 种情况下,在 0 0 之后,插入一个字节,内容是 3。经过这样处理的码流,就不会再和起始码(0 0 1, 0 0 0 1)重复而发生冲突。
当然,在解码过程中,通过起始码成功分割 NALU 数据之后,还要将防竞争字节去掉。
0 0 3 0 => 0 0 0
0 0 3 1 => 0 0 1
0 0 3 2 => 0 0 2
0 0 3 3 => 0 0 3
这样才能得到真正的 NALU 码流。
AnnexB 的原理是在每个 NALU 前面写上一个特殊的起始码,通过这个起始码来当做 NALU 的分隔符,从而分割每个 NALU。而 avcC 则采用了另外一种方式。那就是在 NALU 前面写上几个字节,这几个字节组成一个整数(大端字节序)这个整数表示了整个 NALU 的长度。在读取的时候,先把这个整数读出来,拿到这个 NALU 的长度,然后按照长度读取整个 NALU,我们不妨把这几个字节叫做 NALU Body Length。
在介绍 avcC 格式之前,我们先来介绍一下两个特殊的 NALU,这两个 NALU 就是 SPS 和 PPS,SPS 和 PPS 存放了解码一路 H.264 码流的必要的参数信息,也就是说,你想要解码一路 H.264,就必须首先获取到 SPS 和 PPS。在后面的课程中,我们会详细介绍 SPS 和 PPS,现在你只需要知道,SPS 和 PPS 是特殊且重要的两个 NALU。
在 AnnexB 中,SPS 和 PPS 被当做了普通的 NALU 进行处理;而在 avcC 中,SPS 和 PPS 信息被当做了特殊的信息进行了处理。
在一路采用 avcC 打包的 H.264 流之中,我们首先看到的将是一段被称之为 extradata 的数据,这段数据定义了这个 H.264 流的基本属性数据,当然,也包含了 SPS 和 PPS 数据。
我们来看一下 extradata 数据格式
长度(bits) | 名称 | 备注 |
---|---|---|
8 | version | 总是等于 0x01 |
8 | avc profile | 所存放第一个 SPS 的第一个字节 |
8 | avc compatibility | 所存放第一个 SPS 的第二个字节 |
8 | avc level | 所存放第一个 SPS 的第三个字节 |
6 | reserved | 保留字段 |
2 | NALULengthSizeMinusOne | NALU Body Length 数据的长度减去 1 |
3 | reserved | 保留字段 |
5 | number of SPS NALUs | 有几个 SPS,一般情况下这里是 1 |
for(int i=0; i<number of SPS NALUs; i++){ | ~ | |
16 | SPS size | SPS 的长度 |
变长 | SPS NALU data | SPS NALU 的数据 |
} | ~ | |
8 | number of PPS NALUs | 有几个 PPS,一般情况下这里是 1 |
for(int i=0; i<number of PPS NALUs; i++){ | ~ | |
16 | PPS size | PPS 的长度 |
变长 | PPS NALU data | PPS NALU 的数据 |
} | ~ |
注: avcC 允许存多个 PPS 和 SPS。
我们注意一下上表中的这个值 NALULengthSizeMinusOne 这个字段长度是 2 个 bit,也就是说,这个字段可以存放的数据范围是 0 到 4。所以,NALU Body Length 的长度就是 1 个字节到 5 个字节。
通过将 NALULengthSizeMinusOne 加 1 ,我们就得出了后续每个 NALU 的 NALU Body Length 的长度,单位是字节数
例如,这个 NALULengthSizeMinusOne 是 3,那么每个 NALU 的 NALU Body Length 长度就是 4 个字节。我们在读取后续数据时,可以先读 4 个字节,然后把这四个字节转成整数,就是这个 NALU 的长度了,注意,这个长度并不包含起始的4个字节,是单纯 NALU 的长度。
758033884@qq.com
2022-08-04 12:11:22
770593108@qq.com
2022-03-09 11:16:47
zekun@sina.cn
2021-12-21 17:38:30
632212519@qq.com
2021-05-13 10:47:15
632212519@qq.com
2021-05-12 20:25:39
likanpu@gmail.com
2021-03-03 11:40:26
sunyingchun0312@163.com
2021-01-15 12:03:06
tianqi726@126.com
2021-01-14 14:33:50
632212519@qq.com
2021-01-14 14:26:10
632212519@qq.com
2021-01-14 14:15:37