相关文献:
iOS 音视频(一) - 基础知识
iOS 音视频(二) - 视频编码-H264概念与原理
iOS 音视频(三) - 视频编码-实现H264编解码
iOS 音视频(四) - 音频AAC编解码
文章结掌握内容:
1.H264基础知识
2.H264编码原理
3.宏块划分 与 帧分组
4.帧间压缩原理
5.帧内压缩原理
6.VLC压缩/CABAC压缩
7.H264结构与编码分层
8.H264码流
一、H264基础知识
1.了解IPB帧
I帧
: 关键帧,采用帧内压缩
技术.
举个例子,如果摄像头对着你拍摄,1秒之内,实际你发生的变化是非常少的.1秒钟之内实际少很少有大幅度的变化.摄像机一般一秒钟会抓取几十帧的数据.比如像动画,就是25帧/s,一般视频文件都是在30帧/s左右.对于一些要求比较高的,对动作的精细度有要求,想要捕捉到完整的动作的,高级的摄像机一般是60帧/s.那些对于一组帧的它的变化很小.为了便于压缩数据,那怎么办了?将第一帧完整的保存下来.如果没有这个关键帧后面解码数据,是完成不了的.所以I帧特别关键.
P帧
: 向前参考帧.压缩时只参考前一个帧,属于帧间压缩
技术.
视频的第一帧会被作为关键帧完整保存下来.而后面的帧会向前依赖.也就是第二帧依赖于第一个帧.后面所有的帧只存储于前一帧的差异.这样就能将数据大大的减少.从而达到一个高压缩率的效果.
-
B帧
: 双向参考帧,压缩时即参考前一帧也参考后一帧,属于帧间压缩
技术.
B帧,即参考前一帧,也参考后一帧.这样就使得它的压缩率更高.存储的数据量更小.如果B帧的数量越多,你的压缩率就越高.这是B帧的优点,但是B帧最大的缺点是,如果是实时互动的直播,那时与B帧就要参考后面的帧才能解码,那在网络中就要等待后面的帧传输过来.这就与网络有关了.如果网络状态很好的话,解码会比较快,如果网络不好时解码会稍微慢一些.丢包时还需要重传.对实时互动的直播,一般不会使用B帧.- 如果在
泛娱乐
直播中,可以接受一定度的延时,需要比较高的压缩比就可以使用B帧. - 如果在
实时互动
直播,我们需要提高时效性,这时就不能使用B帧了.
- 如果在
2.GOF(Group of Frame)一组帧
GOF和GOP(Group of Picture)是一个东西。
如果在一秒钟内,有30帧.这30帧可以画成一组.如果摄像机或者镜头它一分钟之内它都没有发生大的变化.那也可以把这一分钟内所有的帧画做一组.
什么叫一组帧?
就是一个I帧
到下一个I帧
的这一组的数据,包括B帧/P帧,被称为GOF
.
3.SPS/PPS
在直播的时候对一段一段的视频帧数据进行传输是没有办法做到解码的,需要SPS/PPS这样的解码参数来进行解码。
SPS/PPS实际上就是存储GOP的参数.
SPS
: (Sequence Parameter Set,序列参数集)存放帧数,参考帧数目,解码图像尺寸,帧场编码模式选择标识等.
- SPS是一组帧的参数集.
PPS
:(Picture Parameter Set,图像参数集).存放熵编码模式选择标识,片组数目,初始量化参数和去方块滤波系数调整标识等.(与图像相关的信息)
只要记住,在一组帧之前我们首先收到的是SPS/PPS数据。如果没有这组参数的话,我们是无法解码。
4.视频花屏/卡顿的原因
我们在观看视频时,会遇到花屏或者卡顿现象.那这个GOF就息息相关了.
- 如果GOP分组中的P帧丢失就会造成解码端的图像发生错误.
- 为了避免花屏问题的发生,一般如果发现P帧或者I帧丢失.就不显示本GOP内的所有帧.只到下一个I帧来后重新刷新图像.
- 当这时因为没有刷新屏幕.丢包的这一组帧全部扔掉了.图像就会卡在哪里不动.这就是卡顿的原因.
总结:花屏是因为你丢了P帧或者I帧.导致解码错误. 而卡顿是因为为了怕花屏,将整组错误的GOP数据扔掉了.直达下一组正确的GOP再重新刷屏.而这中间的时间差,就是我们所感受的卡顿.
二、H264编码原理
H264编码实际上就是对视频的冗余数据进行压缩。
-
帧内预测压缩
:解决的是空域数据冗余问题。
什么是空域数据?就是这幅图里数据在宽高空间内包含了很多颜色/光亮,人的肉眼很难察觉的数据,我们可以认作冗余,直接压缩掉。 -
帧间预测压缩
:解决的是时域数据冗余问题。
摄像头在一段时间内所捕捉的数据没有较大的变化,针对这一时间内的相同的数据压缩掉。这叫时域数据压缩。 -
整数离散余弦变换(DCT)
:将空间上的相关性变为频域上无关的数据然后进行量化。
傅里叶变换可以把一个复杂波形图变换成许多的正弦波。只是他们之间的频率不一样,以及振幅也不一样,如果它们在频率上没有一致性那么我们就可以对它进行压缩处理。 -
CABAC压缩
:是无损压缩。
三、宏块划分 与 帧分组
1.宏块划分
将一个图片(图1)中的左上角用宏块描述,就是宏块是8*8的元素,取出的颜色,像图2的去描述颜色。
将整张图片全部用宏块描述就如下图3:
那么基本的图片的宏块划分就完成了。
那是不是每个宏块都是8*8了?并不是的,还有子块划分。在这个大的宏块里,可以再细化:
比如我们中间这个全部都是蓝色的这个宏块,就可以用一个色块,更加简单描述就行了。没必要全部蓝色宏块都去描述。
子宏块对比旁边的MPEG 2
和H.264
:MPEG2存储时还说比较完整,占用的空间相对于比较多;而H.264还是减少了很多空间,像重复的颜色他们就用非常简单的色块描述了。
2.帧分组
假设台球桌举例:一个台球从一个位置移动到另外一个组。可以发现它的桌面背景是一样的,只是球体位置发生了变换,这个就可以把这一组帧划分为一组。
四、帧间压缩原理
帧间压缩是针对于P/B帧的,因为它解决的是时间的数据冗余。
1.组内宏块查找
如下图,台球从一角滚到另外一角,相邻的两幅图做组内的宏块查找。
将图逐行扫描,扫描到第三行发现了台球,然后围绕它的周围查找.发现了有类似的图块,把这些的图块放在同一张图片中。
2.运动估算
查找到有类似的图块,就会把这些的图块放在同一张图片中,如图4。
就是说台球刚开始从位置1到移动到位置2,这之间有一个运动矢量(矢量会包含运动的方向和距离)。
将所有的图都两两比较.最后就形成了图5。就是图5中红色部分。
每一个红色的箭头标注都是一个运动矢量.很多帧就会形成一个连续的运动估算.
3.运动矢量与补偿压缩
最终将连续的运动估算换算成下图所表现的。
就是对齐进行压缩,所有帧的背景都是一样的。变换在哪里了?变换就是它的运动矢量还有台球的数据。
实际经过我们一运算后,它留下的就只是运动矢量数据+残差值的数据。经过这样的一个计算,帧间压缩数据我们就可以看到实际我们只需要存储一点点数据,而不像以前要将几十帧的所有图片数据保存下来,这就达到了压缩的效果。
整个过程被称之为帧间压缩技术的原理
。
- 重点:
帧间压缩
是解决的时间数据冗余
,将大量在时间轨迹上相同的数据压缩掉,只留下运算估量和残差值。
五、帧内压缩原理
帧内压缩是针对于I帧的.因为它解决的是空间的数据冗余。
首先将图片划分为宏块,对多个宏块进行颜色的排序,针对排序结果去选择宏块去采用不同的模式运算。
当每个宏块都选定了模式之后,就形成了下图的这样的色块分布效果(预测图)。
每个宏块都选择一个帧内预测的模式,帧内预测一共有9种模式。
帧内预测9种模式原理介绍(1)
帧内预测9种模式原理介绍(2)
让每个宏块挑选好模式之后,我们就可以使用块预测模式,预测完了之后,它就得到一个张"预测图"。
预测图&原图计算的预测图和原图是有差别的,这时就做两张图的差计算。
通过预测与原图的差得出一个结果,这个灰色图就是残差值。
预测模式与残差值压缩:
拿到参差值之后,我们就进行压缩,压缩时保存 残差数据和每个宏块选择的模式信息数据。
那么有了这2个数据之后,当我们解码时,首先通过宏块的模式信息计算出预测图,然后将预测图与我们的残差值进行累积,就能还原成原图像。
那这个过程就是"帧内压缩原理"
。
六、DCT压缩
DCT压缩也是整数余弦压缩技术。
将图片划分成可量化的宏块 将量化的宏块根据DCT数学方法进行压缩 达到数据量减少作用的最终结果六、VLC压缩(无损压缩)
VLC类似哈夫曼码.,用短码来记录高频数据.用长码记录低频数据;频率高的就编为短码,频率低的就编为长码。
VLC用短码记录高频数据 长码记录低频数据VLC实际上是MPEG2使用的技术。
CABAC压缩(上下文适应无损压缩技术)
H264使用的是CABAC。
CABAC除了使用哈夫曼短码高频,长码高频的方式;还加上了上下文适应的技术,根据上下文就可以加大压缩比。
对比VLC / CABAC
- VLC压缩的数据都是大块,无损压缩。
- CABAC压缩会随着压缩的数据增大,上下文信息全面,压缩比随之增大,数据块从大块降低成小数据块。
七、结构与编码分层
1.H264结构
在H264结构中,一个视频图像编码后的数据叫做一帧;
一帧由一个片 (slice) 或多个片组成;一个片由一个或多个宏块(MB)组成;一个宏块由16x16的yuv数据组成。宏块作为H264编码的基本单位
。
H264视频压缩后会成为一个序列帧;帧里包含图像;图像分为很多片;每个片可以分为宏块;每个宏块由许多子块组成。
-
场和帧
:视频的一场或一帧可用来产生一个编码图像。在电视中,为减少大面积闪烁现象,把一帧分成两个隔行的场。 -
片
:每个图象中,若干宏块被排列成片的形式。片分为I片、B片、P片和其他一些片(I片只包含I宏块,P片可包含P和I宏块,而B片可包含B和I宏块)。- I宏块利用从当前片中已解码的像素作为参考进行帧内预测;
- P宏块利用前面已编码图象作为参考图象进行帧间预测;
- B宏块利用双向的参考图象(前一帧和后一帧)进行帧间预测;
片的目的是为了限制误码的扩散和传输,使编码片相互间是独立的。(某片的预测不能以其它片中的宏块为参考图像,这样一片的误差才不会传播到其它片中去)
-
宏块
:一个编码图像通常划分为若干宏块组成;一个宏块由一个16x16Y亮度像素和附加的一个 8x8Cb 和一个 8x8Cr 彩色像素块组成。
2.H264编码分层
-
NAL层:(Network Abstraction Layer,视频数据
网络抽象层
):
它的作用是H264只要在网络上传输,在传输的过程每个包以太网是1500宇节,而H264的帧往往会大于1500字节的,所以就要进行拆包,将一个帧拆成多个包进行传输。所有的拆包或者组包都是通过NAL层去处理的。 -
VCL层:(Video Coding Layer,视频数据
编码层
) :
它的作用就是对视频原始数据进行压缩。
八、H264码流
1.了解码流
左边的三帧视频帧是发送給编码器之前的数据,开发者必须将原始图像数据封装为CVPixelBuufer
的数据结构。
右侧是经过编码器后的H264码流
。需要注意的是这些码流是不可以单独使用的,因为解码器并不能对其直接解码。
需要将H264码流合并成H264文件:
码流的种类:
-
SODB 原始数据比特流
(String of Data Bits):
长度不一定是8的倍数,它是由VCL层产生的。因为非8的倍数所以处理比较麻烦。 -
RBSP
(Raw Byte Sequence Payload,SoDBttrailing bits):
算法是在SODB最后一位补1,不按字节对齐补0,如果补齐0,不知道在哪里结束所以补1,如果不够8位则按位补0。 -
EBSP
(Encapsulate Byte Sequence Payload):
就是生成压缩流之后,还要在每个帧之前加一个起始位
。起始位一般是十六进制的0001,但是在整个编码后的数据里,可能会出来连续的2个0x00,那这样就与起始位产生了冲突。
那怎么处理了?H264规范里说明如果处理2个连续的0x00,就额外增加一个0x03,这样就能预防压缩后的数据与起始位产生冲突。
解释EBSP:
每个NAL前有一个
起始码
0x00 00 01(或者0x00 00 00 01 ),解码器检测每个起始码,作为一个NAL的起始标识,当检测到下一个起始码时,当前NAL结束。
同时H.264规定,当检测到0x00 00 01时,也可以表征当前NAL的结束。那么NAL中数据出现0x000001或0x000000时怎么办?H.264引入了防止竞争机制,如果编码器检测到NAL数据存 在0x000001或0x000000时,编码器会在最后个字节前插入一个新的字节0x03,这样:0x000000 ->0x00000300
0x000001 ->0x00000301
0x000002 ->0x00000302
0x000003 ->0x00000303解码器检测到0x000003时,把03抛弃,恢复原始数据(脱壳操作)。解码器在解码时,首先逐个字节读取NAL的数据,统计NAL的长度,然后再开始解码。
-
NALU
(NAL Header(1B)+EBSP):
NALU就是在EBSP的基础上加1B的网络头。H264使用的码流单元。
2.了解NALU (NAL Unit 码流单元)
NAL Unit
= NALU Header
+ 一个切片RBSP
一个切片
= 切片Header
+切片数据
H264结构图提到过一个H264的帧是由多个切片构成的,因为一帧数据一次有可能传不完。
码流分层图- A Annex格式数据:起始码 + Nal Unit 数据
- NAL Unit: NALU头 + NALU数据
- NALU 主体:是由切片组成(切片包括切片头+切片数据)
- Slice数据: 宏块组成
- PCM类: 宏块类型+pcm数据,或者宏块类型+宏块模式+残差数据
- Residual: 残差块
提问:NAL编码层会把一帧图片拆分成很多片,那是怎么知道我当前的片到底是属于I帧、P帧、B帧?还是SPS?PPS?
通过NALU Header
(1bit)去获取nal_unit_type:
NALU Header
解析(1bit=8位):
- 第1位为禁位F
- 第2/3位为参考级别(nal_ref_idc/NRI): 00~11 值越大表示越重要,解码器解码不过来的时候,可以适当丢弃不重要00的NALU
- 第4-8位为NAL类型 (nal_unit_type/TYPE)
举例一个单一类型的NALU:
NALU Header数据是 十六进制 0x67
转二进制 0 11 00111
将4-8位转十进制:00111 -> 7
对应上图中的表,7则是序列参数级SPS。
3.NALU类型介绍:
- 单一类型:一个RTP包只包含一个NALU。就是说一个H264帧里只包含了一个片。例如P帧/B帧都是单一类型。
- 组合类型:一个RTP包含多个NALU。类型是24-27,像PPS.SPS一般都会放在一个包里,因为2个数据单元都非常小。
- 分片类型:一个NALU单元分成多个RTP包。类型是28-29。
单一类型:
第1位:F; 第2-3位:NRI; 第4-8位:TYPE。
分片类型:
•第1个字节:FU indicator,分片单元指示符;
•第2个字节:FU Header,分片单元头。有多个片,就由FU Header组合起来。
FU Header是分片类型的RTP包会有的。
- FU Header的用处:
• S:start bit,用于指明分片的开始。在网络传输时,一个个包,我们知道它是分片的包,但是如何判断它是开始的包还是末尾的包被?如果为1,分片的开始。
• E:end bit,用于指明分片的结束。
• R:未使用,设置为0。
• Type:指明分片NAL类型。网络传输完成后,还是需要将分片组合成NALU单元。这个NAL单元是关键帧还是非关键帧,是SPS还是PPS,就需要根据Type来判断。
思考:我们在传输过程中将一个帧切割成多个片,如果在传输过程顺序打乱了,或者丢失了其中某个片,我们怎么判断NALU单元传输完整了?
答:依据FU Header 的S/E位并借助于RTP包的包头,在RTP的包头包括了每个包的序列号,如果我收到的包,收到了S包,也收到了E包,中间的包的序号是连续的,那就说明我们所有的包是完整的;如果是不连续就是丢包了,如果没有丢包就可以组合起来。
网友评论