zoukankan      html  css  js  c++  java
  • SPS/PPS/NALU细解

    在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。NAL占一个字节。

    H264在网络传输的是NALU,NALU的结构是:NAL头+RBSP,实际传输中的数据流如图所示:


    264句法元素的分层结构

        NAL单元(NALU):NAL的基本语法结构,它包含一个字节的头信息和一系列来自VCL的称为原始字节序列载荷(RBSP)的字节流。 数据流是储存在介质上时: 每个NALU 前添加起始码:0x00000001(或者0x000001),用来指示一个 NALU的起始和终止位置。我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。

        编码器将每个NAL各自独立、完整地放入一个分组,因为分组都有头部,解码器可以方便地检测出NAL的分界,并依次取出NAL进行解码。每个NAL前有一个起始码 0x00 00 01(或者0x00 00 00 01),解码器检测每个起始码,作为一个NAL的起始标识,当检测到下一个起始码时,当前NAL结束。同时H.264规定,当检测到0x000000时,也可以表征当前NAL的结束。那么NAL中数据出现0x000001或0x000000时怎么办?H.264引入了防止竞争机制,如果编码器检测到NAL数据存在0x000001或0x000000时,编码器会在最后个字节前插入一个新的字节0x03,这样:

    0x000000->0x00000300
    0x000001->0x00000301
    0x000002->0x00000302
    0x000003->0x00000303
    解码器检测到0x000003时,把03抛弃,恢复原始数据(脱壳操作)。解码器在解码时,首先逐个字节读取NAL的数据,统计NAL的长度,然后再开始解码。

    NALU头由一个字节组成, 它的语法如下:
          +---------------+
          |0|1|2|3|4|5|6|7|
          +-+-+-+-+-+-+-+-+
          |F|NRI|  Type   |
          +---------------+
    F: 1 个比特.
      forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.
    NRI: 2 个比特.
      nal_ref_idc. 取 00 ~ 11, 似乎指示这个 NALU 的重要性, 如00的NALU解码器可以丢弃它而不影响图像的回放,0~3,取值越大,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需大于0。
    Type: 5 个比特.
     nal_unit_type. 这个 NALU 单元的类型. 简述如下:
    0    没有定义
    1     一个非IDR图像的编码条带 (bp帧)
    slice_layer_without_partitioning_rbsp( )      
    2     编码条带数据分割块A 
    slice_data_partition_a_layer_rbsp( )      
    3     编码条带数据分割块B 
    slice_data_partition_b_layer_rbsp( )      
    4     编码条带数据分割块C 
    slice_data_partition_c_layer_rbsp( )      
    5     IDR图像的编码条带 (i帧)
    slice_layer_without_partitioning_rbsp( )      
    6     辅助增强信息 (SEI) 
    sei_rbsp( )      
    7     序列参数集 (sps帧)
    seq_parameter_set_rbsp( )      
    8     图像参数集 
    pic_parameter_set_rbsp( pps帧)      
    9     访问单元分隔符 
    access_unit_delimiter_rbsp( )      
    10     序列结尾 
    end_of_seq_rbsp( )      
    11     流结尾 
    end_of_stream_rbsp( )      
    12     填充数据 
    filler_data_rbsp( )      
    13     序列参数集扩展 
    seq_parameter_set_extension_rbsp( )      
    14...18     保留      
    19     未分割的辅助编码图像的编码条带 
    slice_layer_without_partitioning_rbsp( )      
    20...23     保留      
    24    STAP-A   单一时间的组合包
    25    STAP-B   单一时间的组合包
    26    MTAP16   多个时间的组合包
    27    MTAP24   多个时间的组合包
    28    FU-A     分片的单元
    29    FU-B     分片的单元
    30-31 没有定义

    当遇到 00 00 00 01 67表示sps帧

    当遇到 00 00 00 01 68 表示pps帧

    由于NAL的语法中没有给出长度信息,实际的传输、存储系统需要增加额外的头实现各个NAL单元的定界。

    其中,AVI文件和MPEG TS广播流采取的是字节流的语法格式,即在NAL单元之前增加0x00000001的同步码,则从AVI文件或MPEG TS PES包中读出的一个H.264视频帧以下面的形式存在:

    00 00 00 01 06 ... 00 00 00 01 67 ... 00 00 00 01 68 ... 00 00 00 01 65 ...
    SEI信息             SPS                PPS                IDR Slice

    而对于MP4文件,NAL单元之前没有同步码,却有若干字节的长度码,来表示NAL单元的长度,这个长度码所占用的字节数由MP4文件头给出;此外,从MP4读出来的视频帧不包含PPS和SPS,这些信息位于MP4的文件头中,解析器必须在打开文件的时候就获取它们。从MP4文件读出的一个H.264帧往往是下面的形式(假设长度码为2字节):

    00 19 06 [... 25 字节...] 24 aa 65 [... 9386 字节...]
    SEI信息                   IDR Slice

    h264语法相关算法解析 

    1、无符号指数哥伦布熵编码

    1.1 编码过程

    1、将待编码的数加1转换为最小的二进制序列(假设一共M位); 
    2、此二进制序列前面补充M-1个0; 
    3、enjoy!

    1.1.1 示例

    对 4 进行无符号指数哥伦布熵编码 
    1、将4加1(为5)转换为最小的二进制序列即 101 (此是M=3) 
    2、此二进制序列前面补充M-1即两个0 
    3、得出的4的无符号指数哥伦布熵编码的序列为 00101

    1.2 解码过程

    1、获取二进制序列开头连续的N个0 
    2、读取之后的N+1位的值,假设为X 
    3、X-1获取解码后的值

    1.2.1 示例

    如对 00101进行无符号指数哥伦布熵解码 
    1、获取开头连续的N个0, 此时N = 2 
    2、再向后读取N+1位的值,即 101,为5 
    3、 5 - 1 =4 获取其解码后码值,enjoy!

    1.3 其他

    注意0的无符号指数哥伦布熵编码的二进制序列为 1

    2 有符号指数哥伦布熵编码

    2.1 编码过程

    1、将待编码的数的绝对值转换为最小的二进制序列(假设一共M位) 
    2、在此二进制序列后补充一位符号位0表示正,1表示负 
    3、在此二进制序列前补充M个0 
    4、enjoy

    2.1.1 示例1

    如对4进行有符号指数哥伦布熵编码 
    1、4的绝对值转为最小二进制序列,即 100 (此时M = 3) 
    2、后面补充符号位,0 即 1000 
    3、前面补充M个0, 即 0001000 
    4、enjoy

    2.1.2 示例2

    如对-15进行有符号指数哥伦布熵编码 
    1、-7的绝对值转为最小二进制序列,即 1111 (此时M = 4) 
    2、后面补充符号位,1,即 11111 
    3、前面补充M个0,即 000011111 
    4、enjoy

    2.2 解码过程

    1、获取二进制序列开头连续的N个0 
    2、读取之后的N位的值,假设为X 
    3、获取最后1位符号位 
    4、获取解码后码值

    2.2.1 示例1

    如对二进制序列 0001000 进行有符号指数哥伦布熵解码 
    1、获取开头连续的N个0, 此时N = 3 
    2、再获取N为数值,即 100 即为4 
    3、获取最后的符号位,0,即为正值 
    4、故此序列解码后的码值为4

    2.2.2 示例2

    如对二进制序列 000011111 进行有符号指数哥伦布熵解码 
    1、获取开头连续的N个0, 此时N = 4 
    2、再获取N为数值,即 1111 即为15 
    3、获取最后的符号位,1,即为负值 
    4、故此序列解码后的码值为-15

    sps语法

     

    pps语法

     

    提高技能如同提升自信心。
  • 相关阅读:
    作为一个前端,可以如何机智地弄坏一台电脑?
    Mysql数据库字符集问题
    代码扫描工具 SonarQube Scanner 配置 & Jenkins 集成
    【C++】统计代码覆盖率(四)
    【Jenkins】各项配置
    python小知识点汇总
    MobaXterm使用
    PHP代码覆盖率
    golang代码覆盖率
    压测工具Locuse的使用
  • 原文地址:https://www.cnblogs.com/chims-liu-touch/p/7986624.html
Copyright © 2011-2022 走看看