H264 基础知识

前言

  最近在做 Android 云手机项目,以前是用传图片的方式显示远程手机的屏幕内容,优点是实现起来简单,缺点是流量有些大。现在要改成更加优化的方案:播放 H264 视频流。(视频流数据来自服务器,通过 Socket 传递。并没有使用流媒体协议)。因此先学习了一下 H264 的相关知识。

H264 官方文档最新版下载地址。也可以访问备用地址

基本术语

  • SPS:序列参数集(Sequence parameter set)
  • PPS:图像参数集(Picture parameter set)
  • NAL:网络抽象层(Network Abstract Layer)
  • I Slice,P Slice,B Slice
  • I Frame,P Frame,B Frame
  • IDR Picture

SPS 包含的是针对一连续编码视频序列的参数,如标识符 seq_parameter_set_id、帧数及 POC 的约束、参考帧数目、解码图像尺寸和帧场编码模式选择标识等等。
两个IDR图像之间为序列参数集。

PPS 对应的是一个序列中某一幅图像或者某几幅图像,其参数如标识符 pic_parameter_set_id、可选的 seq_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等等。

序列和图像参数集机制,减少了重复参数的传送,每个VCL NAL单元包含一个标识,指向有关的图像参数集,每个图像参数集包含一个标识,指向有关的序列参数集的内容因此,只用少数的指针信息,引用大量的参数,大大减少每个VCL NAL单元重复传送的信息。

Slice(数据片):组成片的编码数据存放在 3 个独立的 DP(Slice A、B、C)中,各自包含一个编码片的子集。

Slice A 包含片头和片中每个宏块头数据。

Slice B 包含帧内和 SI 片宏块的编码残差数据。

Slice C 包含帧间宏块的编码残差数据。

每个Slice可放在独立的 NAL 单元并独立传输。

编码器将每个NAL各自独立、完整地放入一个分组,因为分组都有头部,解码器可以方便地检测出NAL的分界,并依次取出NAL进行解码。

每个NAL前有一个起始码 0x00 00 01(或者0x00 00 00 01)(我这边实际遇到的都是0x00 00 00 01开头的 ),解码器检测每个起始码,作为一个NAL的起始标识,当检测到下一个起始码时,当前NAL结束。

同时H.264规定,当检测到0x00 00 00时,也可以表征当前NAL的结束。那么NAL中数据出现0x00 00 010x00 00 00时怎么办?H.264引入了防止竞争机制。如果编码器检测到NAL数据存在0x00 00 010x00 00 00时,编码器会在最后1个字节前插入一个新的字节0x03,例如:

0x00 00 00 -> 0x00 00 03 00
0x00 00 01 -> 0x00 00 03 01
0x00 00 02 -> 0x00 00 03 02
0x00 00 03 -> 0x00 00 03 03

解码器检测到0x00 00 03时,把03抛弃,恢复原始数据(脱壳操作)。解码器在解码时,首先逐个字节读取NAL的数据,统计NAL的长度,然后再开始解码。

I 帧与 IDR 帧

  1. 在 H.264 中 I 帧并不具有随机访问的能力,这个功能由 IDR 承担。以前的标准中由 I 帧承担。

  2. IDR 会导致 DPB (Decoded Picture Buffer 参考帧列表。这是关键所在)清空,而 I 不会。

  3. I 和 IDR 帧其实都是I帧,都是使用帧内预测的。但是 IDR 帧的作用是立刻刷新,使错误不致传播,从 IDR 帧开始,重新算一个新的序列开始编码。

  4. IDR 图像一定是 I 图像,但 I 图像不一定是 IDR 图像。一个序列中可以有很多的 I 图像,I 图像之后的图像可以引用 I 图像之间的图像做运动参考。

  5. 一个新的 IDR 帧开始,可以重新算一个新的 GOP 开始编码,播放器永远可以从一个 IDR 帧播放,因为在它之后没有任何帧引用之前的帧。如果一个视频中没有 IDR 帧,这个视频是不能随机访问的。所有位于 IDR 帧后的B帧和P帧都不能参考 IDR 帧以前的帧,而普通 I 帧后的 B 帧和 P 帧仍然可以参考I帧之前的其他帧。IDR 帧阻断了误差的积累,而I帧并没有阻断误差的积累。

  6. 一个 GOP 序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像,但I帧不一定都是 IDR帧,只有 GOP 序列的第1个I帧是 IDR 帧。

    I 帧特点

  • I 帧:帧内编码帧 ,I帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)
  • 它是一个帧内压缩编码帧,压缩比约为7。它将全帧图像信息进行 JPEG 压缩编码及传输;
  • 解码时仅用I帧的数据就可重构完整图像;
  • I 帧描述了图像背景和运动主体的详情;
  • I 帧不需要参考其他画面而生成;
  • I 帧是 P 帧和 B 帧的参考帧(其质量直接影响到同组中以后各帧的质量);
  • 帧是帧组 GOP 的基础帧(第一帧),在一组中只有一个I帧;
  • I 帧不需要考虑运动矢量;
  • I 帧所占数据的信息量比较大。

