H.264从1999年开始,到2003年形成草案,最后在2007年定稿有待审核。在 ITU 的标准里称为 H.264,在 MPEG 的标准里 MPEG-4的一个组成部分 MPEG-4 Part 10,又叫 Advanced Video Codec,因此常常被称为 MPEG-4 AVC 或者直接叫 AVC。

H264编码原理

在音视频传输过程中,视频文件的传输是一个极大的问题;一段分辨率为1920*1080,每个像素点为 RGB 占用3字节,帧率是25的视频,对于传输带宽要求是:

1920*1080*3*25/1024/1024=148.315MB/s 换算成 bps 则意味着视频每秒带宽为1182.523Mbps,这样的速率对于网络存储是不可接受的,因此视频压缩和编码技术应运而生。

对于视频文件来说,视频由单张图片帧组成,比如每秒25帧,但是图片帧的像素块之间存在相似性,因此视频帧图像可以进行图像压缩;H264采用16*16的分块大小对,视频帧图像进行相似比较和压缩编码,如下图所示:

image.png

H264中的 I 帧、P 帧和 B 帧

H264使用帧内压缩和帧间压缩的方式提高编码压缩率;H264采用了独特的 I 帧、P 帧和 B 帧策略来实现,连续帧之间的压缩;

image.png

帧的分类 中文 意义
I 帧 帧内编码帧 I 帧通常是每个 GOP(MPEG 使用的一种视频压缩技术)的第一个帧,经过适度的压缩,作为随机访问的参考点,可以当初图像。I 帧可以看成是一个图像经过压缩后的产物。
自身可以通过视频解压算法解压成一张单独的完整图片
P 帧 前向预测编码帧 通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧。
需要参考其前面一个I帧或者P帧来生成一张完整的图片
B 帧 双向预测编码帧 即考虑与源图像序列前面已编码帧,也估计源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像,也叫双向预测帧。
要参考其前一个I或者P帧及后面的一个P帧来生成一张完整的图片

压缩率 B>P>I

H264编码结构解析

H264除了实现了对视频的压缩处理之外,为了方便网络传输,提供了对应的视频编码和分片策略;类似于网络数据封装成 IP 帧,在 H264中将其称为组(GOP)、片(slice)、宏块(Macroblock)这些一起组成了 H264的码流分层结构;H264将其组织称为序列、图片、片、宏块、子块五个层次。

image.png

H264将视频分为连续的帧进行传输,在连续的帧之间使用 I 帧、P 帧和 B 帧。同时对于帧内而言,将图像分块为片、宏块和子块进行分片传输;通过这个过程实现对视频文件的压缩包装。一个序列的第一个图像叫做IDR图像(立刻刷新图像)。

注意:IDR 图像一定是 I 帧,I 帧不一定是 IDR 图像

IDR 图像都是 I 帧图像,I 和 IDR 帧都使用帧内预测。I 帧不用参考任何帧,但是之后的 P 帧和 B 帧有可能参考这个 I 帧之前的帧。IDR 就不允许这样,比如(解码的顺序):

IDR1 P4 B2 B3 P7 B5 B6 I10 B8 B9 P13 B11 B12 P16 B14 B15
这里的 B8可以跨过 I10去参考前面的 P7
原始图像:IDR1 B2 B3 P4 B5 B6 P7 B8 B9 I10

IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12
这里的 B9就只能参考 IDR8和 P11,不可以参考 IDR8前面的帧

其核心作用是为了 解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全局输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR 图像之后的图像永远不会使用 IDR 之前的图像的数据来解码。

下面是一个 H264码流的例子,从码流的帧分析可以看出来 B 帧不能被当做参考帧:

image.png

NALU

image.png

  • SPS:序列参数集,SPS 中保存了一组编码视频序列(Coded video sequence)的全局参数。
  • PPS:图像参数集,对应的是一个序列中某幅图像或某几幅图像的参数。
  • I 帧:帧内编码帧,可独立解码生成完整图片
  • P 帧:前向预测编码帧,需要参考前面一个 I 或者 P
  • B 帧:双向预测内插编码帧,则要参考其前一个 I 或者 P 帧及后面一个 P 帧来生成一张完整的图片。

发 I 帧之前,至少要发一次 SPS 和 PPS

NALU 结构

H264原始码流是由一个杰一个 NALU 组成,他的功能分为两层,VCL(视频编码层)和 NAL(网络提取层):

  • VCL:包括核⼼压缩引擎和块,宏块和⽚的语法级别定义,设计⽬标是尽可能地独⽴于⽹
    络进⾏⾼效的编码;
  • NAL:负责将VCL产⽣的⽐特字符串适配到各种各样的⽹络和多元环境中,覆盖了所有⽚级
    以上的语法级别。

在 VCL 进行数据传输或存储之前,这些编码的 VCL 数据被映射或封装进 NAL 单元(NALU)。

