美文网首页
iOS | H.264介绍 & 编码原理

iOS | H.264介绍 & 编码原理

作者: Leon_520 | 来源:发表于2018-12-04 09:09 被阅读0次

    前言

    为什么需要视频编码呢?比如当前屏幕是1280*720一秒30张图片.那么我们一秒的视频数据是1280 * 720 *30 / 8(字节) /1024(KB)/1024(MB) = 3.11MB, 那么一分钟就上百兆了,如果使用流量的话,看个小电影一个月流量也差不多了; 如果我们对视频进行编码的话,将大大节省我们的流量数据~

    本文主要针对H.264进行介绍以及原理分析 , 关于 iOS 的H.264编解码代码,请参考 [iOS] H.264编解码(VideoToolbox硬编解码)

    H.264简介

    H.264是国际标准化组织(ISO)和国际电信联盟(ITU)共同提出的继MPEG4之后的新一代数字视频压缩格式,也是目前使用最广泛的视频压缩标准。其编解码流程主要包括5个部分:帧间和帧内预测(Estimation)、变换(Transform)和反变换、量化(Quantization)和反量化、环路滤波(Loop Filter)、熵编码(Entropy Coding)

    H.264码流分析

    H.264原始码流是由一个接一个NALU组成,它的功能分为两层,VCL(Video Coding Layer 视频编码层)和 NAL(Network Abstraction Layer网络提取层).

    • VCL:包括核心压缩引擎和块,宏块和片的语法级别定义,设计目标是尽可能地独立于网络进行高效的编码。。
    • NAL:负责将VCL产生的比特字符串适配到各种各样的网络和多元环境中,覆盖了所有片级以上的语法级别。

    VCL数据传输或者存储之前,会被映射到一个NALU中,H264数据包含一个个NALU。如下图

    image.png

    一个NALU = 一组对应于视频编码的NALU头部信息 + 一个原始字节序列负荷(RBSP,Raw Byte Sequence Payload).

    image.png

    一个原始的NALU单元结构如下[StartCode] [NALU Header] [NALU Payload]三部分。

    StartCode

    StartCode,是一个NALU单元开始,必须是00 00 00 01 或者00 00 01, 用于分割每个NALU单元。

    NAL Header

    NAL Header 由一个字节组成, 语法:禁止位(1bit)、重要性位(2bit)、NALU类型(5bit)。

    1. F(forbiden): 禁止位,占用NAL头的第一个位,当禁止位值为1时表示语法错误;
    2. NRI: 参考级别,占用NAL头的第二到第三个位;值越大,该NAL越重要。
    3. Type: Nal单元数据类型,也就是标识该NAL单元的数据类型是哪种,占用NAL头的第四到第8个位;
    image.png

    取值的含义如下:


    image.png

    例如:

    00 00 00 01 06:  SEI信息   
    00 00 00 01 67:  0x67&0x1f = 0x07 :SPS
    00 00 00 01 68:  0x68&0x1f = 0x08 :PPS
    00 00 00 01 65:  0x65&0x1f = 0x05: IDR Slice
    

    RBSP(原始字节序列负荷)

    H.264 的编码视频序列包括一系列的NAL单元,每个NAL单元包含一个 RBSP。每个单元都按独立的 NAL 单元传送。单元的信息头(一个字节)定义了 RBSP 单元的类型,NAL 单元的其余部分为 RBSP 数据。
    如图:


    image.png

    下面是RBSP序列的描述

    image.png

    H.264码流分层结构

    image.png
    • 第一层:比特流。该层有两种格式:附录B格式和RTP格式。
    • 第二层:NAL Unit层。包含了NAL Header和NAL Body信息。
    • 第三层:Slice层。
    • 第四层:Slice data层。Slice由宏块(macro block, MB)组成。宏块是编码处理的基本单元。
    • 第五层:PCM类。
    • 第六层:残差层。

    片(slice)

    • 一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice),而装载着这些片(slice)的载体,就是 NALU 了,我们可以来看看 NALU 跟片的关系(slice)。


      image.png
    • 帧(frame)是用作描述一张图片的,一帧(frame)对应一张图片,而片(slice),是 H.264 中提出的新概念,是通过编码图片后切分通过高效的方式整合出来的概念,一张图片至少有一个或多个片(slice)。
    切片
    image.png

    上图中可以看出,片(slice)都是又 NALU 装载并进行网络传输的,但是这并不代表 NALU 内就一定是切片,这是充分不必要条件,因为 NALU 还有可能装载着其他用作描述视频的信息。

    片的主要作用是用作宏块(Macroblock)的载体。片之所以被创造出来,主要目的是为限制误码的扩散和传输。
    如何限制误码的扩散和传输? 每个片(slice)都应该是互相独立被传输的,某片的预测(片(slice)内预测和片(slice)间预测)不能以其它片中的宏块(Macroblock)为参考图像。

    image.png

    我们可以理解为一 张/帧 图片可以包含一个或多个分片(Slice),而每一个分片(Slice)包含整数个宏块(Macroblock),即每片(slice)至少一个 宏块(Macroblock),最多时每片包 整个图像的宏块。

    上图结构中,我们不难看出,每个分片也包含着头和数据两部分:

    • 分片头中包含着分片类型、分片中的宏块类型、分片帧的数量、分片属于那个图像以及对应的帧的设置和参数等信息。
    • 分片数据中则是宏块,这里就是我们要找的存储像素数据的地方。

    宏块

    宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。
    组成部分:一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个 8×8 Cr 彩色像素块组成。每个图象中,若干宏块被排列成片的形式。

    image.png

    从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。

    来看看如何划分宏块的
    编码器先要为每一幅图片划分宏块。以下面这张图为例:

    image.png

    H264默认是使用 16X16 大小的区域作为一个宏块,也可以划分成 8X8 大小。


    image.png

    划分好宏块后,计算宏块的象素值。


    image.png

    以此类推,计算一幅图像中每个宏块的像素值,所有宏块都处理完后如下面的样子。


    image.png

    如何划分子块
    在说着之前,先了解切片(slice)类型跟宏块类型的关系

    • I片:只包 I宏块,I 宏块利用从当前片中已解码的像素作为参考进行帧内预测(不能取其它片中的已解码像素作为参考进行帧内预测)。
    • P片:可包 P和I宏块,P 宏块利用前面已编码图象作为参考图象进行帧内预测,一个帧内编码的宏块可进一步作宏块的分割:即 16×16、16×8、8×16 或 8×8 亮度像素块(以及附带的彩色像素);如果选了 8×8 的子宏块,则可再分成各种子宏块的分割,其尺寸为 8×8、8×4、4×8 或 4×4 亮度像素块(以及附带的彩色像素)。
    • B片:可包 B和I宏块,B 宏块则利用双向的参考图象(当前和 来的已编码图象帧)进行帧内预测。
    • SP片(切换P):用于不同编码流之间的切换,包含 P 和/或 I 宏块
    • SI片:扩展档次中必须具有的切换,它包 了一种特殊类型的编码宏块,叫做 SI 宏块,SI 也是扩展档次中的必备功能。

    H264对比较平坦的图像使用 16X16 大小的宏块。但为了更高的压缩率,还可以在 16X16 的宏块上更划分出更小的子块。子块的大小可以是 8X16、 16X8、 8X8、 4X8、 8X4、 4X4非常的灵活。


    image.png

    上幅图中,红框内的 16X16 宏块中大部分是蓝色背景,而三只鹰的部分图像被划在了该宏块内,为了更好的处理三只鹰的部分图像,H264就在 16X16 的宏块内又划分出了多个子块。


    image.png

    这样再经过帧内压缩,可以得到更高效的数据。下图是分别使用mpeg-2和H264对上面宏块进行压缩后的结果。其中左半部分为MPEG-2子块划分后压缩的结果,右半部分为H264的子块划压缩后的结果,可以看出H264的划分方法更具优势。


    image.png

    其实 H.264 的码流结构并没有大家想的那么复杂,编码后视频的每一组图像(GOP,图像组)都给予了传输中的序列(PPS)和本身这个帧的图像参数(SPS),所以,我们的整体结构,应该如此:


    image.png

    经过压缩后的帧分为:I帧,P帧和B帧:

    • I帧:帧内编码帧 又称intra picture,表示关键帧,I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是一个图像经过压缩后的产物。你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)
    • P帧: 前向预测编码帧 又称predictive-frame,通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧;表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面(I帧)叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)
    • B帧: 双向预测内插编码帧(双向差别帧、双向预测帧) 又称bi-directional interpolated prediction frame,既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像;也就是B帧记录的是本帧与前后帧的差别(具体比较复杂,有4种情况),换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累~。

    除了I/P/B帧外,还有图像序列GOP。

    • GOP:两个I帧之间是一个图像序列,在一个图像序列中只有一个I帧。(图像组)主要用作形容一个 i 帧 到下一个 i 帧之间的间隔了多少个帧,增大图片组能有效的减少编码后的视频体积,但是也会降低视频质量,至于怎么取舍,得看需求了。
      I帧是完整的视频帧,换句话说,客户端只有在获得I帧后才会有完整的视频。如果直接发送,不等I帧,客户端得到的画面会残缺,但是延迟较低。如果等I帧,客户端缓冲时间较长,得到画面会完整,但是延迟至少是一个gop。

    • 一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。
      I和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
      核心作用:
      H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。

    I、P、B帧特点分析

    I 帧特点:
    
        它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输; 
        解码时仅用I帧的数据就可重构完整图像; 
        I帧描述了图像背景和运动主体的详情; 
        I帧不需要参考其他画面而生成; 
        I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量); 
        I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧; 
        I帧不需要考虑运动矢量; 
        I帧所占数据的信息量比较大。 
    
    P帧特点:
    
        P帧是I帧后面相隔1~2帧的编码帧; 
        P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差); 
        解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像; 
        P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧; 
        P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧; 
        由于P帧是参考帧,它可能造成解码错误的扩散; 
        由于是差值传送,P帧的压缩比较高。 
    
    B帧特点
    
        B帧是由前面的I或P帧和后面的P帧来进行预测的; 
        B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量; 
        B帧是双向预测编码帧; 
        B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确; 
        B帧不是参考帧,不会造成解码错误的扩散。
    

    帧内预测

    对一特定宏块儿编码时,利用周围的宏块的预测值和实际值的差进行编码,人眼对图象都有一个识别度,对低频的亮度很敏感,对高频的亮度不太敏感。所以基于一些研究,可以将一幅图像中人眼不敏感的数据去除掉。这样就提出了帧内预测技术。

    H264的帧内压缩与JPEG很相似。一幅图像被划分好宏块后,对每个宏块可以进行 9 种模式的预测。找出与原图最接近的一种预测模式。

    image.png

    下面这幅图是对整幅图中的每个宏块进行预测的过程:


    image.png

    帧内预测后的图像与原始图像的对比如下:


    image.png

    然后,将原始图像与帧内预测后的图像相减得残差值:


    image.png

    再将我们之前得到的预测模式信息一起保存起来,这样我们就可以在解码时恢复原图了。效果如下:


    image.png

    经过帧内与帧间的压缩后,虽然数据有大幅减少,但还有优化的空间。

    帧间预测

    利用连续帧中的时间冗余来进行运动估计和补偿。码流中增加SP帧,方便在不同码率的码流间切换,同时支持随机接入和快速回放。

    帧分组

    对于视频数据主要有两类数据冗余,一类是时间上的数据冗余,另一类是空间上的数据冗余。其中时间上的数据冗余是最大的。下面我们就先来说说视频数据时间上的冗余问题。

    为什么说时间上的冗余是最大的呢?假设摄像头每秒抓取30帧,这30帧的数据大部分情况下都是相关联的。也有可能不止30帧的的数据,可能几十帧,上百帧的数据都是关联特别密切的。
    对于这些关联特别密切的帧,其实我们只需要保存一帧的数据,其它帧都可以通过这一帧再按某种规则预测出来,所以说视频数据在时间上的冗余是最多的。

    为了达到相关帧通过预测的方法来压缩数据,就需要将视频帧进行分组。那么如何判定某些帧关系密切,可以划为一组呢?我们来看一下例子,下面是捕获的一组运动的台球的视频帧,台球从右上角滚到了左下角。

    image.png image.png

    H264编码器会按顺序,每次取出两幅相邻的帧进行宏块比较,计算两帧的相似度。如下图:


    image.png

    通过宏块扫描与宏块搜索可以发现这两个帧的关联度是非常高的。进而发现这一组帧的关联度都是非常高的。因此,上面这几帧就可以划分为一组。其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。

    在这样一组帧中,经过编码后,我们只保留第一帖的完整数据,其它帧都通过参考上一帧计算出来。我们称第一帧为IDR/I帧,其它帧我们称为P/B帧,这样编码后的数据帧组我们称为GOP。

    运动估计与补偿

    在H264编码器中将帧分组后,就要计算帧组内物体的运动矢量了。还以上面运动的台球视频帧为例,我们来看一下它是如何计算运动矢量的。

    H264编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了。下面这幅图就是搜索后的台球移动的位置。


    image.png

    通过上图中台球位置相差,就可以计算出台图运行的方向和距离。H264依次把每一帧中球移动的距离和方向都记录下来就成了下面的样子:

    image.png

    运动矢量计算出来后,将相同部分(也就是绿色部分)减去,就得到了补偿数据。我们最终只需要将补偿数据进行压缩保存,以后在解码时就可以恢复原图了。压缩补偿后的数据只需要记录很少的一点数据。如下所示:


    image.png

    我们把运动矢量与补偿称为帧间压缩技术,它解决的是视频帧在时间上的数据冗余。除了帧间压缩,帧内也要进行数据压缩,帧内数据压缩解决的是空间上的数据冗余。下面我们就来介绍一下帧内压缩技术。

    对残差数据做DCT(量化和反量化)

    步长以12.5%的符合速率递增,不是固定的常数。变换系数的读出有两种:之字扫描和双扫描。多数用之字扫描,双扫描仅用于较小量化级的块内。

    可以将残差数据做整数离散余弦变换,去掉数据的相关性,进一步压缩数据。如下图所示,左侧为原数据的宏块,右侧为计算出的残差数据的宏块。


    image.png

    将残差数据宏块数字化后如下图所示:


    image.png

    将残差数据宏块进行 DCT 转换:


    image.png

    去掉相关联的数据后,我们可以看出数据被进一步压缩了:


    image.png

    熵编码(CABAC)

    熵编码压缩是一种无损压缩,其实现原理是使用新的编码来表示输入的数据,从而达到压缩的效果。常用的熵编码有游程编码,哈夫曼编码和CAVLC编码等。

    CABAC

    CABAC(ContextAdaptive Binary Arithmatic Coding)也是 H.264/MPEG-4AVC中使用的熵编码算法。CABAC在不同的上下文环境中使用不同的概率模型来编码。其编码过程大致是这样:首先,将欲编码的符号用二进制bit表示;然后对于每个bit,编码器选择一个合适的概率模型,并通过相邻元素的信息来优化这个概率模型;最后,使用算术编码压缩数据。

    MPEG-2中使用的VLC就是这种算法,我们以 A-Z 作为例子,A属于高频数据,Z属于低频数据。看看它是如何做的:


    image.png

    CABAC也是给高频数据短码,给低频数据长码。同时还会根据上下文相关性进行压缩,这种方式又比VLC高效很多。其效果如下:


    image.png

    现在将 A-Z 换成视频帧,它就成了下面的样子:


    image.png

    关于 iOS 的H.264编解码,请参考 [iOS] H.264编解码(VideoToolbox硬编解码)

    参考链接:
    https://www.jianshu.com/p/0c296b05ef2a
    https://www.jianshu.com/p/8edb448cf22e
    https://blog.csdn.net/andywang201001/article/details/80274886

    相关文章

      网友评论

          本文标题:iOS | H.264介绍 & 编码原理

          本文链接:https://www.haomeiwen.com/subject/jqhfcqtx.html