写在前面
笔者当初并没有真的想去研究H264算法,(捂脸)之所以会有这篇文章是生活所迫啊。产品一旦投入使用后,必然遇到各种各样的坑,总结这些坑坑洼洼,笔者发现大多和H264编码格式相关,于是痛定思痛,横下心研究一番H264。所以说啊,
“BUG是推动程序员进步的最大动力”。对了,本文介绍的是H264编码格式,并不涉及编码原理和算法,适合工程同学阅读。
本文将介绍的是:
- H264的发展历史。将介绍 H26x 和 MPEG 家族的发展和关联。
- H264的编码格式。主要介绍 VCL 和 NAL ,前者与视频编码数据紧密相关,后者和H264格式相关,也是本文介绍的重点。
- NAL。介绍NAL的组成单元: NALU 。包括NALU的组成结构、类型、几个特殊的NALU,AU等。
- H264其他概念。如H264的两种格式: Annex-B和AVCC , Start Code 。
I. H264的发展历史
本文提出第一个问题
“所有的视频都一定要编码吗?”答案是肯定的。相信查看其他人的博客的理由是一样的:
不经过压缩的视频太大!在网络传输时对带宽的要求和压力太大!为此,
ITU-T与ISO/IEC是制定视频编码标准的两大组织,陆续提出了很多视频压缩标准。
百度百科
ITU-T的历史:
ITU-T的中文名称是国际电信联盟电信标准分局(ITU-T for ITU Telecommunication Standardization Sector), 它是国际电信联盟管理下的专门制定电信标准的分支机构。
该机构创建于1993年,前身是国际电报电话咨询委员会(CCITT 是法语Comité Consultatif International Téléphonique et Télégraphique的缩写,英文是International Telegraph and Telephone Consultative Committee),总部设在瑞士日内瓦。
百度百科
ISO/IEC会发现,其实这是2个机构:
- ISO 并不陌生 - 国际标准化组织,下文同样摘自百度百科(会发现, ISO
就是一个制定国际化标准):
国际标准化组织(International Organization for Standardization,ISO)简称ISO,是一个全球性的非政府组织,是国际标准化领域中一个十分重要的组织。ISO国际标准组织成立于1947年。
ISO负责目前绝大部分领域(包括军工、石油、船舶等垄断行业)的标准化活动。ISO的宗旨是“在世界上促进标准化及其相关活动的发展,以便于商品和服务的国际交换,在智力、科学、技术和经济领域开展合作。”
- IEC
是国际电工委员会,其介绍和历史摘自百度百科:
国际电工委员会(International Electrotechnical Commission,IEC)成立于1906年,它是世界上成立最早的国际性电工标准化机构,负责有关电气工程和电子工程电子工程/5448766)领域中的国际标准化工作。
1947年作为一个电工部门并入国际标准化组织(ISO),1976年又从ISO中分立出来。宗旨促进电气、电子工程领域中标准化及有关问题的国际合作,增进国际间的相互了解。
当然是指定了不少标准咯(坏笑)。下面笔者通过维基百科,整理了如下图1的发展历史,必须要声明一点:因为每个标准有提出时间、完成时间、立草案时间和正式发布时间。笔者采用了维基百科上出现的最早时间(捂脸)。
图1. 编码标准历史
一般认为,
ITU-T的
H.26x标准,主要应用于
实时视频通信领域,如会议电视;
ISO/IEC制定的
MPEG系列标准,主要应用于
视频存储(DVD)、广播电视、因特网或无线网上的流媒体等。
上图1中可以看到几个有趣的事情:
- 两个组织也在一起共同制定了一些标准:H.262标准等同于MPEG-2的视频编码标准;H.264标准等同于MPEG-4,并且被收录到第10部分。之前,笔者之前一直不明白,对于『H.264』,为什么有的地方会称呼为『MPEG4 AVC』,整理到这里终于明白了:这个协议是两个组织机构共同指定的,每个机构收录到各自的标准手册中,为了保持命名统一,所以才衍生了H.264和MPEG4 AVC两个名词。而最新的H.264标准则被纳入MPEG-4的第10部分,所以又称MPEG4 Part 10 AVC。
-
MPEG3并不存在!从维基百科摘录原因如下:
MPEG-3是在制定MPEG-2标准之后准备推出的适用于HDTV(高清晰度电视)的视频、音频压缩标准,但是由于MPEG-2标准已经可以满足要求,故MPEG-3标准并未正式推出。
虽然说H.264标准等同于MPEG-4,但是想必读者从网上一定看到不少关于
“H.264和MPEG-4的区别”的讨论,还有一个pdf文档专门记录了差别:The difference between MPEG-4 and H.264 。
说好的H.264和MPEG-4标准只是名字不一样,内容是由两个机构一起定制的呢?(摔桌子)这个问题似乎并没有得到很好的总结,网上查找很多资料,都会罗列出很多两者的差别。笔者综合StackOverflow上的问题What is the difference between H.264 video and MPEG-4 video? 和Quora上的问题How are x264 and MPEG-4 different?,结合自己的理解。笔者总结入下:
- 首先, 以上提到的都是标准,并不是具体的实现 。在H.264和MPEG-4的实践,前者以x264居多,后者没有具体了解。但是粗粗在网上搜了下,似乎是另一套代码实现。但是网上大家对于 “H.264和MPEG-4的区别” 是否由此引起,笔者不敢肯定;【挖个坑】
- 其次,很多标准也是在不断演化的,比如MPEG-4,大家应该留意到,有些外文中的说法是“ basic MPEG-4 compression”,而且H.264也确实是在后来被ISO收录到MPEG第10卷中的,在那之后,H.264和MPEG才开始保持同步更新的。
- 最后,不知什么原因,H.264这个名词被大家广泛使用,而MPEG-4似乎就没有这么热门。加上两者名词相差甚远,因此被很多人认为是截然不同的东西也是可能的。
II. H264编码格式
笔者还记得最早尝试学习H264的时候开篇就看到了
“NALU”、
“AU”,还有
“NAL”、
“VCL”一类高深莫测的名词,顿时觉得头昏脑胀:“这都是什么鬼”。接下来,笔者尝试把H264编码格式说清楚。
图2. H264功能划分示意图
读者请结合上图2,耐心阅读下段抄录(图片和引用均摘录自博客VCL & NAL )的话:
H264的主要目标是:
1. 高的视频压缩比,当初提出的指标是比H.263,MPEG-4【
怀疑是原作者的笔误,可能是MJPEG-2】,约为它们的2倍,现在都已基本实现;
2. 良好的网络亲和性,即可适用于各种传输网络。
为此,H.264 的功能分为两层,即视频编码层(VCL)和网络提取层(NAL)。
VCL 数据即
编码处理的输出 ,它表示被压缩编码后的视频数据序列。在VCL 数据传输或存储之前,这些编码的VCL 数据,先被映射或封装进NAL 单元中。
从引文中可以得到以下信息:
- H264能同时做到高压缩比和良好的网络亲和性;
- H264可以分为2层:视频编码层『 VCL 』和网络提取层『 NAL 』。
好了,到这里,一部分读者已经晕了:又是
视频编码层,又是
网络抽象层。
“天哪,这听上去这么高大上的名字都是什么玩意儿,更加看不懂了”。但是呢,H264本身就是用了一套复杂的编码/压缩规则来达到
相同码率下压缩率高且图像质量高、同时具有网络亲和性的目的。这里,读者们不要联想到神经网络(笔者最开始就以为他的背后是一套深度学习框架),试想一想,H264是2003年才提出的理论框架,用的还是比较传统的算法(毕竟,当时神经网络还没有现下这么大火)。
其实,仔细考虑,引文解释得其实很清楚了,笔者再啰嗦附加几句自己的解释:
“H264功能划分为『NAL』和『VCL』”的这个说法笔者觉得会在一定程度上误导读者。首先,来看『
VCL』,它的英文全称
Video Coding Layer,解释为:
A layer in H.264/AVC and HEVC that contains coded video data.
(『
VCL 』)是 H.264/AVC和HEVC的一层,它包含了编码的视频数据。
『
VCL』是管理H264视频数据层,对应实现目标1,从Video Coding Layer: What exactly is vcl and what are its functions?的回答也可以佐证这点。那
『VCL』究竟是怎么管理H264视频数据的呢?抛开H264压缩算法细节来看就3步:
- 压缩:预测(帧内预测和帧间预测)-> DCT变化和量化 -> 比特流编码;
- 切分数据,主要为了第三步。这里一点,网上看到的“ 切片(slice) ”、“ 宏块(macroblock) ”是在 VCL 中的概念,一方面提高编码效率和降低误码率、另一方面提高网络传输的灵活性。(这块本文将不展开)
- 包装成『 NAL 』。
对的,没有看错,『
VCL』最后会被包装成『
NAL』!!!
那么接下来就来介绍『
NAL』,英文全称
Network Abstraction Layer,这块和H264压缩算法无关,涉设计出『
NAL』的目的就是为了获得“network-friendly”,即对应实现目标2。关于NAL的描述,参照维基百科的介绍:
The NAL is designed in order to provide "network friendliness" to enable simple and effective customization of the use of VCL for a broad variety of systems. The NAL facilitates the ability to map VCL data to transport layers such as:
· RTP/IP for any kind of real-time wire-line and wireless Internet services.
· File formats, e.g., ISO MP4 for storage and MMS.
· H.32X for wireline and wireless conversational services.
· MPEG-2 systems for broadcasting services, etc.
NAL旨在提供“网络友好性”,以便为各种系统简单有效地定制VCL的使用。 NAL有助于将VCL数据映射到传输层,例如:
· RTP / IP适用于任何类型的实时有线和无线互联网服务。
· 文件格式,例如用于存储和MMS的ISO MP4。
· H.32X用于有线和无线会话服务。
· 用于广播服务等的MPEG-2系统。
直白翻译过来就是:
『NAL』就是为了包装『VCL』以达到更好网络传输效果。于是问题接着来了,
『NAL』究竟是怎么组织数据,能够做到这么好的网络友好性呢?且看后文,在这之前总结下本章,H264功能划分为2层:
- VCL(Video Coding Layer),视频编码层 ,H264编码/压缩的核心,主要负责将视频数据编码/压缩,再切分。
- NAL(Network Abstraction Layer),网络抽象层 ,这里的网络指的是计算机网络而不是神经网络,负责将 VCL 的数据组织打包。
III. 网络抽象层:NAL
终于要讲『
NAL』了,但是,我们需要先看『
NAL』的组成单元 - 『
NALU』。
NAL单元 - NALU
『
NALU』的格式如下图3(引用H264 PDF)所示:
图3. NALU格式示意图
很明显,『
NALU』由
头和
身体两个部分组成:
- 头:一般存储标志信息,譬如 NALU 的类型。第二章讲过 NAL 会打包 VCL 数据,但是这并不意味着所有的 NALU 负载的都是 VCL ,也有一些 NALU 仅仅存储了和编解码信息相关的数据;
- 身体:存储了真正的数据。但实际上,这块也会相对比较复杂,第二章其实也提到过H264的一个目的是“网络友好性”,说白了就是能够很好地适配各种传输格式。所以根据实际采用数据传输流格式,也会对这部分数据格式再进行处理。【这里先挖一个坑】
NALU Header
首先,『
NALU Header』只占1个字节,即8位,其组成如下:
| forbidden_zero_bit | nal_ref_idc | nal_unit_type |
`--------------------+-------------+---------------`
| 1 bit | 2 bit | 5 bit |
下面详细介绍:
- 『forbidden_zero_bit』:禁止位,笔者在有的地方看到是『forbidden_bit』。在网络传输中发生错误时,会被置为1,告诉接收方丢掉该单元;否则为0。这里笔者有几个疑问:【又挖坑了】
- 网络传输发生错误时,由谁来对这个位进行置1处理?
- 置1后被对方接收了,由谁来丢弃?(笔者怀疑是解码器)
- 『nal_ref_idc』:指示当前 NALU 的 优先级 ,或者说 重要性 ,数值越大表明越重要。笔者从文章Introduction to H.264: NAL Unit摘录下文:
On one hand, if it is a reference field / frame / picture, nal_ref_idc is notequal to 0. According to the Recommendation, non-zero nal_ref_idc specifies that the content of the NAL unit contains a sequence parameter set (SPS), a SPS extension, a subset SPS, a picture parameter set (PPS), a slice of a reference picture, a slice data partition of a reference picture, or a prefix NAL unit preceding a slice of a reference picture.
On the other hand, if it is a
non-referencefield / frame / picture, nal_ref_idc is equal to 0.
一方面,如果它是参考字段/帧/图片,则nal_ref_idc不等于0。根据H264标准,非零nal_ref_idcs指示NALU的内容可能为:序列参数集(SPS),SPS扩展,子SPS,图片参数集(PPS),参考图片的切片,参考图片的切片数据分区,或参考图片的切片之前的前缀NALU。 另一方面,如果它是非参考场/帧/图片,则nal_ref_idc等于0。
【挖坑:这里指的参考图像究竟是什么含义呢?是I帧吗?】
- 『nal_unit_type』:表示 NALU
的类型。笔者从Introduction to H.264: NAL Unit摘录了并翻译如下:
(先解释下每列的含义,第一列是『nal_unit_type』的取值,第二列如中文含义,第三列比较迷,笔者还没有完全弄明白,第四列表示该类型的NALU是否为VCL)
笔者删除了13以后类型,接下来让我们逐一来看现在列着的内容:
-
- 1-4:I/P/B帧,合起来介绍的原因是,他们是依据 VLC的slice 区分的,这块因为本文不涉及,一方面是这个太过于细节,真要展开篇幅太长;另一个原因是就算不了解slice、macroblock也不影响对H264格式的理解。
- 5: IDR帧 。 I帧 的一种,告诉解码器,之前依赖的解码参数集合(接下来要出现的SPSPPS等)可以被刷新了。
- 6: SEI ,英文全称 Supplemental Enhancement Information ,翻译为“ 补充增强信息 ”,提供了向视频码流中加入额外信息的方法。
- 7: SPS ,全称 Sequence Paramater Set ,翻译为“ 序列参数集 ”。SPS中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。
- 8: PPS ,全称 Picture Paramater Set ,翻译为“ 图像参数集 ”。该类型保存了整体图像相关的参数。
- 9: AU分隔符 , AU 全称 Access Unit ,它是一个或者多个 NALU 的集合,代表了一个完整的帧。
和
PPS存储了编解码需要一些图像参数,从What are SPS and PPS in video codecs?回答中来说,
在之前的协议中,发现实际网络传输编码好的数据流的时候会出现丢包,而如果丢包数据为图像头等关键信息的时候甚至会导致后续解码失败。在H264之前,为了应对图像头关键信息被丢失的做法是在很多包(也有说法是每一个包)都会携带图像头关键信息(冗余做灾备的思想)。但是,在H264种,为了提高网络传输鲁棒性,重新设计出SPS和PPS。【这里笔者需要给自己挖一个坑:SPS和PPS较之前的做法优越在哪里?是因为SPS和PPS存储的仅仅是一个GOP的信息,而之前的头信息存储的是整个视频关键信息嘛?】
提到这里,笔者插一句题外故事:
有一次,笔者在实际生产环境中遇到过用RTSP连接一款摄像头有问题,会提示“
no-existing PPS 0 referenced”,出错信息如下:
这个错误在用
h264_qsv解码插件的时候出现,但是切换到用cpu做解码的时候就正常了。后来定位到问题出现在视频流本身上:
对于一些解码器,如h264_qsv,必须要有sps和pps才能解码,这是因为很多硬解码插件依赖PPS信息。解决方案是:把摄像头的PPS选项调整到enable就可以了。
特殊的NALU类型:SEI笔者最近想在原始的视频帧上叠加一些OSD,发现了这个有趣的SEI,似乎可以存放用户自定义的数据,SEI那些事儿。【挖个坑】
AU分隔符引发的故事 问题:NALU是H264编码的基本单元,NALU是不是代表了一个完整帧?答案是
未知,或者有时候否定的。解释这个问题前,引入一个新概念『
AU』:H.264 将构成一帧图像所有
NALU的集合称为一个『
AU』,英文全称
Access Unit。『
AU』在H264中被提到频率不高,但是这是一个非常重要的概念,特别是在解码中要识别帧边界,这就是『
AU分隔符』,英文全称
Access unit Delimiter,然而实际解码器只有在解码的过程中,通过更多语法元素的组合才能判断一帧图像是否结束。
至此,
NALU头部分的一些基础知识了解完毕。接下来讲讲
NALU身体部分。
NALU Payload
很少有资料会称身体部分为
Payload,绝大部分资料对
NALU组成的定义是这样子的:
NALU = NALU Header + SODB // 定义1
NALU = NALU Header + RBSP // 定义2
NALU = NALU Header + EBSP // 定义3
于是新的问题来了:
SODB,RBSP和EBSP都是什么东西呢?这块概念,在博客NALU详解二(EBSP、RBSP与SODB)中介绍得非常清楚,总结来说就是:
SODB英文全称
String Of Data Bits,称
原始数据比特流,就是最原始的编码/压缩得到的数据。
RBSP全称
Raw Byte Sequence Payload,又称
原始字节序列载荷。和
SODB关系如下:
RBSP = SODB + RBSP Trailing Bits(RBSP尾部补齐字节)
引入
RBSP Trailing Bits做8位字节补齐。
EBSP全称
Encapsulated Byte Sequence Payload,称为
扩展字节序列载荷。和
RBSP关系如下:
EBSP :RBSP插入防竞争字节( 0x03
)
这里说明下
防止竞争字节(0x03
) :因为讲解顺序的问题,H264 Annex-B格式的
StartCode(0x000001
或 0x00000001
) 将在下一章才讲解到。读者可以先认为H264会插入一个叫做
StartCode的字节串来分割
NALU,于是问题来了,如果
RBSP种也包括了
StartCode(0x000001
或 0x00000001
) 怎么办呢?所以,就有了
防止竞争字节(0x03
) :
编码时,扫描 RBSP ,如果遇到连续两个字节,就在后面添加 防止竞争字节(
0x00
) ;解码时,同样扫描 EBSP ,进行逆向操作即可。
0x03
最后,以一幅图总结本章节:
NALU格式
IV. H264其他概念
了解了
NALU之后,关于H264格式,还有一个问题:
解码器怎么知道一个NALU要结束了?或者说它怎么区分NALU的边界?要回答这个问题,就必须了解H264的
打包方式,通俗来说是
H264如何组织一连串的NALU为完整的H264码流。目前H264主流的两种格式:
- Annex-B :本文关于 NALU 的很多细节介绍都是 Annex-B ,它依靠前文提到的 Start Code 来分隔 NALU
,打包方式如下:
[start code]--[NALU]--[start code]--[NALU]...
- AVCC :笔者对这个格式了解的不多,从网上找到很多资料知道以下几点:
- 由 NALU 和 extradata/sequence header 组成,由于在 extradata/sequence header 中存储了 NALU 的长度,因此 NALU Payload 不需要做字节对齐,不过防竞争字节还是有的;
- SPS 和 PPS 被放在了 extradata/sequence header 。
-
打包方式:
[SIZE (4 bytes)]--[NAL]--[SIZE (4 bytes)]--[NAL]... //请注意,SIZE一般为4字节,但是具体以实际为准
至于为什么要有这两类格式,笔者还需要查阅更多的资料。【挖坑挖坑】不过StackOverflow上关于Possible Locations for Sequence/Picture Parameter Set(s) for H.264 Stream的回答可以帮助深入了解这两种格式,推荐阅读。
Start Code
笔者有一次尝试用qsv硬解解码一个离线视频,结果提示
“no start code is found”,笔者当时就懵了:
什么是start code? Start Code是
Annex-B分隔
NALU的办法,一般采用
0x000001
或
0x00000001
。
写在后面
本文首次完成于2019年7月4日,又于2019年9月11日二次修改。再次修改时重新整理了各个知识点的顺序,新增了H264的历史。
最后,文章中有不严谨的地方,欢迎指摘。