一个 NALU=一组对应视频编码的 NALU 头部信息+一个原始字节序列负荷(RBSP,Raw Byte Senquence Payload)

NALU 结构单元的主体结构如下所示:
一个原始的 H.264NALU 单元通常由:[StartCode][NALUHeader][NALU Payload]组成!

  • Start Code:用于表示这是一个 NALU 单元的开始,必须是“00 00 00 01”或“00 00 01”,除此之外基本相当于一个 NAL Header+RBSP

对于 ffmpeg 解复用后,MP4文件读取出来的 packet 是不带 startcode,但是 ts 文件读取出来的 packet 带 startcode!

解析 NALU

每个 NAL 单元是一个一定语语法元素的可变长字节字符串,包括包含一个字节的头信息(用来表示数据类型),以及若干整数字节的负荷数据。

+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+---+---+---+---+---+---+---+---+
| F |   R   |		  T			|
+---+-------+-------------------+
  • T 为负荷数据类型,占5bit
    • nal_unit_type:这个 NALU 单元的类型,1-12由 H264使用,24-31由 H264以外的应用使用
  • R 为重要性指示位,占2bit
    • nal_ref_idc:取00-11,似乎指示这个 NALU 重要性,如00的 NALU 解码器可以丢弃它而不影响图像的回放;0-3取值越大表示当前的 NAL 越重要,需要优先受到保护。如果当前 NAL 是属于参考帧的片或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必须大于0
  • 最后的 F 为禁止位,占1bit
    • 在 H264规范中规定了这一位必须为0

H264标准指出,当数据流是存储在介质上时,在每个 NALU 前添加起始码:0x000001或0x00000001,用来指示一个 NALU 的启示和终止位置:

  • 在这样的机制下,在码流中监测起始码,作为一个 NALU 的起始标识,当检测到下一个起始码时,当前 NALU 结束。
  • 3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个 slice(片)的时候,包含这些 slice 的 NALU 使用3字节起始码,其余场景都是4字节0x00000001。

H264 annexb 模式

H264有两种封装

  • 一种是 annexb 模式,传统模式,有 startcode,SPS 和 PPS 是在 ES 中
  • 一种是 mp4格式,一般 mp4 mkv 都是 mp4格式,没有 startcode,SPS 和 PPS 以及其他信息都被封装在 container 中每一个 frame 前面4字节是这个 frame 的长度。
    很多解码器只支持 annexb 模式,因此需要将 mp4做转换,在 ffmpeg 中用 h264_mp4toannexb_filter 可以做转换。
// 转换方式
const AVBitStreamFilter* bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
AVBSFContext* bsf_ctx = NULL;

// 初始化过滤器上下文
av_bsf_alloc(bsfilter, &bsf_ctx);
// 添加解码器属性
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);

补充说明

GOP(group of pictures)

GOP 指的是两个 I 帧之间的间隔。比如 GOP 为120,如果是720p60的话,那就是2s 一次 I 帧。在视频编码序列中,主要有三种编码帧:I 帧、P 帧和 B 帧。

  • I 帧是 帧内编码图像帧,不惨开其他图像帧,只利用本帧的信息进行编码。
  • P 帧是 预测编码图像帧,利用之前的 I 帧或 P 帧,采用运动预测的方式进行帧间预测编码。
  • B 帧是 双向预测编码图像帧,提供最高的压缩比,它既需要之前的图像帧,也有需要后面的图像帧,采用运动预测的方式进行帧间双向预测编码。

在视频编码序列中,GOP即Group of picture(图像组),指两个I帧之间的距离,Reference(参考周期)指两个P帧之间的距离。⼀个I帧所占⽤的字节数⼤于⼀个P帧,⼀个P帧所占⽤的字节数⼤于⼀个B帧。

所以在码率不变的前提下,GOP值越⼤,P、B帧的数量会越多,平均每个I、P、B帧所占⽤的字节数就越多,也就更容易获取较好的图像质量;Reference越⼤,B帧的数量越多,同理也更容易获得较好的图像质量。

需要说明的是,通过提⾼GOP值来提⾼图像质量是有限度的,在遇到场景切换的情况时,H.264编码器会⾃动强制插⼊⼀个I帧,此时实际的GOP值被缩短了。另⼀⽅⾯,在⼀个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量⽐较差时,会影响到⼀个GOP中后续P、B帧的图像质量,直到下⼀个GOP开始才有可能得以恢复,所以GOP值也不宜设置过⼤。同时,由于P、B帧的复杂度⼤于I帧,所以过多的P、B帧会影响编码效率,使编码效率降低。另外,过⻓的GOP还会影响Seek操作的响应速度,由于P、B帧是由前⾯的I或P帧预测得到的,所以Seek操作需要直接定位,解码某⼀个P或B帧时,需要先解码得到本GOP内的I帧及之前的N个预测帧才可以,GOP值越⻓,需要解码的预测帧就越多,seek响应的时间也越⻓。