H264编码原理

600 阅读8分钟

H264压缩比

条件

  1. YUV格式为YUV420
  2. 分辨率为640*480
  3. 帧率为15

H264建议码流: 500kbps (码流参考值 : docs.agora.io/cn/Interact… )

结果 : 约 1/100

GOP

group of picture : 一组强相关的视频帧.

I/P/B帧

编码帧的分类

  • I帧 (intraframe frame), 关键帧, 采用帧内压缩技术. IDR帧属于I帧(是一种特殊的I帧, 但I 帧却不一定是IDR帧). I帧编码属于帧内压缩 跟其他帧没有关系.

  • P帧 (forward  predicted frame), 向前参考帧. 压缩时, 只参考前面已经处理的帧, 采用帧间压缩技术. 它占I帧的一半大小.(如果前面的帧没有解码, 它也无法解码).

  • B帧 (Bidirectionally predicted frame), 双向参考帧. 压缩时, 既参考前面已经处理的帧, 也参考后面的帧, 帧间压缩技术. 它占I帧1/4大小.(B帧的压缩率是最高的, 但它占用的CPU和耗时是非常高的, B帧帧数越多延迟越大, 对实时通讯来说是最大的忌讳, 所以大部分实时通讯场景中不使用B帧, 而在音视频转码处理的场景中大量使用B帧以节省空间).

  • 解码一组帧的顺序 一定是I帧第一个 B帧最后一个, 但是播放顺序不受影响 

IDR帧与I帧的区别与联系

  • IDR (Instantaneous Decoder Refresh) 解码器立即刷新.

  • 每当遇到IDR帧时, 解码器就会清空解码器参考buffer中的内容.

  • 每个GOP中的第一帧就是IDR帧.

  • IDR帧是一种特殊的I帧.

  • 每个GOP可以有多个I帧,但只有一个IDR.

  • GOP的最后一帧一定是P帧.

  • 如果GOP中某一帧出现错误, 那么后面的视频将很难进行恢复. 而IDR帧会将解码器中的缓存全部清空, 因为它是第一帧不依赖任何参考帧, 这样防止了错误的传播.

帧与分组的关系

如上图所示 : 第一组 I B B B P 帧, 解码顺序为 I P B B B, I帧作为关键帧 不参考其他帧, 其次参考I帧解码P帧, 再次参考I帧和P帧依次解码每一个B帧, B帧并不参考其他的B帧;  第二组  B B B P 帧,  首先参考前面一组的P帧(此时前面的P帧已解码完成 是一个完成的图像)解码当前的P帧, 再参考前后两个P帧解码中间的B帧.

sps 与 pps

在每个IDR的前面都会有一个SPS和PPS, SPS和PPS一般是成对出现的.

  • SPS (Sepuence Parameter Set) : 序列参数集, 对帧组的设置,作用于一串连续的视频图像. 如seq_parameter_set_id、帧数及POC(picture order count)的约束、参考帧数目(可以参考一帧或多帧)、解码图像尺寸和帧场编码模式选择标识等.

  • PPS (Picture Parameter Set) : 图像参数集, 作用于视频序列中的图像. 如pic_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等.

H264压缩技术

  • 有损压缩技术:
  1. 帧内压缩, 解决的事空域数据冗余问题

  2. 帧间压缩, 解决的是时域数据冗余问题

  • 无损压缩技术:
  1. 整数离散余弦变换(DCT), 将空间上的相关性变为频域上无关的数据然后量化

  2. CABAC压缩: 根据上下文进行数据压缩(较老)

宏块

  • 宏块是视频压缩操作的基本单元
  • 无论是帧内压缩还是帧间压缩, 它们都以宏块为单位
  • 宏块划分的越小 对图像的控制力越强, 但是对于较简洁的画处理速度越慢, 适合纹理细腻的图像.

从上图中可以看出: H264划分的宏块更小, 压缩比更高, 处理起来控制力更强.

宏块的尺寸

帧内压缩(I 帧)

  • 相邻像素差别不是很大, 所以可以进行宏块预测

  • 人对亮度的敏感度超过色度

  • YUV数据很容易将亮度与色度分开

帧内预测的九种模式

不同模式的预测结果

帧间压缩(P帧、B帧)

  • 在同一个GOP中才能进行帧间压缩.
  • 参考帧, 参考其他的帧才能进行帧间压缩.
  • 运动估计 (宏块匹配 + 运动矢量): 通过宏块匹配的方法找到运动矢量.
  • 运动补偿 (解码) : 找到残差值, 解码的时候添加上去.

