写点什么

音视频八股文(8)-- h264 AnnexB

  • 2023-04-28
    北京
  • 本文字数:5917 字

    阅读完需:约 19 分钟

NALU(Network Abstract Layer Unit)

⾳视频编码在流媒体和⽹络领域占有重要地位;流媒体编解码流程⼤致如下图所示:


H264 简介

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

H264 编解码解析

一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice),而装载着这些片(slice)的载体,就是 NALU 了,我们可以来看看 NALU 跟片的关系(slice)。




片(slice)的概念不同与帧(frame),帧(frame)是用作描述一张图片的,一帧(frame)对应一张图片,而片(slice),是 H.264 中提出的新概念,是通过编码图片后切分通过高效的方式整合出来的概念,一张图片至少有一个或多个片(slice)。


上图中可以看出,片(slice)都是又 NALU 装载并进行网络传输的,但是这并不代表 NALU 内就一定是切片,这是充分不必要条件,因为 NALU 还有可能装载着其他用作描述视频的信息。

什么是切片(slice)?

片的主要作用是用作宏块(Macroblock)的载体(ps:下面会介绍到宏块的概念)。片之所以被创造出来,主要目的是为限制误码的扩散和传输。


如何限制误码的扩散和传输?


每个片(slice)都应该是互相独立被传输的,某片的预测(片(slice)内预测和片(slice)间预测)不能以其它片中的宏块(Macroblock)为参考图像。


那么片(slice)的具体结构,我们用一张图来直观说明吧:



我们可以理解为一 张/帧 图片可以包含一个或多个分片(Slice),而每一个分片(Slice)包含整数个宏块(Macroblock),即每片(slice)至少一个 宏块(Macroblock),最多时每片包 整个图像的宏块。


上图结构中,我们不难看出,每个分片也包含着头和数据两部分:1、分片头中包含着分片类型、分片中的宏块类型、分片帧的数量、分片属于那个图像以及对应的帧的设置和参数等信息。2、分片数据中则是宏块,这里就是我们要找的存储像素数据的地方。

什么是宏块?

宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。


组成部分:一个宏块由一个 16×16 亮度像素和附加的一个 8×8 Cb 和一个 8×8 Cr 彩色像素块组成。每个图象中,若干宏块被排列成片的形式。



从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。

H264 编码原理

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


192010803*25/1024/1024=148.315MB/s,换成 bps 则意味着视频每秒带宽为 1186.523Mbps,这样的速率对于⽹络存储是不可接受的。因此视频压缩和编码技术应运⽽⽣。


对于视频⽂件来说,视频由单张图⽚帧所组成,⽐如每秒 25 帧,但是图⽚帧的像素块之间存在相似性,因此视频帧图像可以进⾏图像压缩;H264 采⽤了 16*16 的分块⼤⼩对,视频帧图像进⾏相似⽐较和压缩编码。如下图所示:



H264 中的 I 帧、P 帧和 B 帧


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



如上图所示;



压缩率 B > P > I

H264 编码结构解析

H264 除了实现了对视频的压缩处理之外,为了⽅便⽹络传输,提供了对应的视频编码和分⽚策略;类似于⽹络数据封装成 IP 帧,在 H264 中将其称为组(GOP, group of pictures)、⽚(slice)、宏块(Macroblock)这些⼀起组成了 H264 的码流分层结构;H264 将其组织成为序列(GOP)、图⽚(pictrue)、⽚(Slice)、宏块(Macroblock)、⼦块(subblock)五个层次。GOP (图像组)主要⽤作形容⼀个 IDR 帧 到下⼀个 IDR 帧之间的间隔了多少个帧。



H264 将视频分为连续的帧进⾏传输,在连续的帧之间使⽤I 帧、P 帧和 B 帧。同时对于帧内⽽⾔,将图像分块为⽚、宏块和字块进⾏分⽚传输;通过这个过程实现对视频⽂件的压缩包装。


IDR(Instantaneous Decoding Refresh,即时解码刷新)


⼀个序列的第⼀个图像叫做 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 帧不能被当做参考帧)



I0 B40 B80 B120 P160I0 B160

NALU


SPS:序列参数集,SPS 中保存了⼀组编码视频序列(Coded video sequence)的全局参数。


PPS:图像参数集,对应的是⼀个序列中某⼀幅图像或者某⼏幅图像的参数。