P帧

P 帧:前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或 P 帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面,P帧没有完整画面数据,只有与前一帧的画面差异的数据。P帧的压缩率20。

  • P 帧是 I 帧后面相隔1~2帧的编码帧
    • P 帧采用运动补偿的方法传送它与前面的 I 或 P 帧的差值及运动矢量(预测误差)
    • 解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像
    • P 帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧
    • P 帧可以是其后面 P 帧的参考帧,也可以是其前后的B帧的参考帧
    • 由于P帧是参考帧,它可能造成解码错误的扩散
    • 由于是差值传送,P帧的压缩比较高

      B帧

      B帧:双向预测内插编码帧。B 帧是双向差别帧,也就是 B 帧记录的是本帧与前后帧的差别,要解码 B 帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B 帧压缩率高,约为 50,但是解码时 CPU 会比较累。
    • B 帧是由前面的 I 或 P 帧和后面的 P 帧来进行预测的
    • B 帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量
    • B 帧是双向预测编码帧
    • B 帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确
    • B 帧不是参考帧,不会造成解码错误的扩散

三种不同的数据形式

SODB 数据比特串 -> 最原始的编码数据

RBSP 原始字节序列载荷 -> 在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若干比特“0”,以便字节对齐

EBSP 扩展字节序列载荷 -> 在RBSP基础上填加了仿校验字节(0X03)即刚刚提到的“防止竞争机制”。
原因是:在NALU加到Annexb上时,需要添加每 组NALU之前的开始码StartCodePrefix,如果该NALU对应的slice为一帧的开始则用4位字节表示,ox00000001,否则用3 位字节表示ox000001.为了使NALU主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将 0x03去掉。也称为脱壳操作。

NAL 单元(NALU)

定义了基于分组和比特流系统的基本格式。

头结构

+————————–+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+–+–+–+–+–+–+–+–+
| F | NRI | Type |
+—————————+

将其转为二进制数据后,解读顺序为从左往右,如下:

(1)第1位 forbidden_zero_bit 禁止位,初始为0。当NAL单元有错误时可设置该值为1,以便接收方纠错或丢掉该单元。
(2)第2~3位 nal_ref_idc 参考级别(重要性指示)。重要级别,0b11(3)表示非常重要。值越大,越重要。解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。
(3)第4~8位 nal_unit_type NAL单元类型

示例1: 0x67(0110 0111)(103)
从左往右4-8位为0 0111,转为十进制7,7对应序列参数集 NALU_TYPE_SPS(序列参数集)(Sequence parameter set)

示例2: 0x68(0110 1000)(104)
从左往右4-8位为0 1000,转为十进制8,8对应序列参数集 NALU_TYPE_PPS(图像参数集)(Picture parameter set)

示例3: 0x65(0110 0101)(101)
从左往右4-8位为0 0101,转为十进制5,5对应 NALU_TYPE_IDR 图像中的片(IDR帧)

示例4: 0x41(0100 0001)(65)
从左往右4-8位为0 0001,转为十进制1,1对应非 IDR 图像中的片

整个 NALU & 0001 1111(0x1F)(31) = 5 即 整个 NALU & 31(十进制) = 5 的是 IDR 帧(也就是NALU Type 等于 5 的,是 IDR 上帧):IdrPicFlag = ( ( nal_unit_type = = 5 ) ? 1 : 0 )

