hi h264

417 阅读6分钟

生活中,视频随处可见。假设一个视频分辨率是1080p,帧率是25fps,时长是1小时,如果不做压缩的话,他的大小是 1920 x 1080 x 1.5 x 25 x 1 x 3600 = 260.7G,这样的质量对于数据传输和存储是不可接受的,于是就诞生了一系列的视频编码标准。

0x01 视频编码

谈到视频编码,一般从这几个方向去做压缩:

1.空间冗余: 将一张图片划分为很多图块,相邻的图块之间会有很多相似的地方,这种相似的地方叫做空间冗余,经过图片本身的压缩之后会产生一个压缩之后的图像称之为I帧。

2.时间冗余: 一个帧率为25fps的视频中,一秒会有25帧图片,每帧图片之间的变化比较小,相似度很高,这种称之为时间冗余。经过I帧与后一帧图像对比之后,只记录差异的帧是P帧和B帧,P帧和B帧不能独立解码出一张图片,需要依赖I帧。

3.视觉冗余: 人的眼睛对图像高频信息的敏感度小于低频信息的敏感度,去除一些高频信息,对于人眼没有太大差别,这个叫做视觉冗余。去除高频信息时会将图像的色值出去一个QP值,QP值越大,高频信息的色值越小,画面清晰度越低,码率就越低。

0x02 帧

帧主要包括以下类型:

    • I-frames:  称之为关键帧,包含创建完整图片所需要的所有数据。由于他是独立帧,其尺寸是最大的,但也是解压最快的。
    • P-framse:  称之为预测帧,是从基于最近的I-frames或者P-frames可预测的图片编码得到的。
    • B-frames: 称之为双向帧,是基于使用之前和之后的帧信息编码得到的,因为p帧已经被压缩,p帧与p帧之间差异更小,所有压缩率极高。但其解压过程会耗时较长,因为他依赖周围其他的帧。
    • IDR-framse: 称之为立即刷新关键帧,这个帧不依赖之前的I帧,如果普通的I帧解码错误,会影响其他的帧解码。IDR帧就会阻断这种错误。

帧排列顺序

与帧相关的概念:

    • DTS(decode-Time-Stamp): DTS可以理解为解码顺序,如上图IDR1可以独立解码成图片 B1需要依赖IDR1和P1解码之后才能解码,同理B2也依赖IDR1和P1才能解码。综上所述,因为B帧原因,解码顺序不是按照帧的排列顺序来解码的。
    • PTS(Presentation-Time-Stamp): PTS可以理解为显示顺序,如上图,显示顺序就是按照帧的排列顺序显示。
    • GOP: 是指一个IDR帧开始到另一个IDR帧结束所有帧的集合称之为GOP,GOP里面包含IDR帧,I帧,B帧,P帧所有的帧,一个GOP可以解码出一组图片,如下图。
    • slice: slice也叫做片,是将图片分割成几片,片与片之间相互独立、互不依赖、独立编码。他的作用是为了利用多线程对一张图片并行编码,提高编码速度。
    • Marcoblock : Marcoblock称之为宏块,是视频编码技术的基本单元,宏块就是将图像分割成16*16大小的区域,一个slice包含若干个宏块

Group

图像层次结构的图解


0x03 H264码流结构

    H264就是将以上图像编码之后按照一定的结构组装的文件。可以将视频文件压缩到合适大小,又不影响视频的观看效果。

H264的结构

    H264的结构是Annexb格式,他是以4个字节或者3个字节作为分隔符。分隔符一般是0x000001或0x00000001。分隔符后面就是编码数据,他是以NALU形式排列的。

NAL单元

    NAL称之为网络提取层,设计nal的目的是为了实现网络亲和性,可以适用于各种传输网络。下面是NALU的的格式

图像层次结构的图解

NALU HEADER

  1. forbidden_zero_bit: 禁止位 必须为0 在网络传输中发生错误时,会被置为1,告诉接收方丢掉该单元。

  2. nal_ref_idc 用于表示当前NALU的重要性,值越大,越重要。解码器在解码处理不过来的时候可以丢掉重要性为0的NALU。

  3. nal_unit_type表示NALU的数据类型:

  1. 1-4:  I/P/B帧5 IDR帧 
  2. 6 SEI: 补充增强信息,提供了向视频码流中加入额外信息的方法
  3. 7 SPS: 序列参数集。保存了一组编码视频序列的全局参数。例如图像宽高,YUV格式和位深
  4. 8 PPS: 图像参数集。 保存了整体图像的参数。例如熵编码类型,基础QP,最大参考帧数量 
  5. 9 AU分割符: 它是一个或者多个 NALU 的集合,代表了一个完整的帧,有时候用于解码中的帧边界识别。

NALU Payload结构前面说过NALU的分隔符是0x000001或者0x00000001,如果在编码数据里出现这个分隔符就会影响整体的编码结构。为了防止这种情况,h264会将编码数据中出现的分隔符做一个替换:   

      00 00 00  替换为  00 00 03 00      00 00 01  替换为  00 00 03 01      00 00 02  替换为  00 00 03 02      00 00 03  替换为  00 00 03 03
  1. EBSP: 替换之后的编码数据称之为EBSP  Encapsulated Byte Sequence Payload,称为扩展字节序列载荷1

  2. SODB: 替换之前的编码数据称之为 SODB 英文全称 String Of Data Bits,称原始数据比特流,就是最原始的编码/压缩得到的数据。

  3. RBSP 为了提高读写效率引入RBSP  英文全称 Raw Byte Sequence Payload,又称原始字节序列载荷。和 SODB 关系如下:

    引入 RBSP Trailing Bits 做 8 位字节补齐。

    RBSP = SODB + RBSP Trailing Bits(RBSP尾部补齐字节)
    

NALU Payload的结构图如下:

h264结构中帧类型的NALU payload存储的并不是帧,而是slice,相当于帧类型的NALU存储了一个slice类型的NALU,slice类型的NALU存储这若干个MB结构如下图:

下图就是整体的h264结构