H264编码原理及NALU介绍


一、简述

流媒体编解码流程大致如图1所示:

【流媒体编解码流程 图1】
H264编码原理及NALU介绍

视频数据编解码层格式包含有:H264,H265,MPEG4等。

本文我们主要对H264编码原理进行整理,并对NALU做简要介绍。


二、H264编解码

2.1、H264简介

参考来源:H264百度百科

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

比如下面使用 MediaInfo工具查看flv音视频文件的信息,可以看到video格式为AVC,其实也就是H264格式。

【flv音视频文件基本信息 图2】
H264编码原理及NALU介绍

2.2、H264编解码原理

参考来源:H264 编解码协议详解深入浅出理解视频编码H264结构h264编解码结构框图

1、H264概述

问题:为什么要对音视频文件进行H264编解码?


因为,在音视频传输过程中,视频文件的传输是个极大的问题;一段分辨率为1920 * 1080,每个像素点为RGB占用3个字节,帧率是25的视频,对于传输带宽的要求是:1920 * 1080 * 3 * 25/1024/1024=148.315MB/s,换成bps则意味着视频每秒带宽为1186.523Mbps,这样的速率对于网络存储是不可接受的。因此视频压缩和编码技术应运而生。

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

【图像切分 图3】
H264编码原理及NALU介绍
压缩编码可以分为内部压缩和外部压缩。

1)内部压缩
内部压缩指的是一帧图片的内部压缩。当H264对图片进行 16 * 16 分块后,会对每个小块内的图像进行分析,如果2个小块图像比较相近,那么住需要存储一张即可,无需存储重复图块。这样可以有效压缩图片的存储大小。

比如下面一张图片,划分的A、B小块图像分析后是基本一样的,那么只需要存储A即可,B不需要进行存储。

【内部压缩 图4】
H264编码原理及NALU介绍

2)外部压缩
外部压缩指的是图片间的图像压缩。在每帧图片划分成16 * 16 小块的图像进行分析基础上,比图片间的数据,如果两张图片比较相近,对相同的图像模块只需存储一份,对不同的部分再做存储。避免了重复数据的存储,极大改善了图片压缩空间。

比如下面两张图片 ,除了E小块不同之外,其他都一样,那么存储图1数据后,图2片只需要存储与图片1不同的数据即可。

【外部压缩 图5】
H264编码原理及NALU介绍


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

【H264 IBP帧排序 图6】
H264编码原理及NALU介绍
1)I 帧 (帧内编码帧 intra picture)
I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是一个图像经过压缩后的产物。自身可以通过视频解压算法解压成一张单独的完整的图片。


2)P帧 (前向预测编码帧 predictive-frame)
通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧。需要参考其前面的一个I frame 或者P frame来生成一张完整的图片。


3)B 帧 (双向预测帧 bi-directional interpolated prediction frame)
既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像,也叫双向预测帧。则要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片。

压缩率比较:B帧 > P 帧 > I 帧



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

【H264结构组织 图7】
H264编码原理及NALU介绍

H264将视频分为连续的帧进行传输,在连续的帧之间使用 I帧、P帧和B帧。同时对于帧内而言,将图像分块为片、宏块和字块进行分片传输;通过这个过程实现对视频文件的压缩包装。
GOP (图像组)主要用作形容一个IDR帧 到下一个IDR帧之间的间隔了多少个帧。

IDR(Instantaneous Decoding Refresh,即时解码刷新)
一个序列的第一个图像叫做IDR图像(立即刷新图像),IDR图像都是 I 帧图像。(I帧图像不一定是IDR图像)
I 帧和IDR帧都使用帧内预测。I 帧不用参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之前的帧。但 IDR不允许这样做。

比如原始图像帧序为: IDR1 B2 B3 P4 B5 B6 P7 B8 B9 I10
解码顺序:

  • IDR1 P4 B2 B3 P7 B5 B6 I10 B8 B9 P13 B11 B12 P16 B14 B15 这里的B8可以跨过I10去参考P7
  • IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12 这⾥的B9就只能参照IDR8和P11,不可以
    参考IDR8前面的帧

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

下图为一个 H264 码流的示例(从码流帧分析可以看出来B帧不能被当作参考帧)

【H264 码流的示例 图8】
H264编码原理及NALU介绍
做直播时,一般不用B帧,因为B帧需要占用较大的缓存,并且容易出现延迟。因为B帧要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片,因此在编码的时候B帧要等到P帧才能发送出去。

比如:收到的I、B、P帧的序列为(后面数值表示收到时间,单位 ms):I0 B40 B80 B120 P160,P帧是160ms的时候才收到, 这样B40帧从收到到发出就会延迟 160-40=120ms。


三、NALU介绍


【NALU示意 图8】
H264编码原理及NALU介绍

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


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

  • VCL:包括核心压缩引擎和块,宏块和片的语法级别定义,设计目标是尽可能地独立于网络进行高效的编码。
  • NAL:负责将VCL产生的比特字符串适配到各种各样的网络和多元环境,覆盖所有片级以上的语法级别。

在VCL进行数据传输或存储之前,这些编码的VCL数据,被映射或封装进NAL单元。
NALU=对应视频编码的NALU头部信息+原始字节序列负荷(RBSP,RawByte Sequence Payload)


【NALU结构单元的主体结构 图9】
H264编码原理及NALU介绍
一个原始的H264 NALU单元通常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成,其中Start Code用于表示这是一个NALU单元的开始,必须是“00 00 00 01” 或 “00 00 01”,除此之外基本相当于一个NAL header+RBSP。

对于FFmpeg解复用,MP4,flv等文件读取出来的packet不带Start code,TS文件读取出来的packet带StartCode,因此在对MP4、flv文件解码封装的的时候,需要添加上Startcode,否则会出现生成文件损坏导致无法播放问题。


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

NALU头信息(占一个字节大小):
【NALU头信息 图10】
H264编码原理及NALU介绍
字节位参数说明:

  • T:负荷数据类型,占 5bit。
    nal_unit_type:这个NALU单元的类型,1~12由H.264使用,24~31由H.264以外的应用使用。

  • R:指示位,占2bit。
    nal_ref_idc.:取00~11,似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它而不影响图像的回放,0~3,取值越大,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需大于0。

  • F:禁止位,占1bit。
    forbidden_zero_bit: 在 H.264 规范中规定了这⼀位必须为 0。

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

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

例子:
0x00 00 00 01 67
0x00 00 00 01 68
0x00 00 00 01 65

0x67:二进制 0110 0111 ,nal_unit_type:0 0111=7(十进制)

【nal_unit_type数值对应表1】
H264编码原理及NALU介绍H264编码原理及NALU介绍


四、H264 annexb模式

H264有两种封装模式:annexb模式和mp4模式。

  • 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;
// 2 初始化过滤器上下文
av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
// 3 添加解码器属性
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);
上一篇:自己动手写 H.264 解码器---AnnexB 和 avcC


下一篇:js对flv提取h264、aac音视频流