问题背景:
前面在讲封装格式过程中,都有一个章节讲解如何将H.264的NALU单元如何打包到TS、FLV、RTP中,解装刚好相反,怎么从这些封装格式里面解析出一个个NALU单元。NALU即是编码器的输出数据又是解码器的输入数据,所以在封装和传输时,我们一般处理对象就是NALU,至于NALU内部到底是什么则很少关心。甚至我们在编解码时,我们只需要初始化好x264编码库,然后输入YUV数据,它就会给你经过一系列压缩算法后输出NALU,或者将NALU输入到x264解码库就会输出YUV数据。
这篇文章就初步带你看下NALU能传输那些数据,NALU的类型和结构以及H264码流的层次,最后通过分析工具分析下裸码流记性验证,你可以选择感兴趣章节阅读。
NALU结构:
H.264的基本流(elementary stream)就是一系列NALU的集合,如下图所示:
NALU结构分为两层,包含了视频编码层(VCL)和网络适配层(NAL):
视频编码层(VCL即Video Coding Layer):负责高效的视频内容表示,这是核心算法引擎,其中对宏块、片的处理都包含在这个层级上,它输出的数据是SODB;
网络适配层(NAL即Network Abstraction Layer):以网络所要求的恰当方式对数据进行打包和发送,比较简单,先报VCL吐出来的数据SODB进行字节对齐,形成RBSP,最后再RBSP数据前面加上NAL头则组成一个NALU单元。
分层目的:
这样做的目的:VCL只负责视频的信号处理,包含压缩,量化等处理,NAL解决编码后数据的网络传输,这样可以将VCL和NAL的处理放到不同平台来处理,可以减少因为网络环境不同对VCL的比特流进行重构和重编码;
NLAU结构:
其实NALU的承载数据真实并不是RBSP而是EBSP即(Extent Byte Sequence Payload),EBSP和RBSP的区别就是在 RBSP里面加入防伪起始码字节(0x03),因为H.264规范规定,编码器吐出来的数据需要在每个NALU添加起始码:0x00 00 01或者0x00 00 00 01,用来指示一个NALU的起始和终止位置,那么RBSP数据内部是有可能含有这种字节序列的,为了防止解析错误,所以在RBSP数据流里面碰到0x 00 00 00 01的0x01前面就会加上0x03,解码时将NALU的EBSP中的0x03去掉成为RBSP,称为脱壳操作。
原始数据字节流RBSP即Raw Byte Sequence Playload,因为VCL输出的原始数据比特流SODB即String Of Data Bits,其长度不一定是8bit的整数倍,为了凑成整数个字节,往往需要对SODB最后一个字节进行填充形成RBSP,所以从SODB到RBSP的示意图如下:
具体填充方式就是对VCL的输出数据进行8bit进行切分,最后一个不满8bit的字节第一bit位置1,然后后面缺省的bit置0即可,示意图已经非常明确。
其中H.264规范规定,编码器吐出来的数据需要在每个NALU添加起始码:0x00 00 01或者0x00 00 00 01,用来指示一个NALU的起始和终止位置。
所以H.264编码器输出的码流中每个帧开头3-4字节的start code起始码为0x00 00 01或者0x00 00 00 01。
上面我们分析了NALU的结构以及每层输出数据的处理方法,但是对于NALU的RBSP数据二进制表示的什么含义并不清楚,下面分析下NALU的类型。
NALU类型:
NALU的类型即RBSP可以承载的数据类型如下所示:
Nalu_Type | NALU内容 | 备注 |
---|---|---|
0 | 未指定 | |
1 | 非IDR图像编码的slice | 比如普通I、P、B帧 |
2 | 编码slice数据划分A | 2类型时,只传递片中最重要的信息,如片头,片中宏块的预测模式等;一般不会用到; |
3 | 编码slice数据划分B | 3类型是只传输残差;一般不会用到; |
4 | 编码slice数据划分C | 4时则只可以传输残差中的AC系数;一般不会用到; |
5 | IDR图像中的编码slice | IDR帧,IDR一定是I帧但是I帧不一定是IDR帧。 |
6 | SEI补充增强信息单元 | 可以存一些私有数据等; |
7 | SPS 序列参数集 | SPS对如标识符、帧数以及参考帧数目、解码图像尺寸和帧场模式等解码参数进行标识记录 |
8 | PPS 图像参数集 | PPS对如熵编码类型、有效参考图像的数目和初始化等解码参数进行标志记录。 |
9 | 单元定界符 | 视频图像的边界 |
10 | 序列结束 | 表明下一图像为IDR图像 |
11 | 码流结束 | 表示该码流中已经没有图像 |
12 | 填充数据 | 哑元数据,用于填充字节 |
13-23 | 保留 | |
24-31 | 未使用 |
举例说明:
这其中NALU的RBSP除了能承载真实的视频压缩数据,还能传输编码器的配置信息,其中能传输视频压缩数据的为slice。
那么如果NLAU传输视频压缩数据时,编码器没有开启DP(数据分割)机制,则一个片就是一个NALU,一个 NALU 也就是一个片。否则,一个片由三个 NALU 组成,即DPA、DPB和DPC,对应的nal_unit_type 类型为 2、3和4。
通常情况我们看到的NLAU类型就是SPS、PPS、SEI、IDR的slice、非IDR这几种。
上面站在NALU的角度看了NALU的类型、结构、数据来源、分层处理的原因等,其中NLAU最主要的目的就是传输视频数据压缩结果。那么站在对数据本身的理解上,我们看下H.264码流的层次结构。
H.264层次结构:
其实为了理解H.264是如何看待视频数据,先要了解下视频的形成过程。其实你把多副连续的有关联图像连续播就可以形成视频,这主要利用了人视觉系统的暂留效应,当把连续的图片以每秒25张的速度播放,人眼基本就感觉是连续的视频了。为了说明这个概念,我下面准备了一个视频大家点开看下,加深对视频的理解。
从上面的视频播放过程中,我们大概能看出视频有下面几个特点:
一张图像里面相邻的区域或者一段时间内连续图像的相同位置,像素、亮度、色温差别比较小,所以视频压缩本质就是利于这种空间冗余和时间上冗余进行编码,我们可以选取一段时间第一幅图像的YUV值,后面的只需要记录和这个的完整图像的差别即可,同时即使记录一副图像的YUV值,当有镜头完全切换时,我们又选取切换后的第一张作为基本图像,后面有一篇文章回讲述下目前视频压缩的基本原理。
所以从这里面就可以引申以下几个概念:
GOP:一段时间内图像变化不大的图像集我们就可以称之为一个序列,这里的图像又在H264里面称为帧,所以就是一组视频帧,其中第一个我们称为是IDR帧。
帧:一副图像编码后的视频数据也叫做一帧,其中有I帧、B帧、P帧,前文多次提到,不再赘述;
片:一帧图像又可以划分为很多片,由一个片或者多个片组成;
宏块:视频编码的最小处理单元,承载了视频的具体YUV信息,一片由一个或者多个宏块组成;
所以视频流分析的对象可以用下面的图片描述:
如果站在数据的角度分析NALU的层次关系,如下图:
这里视频帧被划分为一个片或者多个片,其中slice数据主要就是通过NLAU进行传输,其中slice数据又是由:
一个Slice = Silce + Slice Data
Slice片类型:
片类型 | 含义 |
---|---|
I片 | 只包含I宏块 |
P片 | 包含P和I宏块 |
B片 | 包含B和I宏块 |
SP片 | 包含P 和/或 I宏块,用于不同码流之间的切换 |
SI片 | 一种特殊类型的编码宏块 |
设置片的目的是限制误码的扩散和传输,也就是一帧图像中它们的编码片是互相独立的,这样假设其中一张图像的某一个片有问题导致解码花屏,但是这个影响范围就控制在这个片中,这就是我们平时看视频发现只有局部花屏和绿屏的原因。
Slice Data里面传输的是一个个宏块,宏块中的数据承载各个像素点YUV的压缩数据。一个图像通常被我们划分成宏块来研究,通常有16*16、16*8等格式。我们解码的过程也就是恢复这些像素阵列的过程,如果知道了每个像素点的亮度和色度,就能渲染出一张完整的图像,图像的快速播放即是视频。
其中宏块MB的类型:
宏块分类 | 意义 |
---|---|
I宏块 | 利用从当前片中已解码的像素作为参考进行帧内预测 |
P宏块 | 利用前面已编码图像作为参考进行帧内预测,一个帧内编码的宏块可进一步作宏块的分割:即16×16.16×8.8×16.8×8亮度像素块。如果选了8×8的子宏块,则可再分成各种子宏块的分割,其尺寸为8×8,8×4,4×8,4×4 |
B宏块 | 利用双向的参考图像(当前和未来的已编码图像帧)进行帧内预测 |
宏块的结构:
H.264码流示例分析:
这里我们分析一下H.264的NLAU数据,其中包括了非VCL的NALU数据和VCL的NALU。
H.264码流的NLAU单元:
1. 我们发现视频流数据就是由一系列SPS、PPS、I、P、B帧序列组成,这些数据都是通过NALU进行承载的;
2. 我们看到了NALU不仅仅可以承载SPS、PPS、SEI等非VCL数据,也可以传输I、B、P帧的切片VCL数据。
3. 我们同时看到了NALU的Data部分,如果是VCL数据,则就是slice header+silce data这种结构,其中对VCL的SODB做了bit填充的字节对齐处理;
4. 这里由于没有数据分割机制,所以一个NALU承载一个片,同时一个片就是一个视频帧;
4.至于NALU的非VCL数据SPS、PPS、SEI各个字段的含义具体解析放到下篇文章,这个信息对于解码器进行播放视频很重要,很多播放问题都是这个数据有问题导致的;
上面看了视频的GOP序列,视频帧信息和片的组成,下面分析片中的宏块信息;
H.264的层次结构:
1. 我们能看到整个视频的视频序列,显示看了该视频有I、B、P帧信息,这和上面的分析结果是一致的;
2. 中间图片部分用不同颜色的点显示了宏块和子宏块信息,右上角是对宏块内容的具体说明;
3. 其中不同的帧类型上面的宏块类型也是不一样的;
总结:
本文主要讲述了平时研究和分析视频流对象的层次,然后这些视频数据通过NALU传输时,NALU的类型和层次关系,以及NALU数据在不同层次的输出。最后用视频分析工具分析了H.264裸码流验证了上述层次关系。
所以对H.264数据分析时,一定要了解你现在分析的层次和框架,因为每个层次我们关心的数据处理对象是不一样的,这个非常重要。
一般H.264的分析工具都是收费的,也有一些免费和裁剪版本供大家学习和使用。推荐几个:Elecard StreamEye、CodecVisa、VideoEye、H264Analyzer、H264Visa等,有时需要交叉使用才能完成对你关心信息的分析,这些都放到我的Git上了,大家获取使用即可。
今天就说这么多,祝您国庆快乐,工作顺利!
如果有疑问,你可以在公众号后台发消息咨询我。