ZigZag Sin
登 陆
上一篇:什么是 NALU 下一篇:*代码-读取 AnnexB 格式的 H.264 数据

AnnexB 和 avcC

乔红
2020-11-26 17:13 阅读 8097

引言

目前视频中的 H.264 流行的包装方式有两种,一种叫做 AnnexB,一种叫做 avcC。对于这两种格式,各家的支持程度也不太一样,例如,Android 硬解码 MediaCodec 只接受 AnnexB 格式的数据,而 Apple 的 VideoToolBox,只支持 avcC 的格式。所以这就需要我们从业者对两种格式都有一个了解。本章,我们先来介绍 AnnexB

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 NALU

0 0 0 1 还是 0 0 1

对于程序员来说,不确定的事情是非常难受的,我们刚说过,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 给你看,别问我怎么知道的。

防竞争字节 (Emulation Prevention Bytes)

但是只在 NALU 前面加上起始码是会产生问题了,因为原始码流中,是有可能出现 0 0 0 1 或者 0 0 1 的,这样就会导致读取程序将一个 NALU 误分割成多个 NALU。为了防止这种情况发生,AnnexB 引入了防竞争字节(Emulation Prevention Bytes)的概念。

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)重复而发生冲突。

Emulation Prevention Bytes

当然,在解码过程中,通过起始码成功分割 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 码流。

avcC

AnnexB 的原理是在每个 NALU 前面写上一个特殊的起始码,通过这个起始码来当做 NALU 的分隔符,从而分割每个 NALU。而 avcC 则采用了另外一种方式。那就是在 NALU 前面写上几个字节,这几个字节组成一个整数(大端字节序)这个整数表示了整个 NALU 的长度。在读取的时候,先把这个整数读出来,拿到这个 NALU 的长度,然后按照长度读取整个 NALU,我们不妨把这几个字节叫做 NALU Body Length

avcC 详解

在介绍 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)名称备注
8version总是等于 0x01
8avc profile所存放第一个 SPS 的第一个字节
8avc compatibility所存放第一个 SPS 的第二个字节
8avc level所存放第一个 SPS 的第三个字节
6reserved保留字段
2NALULengthSizeMinusOneNALU Body Length 数据的长度减去 1
3reserved保留字段
5number of SPS NALUs有几个 SPS,一般情况下这里是 1
for(int i=0; i<number of SPS NALUs; i++){~
16        SPS sizeSPS 的长度
变长        SPS NALU dataSPS NALU 的数据
}~
8number of PPS NALUs有几个 PPS,一般情况下这里是 1
for(int i=0; i<number of PPS NALUs; i++){~
16        PPS sizePPS 的长度
变长        PPS NALU dataPPS NALU 的数据
}~

注: avcC 允许存多个 PPS 和 SPS。

NALULengthSizeMinusOne

我们注意一下上表中的这个值 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 的长度。

NALULengthSizeMinusOne

上一篇:什么是 NALU 下一篇:*代码-读取 AnnexB 格式的 H.264 数据
给我买个键盘吧。。。求打赏。。。
欢迎加群,一起交流~~~

758033884@qq.com

那码流里本身有0 0 3 3 1 的呢

2022-08-04 12:11:22

770593108@qq.com

本身有0 0 3 1的数据会 修改 0 0 3 3 1 还原 0 0 3 1

2022-03-09 11:16:47

zekun@sina.cn

如果流中本身就有0 0 3 1这种数据,而解码时,我们把它搞成了0 0 1,这就错误了啊,咋整?

2021-12-21 17:38:30

632212519@qq.com

NALULengthSizeMinusOne 是不是要翻译成 NALU Body Length 数据的长度 - 1 比较好

2021-05-13 10:47:15

632212519@qq.com

好奇防竞争字节为啥还要转换002和003?

2021-05-12 20:25:39

likanpu@gmail.com

非常感谢,写得很通俗易懂

2021-03-03 11:40:26

sunyingchun0312@163.com

感谢大神把这么重要的知识讲解的这么透彻,简直是音视频行业的一股清流,get 了

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