具体NAL单元类型编码表(T-REC-H.264-201906-I!!PDF-E.pdf 第65页),请参见下图:

NAL单元类型编码表

从这个表我们也可以看出来,IDR 的 I 帧是非常重要的,没有它这个序列的所有帧都无法解码。
序列参数集(SPS)和图像参数集(PPS)也很重要。没有序列参数集(SPS),这个序列的帧就无法解码。没有图像参数集(PPS),那么用到这个图像参数集的帧都无法解码。

顺序要求

H.264/AVC标准对送到解码器的NAL单元顺序是有严格要求的,如果NAL单元的顺序是混乱的,必须将其重新依照规范组织后送入解码器,否则解码器不能够正确解码。

  1. 序列参数集 NALU
    必须在传送所有以此参数集为参考的其他NAL单元之前传送,不过允许这些NAL单元中间出现重复的序列参数集NAL单元。
    所谓重复的详细解释为:序列参数集NAL单元都有其专门的标识,如果两个序列参数集NAL单元的标识相同,就可以认为后一个只不过是前一个的拷贝,而非新的序列参数集。
  2. 图像参数集NALU
    必须在所有以此参数集为参考的其他NAL单元之前传送,不过允许这些NAL单元中间出现重复的图像参数集NAL单元,这一点与上述的序列参数集NAL单元是相同的。
  3. 不同基本编码图像中的片段(slice)单元和数据划分片段(data partition)单元在顺序上不可以相互交叉,即不允许属于某一基本编码图像的一系列片段(slice)单元和数据划分片段(data partition)单元中忽然出现另一个基本编码图像的片段(slice)单元片段和数据划分片段(data partition)单元。
  4. 参考图像的影响:如果一幅图像以另一幅图像为参考,则属于前者的所有片段(slice)单元和数据划分片段(data partition)单元必须在属于后者的片段和数据划分片段之后,无论是基本编码图像还是冗余编码图像都必须遵守这个规则。
  5. 基本编码图像的所有片段(slice)单元和数据划分片段(data partition)单元必须在属于相应冗余编码图像的片段(slice)单元和数据划分片段(data partition)单元之前。
  6. 如果数据流中出现了连续的无参考基本编码图像,则图像序号小的在前面。
  7. 如果arbitrary_slice_order_allowed_flag置为1,一个基本编码图像中的片段(slice)单元和数据划分片段(data partition)单元的顺序是任意的,如果arbitrary_slice_order_allowed_flag置为零,则要按照片段中第一个宏块的位置来确定片段的顺序,若使用数据划分,则A类数据划分片段在B类数据划分片段之前,B类数据划分片段在C类数据划分片段之前,而且对应不同片段的数据划分片段不能相互交叉,也不能与没有数据划分的片段相互交叉。
  8. 如果存在SEI(补充增强信息)单元的话,它必须在它所对应的基本编码图像的片段(slice)单元和数据划分片段(data partition)单元之前,并同时必须紧接在上一个基本编码图像的所有片段(slice)单元和数据划分片段(data partition)单元后边。假如SEI属于多个基本编码图像,其顺序仅以第一个基本编码图像为参照。
  9. 如果存在图像分割符的话,它必须在所有SEI 单元、基本编码图像的所有片段slice)单元和数据划分片段(data partition)单元之前,并且紧接着上一个基本编码图像那些NAL单元。
  10. 如果存在序列结束符,且序列结束符后还有图像,则该图像必须是IDR(即时解码器刷新)图像。序列结束符的位置应当在属于这个IDR图像的分割符、SEI 单元等数据之前,且紧接着前面那些图像的NAL单元。如果序列结束符后没有图像了,那么它的就在比特流中所有图像数据之后。
  11. 流结束符在比特流中的最后。

H264有两种封装:
一种是 annexb 模式,传统模式,有startcode,SPS 和 PPS是在 ES 中。
一种是 mp4 模式,一般 mp4 mkv 会有,没有startcode,SPS 和 PPS 以及其它信息被封装在 container 中,每一个 frame 前面是这个 frame 的长度。
很多解码器只支持 annexb 这种模式,因此需要将 mp4 做转换:
在 ffmpeg 中用 h264_mp4toannexb_filter 可以做转换。

