————————————————分割线———1 序言————————————————
最近从iot公司跳到了安防公司,由于之前没有接触过音视频相关对项目,所以在这边记录一下音视频相关的学习过程
那么什么是h264编码?作用是什么呢?
答:h264编码是MPEG-4标准定义的最新格式,也称AVC,主要作用是对视频数据进行压缩的编码
学习h264编码的具体细节之前,首先需要理解一些h264相关的一些概念,这些概念对后面的学习很有帮助
————————————————分割线———2 概念篇————————————————
1 h264帧的组成
一个帧 由 一个或者多个片 组成
一个片 由 一个或者多个宏快组成
一个宏块由16*16或者8*8的yuv数据组成(宏块还能划分出更小的子块(8*16 8*8 4*8 4*4等))
每个片都是一个独立的编码单位
宏块是h264编码的基本单位
2 H264帧类型
I帧:关键帧
一帧画面的完整保留,解码只需要本帧数据即可,不依赖任何的其它帧
特点
I帧是P和B帧参考帧,其质量直接影响整个GOP组后各帧的质量
采用帧内压缩技术
压缩率:%7-10
P帧:向前预测编码帧
P帧表示这一帧和之前的I或者P帧的差别,解码解码依赖前面的I frame或P frame,需要用之前缓存的画面叠加本帧定义的差别生成最终画面(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)
特点
1 P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差)
2 P帧也可以是其后P帧和B帧的参考帧
3 若P帧作为后续帧的参考帧,可能会造成解码错误的扩散
采用帧间压缩技术
压缩率:%20
B帧:双向预测内插编码帧
B帧记录的是本帧与前后帧的差别,解码依赖前最近的一个I frame或P frame 及其后最近的一个P frame,不仅需要取得之前的缓存画面,还得解码之后画面,通过前后画面与本帧数据的叠加取得最终画面
特点
1 B帧不可作为其他帧的参考帧
2 B帧传送的是它与前面I(或P)以及后面的P帧之间的预测误差及运动矢量
3 B帧压缩率高,但是解码时CPU会比较累
采用帧间压缩技术
压缩率:%50
3 pts和dts
3 图像序列GOP
GOP是一段时间内变化不大的图像集
GOP结构有两个数字比较重要----M和N
M指的是I帧和P帧之间的距离,N指的是两个I帧之间的距离
例如:M=3,N=12
GOP结构为:IBBPBBPBBPBBI。
4 IDR帧(立即刷新图像)
在一个GOP中,首个I帧和其他I帧不同,是IDR帧(大华的I帧=IDR帧,不会有除了IDR之外的I帧)
IDR帧肯定是I帧,但是I帧不一定是IDR帧
IDR帧的主要作用是立即刷新,使错误不至传播
I帧有被跨帧参考的风险,IDR不会,IDR之后的帧都不能参考IDR之前的帧
I帧不用参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之前的帧的。IDR就不允许这样
例如:
IDR1 P4 B2 B3 P7 B5 B6 I10 B8 B9 P13 B11 B12 P16 B14 B15
这里的B8可以跨过I10去参考P7
------------------------------------------------------------------------
IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12
这里的B9就只能参照IDR8和P11,不可以参考IDR8前面的帧
从随机存取的视频流中,播放器永远可以从一个IDR帧播放,因为在它之后没有任何帧引用之前的帧。
h264引入IDR帧的目的是什么?
为了解码的重同步
当解码器解码到 IDR图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。
5 h264压缩方式及其之间的差别
帧内预测压缩(空间压缩)
帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,所以可以独立的解码、显示
帧间压缩
相邻几帧的数据有很大的相关性,信息变化很小。
也即连续的视频其相邻帧之间具有冗余信息,根据这一特性,压缩相邻帧之间的冗余量就可以进一步提高压缩量,减小压缩比。
帧间压缩也称为时间压缩,它通过比较时间轴上不同帧之间的数据进行压缩。
帧间压缩一般是无损的。
帧差值(Framedifferencing)算法是一种典型的时间压缩法,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量。
6 花屏现象以及解决方法
花屏现象
如果在GOP分组中的P帧丢失,会造成解码端的图像发生错误。这就是花屏。GOP一组帧呈现出的连贯效果,由于P帧丢失,它需要更新的部分就没有,所以无法正常呈现。故出现花屏现象。
解决方法
为了解决花屏问题,我们可以将丢失P帧或是I帧 的 GOP 丢掉(包含其中的所有帧),直到下一个I帧再重新刷新图像。但是由于这一帧丢掉了,所以会出现卡顿。
————————————————分割线———3 分析篇————————————————
思考:当摄像头采集数据后如何进行h264压缩?
请见下文
1 宏块的划分
2 图像分组
3 帧内压缩技术原理
4 帧间压缩技术原理。
5 DCT
6 CABAC压缩原理。
简要概述一下
举例:
1 摄像头1s采集到30张图,将30张图传给h264编码器缓存区中
下面是一张摄像头采集到的的图
2 宏块划分与填充
2.1对每一幅图进行宏块划分,计算每个宏块的像素值(宏块一般为16*16或者8*8)
2.2 计算一幅图中每个宏块的像素值,处理完如下图
3 对宏块进行子块划分(子块大小为4*4 8*4 8*8 16*8)
上图中三只老鹰在宏块内,为了更好处理则在16*16的宏块中又划分了几个子块,如下
3 帧分组(将一系列变换不大的图像归为一个组,即GOP(序列))
以打台球为例
H264编码器会按顺序,每次取出两幅相邻的帧进行宏块比较,计算两帧的相似度
通过宏块扫描与宏块搜索可以发现这两帧的关联度非常高,进而发现这一组帧的关联度都非常高。
其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。
帧分组后我们只保留GOP中第一帧为完整数据I帧,然后根据I帧预测出P,B帧
4 运动估计与补偿
H264编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了
H264依次把每一帧中球移动的距离和方向都记录下来,如下
计算出运动矢量后,将每张图片相同的部分(绿色部分)减去,则得到补偿数据,我们只需要保存补偿数据即可(数据很小),这就是帧间压缩技术
5 帧内预测
将一幅图像中人眼不敏感的数据去除掉。这样就提出了帧内预测技术
一幅图像被划分好宏块后,对每个宏块可以进行 9 种模式的预测。找出与原图最接近的一种预测模式
下图为9种预测模式
预测完后的图像如下
帧内预测后的图像与原始图像的对比如下:
将原始图像与帧内预测后的图像相减得残差值
将1 残差值和之前的2 预测模式信息一起保存起来即可
这样就完成了帧内预测,对数据进行了大幅压缩(但是还可以进一步的压缩,见6)
6 残差数据做DCT
将残差数据做整数离散余弦变换DCT,去掉数据的相关性,进一步压缩数据。
将残差数据宏块数字化后如下图
将残差数据宏块进行 DCT 转换
去掉相关联的数据后,我们可以看出数据被进一步压缩了
做完 DCT 后,还不够,还要进行 CABAC 进行无损压缩
7 CABAC无损压缩
VLC就是这种算法,我们以 A-Z 作为例子,A属于高频数据,Z属于低频数据。看看VCL是如何做的
CABAC也是给高频数据短码,给低频数据长码。同时还会根据上下文相关性进行压缩,这种方式又比VLC高效很多。其效果如下:
现在将 A-Z 换成视频帧,它就成了下面的样子
从上面这张图中明显可以看出采用 CACBA 的无损压缩方案要比 VLC 高效的多
————————————————分割线———4 数据篇————————————————
1 H264的分层结构
h264分为VLC层和NAL层
VLC层(视频编码层)
被压缩后的视频数据序列,负责表示有效视频数据的内容,最终输出编码后的原始数据SODB(数据比特串)
NAL层(网络提取层)
NAL层定义了片级以上的语法级别(如序列参数集和图像参数集,针对网络传输),负责以网络所要求的恰当方式去格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。
2 聊一聊NULA和EBSP RBSP SODP
NALU的起始码为0x000001或0x00000001
NALU的结束码为0x000000
SODB:原始比特流
RBSP:始字节序列载荷
EBSP:扩展字节序列载荷
EBSP相较于RBSP,在两个0x00 x00后,加了防止竞争的一个字节:0x03,这样可以避免数据内误判断起始码和结束码
在h264的文档中,并没有EBSP这一名词出现,但是在h264的官方参考软件JM里,却使用了EBSP,所以有的地方说NALU(NUL单元) = NALU Header + RBSP也是对的
RBSP尾部语法
1 SODB在它的最后一个字节的最后一个比特后,紧跟值为1的1个比特,然后增加若干比特的0,以补齐这个字节
2 当NALU类型为条带时,也即nal_unit_type等于1~5时,这时RBSP使用下面这种尾部
当entropy_coding_mode_flag值为1,也即当前采用的熵编码为CABAC,而且more_rbsp_trailing_data()返回为true,也即RBSP中有更多数据时,添加一个或多个0x0000
总结一下
NALU(NUL单元) = NALU Header + EBSP(RBSP + 0x00000003)
EBSP = RBSP + 插入的防止竞争的0x03
RBSP = SODB + + RBSP尾部
EBSP包含RBSP,RBSP包含SODB
h264码流结构
一个gop中一般都会有SPS PPS IDR等等
nal单元
nal头
NAL单元的头部是由forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)三个部分组成的
F:0表示正常,1表示错误
NRI:重要级别,11表示最重要,00最不重要
TYPE:见上图
看一个大华抓出来的帧数据例子
1)、SPS(序列参数集):SPS对如标识符、帧数以及参考帧数目、解码图像尺寸和帧场模式等
解码参数进行标识记录。
2)、PPS(图像参数集):PPS对如熵编码类型、有效参考图像的数目和初始化等解码参数进行标志记录。
3)、SEI(补充增强信息):这部分参数可作为H264的比特流数据而被传输,每一个SEI信息被封装成一个NAL单元。SEI对于解码器来说可能是有用的,但是对于基本的解码过程来说,并不是必须的
H264常见帧头数据
SPS:00 00 00 00 67
PPS:00 00 00 00 68
IDR:00 00 00 00 65
非IDR:00 00 00 00 61
实际码流解读
I帧码流解读
我们可以看到除了起始帧00 00 00 01(也可以认为结束帧00 00 00,看你个人怎么去理解),其他帧中有两个00的,都会插入一个03防止竞争,将RBSP组成EBSP
这边码流类型如下
SPS + PPS + IDR
P帧码流类型
非I帧61
h264码流的分层结构
每个分片也包含着头和数据两部分,分片头中包含着分片类型、分片中的宏块类型、分片帧的数量以及对应的帧的设置和参数等信息,而分片数据中则是宏块,这里就是我们要找的存储像素数据的地方;宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。宏块数据的组成如下图12所示:
从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。
————————————————分割线———5 代码篇————————————————
下期会写一个手写简单的h264编解码器,敬请期待