zoukankan      html  css  js  c++  java
  • 【H264】码流结构详解

    无论是解析视频文件或者通过网络传输,其实都是一串字节序列。H264 码流就是按照一定的规则组织排列的字节串。


    一、码流的组织形式

    在 H264 中完全没有 I 帧、P 帧、B 帧、IDR 帧的概念,之所以沿用这些说法是为了表明数据的编码模式。H264 码流的组织形式从大到小排序是:视频序列(video sequence)、图像(frame/field-picture)、片组(slice group)、片(slice)、宏块(macroblock)、子块(sub-block)、像素(pixel)。
    H264_Struct_A.png


    二、码流功能的角度

    从码流功能的角度可以分为两层:视频编码层(VCL)和网络提取层(NAL)

    • VCL:进行视频编解码,包括预测(帧内预测和帧间预测),DCT 变化和量化,熵编码和切分数据等功能,是为了实现更高的视频压缩比。
    • NAL:负责以网络所要求的恰当的方式对 VCL 数据进行打包和传送。

    VCL 是管理 H264 的视频数据层,是为了实现更高的视频压缩比,那 VCL 究竟是怎么管理 H264 视频数据的呢?抛开 H264 压缩算法细节来看就 3 步:

    1. 压缩:预测(帧内预测和帧间预测)-> DCT 变化和量化 -> 比特流编码;
    2. 切分数据,主要为了第三步。这里一点,网上看到的“切片(slice)”、“宏块(macroblock)”是在VCL 中的概念,一方面提高编码效率和降低误码率、另一方面提高网络传输的灵活性。
    3. 压缩切分后的 VCL 数据会包装成为 NAL 中的一部分。

    下面要重点讲解下 NAL。


    三、网络提取层(NAL)

    NAL,英文全称为 Network Abstraction Layer,这块和 H264 压缩算法无关,涉设计出 NAL 的目的就是为了获得 “network-friendly”,即为了实现良好的网络亲和性,即可适用于各种传输网络。

    终于要讲 NAL 了,但是,我们需要先看 NAL的组成单元 - NALU


    3.1 NAL单元 - NALU

    NALU 的格式如下图(引用H264 PDF)所示:

    FFmpeg_H264_NALU.png


    很明显,NALU 由身体两个部分组成:

    • 头:一般存储标志信息,譬如 NALU 的类型。前面讲过 NAL 会打包 VCL 数据,但是这并不意味着所有的 NALU 负载的都是 VCL,也有一些 NALU 仅仅存储了和编解码信息相关的数据;
    • 身体:存储了真正的数据。但实际上,这块也会相对比较复杂,前面其实也提到过 H264 的一个目的是“网络友好性”,说白了就是能够很好地适配各种传输格式。所以根据实际采用数据传输流格式,也会对这部分数据格式再进行处理。

    3.2 NALU Header

    首先,NALU Header只占 1 个字节,即 8 位,其组成如下图所示:

    H264_Struct_B.jpg


    • forbidden_zero_bit

      在网络传输中发生错误时,会被置为 1,告诉接收方丢掉该单元;否则为 0。

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

    • nal_unit_type
      表示 NALU 数据的类型,有以下几种:

    H264_Struct_C.png


    其中比较注意的应该是以下几个:

    • 1-4:I/P/B帧,如果 nal_ref_idc 为 0,则表示 I 帧,不为 0 则为 P/B 帧。
    • 5:IDR帧,I 帧的一种,告诉解码器,之前依赖的解码参数集合(接下来要出现的 SPSPPS 等)可以被刷新了。
    • 6:SEI,英文全称 Supplemental Enhancement Information,翻译为“补充增强信息”,提供了向视频码流中加入额外信息的方法。
    • 7:SPS,全称 Sequence Paramater Set,翻译为“序列参数集”。SPS 中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。
    • 8: PPS,全称 Picture Paramater Set,翻译为“图像参数集”。该类型保存了整体图像相关的参数。
    • 9:AU 分隔符,AU 全称 Access Unit,它是一个或者多个 NALU 的集合,代表了一个完整的帧,有时候用于解码中的帧边界识别。

    特殊的 NALU 类型:SPS和PPS

    SPSPPS 存储了编解码需要一些图像参数,SPS,PPS 需要在 I 帧前出现,不然解码器没法解码。而 SPS,PPS 出现的频率也跟不同应用场景有关,对于一个本地 h264 流,可能只要在第一个 I 帧前面出现一次就可以,但对于直播流,每个 I 帧前面都应该插入 sps 或 pps,因为直播时客户端进入的时间是不确定的。


    3.3 NALU Payload

    很少有资料会称身体部分为 Payload,绝大部分资料对 NALU 组成的定义是这样子的:

    NALU = NALU Header + SODB // 定义1
    NALU = NALU Header + RBSP // 定义2
    NALU = NALU Header + EBSP // 定义3
    

    于是新的问题来了:SODB,RBSP和EBSP都是什么东西呢?这块概念,在博客NALU详解二(EBSP、RBSP与SODB)中介绍得非常清楚,总结来说就是:

    SODB

    英文全称 String Of Data Bits,称原始数据比特流,就是最原始的编码/压缩得到的数据。

    RBSP

    英文全称 Raw Byte Sequence Payload,又称原始字节序列载荷。和 SODB 关系如下:

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

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

    EBSP

    英文全称 Encapsulated Byte Sequence Payload,称为扩展字节序列载荷。和 RBSP 关系如下:

    EBSP :RBSP插入防竞争字节(`0x03`)
    

    这里说明下防止竞争字节(0x03):读者可以先认为 H264 会插入一个叫做 StartCode 的字节串来分割 NALU,于是问题来了,如果 RBSP 中也包括了 StartCode(0x000001 或 0x00000001)怎么办呢?所以,就有了防止竞争字节(0x03)

    编码时,扫描 RBSP,如果遇到连续两个 0x00 字节,就在后面添加 防止竞争字节(0x03);解码时,同样扫描 EBSP,进行逆向操作即可。
    

    最后,以一幅图总结 NALU 这段内容:

    H264_Struct_D.jpg


    四、码流解析的角度

    H264 码流实际可以理解为由一个一个的 NALU 单元组成。(下图中的 RBSP 类似 NALU Payload)

    H264_Struct_E.png


    前面提到的一帧图像(I 帧, P 帧, B 帧)就是一个 NALU 单元,NALU 单元除了代表图像外还能包含其他类型的数据,如 PPS 和 SPS。


    五、H264更详细的分层结构

    H264_Struct_F.png


    • 第一层:比特流。该层有两种格式:Annexb 格式和 RTP 格式。
    • 第二层:NAL Unit 层。包含了 NAL Header 和 NAL Body 信息。
    • 第三层:Slice 层。一帧视频图像可编码成一个或者多个片,每片包含整数个宏块,即每片至少 一个宏块,最多时包含整个图像的宏块。
    • 第四层:Slice data 层。Slice 由宏块(macro block, MB)组成。宏块是编码处理的基本单元。
    • 第五层:PCM 类。
    • 第六层:残差层。

    片的目的:

    为了限制误码的扩散和传输,使编码片相互间保持独立。片共有 5 种类型: I 片(只包含 I 宏块)、P 片(P 和 I 宏块)、B 片(B 和 I 宏块)、SP 片(用于不同编码流之 间的切换)和 SI 片(特殊类型的编码宏块)。


    六、扩展:怎么区分 NALU 的边界?

    了解了 NALU 之后,关于 H264 格式,还有一个问题:解码器怎么知道一个 NALU 要结束了?或者说它怎么区分 NALU 的边界?

    要回答这个问题,就必须了解 H264 的打包方式,通俗来说是H264 如何组织一连串的 NALU 为完整的 H264 码流。目前 H264 主流的两种格式:

    • Annex-B:本文关于 NALU 的很多细节介绍都是 Annex-B,它依靠前文提到的 Start Code 来分隔 NALU,打包方式如下:

      [start code]--[NALU]--[start code]--[NALU]...
      
    • AVCC:笔者对这个格式了解的不多,从网上找到很多资料知道以下几点:

      • NALUextradata/sequence header 组成,由于在 extradata/sequence header 中存储了 NALU 的长度,因此 NALU Payload 不需要做字节对齐,不过防竞争字节还是有的;

      • SPSPPS 被放在了 extradata/sequence header

      • 打包方式如下:

        [SIZE (4 bytes)]--[NAL]--[SIZE (4 bytes)]--[NAL]... // 请注意,SIZE一般为4字节,但是具体以实际为准
        

    至于为什么要有这两类格式,还需要查阅更多的资料。不过 StackOverflow 上关于Possible Locations for Sequence/Picture Parameter Set(s) for H.264 Stream的回答可以帮助深入了解这两种格式,推荐阅读。


    下面是一个 H264 码流,可以看到每个 NALU 前有一个 StartCode(0x000001 或 0x00000001),作为 NALU 的分割符:

    H264_Struct_G.jpg


    分析其中比较有代表性的3帧:

    • 00 00 00 01 67
      00 00 00 01 是一个 NALU 开始,67 是Header, 二进制为 0110 0111, nal_unit_type 为00111,即7为 SPS 帧。
    • 00 00 00 01 68
      68 二进制为 0110 1000,nal_unit_type 为00111,即 8 为 SPS 帧。
    • 00 00 00 01 65
      65 二进制为 0110 0101,nal_unit_type 为 00101, 即 5 为 IDR 帧。

    参考:

    H264系列--码流组成和分层结构

    知乎 - 视频和视频帧:H264编码格式整理

    (十三) h264标准解析


  • 相关阅读:
    常见寻找OEP脱壳的方法
    Windows内核原理系列01
    HDU 1025 Constructing Roads In JGShining's Kingdom
    HDU 1024 Max Sum Plus Plus
    HDU 1003 Max Sum
    HDU 1019 Least Common Multiple
    HDU 1018 Big Number
    HDU 1014 Uniform Generator
    HDU 1012 u Calculate e
    HDU 1005 Number Sequence
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/14533228.html
Copyright © 2011-2022 走看看