分享

H264编码原理及NALU介绍...

 创始元灵6666 2022-10-17 发布于河北

一、简述

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

【流媒体编解码流程 图1】
在这里插入图片描述

视频数据编解码层格式包含有: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】
在这里插入图片描述

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】
在这里插入图片描述
压缩编码可以分为内部压缩和外部压缩。

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

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

【内部压缩 图4】
在这里插入图片描述

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

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

【外部压缩 图5】
在这里插入图片描述


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

【H264 IBP帧排序 图6】
在这里插入图片描述
1)I 帧 (帧内编码帧 intra picture)
I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧表示关键帧,解码时只需要本帧数据就可以完成。I帧可以看成是一个图像经过压缩后的产物。自身可以通过视频解压算法解压成一张单独的完整的图片。

I帧特点:

  • 是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输。
  • 解码时仅用 I帧的数据就可重构完整图像。
  • I帧描述了图像背景和运动主体的详情。
  • I帧不需要参考其他画面而生成。
  • I帧是P帧和B帧的参考帧(I帧质量直接影响到同组以后各帧的质量)。
  • I帧是帧组GOP的基础帧(如果为IDR则为第一帧),在一组中只有一个IDR帧,一个或多个I帧(包括IDR帧)。
  • I帧不需要考虑运动矢量。
  • I帧所占数据的信息量比较大。


2)P帧 (前向预测编码帧 predictive-frame)
通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧。
P帧表示这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。
需要参考其前面的一个I frame 或者P frame来生成一张完整的图片。

P帧的预测和重构:
P帧是以 I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加得到P帧“某点”样值,从而得到完整的P帧。

P帧特点:

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


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

B帧是双向差别帧,B帧记录的是本帧与前后帧的差别。要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面与本帧的叠加取得最终画面。

B帧的预测和重构:
B帧以前面的 I帧或P帧为参考帧,找出B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运行矢量在两个参考帧中找出预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。

B帧特点:

  • B帧是由前面的I帧或P帧和后面的P帧来进行预测的。
  • B帧传送的是它与前面的I帧或P帧之间的预测误差及运动矢量。
  • B帧是双向预测编码帧。
  • B帧压缩比最高,因为它只反映参考帧间运动主体的变化情况,预测比较准确。
  • B帧不是参考帧,不会造成解码错误导致的扩散。

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



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

【H264结构组织 图7】
在这里插入图片描述

H264将视频分为连续的帧进行传输,在连续的帧之间使用 I帧、P帧和B帧。同时对于帧内而言,将图像分块为片、宏块和字块进行分片传输;通过这个过程实现对视频文件的压缩包装。

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】
在这里插入图片描述
GOP (图像组)主要用作形容一个IDR帧 到下一个IDR帧之间的间隔了多少个帧。

比如说GOP为120,如果是720 p60的话,就是2s一次 I帧。

在码率不变的前提下,GOP值越大,P、B帧的数量越多,平均每个I、P、B帧占用的字节数越多,也更容易获取较好的图像质量。

Reference(参考周期)指两个P帧之间的距离。一个I帧所占用的字节数大于一个P帧,一个P帧所占用的字节数大于一个B帧。Reference越大,B帧的数量越多,同理也更容易获得较好的图像质量。

不过通过提高GOP值来提高图像质量是有限度的,因为:

  • 在遇到场景切换时,H264编码器会自动强制插入一个 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响应的时间也越长。

做直播时,一般不用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】
在这里插入图片描述

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

注意:
1)从上图我们可以知道,一张图片可以有多个NALU。
2)对解码器来说,需要先收到SPS和PPS进行初始化,否则解码器无法解出正常的帧数据。
3)发I帧之前,至少要发送一次SPS和PPS,因此如果在实际应用中遇到H264无法解码的时候,检查SPS和PPS是否有接收到并正常初始化。


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

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

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


【NALU结构单元的主体结构 图9】
在这里插入图片描述
一个原始的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】
在这里插入图片描述
字节位参数说明:

  • 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 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);

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多