前言:
为什么需要编码呢?比如当前屏幕是1280*720.一秒24张图片.那么我们一秒的视频数据是
1280*720(位像素)*24(张) / 8(1字节8位)(结果:B) / 1024(结果:KB) / 1024 (结果:MB) = 2.64MB
一秒的数据有2.64MB数据量。1分钟就会有100多MB。这对用户来说真心是灾难。所以现在我们需要一种压缩方式减小数据的大小.在更低 比特率(bps)的情况下依然提供清晰的视频。
H264: H264/AVC是广泛采用的一种编码方式。我们这边会带大家了解。从大到小排序依次是 序列,图像,NALU,片,宏块,亚宏块,块,像素。
问题背景:
前面在讲封装格式过程中,都有一个章节讲解如何将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的集合,如下图所示:
用Notepad十六进制形式打开,以annexb格式存储的h264裸流文件内容:
字节流格式(Annex B)和RTP格式流浅析:
AnnexB(附录B)格式:
NALU数据+开始前缀(00 00 00 01或00 00 01),针对H.320电话会议。
RTP格式:
NALU数据+20个字节的类似的并不符合RTP协议的RTP头。针对IP网络的RTP打包方式。为原始的NAL打包格式,就是开始的若干字节(1,2,4字节)是NAL的长度,而不是start_code,此时必须借助某个全局的数据来获得编码器的profile,level,PPS,SPS等信息才可以解码。
tips:
H.264协议只规定了字节流格式,没有规定 RTP 格式。可能也是因为这个原因,RTP 格式没有被用到任何场合场合中,成为了摆设。
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(Raw Byte Sequence Playload)而是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即可
具体填充语法见下文:
参考ISO IEC 14496-10文档
7.3.2.11 RBSP trailing bits syntax
rbsp_trailing_bits( ) {
rbsp_stop_one_bit /* equal to 1 /
while( !byte_aligned( ) )
rbsp_alignment_zero_bit / equal to 0 */
}
原来文档中的解释:
An RBSP is specified as an ordered sequence of bytes as follows.The RBSP contains an SODB as follows:– If the SODB is empty (i.e., zero bits in length), the RBSP is also empty.– Otherwise, the RBSP contains the SODB as follows:1) The first byte of the RBSP contains the (most significant, left-most) eight bits of the SODB; the next byte ofthe RBSP contains the next eight bits of the SODB, etc., until fewer than eight bits of the SODB remain.2) rbsp_trailing_bits( ) are present after the SODB as follows:i) The first (most significant, left-most) bits of the final RBSP byte contains the remaining bits of the SODB(if any).ii) The next bit consists of a single rbsp_stop_one_bit equal to 1.
iii)
When the rbsp_stop_one_bit is not the last bit of a byte-aligned byte, one or morerbsp_alignment_zero_bit is present to result in byte alignment.3) One or more cabac_zero_word 16-bit syntax elements equal to 0x0000 may be present in some RBSPs after therbsp_trailing_bits( ) at the end of the RBSP.
主要的意思我的理解如下:
- 如果SODB恰好占到8的倍数个bits,这时候还是需要RBSP
trailing bits,即二进制的‘10000000’ - 如果SODB按8位字节占据,多余7个bits,这时候的RBSP
trailing bits,即二进制的‘1’,后面不用再添0了 - 普通的情况(多余1-6bits)是后面补齐二进制1和(1-6个)0
还有一种情况是,有的RBSP最后还会出现1个或多个cabac_zero_word,即16位的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的类型。
1. NALU Header
名词解释
IDR(Instantaneous Decoding Refresh): 即时解码刷新。
I和IDR帧都是使用帧内预测的。它们都是同一个东西, 在编码和解码中为了方便,要把首个I帧和其他I帧区别开,所以首个I帧叫IDR,以方便控制编码和解码流程。IDR帧的作用是立刻刷新, 使错误不致传播。从IDR帧开始, 重新算一个新的序列开始编码。而I帧不具有随机访问的能力,这个功能是由IDR承担。
头信息协议如上图。
举例说明:
1. 00 00 00 01 06: SEI信息
2. 00 00 00 01 67: 0x67&0x1f = 0x07 :SPS,Nalu_Type占Nalu header后5bit,所以&0x1f
3. 00 00 00 01 68: 0x68&0x1f = 0x08 :PPS
4. 00 00 00 01 65: 0x65&0x1f = 0x05 :IDR Slice
5. 00 00 00 01 41: 0x41&0x1f = 0x01 :非IDR Slice(P Slice or B Slice)
这其中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:Group Of Pictures(图像组),指的就是两个I帧之间的间隔. 比较说GOP为120,如果是720 p60 的话,那就是2s一次I帧.
帧:一副图像编码后的视频数据也叫做一帧,其中有I帧、B帧、P帧,前文多次提到,不再赘述;
片:一帧图像又可以划分为很多片,由一个片或者多个片组成;
宏块:视频编码的最小处理单元,承载了视频的具体YUV信息,一片由一个或者多个宏块组成;
所以视频流分析的对象可以用下面的图片描述:
如果站在数据的角度分析NALU的层次关系,如下图:
这里视频帧被划分为一个片或者多个片,其中slice数据主要就是通过NLAU进行传输,其中slice数据又是由:
一个Slice = Silce + Slice Data
一帧图片跟 NALU 的关联 :
究竟 NALU 是怎么由一帧图片变化而来的呀,H.264究竟为什么这么神奇?
一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice),而装载着这些片(slice)的载体,就是 NALU 了,我们可以来看看 NALU 跟片的关系(slice)。
引用自://www.greatytc.com/p/9522c4a7818d
Slice片类型:
片类型 | 含义 |
---|---|
I片 | 只包含I宏块 |
P片 | 包含P和I宏块 |
B片 | 包含B和I宏块 |
SP片 | 包含P 和/或 I宏块,用于不同码流之间的切换 |
SI片 | 一种特殊类型的编码宏块 |
设置片的目的是限制误码的扩散和传输,也就是一帧图像中它们的编码片是互相独立的,这样假设其中一张图像的某一个片有问题导致解码花屏,但是这个影响范围就控制在这个片中,这就是我们平时看视频发现只有局部花屏和绿屏的原因。
Slice Data里面传输的是一个个宏块,宏块中的数据承载各个像素点YUV的压缩数据。一个图像通常被我们划分成宏块来研究,通常有1616、168等格式。我们解码的过程也就是恢复这些像素阵列的过程,如果知道了每个像素点的亮度和色度,就能渲染出一张完整的图像,图像的快速播放即是视频。
刚才提到了宏块.那么什么是宏块呢?
宏块是视频信息的主要承载者。一个编码图像通常划分为多个宏块组成.包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中像素阵列。
一个宏块 = 一个16*16的亮度像素 + 一个8×8Cb + 一个8×8Cr彩色像素块组成。(YCbCr 是属于 YUV 家族的一员,在YCbCr 中 Y 是指亮度分量,Cb 指蓝色色度分量,而 Cr 指红色色度分量)
其中宏块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单元:
我们发现视频流数据就是由一系列SPS、PPS、I、P、B帧序列组成,这些数据都是通过NALU进行承载的;
我们看到了NALU不仅仅可以承载SPS、PPS、SEI等非VCL数据,也可以传输I、B、P帧的切片VCL数据。
我们同时看到了NALU的Data部分,如果是VCL数据,则就是slice header+silce data这种结构,其中对VCL的SODB做了bit填充的字节对齐处理;
4. 这里由于没有数据分割机制,所以一个NALU承载一个片,同时一个片就是一个视频帧;
4.至于NALU的非VCL数据SPS、PPS、SEI各个字段的含义具体解析放到下篇文章,这个信息对于解码器进行播放视频很重要,很多播放问题都是这个数据有问题导致的;
上面看了视频的GOP序列,视频帧信息和片的组成,下面分析片中的宏块信息;
H.264的层次结构:
我们能看到整个视频的视频序列,显示看了该视频有I、B、P帧信息,这和上面的分析结果是一致的;
中间图片部分用不同颜色的点显示了宏块和子宏块信息,右上角是对宏块内容的具体说明;
其中不同的帧类型上面的宏块类型也是不一样的;
总结:
本文主要讲述了平时研究和分析视频流对象的层次,然后这些视频数据通过NALU传输时,NALU的类型和层次关系,以及NALU数据在不同层次的输出。最后用视频分析工具分析了H.264裸码流验证了上述层次关系。
所以对H.264数据分析时,一定要了解你现在分析的层次和框架,因为每个层次我们关心的数据处理对象是不一样的,这个非常重要。
一般H.264的分析工具都是收费的,也有一些免费和裁剪版本供大家学习和使用。推荐几个:Elecard StreamEye、CodecVisa、VideoEye、H264Analyzer、H264Visa等,有时需要交叉使用才能完成对你关心信息的分析,这些都放到我的Git上了,大家获取使用即可。
rtsp 保存为.h264分析
25fps i帧间隔50 用分析软件查看可以看出每50帧一个i帧,并发送sps、pps、sei
sps、pps、sei、I帧绑在一起发送