引言
目前 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, 起始码。
防竞争字节 (Emulation Prevention Bytes)
但是只在 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 的字节,然后对其进行如下修改
1 2 3 4 | 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 数据之后,还要将防竞争字节去掉。
1 2 3 4 | 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。
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 数据格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | bits 8 version ( always 0x01 ) 8 avc profile ( sps[0][1] ) 8 avc compatibility ( sps[0][2] ) 8 avc level ( sps[0][3] ) 6 reserved ( all bits on ) 2 NALULengthSizeMinusOne // 这个值是(前缀长度-1) 3 reserved ( all bits on ) 5 number of SPS NALUs (usually 1) repeated once per SPS: 16 SPS size variable SPS NALU data 8 number of PPS NALUs (usually 1) repeated once per PPS 16 PPS size variable PPS NALU data复制 |
我们注意一下这个值 NALULengthSizeMinusOne,通过将这个值加 1 ,我们就得出了后续每个 NALU 前面前缀(也就是表示长度的整数)的字节数
例如,这个 NALULengthSizeMinusOne 是 3,那么每个 NALU 前面前缀的长度就是 4 个字节。我们在读取后续数据时,可以先读 4 个字节,然后把这四个字节转成整数,就是这个 NALU 的长度了,注意,这个长度并不包含起始的4个字节,是单纯 NALU 的长度。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2015-01-24 linux 进程(二) --- 进程的创建及相关api
2015-01-24 linux 进程(一)---基本概念
2015-01-24 linux进程编程:子进程创建及执行函数简介
2015-01-24 【操作系统】linux创建子进程--fork()方法
2015-01-24 Linux下利用fork()创建子进程并使父进程等待子进程结束
2015-01-24 windows、linux创建子进程
2015-01-24 linux中fork创建进程讲解(转)