目录
H264概念
压缩处理
I、B、P帧简介
I帧简介
P帧简介
B帧简介
SPS 和 PPS简介
压缩方式说明
划分宏块
划分子块
运动估计与补偿
对残差数据做DCT
编码处理
H264视频压缩算法现在无疑是所有视频压缩技术中使用最广泛,最流行的。随着 x264/openh264以及ffmpeg等开源库的推出,大多数使用者无需再对H264的细节做过多的研究,这大降低了人们使用H264的成本。
H264结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的yuv数据组成。宏块作为H264编码的基本单位。
在H264协议内定义了三种帧,分别是I帧、B帧与P帧。I帧就是之前所说的一个完整的图像帧,而B、帧与P帧所对应的就是之前说的不编码全部图像的帧。P帧与B帧的差别就是P帧是参考之前的I帧而生成的,而B帧是参考前后图像帧编码生成的。
经过压缩后的帧分为:I帧,P帧和B帧:
I帧:关键帧,采用帧内压缩技术。你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)
P帧:向前参考帧,在压缩时,只参考前面已经处理的帧。采用帧间压缩技术。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)
B帧:双向参考帧,在压缩时,它既参考前而的帧,又参考它后面的帧。采用帧间压缩技术。B帧记录的是本帧与前后帧的差别(具体比较复杂,有4种情况),换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累~。
还有一个概念是,IDR帧:
一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。IDR 图像一定是 I 图像,但I图像不一定是 IDR 图像。一个序列中可以有很多的I图像,I 图像之后的图像可以引用 I 图像之间的图像做运动参考。
还需要补充一个概念(序列)
一个序列就是一段内容差异不太大的图像编码后生成的一串数据流。当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个 I 帧,然后一直 P 帧、B 帧了。当运动变化多时,可能一个序列就比较短了,比如就包含一个 I 帧和 3、4个P帧。将视频分割为若干小的图像序列成为图像组(group of pictures ,GOP)。其中GOP可以分为封闭GOP以及开放GOP。在封闭非GOP中,每个GOP以IDR图像开始各个Gop为独立编解码;而在开放的GOP中,第一个GOP中的第一个图像为IDR图像,后续GOP中的第一个帧内编码图像为non-IDR图像,使用前一个GOP中已经编码的图像进行参考。
PICTURE:/home/zhao/.config/tencent-qq//AppData/file//sendpix0.jpg
GOP是画面组,一个GOP是一组连续的画面。GOP一般有两个数字,如M=3,N=12.M制定I帧与P帧之间的距离,N指定两个I帧之间的距离。那么现在的GOP结构是
I BBP BBP BBP BB I
I 帧、B帧、P帧还有一些特点,如下:
I帧特点:
P帧特点:
B帧特点:
注:I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I帧的压缩率是7(跟JPG差不多),P帧是20,B帧可以达到50。可见使用B帧能节省大量空间,节省出来的空间可以用来保存多一些I帧,这样在相同码率下,可以提供更好的画质。
SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。存放包括:帧数、参考帧数目、解码图像尺寸、帧场编码模式选择标识等。所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。但在某些特殊情况下,在码流中间也可能出现这两种结构,主要原因可能为:
解码器需要在码流中间开始解码;编码器在编码的过程中改变了码流的参数(如图像分辨率等);H.264中另一重要的参数集合为图像参数集Picture Paramater Set(PPS)。和图像相关的参数集,存放包括:熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等。通常情况下,PPS类似于SPS,在H.264的裸码流中单独保存在一个NAL Unit中,只是PPS NAL Unit的nal_unit_type值为8;而在封装格式中,PPS通常与SPS一起,保存在视频文件的文件头中。
在一组帧之前,首先要收到SPS 和 PPS ,不然的话是无法解码的。这两组数据划分为I帧,是不能丢的。
视频传输中会出现连个比较常见的现象,花屏 和 卡顿
(1)如果在GOP分组中的P帧丢失,会造成解码端的图像发生错误。这就是花屏。GOP一组帧呈现出的连贯效果,由于P帧丢失,它需要更新的部分就没有,所以无法正常呈现。故出现花屏现象。
(2)为了解决花屏的问题发生,我们可以将丢失 P帧 或是 I帧 的 GOP 丢掉(包含其中的所有帧),直到下一个I帧再重新刷新图像。但是由于这一帧丢掉了,所以会出现卡顿。
H264默认是使用 16X16 大小的区域作为一个宏块,也可以划分成 8X8 大小。
划分好宏块后,计算宏块的象素值。
以此类推,计算一幅图像中每个宏块的像素值,所有宏块都处理完后如下面的样子。
H264对比较平滑的图像使用 16X16 大小的宏块。但为了更高的压缩率,还可以在 16X16 的宏块上更划分出更小的子块。子块的大小可以是 8X16、 16X8、 8X8、 4X8、 8X4、 4X4非常的灵活。
上幅图中,红框内的 16X16 宏块中大部分是蓝色背景,而三只鹰的部分图像被划在了该宏块内,为了更好的处理三只鹰的部分图像,H264就在 16X16 的宏块内又划分出了多个子块。
这样再经过帧内压缩,可以得到更高效的数据。下图是分别使用mpeg-2和H264对上面宏块进行压缩后的结果。其中左半部分为MPEG-2子块划分后压缩的结果,右半部分为H264的子块划压缩后的结果,可以看出H264的划分方法更具优势。
对于视频数据主要有两类数据冗余,一类是时间上的数据冗余,另一类是空间上的数据冗余。其中时间上的数据冗余是最大的。下面我们就先来说说视频数据时间上的冗余问题。假设摄像头每秒抓取30帧,这30帧的数据大部分情况下都是相关联的。也有可能不止30帧的的数据,可能几十帧,上百帧的数据都是关联特别密切的。
对于这些关联特别密切的帧,其实我们只需要保存一帧的数据,其它帧都可以通过这一帧再按某种规则预测出来,所以说视频数据在时间上的冗余是最多的。
为了达到相关帧通过预测的方法来压缩数据,就需要将视频帧进行分组。那么如何判定某些帧关系密切,可以划为一组呢?我们来看一下例子,下面是捕获的一组运动的台球的视频帧,台球从右上角滚到了左下角。
H264编码器会按顺序,每次取出两幅相邻的帧进行宏块比较,计算两帧的相似度。如下图:
通过宏块扫描与宏块搜索可以发现这两个帧的关联度是非常高的。进而发现这一组帧的关联度都是非常高的。因此,上面这几帧就可以划分为一组。其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。
在这样一组帧中,经过编码后,我们只保留第一帧的完整数据,其它帧都通过参考上一帧计算出来。我们称第一帧为IDR/I帧,其它帧我们称为P/B帧,这样编码后的数据帧组我们称为GOP。
在H264编码器中将帧分组后,就要计算帧组内物体的运动矢量了。还以上面运动的台球视频帧为例,我们来看一下它是如何计算运动矢量的。
H264编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了。下面这幅图就是搜索后的台球移动的位置。
运动矢量计算出来后,将相同部分(也就是绿色部分)减去,就得到了补偿数据。我们最终只需要将补偿数据进行压缩保存,以后在解码时就可以恢复原图了。压缩补偿后的数据只需要记录很少的一点数据。如下所示:
我们把运动矢量与补偿称为帧间压缩技术,它解决的是视频帧在时间上的数据冗余。除了帧间压缩,帧内也要进行数据压缩,帧内数据压缩解决的是空间上的数据冗余。下面我们就来介绍一下帧内压缩技术。
人眼对图象都有一个识别度,对低频的亮度很敏感,对高频的亮度不太敏感。所以基于一些研究,可以将一幅图像中人眼不敏感的数据去除掉。这样就提出了帧内预测技术。
H264的帧内压缩与JPEG很相似。一幅图像被划分好宏块后,对每个宏块可以进行 9 种模式的预测。找出与原图最接近的一种预测模式。
下面这幅图是对整幅图中的每个宏块进行预测的过程。
然后,将原始图像与帧内预测后的图像相减得残差值。再将我们之前得到的预测模式信息一起保存起来,这样我们就可以在解码时恢复原图了。效果如下:
经过帧内与帧间的压缩后,虽然数据有大幅减少,但还有优化的空间。
可以将残差数据做整数离散余弦变换,去掉数据的相关性,进一步压缩数据。如下图所示,左侧为原数据的宏块,右侧为计算出的残差数据的宏块。
将残差数据宏块数字化后如下图所示:
将残差数据宏块进行 DCT 转换。
去掉相关联的数据后,我们可以看出数据被进一步压缩了。
上面的帧内压缩是属于有损压缩技术。也就是说图像被压缩后,无法完全复原。而CABAC属于无损压缩技术。
无损压缩技术大家最熟悉的可能就是哈夫曼编码了,给高频的词一个短码,给低频词一个长码从而达到数据压缩的目的。MPEG-2中使用的VLC就是这种算法,我们以 A-Z 作为例子,A属于高频数据,Z属于低频数据。看看它是如何做的。
CABAC也是给高频数据短码,给低频数据长码。同时还会根据上下文相关性进行压缩,这种方式又比VLC高效很多。其效果如下
H264的主要目标是为了有高的视频压缩比和良好的网络亲和性,为了达成这两个目标,H264的解决方案是将系统框架分为两个层面,分别是视频编码层面(VCL)和网络抽象层面(NAL),如图;
H.264原始码流(裸流)是由一个接一个NALU组成,它的功能分为两层,VCL(视频编码层)和 NAL(网络抽象层).
因为H264最终还是要在网络上进行传输,在传输的时候,网络包的最大传输单元是1500字节,一个H264的帧往往是大于1500字节的,所以需要将一个帧拆成多个包进行传输。这些拆包、组包等工作都在NAL层去处理。
在VCL进行数据传输或存储之前,这些编码的VCL数据,被映射或封装进NAL单元(NALU),H264码流是由一个个的NAL单元组成,其中SPS、PPS、IDR和SLICE是NAL单元某一类型的数据。
一个NALU = 一组对应于视频编码的NALU头部信息 + 一个原始字节序列负荷(RBSP,Raw Byte Sequence Payload).
如图所示,下图中的NALU的头 + RBSP 就相当于一个NALU(Nal Unit),每个单元都按独立的NALU传送。H.264的结构全部都是以NALU为主,理解了NALU,就理解了H.264的结构。
一个原始的H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成,其中 Start Code 用于标示这是一个NALU 单元的开始,必须是”00 00 00 01” 或”00 00 01”
3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个slice的时候,包含这些slice的nalu使用3字节起始码。其余场合都是4字节的。
NAL单元的头部是由forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)三个部分组成的。
NAL单元的头部是由forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)三个部分组成的。
1、F(forbiden):禁止位,占用NAL头的第一个位,当禁止位值为1时表示语法错误;
2、NRI:参考级别,占用NAL头的第二到第三个位;值越大,该NAL越重要。
3、Type:Nal单元数据类型,也就是标识该NAL单元的数据类型是哪种,占用NAL头的第四到第8个位;
00 00 00 01 06: SEI信息
00 00 00 01 67: 0x67&0x1f = 0x07 :SPS
00 00 00 01 68: 0x68&0x1f = 0x08 :PPS
00 00 00 01 65: 0x65&0x1f = 0x05: IDR Slice
在具体介绍NAL数据类型前,有必要知道NAL分为VCL和非VCL的NAL单元。其中SPS、SEI、PPS等非VCL的NAL参数对解码和显示视频都是很有用的。
而另外一个需要了解的概念就是参数集(Parameter sets),参数集是携带解码参数的NAL单元,参数集对于正确解码是非常重要的,在一个有损耗的传输场景中,传输过程中比特列或包可能丢失或损坏,在这种网络环境下,参数集可以通过高质量的服务来发送,比如向前纠错机制或优先级机制。Parameter sets与其之外的句法元素之间的关系如图9所示:
1)、 头信息块,包括宏块类型,量化参数,运动矢量。这些信息是最重要的,因为离开他们,被的数据块种的码元都无法使用。该数据分块称为A类数据分块。
2)、 帧内编码信息数据块,称为B类数据分块。它包含帧内编码宏块类型,帧内编码系数。对应的slice来说,B类数据分块的可用性依赖于A类数据分块。和帧间编码信息数据块不通的是,帧内编码信息能防止进一步的偏差,因此比帧间编码信息更重要。
3)、 帧间编码信息数据块,称为C类数据分块。它包含帧间编码宏块类型,帧间编码系数。它通常是slice种最大的一部分。帧间编码信息数据块是不重要的一部分。它所包含的信息并不提供编解码器之间的同步。C类数据分块的可用性也依赖于A类数据分块,但于B类数据分块无关。
以上三种数据块每种分割被单独的存放在一个NAL单元中,因此可以被单独传输。
SODB与RBSP
SODB 数据比特串 -> 是编码后的原始数据。RBSP 原始字节序列载荷 -> 在原始编码数据的后面添加了 结尾比特。一个 bit“1”若干比特“0”,以便字节对齐。
(1)SODB ,String Of Data Bits 原始数据比特流
因为它是流的形式,所以长度不一定是8倍数,它是由 VLC 层产生的。由于我们计算机是以8倍数去处理数据所以计算机在处理H264时,就需要 RBSP。
(2)RBSP,SODB + tailing bits (原始字节序列载荷)
由于它是一个压缩流,SODB 不知道是在何处结束,所以算法在SODB最后一位补一个1,没有按字节对齐的则补 0。
(3)EBSP (扩展字节序列载荷)
在生成压缩流之后,在每一帧的开头加一个起始位,这个起始位一般是 00 00 00 01 或者是 00 00 01。所以在h264码流中规定每有两个连续的00 00,就增加一个0x03。
其实到这里可能就比较难理解了,为什么数据NAL单元中有这么多数据类型,这个SLICE又是什么东西,为什么不直接是编码后出来的原始字节序列载荷,所以我觉得在这里再讲述帧所细分的一些片和宏的概念应该是比较合适的,也是能够参照上下文更能理解这些概念的位置,又能给这些困惑做一个合理一点的解释,所以在此做一个描述:
1帧(一幅图像) = 1~N个片(slice) //也可以说1到多个片为一个片组
1个片 = 1~N个宏块(Marcroblock)
1个宏块 = 16X16的YUV数据(原始视频采集数据)
从数据层次角度来说,一幅原始的图片可以算作广义上的一帧,帧包含片组和片,片组由片来组成,片由宏块来组成,每个宏块可以是4*4、8*8、16*16像素规模的大小,它们之间的联系如图10所示。每个片都是一个独立的编码单位。
NAL单元除了容纳Slice编码的码流外,还可以容纳其他数据这也就是为什么有SPS、PPS等这些数据出现的原因,并且这些数据在传输H264码流的过程中起到不可或缺的作用,具体作用上面也是有讲到的。
序列>图像>片>宏>像素
同时有几点需要说明一下,这样能便于理解NAL单元:
(1)、如果不采用 FMO(灵活宏块排序) 机制,则一幅图像只有一个片组;
(2)、如果不使用多个片,则一个片组只有一个片;
(3)、如果不采用 DP(数据分割)机制,则一个片就是一个 NALU,一个 NALU 也就是一个片。
否则,一个片的组成需要由 三个 NALU 组成,也就是上面说到的A、B、C类数据块。
如我们所见,每个分片也包含着头和数据两部分,分片头中包含着分片类型、分片中的宏块类型、分片帧的数量以及对应的帧的设置和参数等信息,而分片数据中则是宏块,这里就是我们要找的存储像素数据的地方;宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。宏块数据的组成如下图所示:
从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。
至此,我们对 H.264 的码流数据结构应该有了一个大致的了解。
Slice(片):
如图所示,NALU的主体中包含了Slice(片).
一个片 = Slice Header + Slice Data
片是H.264提出的新概念,通过编码图片后切分通过高效的方式整合出来的概念。一张图片有一个或者多个片,而片由NALU装载并进行网络传输的。但是NALU不一定是切片,这是充分不必要条件,因为 NALU 还有可能装载着其他用作描述视频的信息.
设置片的目的是为了限制误码的扩散和传输,应使编码片相互间是独立的。某片的预测不能以其他片中的宏块为参考图像,这样某一片中的预测误差才不会传播到其他片中。
可以看到上图中,每个图像中,若干宏块(Macroblock)被排列成片。一个视频图像可编程一个或更多个片,每片包含整数个宏块 (MB),每片至少包含一个宏块。
片有一下五种类型:
片 |
意义 |
I 片 |
只包含I宏块 |
P 片 |
包含P和I宏块 |
B 片 |
包含B和I宏块 |
SP 片 |
包含P 和/或 I宏块,用于不同码流之间的切换 |
SI 片 |
一种特殊类型的编码宏块 |
宏块(Macroblock):宏块是视频信息的主要承载者。一个编码图像通常划分为多个宏块组成.包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中像素阵列。
一个宏块 = 一个16*16的亮度像素 + 一个8×8Cb + 一个8×8Cr彩色像素块组成。(YCbCr 是属于 YUV 家族的一员,在YCbCr 中 Y 是指亮度分量,Cb 指蓝色色度分量,而 Cr 指红色色度分量)
宏块分类
句法元素的分层结构有助于更有效地节省码流。例如,再一个图像中,经常会在各个片之间有相同的数据,如果每个片都同时携带这些数据,势必会造成码流的浪费。更为有效的做法是将该图像的公共信息抽取出来,形成图像一级的句法元素,而在片级只携带该片自身独有的句法元素。
宏块分类
意义
mb_type:确定该 MB 是帧内或帧间(P 或 B)编码模式,确定该 MB 分割的尺寸
mb_pred:确定帧内预测模式(帧内宏块)确定表 0 或表 1 参考图 像,和每一宏块分割的差分编码的运动矢量(帧间宏块,除 8×8 宏块分割的帧内 MB)
sub_mb_pred:(只对 8×8MB 分割的帧内 MB)确定每一子宏块的子宏 块分割,每一宏块分割的表 0 和/或表 1 的参考图象;每一 宏块子分割的差分编码运动矢量。
coded_block_pattern:指出哪个 8×8 块(亮度和彩色)包 编码变换系数
mb_qp_delta:量化参数的改变值
residual:预测后对应于残差图象取样的编码变换系数