I 帧:帧内编码帧,可独⽴解码⽣成完整的图⽚。


P 帧: 前向预测编码帧,需要参考其前⾯的⼀个 I 或者 B 来⽣成⼀张完整的图⽚。


B 帧: 双向预测内插编码帧,则要参考其前⼀个 I 或者 P 帧及其后⾯的⼀个 P 帧来⽣成⼀张完整的图⽚。


发 I 帧之前,⾄少要发⼀次 SPS 和 PPS。

NALU 结构

H.264 原始码流(裸流)是由⼀个接⼀个 NALU 组成,它的功能分为两层,VCL(视频编码层)和 NAL(⽹络提取层):


VCL:包括核⼼压缩引擎和块,宏块和⽚的语法级别定义,设计⽬标是尽可能地独⽴于⽹络进⾏⾼效的编码;


NAL:负责将 VCL 产⽣的⽐特字符串适配到各种各样的⽹络和多元环境中,覆盖了所有⽚级以上的语法级别


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


(NALU)


NALU 结构单元的主体结构如下所示;⼀个原始的 H.264 NALU 单元通常由[StartCode] [NALU Header] [NALU Payload]三部分组成,其中 Start Code ⽤于标示这是⼀个 NALU 单元的开始,必须是"00 00 00 01" 或"00 00 01",除此之外基本相当于⼀个 NAL header + RBSP



(对于 FFmpeg 解复⽤后,MP4⽂件读取出来的 packet 是不带 startcode,但 TS⽂件读取出来的 packet 带了 startcode)

解析 NALU

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


NALU 头信息(⼀个字节):



其中:


T 为负荷数据类型,占 5bitnal_unit_type:这个 NALU 单元的类型,1~12 由 H.264 使⽤,24~31 由 H.264 以外的应⽤使⽤


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


最后的 F 为禁⽌位,占 1bitforbidden_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 …67:⼆进制:0110 011100111 = 7(⼗进制)



对于 NALU 分析这节课主要关注 5/6/7/8 四种类型。

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;// 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);
复制代码

补充讲解

GOP group of pictures


GOP 指的就是两个 I 帧之间的间隔. ⽐较说 GOP 为 120,如果是 720 p60 的话,那就是 2s⼀次 I 帧.在视频编码序列中,主要有三种编码帧:I 帧、P 帧、B 帧,如下所示:


  1. I 帧即 Intra-coded picture(帧内编码图像帧),不参考其他图像帧,只利⽤本帧的信息进⾏编码。

  2. P 帧即 Predictive-codedPicture(预测编码图像帧),利⽤之前的 I 帧或 P 帧,采⽤运动预测的⽅式进⾏帧间预测编码。

  3. B 帧即 Bidirectionallypredicted picture(双向预测编码图像帧),提供最⾼的压缩⽐,它既需要之前的图像帧(I 帧或 P 帧),也需要后来的图像帧(P 帧),采⽤运动预测的⽅式进⾏帧间双向预测编码。


在视频编码序列中,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 响应的时间也越⻓。

H.264 中的 I 帧,B 帧和 P 帧

在 H264 中的图像以序列为单位进⾏组织,⼀个序列是⼀段图像编码后的数据流,以 I 帧开始,到下⼀个 I 帧结束。


IDR 图像:⼀个序列的第⼀个图像叫做 IDR 图像(⽴即刷新图像),IDR 图像都是 I 帧图像。


H.264 引⼊IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀个序列出现重⼤错误,在这⾥获得重新同步的机会。IDR 图像之后的图像永远不会使⽤IDR 之前的图像数据来解码。


⼀个序列就是⼀段内容差别不是很⼤的图像编码后⽣成的⼀串数据流。当运动变化⽐较少的时候,⼀个序列可以很⻓,因为运动变化的少就代表图像画⾯的内容变动很⼩,所以就可以编⼀个 I 帧,然后⼀直 P 帧、B 帧了。当运动变化多时,可能⼀个序列就⽐较短了,⽐如就包含⼀个 I 帧和 3、4 个 P 帧。


I P B 三种帧的说明


1、I 帧 I 帧:帧内编码帧 ,I 帧表示关键帧,你可以理解为这⼀帧画⾯的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画⾯)


