音视频学习(六、初识h264 NALU)

深入浅出理解视频编码H264结构(内涵福利)
H264编码原理以及I帧、B和P帧详解, H264码流结构分析
H264编码之GOP含义

简单的推流拉流已经实现了,我们之前推流的数据是通过读取flv视频文件的,正式的应用是不会这么推流的,正式的应用是采集电脑屏幕数据,然后进行编码,传输,接收端接收到数据之后再解码然后显示,这是的编码就是把YUV格式的视频数据编码成h264的,这个h264的结构还是比较重要的,虽然网上也有很多人总结,并且总结的还不错,但是总感觉看了几遍还是记不住,这个就是所谓的好记性不如烂笔头把,所以我这里借鉴了网上的一些大神的总结,然后再加上自己的总结,这样印象深刻一点。

6.1 h264介绍

首先来一段大家都熟悉的官方话来介绍一下 H.264

H.264: H.264/AVC项目的目的是为了创建一个比以前的视频压缩标准,在更低的比特率的情况下依然能够提供良好视频质量的标准(如,一半或者更少于MPEG-2,H.263,或者MPEG-4 Part2 )。同时,还要不会太大的增加设计的复杂性。
优势:
1)网络亲和性,即可适用于各种传输网络
2)高的视频压缩比,当初提出的指标是比 H.263,MPEG-4,约为它们的 2 倍,现在都已基 实现;

那么很明显,什么时候需要到压缩呢?当然是文件体积太大的时候啦,我们想想,所谓的视频,就是像小时候的连环画一样,在一秒内翻过 24 张以上的图片,就感觉图像是连续的了,这就是视频的原理。但是大家有没有想过,一张图片有多大呢?我们的屏幕分辨率按 1280 * 720 算的话,一秒钟的视频大概就 2.64 MB 了,大家想想,我们大部分的小伙伴为了下载个小嗨片省吃俭用才开了个 1M 的网线,然后连个直播都看不了是什么感觉。那肯定不能这样了,所以我们要进行压缩,而 H.264 不仅压缩比比较高,对网络的兼容性也非常好,所以大多数人做直播也就选择了 H.264 作为编码格式了。

6.1.1 编码流程

那么 H.264 其编解码流程是怎么样的呢?其实可以主要分为 5 部分: 帧间和帧内预测(Estimation)、变换(Transform)和反变换、量化(Quantization)和反量化、环路滤波(Loop Filter)、熵编码(Entropy Coding)。
看起来很高深的样子,实际上也是很高深的样子,因为这里面包含着许许多多的算法和专业知识,这里我们就不做过多的讲解,有兴趣的同学可以上网翻翻,够你看到睡觉的了。H.264详细文档

6.2 I帧 P帧 B帧

H264是新一代的编码标准,以高压缩高质量和支持多种网络的流媒体传输著称,在编码方面,我理解的理论依据是:参照一段时间内图像的统计结果表明,在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内。所以对于一段变化不大图像的画面,我们可以先编码出一个完整的图像帧A,随后的B帧就不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的1/10或更小!B帧之后的C帧如果变化不大,我们可以继续以参考B的方式编码C帧,这样循环下去。这段图像我们称为一个序列(序列就是有相同特点的一段数据),当某个图像与之前的图像变化很大,无法参考前面的帧来生成,那我们就结束上一个序列,开始下一段序列,也就是对这个图像生成一个完整帧A1,随后的图像就参考A1生成,只写入与A1的差别内容。
在H264协议里定义了三种帧,完整编码的帧叫I帧,参考之前的I帧生成的只包含差异部分编码的帧叫P帧,还有一种参考前后的帧编码的帧叫B帧。
H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。

6.2.1 I帧

帧内编码帧,I帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)
I帧特点:
1)它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
2)解码时仅用I帧的数据就可重构完整图像;
3)I帧描述了图像背景和运动主体的详情;
4)I帧不需要参考其他画面而生成;
5)I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
6)I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;
7)I帧不需要考虑运动矢量;
8)I帧所占数据的信息量比较大。

6.2.2 P帧

前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)
P帧的预测与重构:P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。
P帧特点:
1)P帧是I帧后面相隔1~2帧的编码帧;
2)P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
3)解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
4)P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧;
5)P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧;
6)由于P帧是参考帧,它可能造成解码错误的扩散;
7)由于是差值传送,P帧的压缩比较高。

