• H.264 视频编解码技术分享AnnexB 和 avcC


    引言

    目前 H.264 流行的包装方式有两种,一种叫做 AnnexB,一种叫做 avcC。对于这两种格式,各家的支持程度也不太一样,例如,Android 硬解码 MediaCodec 只接受 AnnexB 格式的数据,而 Apple 的 VideoToolBox,只支持 avcC 的格式。所以这就需要我们从业者对两种格式都有一个了解。本章,我们先来介绍 AnnexB

    AnnexB

    假如我们把多个 NALU 写到一个文件里面去,多个 NALU 首位相连穿成一串,因为 NALU 本身长度不一,也没有具体的标识符用来表明自己是一个独立的 NALU,那么我们在读取这个文件的时候其实并没有办法将写到一起 NALU 有效得进行区分。为了解决这个问题,我们必须给 NALU 添加上一些数据,将各个 NALU 进行分割。 AnnexB 就是用来对 NALU 层进行包装的一种格式。

    AnnexB 格式的原理非常简单,就是在一个 NALU 前面加上三个或者四个字节,这些字节的内容是 0 0 0 1 或者 0 0 1。当我们读取一个 H264 流的时候,一旦遇到 0 0 0 1 或者 0 0 1,我们就认为一个新的 NALU 开始了,因此,这些用来做分隔符的字节,一般也被称为 start code, 起始码。

    防竞争字节 (Emulation Prevention Bytes)

    但是只在 NALU 前面加上起始码是会产生问题了,因为原始码流中,是有可能出现 0 0 0 1 或者 0 0 1 的,这样就会导致读取程序将一个 NALU 误分割成多个 NALU。为了防止这种情况发生,AnnexB 引入了防竞争字节(Emulation Prevention Bytes)的概念。

    所谓防竞争字节(Emulation Prevention Bytes),就是在给 NALU 添加起始码之前,先对码流进行一次遍历,查找码流里面的存在的 0 0 0,0 0 1,0 0 2,0 0 3 的字节,然后对其进行如下修改

    0 0 0 => 0 0 3 0
    0 0 1 => 0 0 3 1
    0 0 2 => 0 0 3 2
    0 0 3 => 0 0 3 3复制

    即在上面的 4 种情况下,在 0 0 之后,插入一个字节,内容是 3。经过这样处理的码流,就不会再和起始码(0 0 1, 0 0 0 1)重复而发生冲突。

    当然,在解码过程中,通过起始码成功分割 NALU 数据之后,还要将防竞争字节去掉。

    0 0 3 0 => 0 0 0
    0 0 3 1 => 0 0 1
    0 0 3 2 => 0 0 2
    0 0 3 3 => 0 0 3复制

    这样才能得到真正的 NALU 码流。

    avcC

    AnnexB 的原理是在每个 NALU 前面写上一个特殊的起始码,通过这个起始码来当做 NALU 的分隔符,从而分割每个 NALU。而 avcC 则采用了另外一种方式。那就是在 NALU 前面写上几个字节,这几个字节组成一个整数(大端字节序)这个整数表示了整个 NALU 的长度。在读取的时候,先把这个整数读出来,拿到这个 NALU 的长度,然后按照长度读取整个 NALU。

    avcC 详解

    在介绍 avcC 格式之前,我们先来介绍一下两个特殊的 NALU,这两个 NALU 就是 SPS 和 PPS,SPS 和 PPS 存放了解码一路 H.264 码流的必要的参数信息,也就是说,你想要解码一路 H.264,就必须首先获取到 SPS 和 PPS。在后面的课程中,我们会详细介绍 SPS 和 PPS,现在你只需要知道,SPS 和 PPS 是特殊且重要的两个 NALU。

    在 AnnexB 中,SPS 和 PPS 被当做了普通的 NALU 进行处理;而在 avcC 中,SPS 和 PPS 信息被当做了特殊的信息进行了处理。

    在一路采用 avcC 打包的 H.264 流之中,我们首先看到的将是一段被称之为 extradata 的数据,这段数据定义了这个 H.264 流的基本属性数据,当然,也包含了 SPS 和 PPS 数据。

    我们来看一下 extradata 数据格式

    bits      
    8   version ( always 0x01 )  
    8   avc profile ( sps[0][1] )  
    8   avc compatibility ( sps[0][2] )  
    8   avc level ( sps[0][3] )  
    6   reserved ( all bits on )  
    2   NALULengthSizeMinusOne    // 这个值是(前缀长度-1)
    3   reserved ( all bits on )  
    5   number of SPS NALUs (usually 1)  
            repeated once per SPS:  
    16  SPS size  
            variable SPS NALU data  
    8   number of PPS NALUs (usually 1)  
            repeated once per PPS  
    16  PPS size  
            variable PPS NALU data复制

    我们注意一下这个值 NALULengthSizeMinusOne,通过将这个值加 1 ,我们就得出了后续每个 NALU 前面前缀(也就是表示长度的整数)的字节数

    例如,这个 NALULengthSizeMinusOne 是 3,那么每个 NALU 前面前缀的长度就是 4 个字节。我们在读取后续数据时,可以先读 4 个字节,然后把这四个字节转成整数,就是这个 NALU 的长度了,注意,这个长度并不包含起始的4个字节,是单纯 NALU 的长度。

  • 相关阅读:
    2019-4-16-C#-使用反射获取私有属性的方法
    2019-5-21-C#-命令行如何静默调用-del-删除文件
    2019-6-14-WPF-shows-that-some-windows-in-multithreading-will-be-locked-in-the-PenThreadWorker-constr...
    2019-9-11-完整的-P2P-应用需要包含哪些功能
    2019-3-1-VisualStudio-扩展开发-获得输出窗口内容
    2018-10-19-C#-序列类为-xml-可以使用的特性大全
    2018-8-10-win10-uwp-手把手教你使用-asp-dotnet-core-做-cs-程序
    2018-10-31-win10-uwp-使用-asp-dotnet-core-做图床服务器客户端
    2018-8-10-docfx-做一个和微软一样的文档平台
    linux分区方案
  • 原文地址:https://www.cnblogs.com/lidabo/p/15840270.html
Copyright © 2020-2023  润新知