宏块查找算法

  • 三步搜索

  • 二维对数搜索

  • 四步搜索

  • 钻石搜索

运动估计

上图中展示的是一个桌球桌面上球的移动的一组帧, 这组帧的存储方法是 用很小的数据量存储背景, 再单独存储球的宏块从起点到终点的运动矢量. 其中宏块的对比会有差值, 例如当宏块相似度达到95%的时候认为这是同一个宏块, 那么另外的5% 就是差值. 所以在解码的时候 需要用运动矢量同时补全宏块的残差值,这样才能还原出原来的画面.

视频花屏的原因:

如果GOP中有帧丢失也就是运动矢量和残差值丢失, 就会造成解码端图像花屏(马赛克).

视频卡顿的原因:

为了避免花屏问题的发生, 当发现有帧丢失时, 就丢弃GOP内的所有帧, 直到下一个IDR帧重新刷新图像.

I帧是按帧周期来的, 需要一个比较长的时间周期, 如果在下一个I帧来之前不显示后来的图像, 那么视频就静止不动了, 这就是所谓的卡顿现象.

花屏和卡顿是无法兼顾的, 如果想要避免卡顿时间过长, 可以适当增加I帧的个数, 例如每秒一帧I帧, 这样卡顿最多持续一秒钟, 但是这样就会带来数据量加大的问题, 对带宽造成影响. 具体根据自己的需求定位进行取舍.

无损压缩的流程

*** DCT变换**

经过有损压缩之后, 数据是分散在二维表的各个点上的, 经过DCT变换之后, 分散的数据被集中到一块, 以此在进行无算压缩就会很方便, 由此可见, DCT变换就是将数据从分散到集中的一个过程.

  • VLC压缩(MPEG2), 可变长度编码, 如下图A~Z,  假如A的使用频率最高, 则用一个短码将其标识, 反之用长码标识, 高频字符越多压缩率越高, 解压时执行反操作即可无损还原数据.

  • CABAC(压缩率更高) : (Context-based Adaptive Binary Arithmetic Coding),基于上下文的自适应二进制算术编码。CABAC是H.264/AVC标准中两种熵编码中的一种,它的编码核心算法就是算术编码(Arithmetic Coding)

H264编码流程

Fn(current) : 当前第一帧为IDR -> 选择帧内预测模式 -> 帧内预测 -> 宏块计算 -> 计算与当前帧的残差值 -> (宏块+残差值)转换量化(无损编码) -> 拆包 -> NAL -> 输出.

F`n-1(帧间压缩) -> 运动评估(宏块匹配查找) -> 得到运动矢量 -> 推算出运动后帧的值 -> 计算与当前帧的残差值-> (宏块+残差值)转换量化(无损编码) -> 拆包 -> NAL -> 输出.

H264解码流程

组帧 -> 反量化 -> 反DCT转换 -> 帧间匹配 -> 还原数据

H264参考资料

H264码流分层

  • NAL层 : Network Abstraction Layer, 视频数据网络抽象层, 作用是方便在网络上传输视频流, 接收端可以识别丢包、乱序、重传的问题并进行处理, 通过NAL层可以知道包的起始和结束的位置, 配合RTP的seqNum识别是否乱序和丢包.

  • VCL层 : Video Coding Layer, 视频数据编码层.

VCL结构关系

每帧图像都可以分割成多个slice(每个slice由多个宏块组成),目的是通过编解码器将其分割成多个小块以方便网络传输和提高处理的灵活度, 而一般情况下, 一个slice就代表了一帧图像.

码流基本概念

SODB (String Of Data Bits) : 二进制数据串, 原始数据比特流, 以位位单位, 长度不一定是8的倍数, 故需要补齐. 它是由VCL层产生的.

RBSP ( Raw Byte Sequence Payload) : 按字节存储的原始数据, SODB + trailing bits, 即将SODB补齐, 算法是如果SODB最后一位不对齐, 则补一个1和多个0.

NALU : NAL单元, NAL Header(1B) + RBSP.

Annexb格式: 每一个NAL单元前面都加一个StartCode(00000001或者000001), 与NAL Unit 共同构成Annexb 格式, 可以用于本地存储成文件, 如果前面没有startcode, 播放器不知道每个NAL单元是如何分割的,也就无法播放这个多媒体文件.

RTP格式 : 用于网络传输, 不需要加StartCode, 因为网络是组成一帧就解码一帧.

MB data中的 mb_type : 宏块类型, mb_pred/sub_mb_pred : 预测值/子宏块预测值 , residual data : 残差值.