天天看点

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

        视频有很多种编码标准,H.264,H.265,AV1等等,其中我们可能最常见的便是H.264,因此,本文我们就主要来分下下H.264编码的码流结果具体是怎么样的。

一、前置知识

        了解H264码流结构之前,需要先了解一些前置的知识。

1、I/P/B帧和GOP

        需要先了解图像序列的层次结构,也就是一帧帧视频帧的一些概念。这些可以参考我前面的一篇文章 视频相关的一些基本概念 ,会比较系统和具体地讲解。

2、Slice

        上面主要是图像序列的层次结构,而图像的内部层次结构是怎么样的,这主要涉及Slice这个概念。

        Slice其实就是“片”的概念,图像内的层次结构就是一帧图像可以划分成一个或多个 Slice,而一个 Slice 包含多个宏块,且一个宏块又可以划分成多个不同尺寸的子块。大概就是类似下面这样的结构图:

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

二、H264码流结构

1、码流格式

        H264 码流有两种格式:一种是 Annexb 格式;一种是 MP4 格式。

(1)、Annexb 格式使用起始码来表示一个编码数据的开始。起始码本身不是图像编码的内容,只是用来分隔用的。起始码有两种,一种是 4 字节的“00 00 00 01”,一种是 3 字节的“00 00 01”。

        由于图像编码出来的数据中也有可能出现“00 00 00 01”和“00 00 01”的数据。为了防止出现这种情况,H264 会将图像编码数据中的下面的几种字节串做如下处理:

        “00 00 00”修改为“00 00 03 00”;

        “00 00 01”修改为“00 00 03 01”;

        “00 00 02”修改为“00 00 03 02”;

        “00 00 03”修改为“00 00 03 03”。

        其实也就是转义,同样地在解码端,我们在去掉起始码之后,也需要将对应的字节串转换回来。

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

 (2)、MP4 格式没有起始码,而是在图像编码数据的开始使用了 4 个字节作为长度标识,用来表示编码数据的长度,这样我们每次读取 4 个字节,计算出编码数据长度,然后取出编码数据,再继续读取 4 个字节得到长度,一直继续下去就可以取出所有的编码数据了。

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

         这两种格式差别不大,接下来我们主要使用 Annexb 格式来讲解 H264 码流结构。

2、SPS和PPS

        视频编码的时候还有一些编码参数数据的,为了能够将一些通用的编码参数提取出来,不在图像编码数据中重复,H264 设计了两个重要的参数集:一个是 SPS(序列参数集);一个是 PPS(图像参数集)。

        SPS 主要包含的是图像的宽、高、YUV 格式和位深等基本信息;PPS 则主要包含熵编码类型、基础 QP 和最大参考帧数量等基本编码信息。

        如果没有 SPS、PPS 里面的基础信息,之后的 I 帧、P 帧、B 帧就都没办法进行解码。因此 SPS 和 PPS 是至关重要的。

        这样的话,H264码流主要包含了 SPS、PPS、I 帧、P 帧和 B 帧。由于帧又可以划分成一个或多个 Slice。因此,帧在码流中实际上是以 Slice 的形式呈现的。所以,H264 的码流主要是由 SPS、PPS、I Slice、P Slice和B Slice 组成的。如下图所示:

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

 3、NALU

        上面说到H264码流的组成部分,但是每个部分是如何区分开?为了解决这个问题,H264 设计了 NALU(网络抽象层单元)。SPS 是一个 NALU、PPS 是一个 NALU、每一个 Slice 也是一个 NALU。每一个 NALU 又都是由一个 1 字节的 NALU Header 和若干字节的 NALU Data 组成的。而对于每一个 Slice NALU,其 NALU Data 又是由 Slice Header 和 Slice Data 组成,并且 Slice Data 又是由一个个 MB Data 组成。其结构如下:

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

        其中,NALU Header总共占用 1 个字节,具体如下图所示。

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

        其中,

        --> F:forbidden_zero_bit,占 1bit,禁止位,H264 码流必须为 0;

        --> NRI: nal_ref_idc,占 2bits,可以取 00~11,表示当前 NALU 的重要性。参考帧、SPS 和 PPS 对应的 NALU 必须要大于 0;

        --> Type: nal_unit_type,占 5bits,表示 NALU 类型。其取值如下表所示。

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

         有了这个,我们解析出 NALU Header 的 Type 字段,查询表格就可以得到哪个 NALU 是 SPS,哪个是 PPS,以及哪个是 IDR 帧了。

        不过NALU 类型只区分了 IDR Slice 和非 IDR Slice,至于非 IDR Slice 是普通 I Slice、P Slice 还是 B Slice,则需要继续解析 Slice Header 中的 Slice Type 字段得到。

        下面我们通过两个例子来看看常见的 NALU 里的 NALU Header 是怎样的:

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

         下面我们再来看一个实际码流的例子,看看在实际编码出来的二进制数据中,各种 NALU 是怎么“放置”在数据中的。下图是用二进制查看工具打开实际编码后的码流数据。我们可以看到在码流的开始部分是一个起始码,之后紧接着是一个 SPS 的 NALU。在 SPS 后面是一个 PPS 的 NALU。然后就是一个 IDR Slice 的 NALU 和一个非 IDR Slice NALU。

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的
H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

 三、如何判断哪几个 Slice 是同一帧的

         根据上面的分析,在H264 码流中,帧是以 Slice 的方式呈现的,或者可以说在 H264 码流里是没有“帧“这种数据的,只有 Slice。

        那么有个问题,一帧有几个 Slice 是不知道的。也就是说码流中没有字段表示一帧包含几个 Slice。既然没有办法知道一帧有几个 Slice,那我们如何知道多 Slice 编码时一帧的开始和结束分别对应哪个 Slice 呢?

        其实,Slice NALU 由 NALU Header 和 NALU Data 组成,其中 NALU Data 里面就是 Slice 数据,而 Slice 数据又是由 Slice Header 和 Slice Data 组成。在 Slice Header 开始的地方有一个 first_mb_in_slice 的字段,表示当前 Slice 的第一个宏块 MB 在当前编码图像中的序号。我们只要解析出这个宏块的序号出来。

        --> 如果 first_mb_in_slice 的值等于 0,就代表了当前 Slice 的第一个宏块是一帧的第一个宏块,也就是说当前 Slice 就是一帧的第一个 Slice。

        --> 如果 first_mb_in_slice 的值不等于 0,就代表了当前 Slice 不是一帧的第一个 Slice。并且,使用同样的方式一直往下找,直到找到下一个 first_mb_in_slice 为 0 的 Slice,就代表新的一帧的开始,那么其前一个 Slice 就是前一帧的最后一个 Slice 了。

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

         其中,first_mb_in_slice 是以无符号指数哥伦布编码的,需要使用对应的解码方式才能解码出来。但是有一个小技巧,如果只是需要判断 first_mb_in_slice 是不是等于 0,不需要计算出实际值的话,只需要通过下面的方式计算就可以了。

H.264码流结构是怎么样的一、前置知识二、H264码流结构 三、如何判断哪几个 Slice 是同一帧的

         以上便是对H264码流结构的讲解。

继续阅读