视频是由一帧帧图像组成,视频为了不卡顿,一秒钟至少要16帧画面,但是图片内容太大,传输不现实。因此需要对他们编码。
官方文档:http://www.itu.int/rec/T-REC-H.264
翻译文档:http://www.itu.int/rec/T-REC-H.264-200503-S/en
H264通过比较相邻几张图像的像素差(编码),来保存这几张图片的真实数据,当两张图片的像素点不大的时候则不去编码来减小传递张数(压缩)。来大大提高视频质量和传输速率。
例:
编码的原理:
在进行当前信号编码时,编码器首先会产生对当前信号做预测的信号,称作预测信号。也是为了解决两个方向的数据冗余
。
视频处理中的预测编码主要分为两大类:帧内预测和帧间预测。
通常在视频码流中,I帧全部使用帧内编码
,P帧/B帧中的数据
可能使用帧内或者帧间编码
。
为了提高编码效率,主流的视频编码均属于有损编码,因此会对视频内的图片造成信息损失。
视频编码算法常用正交变换进行变换编码,常用的变换方法有:**离散余弦变换(DCT)、离散正弦变换(DST)**等。
将上述的残差数据做整数离散余弦变换,去除数据的相关性,进行二次压缩数据。
如一个图片为 4x4 的图片矩阵:
变换后:
可以看到能量(低频信号)集中到了左上方,而变换后的数据完全可以通过反变换还原。为了达到压缩数据,我们需要丢弃掉一些能量低的数据(高频信号),而对图像质量影响很小。
最后再去除相关性,可以得到更大的压缩量。
具体的转换可以看下大佬的博客:
https://www.cnblogs.com/xkfz007/archive/2012/07/31/2616791.html
主要用于消除视频信息中的统计冗余。由于信号中每个符号出现的概率并不一致,导致使用统一长度的码字表示所有符号会造成浪费。通过熵编码,可以针对不同语法元素分配不同长度的码元,消除视频信息中由于符号概率导致的冗余。
常用的编码方式有:指数哥伦布编码(UVLC)、变长编码(CAVLC)、**二进制算术编码(CABAC)**等。
CABAC
上面的帧内压缩是属于有损压缩技术。也就是说图像被压缩后,无法完全复原。而CABAC属于无损压缩技术。
属于熵编码的一种,包括霍夫曼编码、指数哥伦布编码、CABAC等等。这类编码主旨就是,找到一种编码,使得码字的平均码长达到熵极限。
如:取得概率较大的符号,取较短的码长,而对于概率较小的符号,取较大的码长。
熵:信息越是随机,它的熵值越高。而信息熵,就是为了解决信息的量化度量问题,它描述了整个信源的平均信息量。在使用熵编码时,码字的平均码长尽量达到熵极限,表明熵编码的压缩效率越高。
H264编码使用的是0阶哥伦布编码方式压缩,但是这种方式可能在某些时候不减数据量,反而增大。而使用CABAC则可以大大减小数据量
简介
每一帧的H图像被分为**一个或多个条带(slice)**进行编码。
每一个条带包含多个宏块(MB)。他是H264中基本的编码单元,基本结构包含一个16×16个亮度像素块和两个8×8色度像素块,以及其他一些宏块头信息。
在对宏块进行编码时,每一个宏块会分割成多种不同大小的子块进行预测。帧内预测采用的块大小可能为16×16或者4×4,帧间预测采用的块可能有7种不同的形状:16×16、16×8、8×16、8×8、8×4、4×8和4×4。
在变换编码方面,针对预测残差数据进行的变换块大小为4×4或8×8。
H.264标准中采用的熵编码方法主要有上下文自适应的变长编码CAVLC和上下文自适应的二进制算数编码CABAC,根据不同的语法元素类型指定不同的编码方式。通过这两种熵编码方式达到一种编码效率与运算复杂度之间的平衡。
条带也具有不同的类型,最常用的有I条带、P条带和B条带。另外,为了支持码流切换,在扩展档次中还定义了SI和SP片。
视频编码中采用的如预测编码、变化量化、熵编码等编码工具主要工作在slice层或以下,这一层通常被称为视频编码层(Video Coding Layer, VCL)。
相对的,在slice以上所进行的数据和算法通常称之为网络抽象层(Network Abstraction Layer, NAL)。主要意义在于提升H.264格式的视频对网络传输和数据存储的亲和性。
H264的三种档次
交错视频编码
针对隔行扫描的视频,H.264专门定义了用于处理此类交错视频的算法。
一些需要提前准备的信息打基础,否则大量的术语会懵了。
上述的过程就是H264的编码的大部分过程(核心算法)。这几个取帧的命名有以下几个:
I帧:完整编码。该帧可压缩程度最低,也不需要通过其他视频帧解码。自身可以通过视频解压算法解压成一张单独的完整的图片。
IDR帧:一个序列的第一个图像叫做IDR图像(立即刷新图像),IDR帧都是I帧。引入IDR是为了在解码的时候可以立即同步,将已解码的数据全部抛出。
P帧:参考之前的I帧生成的只包含差异部分编码的帧。该帧可以引用前面的帧的数据来解压缩并且相对于I帧来说,该帧可以压缩程度更高。需要参考其前面的一个I frame 或者B frame来生成一张完整的图片。
B帧:参考前后的帧编码的帧叫B帧。该帧可以引用前面的帧和后面的帧的数据,从而压缩程度最高。参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片。相较于P帧,压缩量更大,预测效果更好,但是在实时互动的情况下,会引起延时,特别是在网络较差的情况。
I帧为帧内压缩,P帧和B帧为帧间压缩。B帧不能作为参考帧。
帧和场都是来产生一个编码的图像,一帧都是一个完整的图像,但是场却不一定。在使用隔行扫描的时候,就会有一帧为两场的情况。这是因为当屏幕过大导致的逐行扫描可能导致图像上下亮度不一致导致的缓和显示方式。
I帧、P帧、B帧都是被封装成一个或者多个NALU进行传输或者存储的。 每一个I帧开始之前也有非VCL的NALU单元,用于保存其他信息,它们是PPS
、SPS
。
在H264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流。也称GOP序列
,每一个序列的第一个图片是IDR图像
(立即刷新图像),也就是I帧
。
其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。
NALU是将每一帧数据写入到一个NALU单元中,进行传输或存储的NALU设计的目的,是根据不同的网络把数据打包成相应的格式,将VCL产生的比特字符串适配到各种各样的网络和多元环境中。
NALU是将每一帧数据写入到一个NALU单元中,进行传输或存储的。
NALU的封装方式:NALU分为NALU头和NALU体
多个NALU数据组合在一起形成总的输出码流。对于不同的应用场景,NAL规定了一种通用的格式适应不同的传输封装类型。
每帧图片中都含有多个切片,他们承载这多个宏块数据。片是H264中提出的新概念,在编码图片后切分并整合出来的一个概念。
片之所以被创造出来,主要目的是为限制误码的扩散和传输。使编码片相互间是独立的。某片的预测不能以其他片中的宏块为参考图像,这样某一片中的预测误差才不会传播到其他片中。
片又分为:切片头
和切片数据
类型:
H264默认是使用16x16大小的区域作为一个宏块。
宏块是视频信息的主要承载者。也就是我们最想知道的图片信息(YUV格式)
。一个编码图像通常划分为多个宏块组成。
如果需要更高的压缩率,还可以在宏块的基础上划出更小的子块。如:16x8、8x16、8x8、… 4x4。
编码图像中的当前宏块相对于参考图像的宏块所移动的距离和方向就是运动向量。
寻找最佳匹配宏块的过程。
描述相邻(编码关系伤的相邻,在播放顺序上两帧未必相邻)差别的方法,例:描述前一帧的每个小块如何移动到当前帧中的某个位置。
参考帧(编码后且经过滤波)中的宏块经运动补偿得到。
根据当前帧中**已编码的宏块(未经过滤波)**对当前宏块预测。
当前宏块减去预测宏块就得残差宏块,表示预测的误差。
DTS 与 PTS 的不同:
DTS 主要用户视频的解码,在解码阶段使用。PTS主要用于视频的同步和输出,在 display 的时候使用。再没有 B frame 的时候输出顺序是一样的。
H264在概念上分为两层:
264的两种码流格式,它们分别为:字节流格式和RTP包格式。
前面说到264的字节流就是起始码+NALU+...
NALU header
和EBSP
分为NALU header
和EBSP
。
由三个元素组成:forbidden_zero_bit(1 bit)
、nal_ref_idc(2 bit)
和nal_unit_type(5 bit)
。它们总共占据一个字节。
这个值应该为0,当它不为0时,表示网络传输过程中,当前NALU中可能存在错误,解码器可以考虑不对这个NALU进行解码。
取值0~3,代表当前这个NALU的重要性,取值越大,代表当前NALU越重要。
尤其是当前NALU为图像参数集、序列参数集或IDR图像时,或者为参考图像条带(片/Slice),或者为参考图像的条带数据分割时,nal_ref_idc值肯定不为0。
而当NALU 类型,nal_unit_type为6、9、10、11、或12时,nal_ref_idc都为0。
表示NALU Header后面的RBSP的数据结构的类型。
需要注意的几个值,他们在编码解码中需要特别注意:
首先这里有三个概念:EBSP、RBSP、SODB。(大小顺序从左到右)
由于NALU依靠起始码来分割NALU段,0x03就是为了防止这些可能在出现的干扰(如:在NALU中出现了0x000001或0x00000就会被认为是新NALU点,但这样肯定是不对的)。为了解决这个问题,提供了0x03的一个安全机制。
当检测到它们存在时,编码器就在最后一个字节前,插入一个新的字节:0x03。
0x00 00 00 -> 0x00 00 03 00
0x00 00 01 -> 0x00 00 03 01
0x00 00 02 -> 0x00 00 03 02
0x00 00 03 -> 0x00 00 03 03
这里的 0x000000 和 0x000001上面已经讲了,0x000002作为保留项,0x000003则是因为在NALU内部也存在使用情况。
差别在于SODB的尾部添加结束字符。有以下两种方式
方式一
大多数的NALU使用的RBSP_STOP_ONE_BIT、RBSP_ALIGNMENT_ZERO_BIT
。
RBSP_STOP_ONE_BIT:1个比特,值为1。
RBSP_ALIGNMENT_ZERO_BIT:值为0,为使字节对齐。
也即:
SODB + RBSP_STOP_ONE_BIT + RBSP_ALIGNMENT_ZERO_BIT(0…)
方式二
另一种尾部,就是当NALU类型为片时,也即nal_unit_type等于1~5时。
只是当entropy_coding_mode_flag值为1,采用CABAC无损压缩技术,并且more_rbsp_trailing_data()返回为true,也即RBSP中有更多数据时,添加一个或多个0x0000。
以上就是EBSP的这个数据结构。