6.2.3 B帧

双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别(具体比较复杂,有4种情况,但我这样说简单些),换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。
B帧的预测与重构
B帧以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。
B帧特点
1)B帧是由前面的I或P帧和后面的P帧来进行预测的;
2)B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
3)B帧是双向预测编码帧;
4)B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确;
5)B帧不是参考帧,不会造成解码错误的扩散。

注:I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I帧的压缩率是7(跟JPG差不多),P帧是20,B帧可以达到50。可见使用B帧能节省大量空间,节省出来的空间可以用来保存多一些I帧,这样在相同码率下,可以提供更好的画质。

6.3 NALU

H.264 原始码流(又称为裸流),是有一个接一个的 NALU 组成的,而它的功能分为两层:

  • 视频编码层(VCL, Video Coding Layer):包括核心压缩引擎和块,宏块和片,设计目标是尽可能的独立于网络进行高效的编码。

  • 网络提取层(NAL, Network Abstraction Layer):负责将VCL产生的比特字符串适配到各种各样的网络和多元环境中,覆盖所有片级以上的语法级别。

VCL 数据即编码处理的输出,它表示被压缩编码后的视频数据序列。在 VCL 数据传输或存储之前,这些编码的 VCL 数据,先被映射或封装进 NAL 单元(以下简称 NALU,Nal Unit) 中。
每个 NALU 包括一个原始字节序列负荷(RBSP, Raw Byte Sequence Payload)、一组 对应于视频编码的 NALU 头部信息。
RBSP 的基本结构是:在原始编码数据的后面填加了结尾 比特。一个 bit“1”若干比特“0”,以便字节对齐。
在这里插入图片描述
NAL 单元排列
上图中的 NALU头 + RBSP 就相当与一个 NALU (Nal Unit), 每个单元都按独立的 NALU 传送。 其实说白了,H.264 中的结构全部都是以 NALU 为主的,理解了 NALU,就理解 H.264 的结构了。
还有另外一种结果startcode + NALU头 + RBSP(我也是刚接触,也不知道究竟是哪个)三部分组成,其中startcode用于标识这是一个NALU单元的开始,必须是“00 00 00 01”或“00 00 01”。

6.3.1 NALU头解析

每个NAL单元是一个一定语法元素的可变长字节字符串,包括包含一个字节的头信息(用来表示数据类型),以及若⼲整数字节的负荷数据。

NALU头信息(一个字节):
在这里插入图片描述
forbidden_bit(F):禁止位,在h264规范中这一位必须为0.
nal_reference_bit(R):当前NAL的优先级,值越大,该NAL越重要。如果00的话NALU解码器可以丢弃它而不影响录像的回放,比如SEI帧。
在这里插入图片描述
nal_unit_type (T):NAL类型,112由h264使用,2431由h264以为的应用使用,详情参见下表:
在这里插入图片描述
5是IDR帧 6是SEI 7是SPS 8是PPS

H264标准指出,当数据流是存储在介质上时,在每个NALU前添加起始码:0x000001或0x00000001,用来指示一个NALU的起始和终止位置:

  • 在这样的机制下,在码流中检测起始码,作为一个NALU得起始标识,当检测到下一个起始码时,当前NALU结束。

  • 3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个slice(片)的时候,包含这些slice的NALU使用3字节起始码。其余场合都是4字节0x00000001的。

(上面遗留的问题就找到答案了,是存储在介质中,才需要一个NALU一个开始,只有这样隔离了才能知道一个NALU和一个NALU,如果是网络传输的话,就不要,这个问题后面分析rtmp的时候再看看)

6.3.2 NALU流解析

上面讲解了NALU的基本信息,还有NALU的头信息,发现这个头信息有好多种类型,所以这里就先对h264码流做一个讲解,我们之前了解了I帧,P帧,B帧,并且了解到这些编码后的VCL数据会封装到NALU中,然后再上一节又了解了到SPS,PPS,这些信息是怎么串联起来的呢?我之前就是一直对这个模糊不理解,所以一直对h264结构比较迷糊,现在看到丹老师的一个图片之后,瞬间醍醐灌顶,分享一下给大家看看:
在这里插入图片描述
多么有意义的一幅图,写明了很多东西,还是介绍一下图里面的各种帧
SPS:序列参数集,SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。
PPS:图像参数集,对应的是一个序列中某一幅图像或者某几幅图像的参数。
I帧:帧内编码帧,可独立解码生成完整的图片
P帧:前向预测编码帧,需要参考前面一个I后者B来生成一张完整的图片。
B帧:双向预测内插编码帧,则要参考器前一个I或者P帧及其后面一个P帧来生成一张完整图片。