关于 Profile

具体含义及详细说明,详见T-REC-H.264-201906-I!!PDF-E.pdf 第285页。
以下是简要说明:
profile_idc的u(8) 表示 Profile 类型。

说明:以下 profile_idc 值均为十进制。

  • 66 Baseline 基本画质(只有 I 片(Slice)和 P 片(Slice),并且 nal_unit_type 不应该等于2,3,4)
    有一种特殊的 Baseline ProfileConstrained Baseline,详见 PDF 文档,第285页。
  • 77 Main 主流画质(只有 I 片(Slice),P 片(Slice)和 B 片(Slice),并且 nal_unit_type 不应该等于2,3,4)
  • 88 Extended 进阶画质
  • 100 High 高级画质(只有 I 片(Slice),P 片(Slice)和 B 片(Slice),并且 nal_unit_type 不应该等于2,3,4)
    有一种特殊的 High ProfileProgressive High profileprofile_idc 为 100,并且 constraint_set4_flag 等于 1)详见 PDF 文档,第287页。
    还有一种 High ProfileConstrained High profileprofile_idc 为 100,并且 constraint_set4_flagconstraint_set5_flag 都等于 1)详见 PDF 文档,第288页。
  • 110 High 10(只有 I 片(Slice),P 片(Slice)和 B 片(Slice),并且 nal_unit_type 不应该等于2,3,4)
    有一种特殊的 High 10 ProfileProgressive High 10 profileprofile_idc 为 110,并且 constraint_set4_flag 等于 1),详见 PDF 文档,第288页。
  • 122 High 4:2:2(只有 I 片(Slice)和 P 片(Slice),并且 nal_unit_type 不应该等于2,3,4)
  • 244 High 4:4:4 Predictive(只有 I 片(Slice)和 P 片(Slice),并且 nal_unit_type 不应该等于2,3,4)
  • High 10 Intra(满足High 10 profil的所有限制条件,所有图像都是 IDR 图像)(profile_idc等 110, 并且 constraint_set3_flag 等于 1)
  • High 4:2:2 Intra(满足 High 4:2:2 profile 的所有限制条件,所有图像都是 IDR 图像)(profile_idc等 122, 并且 constraint_set3_flag 等于 1)
  • High 4:4:4 Intra(满足 High 4:4:4 profile 的所有限制条件,所有图像都是 IDR 图像)(profile_idc等 244, 并且 constraint_set3_flag 等于 1)
  • 44 CAVLC 4:4:4 Intra(满足 High 4:4:4 Intra profile 的所有限制条件)

说明:
另外,每种 Profile 对解码器都有相应的处理要求,具体详见 PDF 文档。

H264 码率控制

  • VBR:Variable BitRate,动态比特率,其码率可以随着图像的复杂程度的不同而变化,因此其编码效率比较高,Motion发生时,马赛克很少。码率控制算法根据图像内容确定使用的比特率,图像内容比较简单则分配较少的码率(似乎码字更合适),图像内容复杂则分配较多的码字,这样既保证了质量,又兼顾带宽限制。这种算法优先考虑图像质量。
  • ABR:Average BitRate,平均比特率 是VBR的一种插值参数。ABR在指定的文件大小内,以每50帧 (30帧约1秒)为一段,低频和不敏感频率使用相对低的流量,高频和大动态表现时使用高流量,可以做为VBR和CBR的一种折衷选择。
  • CBR:Constant BitRate,是以恒定比特率方式进行编码,有Motion发生时,由于码率恒定,只能通过增大QP来减少码字大小,图像质量变差,当场景静止时,图像质量又变好,因此图像质量不稳定。优点是压缩速度快,缺点是每秒流量都相同容易导致空间浪费。
  • CVBR:Constrained Variable it Rate,VBR的一种改进,兼顾了CBR和VBR的优点:在图像内容静止时,节省带宽,有Motion发生时,利用前期节省的带宽来尽可能的提高图像质量,达到同时兼顾带宽和图像质量的目的。这种方法通常会让用户输入最大码率和最小码率,静止时,码率稳定在最小码率,运动时,码率大于最小码率,但是又不超过最大码率。

参考文献

坚持原创及高品质技术分享,您的支持将鼓励我继续创作!