I 帧特点:


  1. 它是⼀个全帧压缩编码帧。它将全帧图像信息进⾏JPEG 压缩编码及传输;

  2. 解码时仅⽤I 帧的数据就可重构完整图像;

  3. I 帧描述了图像背景和运动主体的详情;

  4. I 帧不需要参考其他画⾯⽽⽣成;

  5. I 帧是 P 帧和 B 帧的参考帧(其质量直接影响到同组中以后各帧的质量);

  6. I 帧是帧组 GOP 的基础帧(如果为 IDR 则为第⼀帧),在⼀组中只有⼀个 IDR 帧,⼀个或多个 I 帧(包括 IDR 帧);

  7. I 帧不需要考虑运动⽮量;

  8. I 帧所占数据的信息量⽐较⼤。


2、P 帧


P 帧:前向预测编码帧。P 帧表示的是这⼀帧跟之前的⼀个关键帧(或 P 帧)的差别,解码时需要⽤之前缓存的画⾯叠加上本帧定义的差别,⽣成最终画⾯。(也就是差别帧,P 帧没有完整画⾯数据,只有与前⼀帧的画⾯差别的数据)


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


P 帧特点:


  1. P 帧是 I 帧后⾯相隔 1~2 帧的编码帧;

  2. P 帧采⽤运动补偿的⽅法传送它与前⾯的 I 或 P 帧的差值及运动⽮量(预测误差);

  3. 解码时必须将 I 帧中的预测值与预测误差求和后才能重构完整的 P 帧图像;

  4. P 帧属于前向预测的帧间编码。它只参考前⾯最靠近它的 I 帧或 P 帧;

  5. P 帧可以是其后⾯P 帧的参考帧,也可以是其前后的 B 帧的参考帧;

  6. 由于 P 帧是参考帧,它可能造成解码错误的扩散;

  7. 由于是差值传送,P 帧的压缩⽐较⾼。


3、B 帧


B 帧:双向预测内插编码帧。B 帧是双向差别帧,也就是 B 帧记录的是本帧与前后帧的差别(具体⽐较复杂,有 4 种情况,但我这样说简单些),换⾔之,要解码 B 帧,不仅要取得之前的缓存画⾯,还要解码之后的画⾯,通过前后画⾯的与本帧数据的叠加取得最终的画⾯。B 帧压缩率⾼,但是解码时 CPU 会⽐较累。


B 帧的预测与重构


B 帧以前⾯的 I 或 P 帧和后⾯的 P 帧为参考帧,“找出”B 帧“某点”的预测值和两个运动⽮量,并取预测差值和运动⽮量传送。接收端根据运动⽮量在两个参考帧中“找出(算出)”预测值并与差值求和,得到 B 帧“某点”样值,从⽽可得到完整的 B 帧。


B 帧特点


1)B 帧是由前⾯的 I 或 P 帧和后⾯的 P 帧来进⾏预测的;


2)B 帧传送的是它与前⾯的 I 或 P 帧和后⾯的 P 帧之间的预测误差及运动⽮量;


3)B 帧是双向预测编码帧;


4)B 帧压缩⽐最⾼,因为它只反映两参考帧间运动主体的变化情况,预测⽐较准确;


5)B 帧不是参考帧,不会造成解码错误的扩散。


注:I、B、P 各帧是根据压缩算法的需要,是⼈为定义的,它们都是实实在在的物理帧。⼀般来说,I 帧的压缩率是 7(跟 JPG 差不多),P 帧是 20,B 帧可以达到 50。可⻅使⽤B 帧能节省⼤量空间,节省出来的空间可以⽤来保存多⼀些 I 帧,这样在相同码率下,可以提供更好的画质。

H264 的码流六层结构,非常重要

这图本来想自己画的,但是这样就跟网上的图片不兼容了,不兼容不利于传播,所以就直接引用了。


下图的第一二三层很重要,开发中会遇到。



H.264 编码后视频的每一组图像(GOP,图像组)都给予了传输中的序列(PPS)和本身这个帧的图像参数(SPS),所以,我们的整体结构,应该如此:



H.264 中,句法元素共被组织成 序列、图像、片、宏块、子宏块五个层次。

发布于: 刚刚阅读数: 8
用户头像

公众号:福大大架构师每日一题 2021-02-15 加入

公众号:福大大架构师每日一题

评论

发布
暂无评论
音视频八股文(8)-- h264 AnnexB_音视频_福大大架构师每日一题_InfoQ写作社区