发I帧之前,至少要发一次SPS和PPS
现在是不是明星的感觉到了h264码流是一个接一个的NALU组成的,把其中有效的信息都封装进NALU中,这样就组成了一个码流。这里是不是有人要问,为什么I帧和P帧会有两个NALU,这个后面会介绍,这里就先留一个疑问,有疑问才有激情学习。

6.3.3 GOP

都讲到码流部分了,就不得不提一样GOP的概念:
在H264中图像以序列为单位进行组织(GOP),一个序列是一段图像编码后的数据流,以I帧开始,到下一个I帧结束。(上一节的图可以看成是一个GOP,一个GOP只有一个I帧)
一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。
一个序列就是一段内容差异不太大的图像编码后生成的一串数据流。当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个I帧,然后一直P帧、B帧了。当运动变化多时,可能一个序列就比较短了,比如就包含一个I帧和3、4个P帧。

GOP说白了就是两个I帧之间的间隔.比较说GOP为120,如果是720p60的话,那就是2s一次I帧.
在视频编码序列中,GOP即Group of picture(图像组),指两个I帧之间的距离,Reference(参考周期)指两个P帧之间的距离(如下图3.1)。一个I帧所占用的字节数大于一个P帧,一个P帧所占用的字节数大于一个B帧(如下图3.1所示)。
在这里插入图片描述
所以在码率不变的前提下,GOP值越大,P、B帧的数量会越多,画面细节更多,也就更容易获取较好的图像质量;Reference越大,B帧的数量越多,同理也更容易获得较好的图像质量。

需要说明的是,通过提高GOP值来提高图像质量是有限度的,在遇到场景切换的情况时,H.264编码器会自动强制插入一个I帧,此时实际的GOP值被缩短了。另一方面,在一个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量比较差时,会影响到一个GOP中后续P、B帧的图像质量,直到下一个GOP开始才有可能得以恢复,所以GOP值也不宜设置过大。
同时,由于P、B帧的复杂度大于I帧,所以过多的P、B帧会影响编码效率,使编码效率降低。另外,过长的GOP还会影响Seek操作(找I帧)的响应速度,由于P、B帧是由前面的I或P帧预测得到的,所以Seek操作需要直接定位,解码某一个P或B帧时,需要先解码得到本GOP内的I帧及之前的N个预测帧才可以,GOP值越长,需要解码的预测帧就越多,seek响应的时间也越长。

从宏观的角度看,我们的视频流被h264分成好多个组(GOP),每一个组中,包含了一个SPS,PPS,I帧,P帧,B帧。然后NALU数据就是封装上面各种帧的,所以接下来分析的是NALU的RBSP部分,看看怎么把不同类型的帧封装到NALU中的。

6.3.4 NALU的RBSP

上面两小节讲了其他部分,不过也需要讲一讲其他部分,毕竟上面的部分是从比较大的视角看h264码流结构,接下来就从一个NALU分析,

究竟 NALU 是怎么由一帧图片变化而来的呀,H.264究竟为什么这么神奇?

一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice),而装载着这些片(slice)的载体,就是 NALU 了,我们可以来看看 NALU 跟片的关系(slice)。
在这里插入图片描述
图片编码后

在这里插入图片描述
NALU 结构

小伙伴们要明白,片(slice)的概念不同与帧(frame),帧(frame)是用作描述一张图片的,一帧(frame)对应一张图片,而片(slice),是 H.264 中提出的新概念,是通过编码图片后切分通过高效的方式整合出来的概念,一张图片至少有一个或多个片(slice)。
上图中可以看出,片(slice)都是又 NALU 装载并进行网络传输的,但是这并不代表 NALU 内就一定是切片,这是充分不必要条件,因为 NALU 还有可能装载着其他用作描述视频的信息。

看到这里,是不是就瞬间明白了,为什么上面的I帧包含了两个NALU,就是因为切片了,是不是还记得,一个帧之后,如果切片的NALU数据是通过0x000001来分割的,我们就拿二进制数据找一找
既然都要找了,就把之前的SPS和PPS的数据也找一下,其实雷神自己写了一个软件就是分析h264码流的,在这里我们也能清楚的看到h264码流的编码情况,不过还是为了过瘾,我们自己去找一下二进制的编码,看看是不是真的就是这样
在这里插入图片描述

SEI:
在这里插入图片描述
这是SEI帧:nal_unit_type 为6,nal_ref_idc 为0,是可以丢弃的

SPS、PPS?IDR
在这里插入图片描述
这幅图都找齐了,0x67,nal_unit_type 为7 就是SPS帧,0x68,nal_unit_type 为8 就是PPS帧,0x65,nal_unit_type 为5 就是I帧,算了算了,不找那种一帧数据分为两个NALU的数据了,太多二进制数了,不好找。

6.3.5 NALU的片

片的主要作用是用作宏块(Macroblock)的载体(ps:下面会介绍到宏块的概念)。片之所以被创造出来,主要目的是为限制误码的扩散和传输。
如何限制误码的扩散和传输?
每个片(slice)都应该是互相独立被传输的,某片的预测(片(slice)内预测和片(slice)间预测)不能以其它片中的宏块(Macroblock)为参考图像。

那么片(slice)的具体结构,我们用一张图来直观说明吧:
在这里插入图片描述
我们可以理解为一 张/帧 图片可以包含一个或多个分片(Slice),而每一个分片(Slice)包含整数个宏块(Macroblock),即每片(slice)至少一个 宏块(Macroblock),最多时每片包 整个图像的宏块。

上图结构中,我们不难看出,每个分片也包含着头和数据两部分:
1、分片头中包含着分片类型、分片中的宏块类型、分片帧的数量、分片属于那个图像以及对应的帧的设置和参数等信息。
2、分片数据中则是宏块,这里就是我们要找的存储像素数据的地方。

这个细节就不用找了,再找就找不下去了,知道就好。

6.3.5 NALU的宏块

宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。
组成部分:一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个 8×8 Cr 彩色像素块组成。每个图象中,若干宏块被排列成片的形式。

我们先来看看宏块的结构图:
在这里插入图片描述
从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。

讲了这里是不是又看了熟悉东西,QP值,还有YUV的数据,我们获取的视频流本来就是YUV数据,然后经过h264层层加工,才形成我们最后的h264编码

切片(slice)类型跟宏块类型的关系:
对于切片(slice)来讲,分为以下几种类型:
I片:只包 I宏块,I 宏块利用从当前片中已解码的像素作为参考进行帧内预测(不能取其它片中的已解码像素作为参考进行帧内预测)。

P片:可包 P和I宏块,P 宏块利用前面已编码图象作为参考图象进行帧内预测,一个帧内编码的宏块可进一步作宏块的分割:即 16×16、16×8、8×16 或 8×8 亮度像素块(以及附带的彩色像素);如果选了 8×8 的子宏块,则可再分成各种子宏块的分割,其尺寸为 8×8、8×4、4×8 或 4×4 亮度像素块(以及附带的彩色像素)。

B片:可包 B和I宏块,B 宏块则利用双向的参考图象(当前和 来的已编码图象帧)进行帧内预测。

SP片(切换P):用于不同编码流之间的切换,包含 P 和/或 I 宏块

SI片:扩展档次中必须具有的切换,它包 了一种特殊类型的编码宏块,叫做 SI 宏块,SI 也是扩展档次中的必备功能。

6.4 整体总结

通过剖析了这么多个小零件,是时候个大家一个世界地图了,
那么我们的 NALU 整体结构可以呼之欲出了,以下就引用 H.264 文档当中的一幅图了
在这里插入图片描述
其实 H.264 的码流结构并没有大家想的那么复杂,编码后视频的每一组图像(GOP,图像组)都给予了传输中的序列(PPS)和本身这个帧的图像参数(SPS),所以,我们的整体结构,应该如此:
在这里插入图片描述

这一篇文章总体都是借鉴了网上各个大神的总结,再由我自己整理总结,把这些知识消化成自己的知识,总结了这一篇文章之后,终于对h264的结构有总体的认识了,所以这篇文章就写成转载把,参考的原文链接在开头,想看的可以去看看。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值