h264基本概念结构图
H264视频压缩后会成为一个序列帧,帧里包含图像,图像分为很多片,每个片可以分为宏块,每个宏块由许多子块组成
H264结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的yuv数据组成。宏块作为H264编码的基本单位。
场和帧:
视频的一场或者一帧可以用来产生一个编码图像。在PAL电视中,每个电视帧都是通过扫描屏幕两次而产生的,第二个扫描的线条刚好填满第一次扫描所留下的缝隙。每个扫描即称为一个场(顶场底场)。因此 30 帧/秒的电视画面实际上为 60 场/秒
I帧 P帧 B帧,引用斯巴克兄弟的博客,图文并茂
举个简单的例子,网络上的电影很多采用了B帧,因为B帧记录的是前后帧的差别,比P帧能节约更多的空间。但是如果遇到不支持B帧的播放器就会很尴尬,因为虽然文件通过B帧压缩确实缩小了非常多,但是在解码时,不仅要用之前缓存的画面,还要知道下一个I或者P的画面。如果B帧丢失,就会用之前的画面简单重复,就会造成画面卡,B帧越多,画面越卡。
一般来说,I 帧的压缩率是7(跟JPG差不多),P 帧是20,B 帧可以达到50。
在如上图中,GOP (Group of Pictures)长度为13,S0 ~ S7 表示 8个视点,T0~T12 为 GOP的 13个时刻。每个 GOP包含帧数为视点数 GOP 长度的乘积。在该图中一个 GOP 中,包含94 个 B帧。B 帧占一个 GOP 总帧数的 90.38%。GOP 越长,B 帧所占比例更高,编码的率失真性能越高
I帧只包含I宏块
P帧包含I宏块和P宏块
B帧包含I宏块、P宏块、B宏块
当帧内slice全部为I像片时,则此帧为I帧
当全部为P slice或和I slice的组合时,则为P帧
当为B slice或和I、P slice的组合时,则为B帧。
片(slice):
每个图像中,若干个宏块被排列成片。片的目的:为了限制误码的扩散和传输,使编码片相互间保持独立。片共有5种类型:I片(只包含I宏块)、P片(P宏块和/或I宏块)、B片(B宏块和/或I宏块)、SP片(用于不同编码流之间的切换,包含P宏块和/或I宏块)和SI片(特殊类型的编码宏块,包含SI宏块)。
I slice只包含I宏块
P slice包含P宏块和/或I宏块
B sliceB宏块和/或I宏块。
宏块:
一个编码图像首先要划分成多个块(4x4 像素)才能进行处理,显然宏块应该是整数个块组成,通常宏块大小为16x16个像素。宏块分为I、P、B宏块,I宏块只能利用当前片中已解码的像素作为参考进行帧内预测;P宏块可以利用前面已解码的图像作为参考图像进行帧内预测;B宏块则是利用前后向的参考图形进行帧内预测
序列(切片slice)和宏块的关系如下
切片头:包含了一组片的信息,比如片的数量,顺序等等
H264编码分层
NAL层:(Network Abstraction Layer,视频数据网络抽象层): 关心压缩后如何进行传输,它的作用是H264只要在网络上传输,在传输的过程每个包以太网是1500字节,而H264的帧往往会大于1500字节,所以要进行拆包,将一个帧拆成多个包进行传输,所有的拆包或者组包都是通过NAL层去处理的。
VCL层:(Video Coding Layer,视频数据编码层): 关心如何进行压缩,对视频原始数据进行压缩
H.264编码原理
H.264采用的编码算法就是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。
视频中一般会存在一段变化不大的图像画面,我们可以先编码出一个完整的图像帧t1,之后的图像帧t2就可以不编码全部图像,而只写入与t1帧的差别,之后的图像帧参考t3帧仍旧可以参照这种方式继续进行编码。这样循环下去就可以得到一个序列(slice)。如果下一个图像帧t4与之前的图像变化很大,无法参考前面的图像帧,就结束上一序列,并开始下一序列。
序列就可以定义为一段图像编码后的数据流,以I帧开始,到下一个I帧结束,H.264中图像就是以序列为单位进行组织的。
码流的具体分析
码流的整体结构如下
SODB:(String of Data Bits,原始数据比特流):
由VCL层产生,数据长度不一定是8的倍数,所以处理起来比较麻烦
RBSP:(Raw Byte Sequence Payload,SODB+trailing bits,编码后的数据流):
相当于去掉附加03字节之后的数据,同原始的相比,算法是在SODB最后一位补1,不按字节对齐补0,如果补齐0,不知道在哪里结束,所以补1,如果不够8位则按位补0
EBSP:(Encapsulate Byte Sequence Payload):
生成编码后的数据流之后,我们还要在每个帧之前加一个起始位,需要开发者人为添加。起始位一般是十六进制的0001。但是在整个编码后的数据里,可能会出来连续的2个0x00。那这样就与起始位产生了冲突.那怎么处理了? H264规范里说明如果处理2个连续的0x00,就额外增加一个0x03 。这样就能预防压缩后的数据与起始位产生冲突
举个例子:
NALU: (NAL Header(1B)+EBSP)
.NALU就是在EBSP的基础上加1B的网络头.
H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的,结构是这样的:
首先NALU是H.264编码数据存储或传输的基本单元,一般H.264码流最开始的两个NALU是SPS和PPS,第三个NALU是IDR。而SPS、PPS、SEI这三种NALU不属于帧的范畴,它们的定义如下:
SPS(Sequence Parameter Sets):序列参数集,作用于一系列连续的编码图像。
PPS(Picture Parameter Set):图像参数集,作用于编码视频序列中一个或多个独立的图像。
SEI(Supplemental enhancement information):附加增强信息,包含了视频画面定时等信息,一般放在主编码图像数据之前,在某些应用中,它可以被省略掉。
IDR(Instantaneous Decoding Refresh):即时解码刷新。
HRD(Hypothetical Reference Decoder):假想码流调度器。
起始码
每个NALU之间通过起始码进行分隔,起始码分成两种:0x000001(3Byte)或者0x00000001(4Byte)。NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段 。
起始码很容易被忽视但是有很重要,博主在遇见清空保存码流需求的时候曾直接cat /dev/null/ 定向了码流文件,重新保存码流时看见文件大小增加,但是重新保存后的文件就打不开了,原因就在重定向原大小的区域全部变成了00,播放器搜索了很久也没有找到起始码,可能就判定文件格式不对了
在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……
一个H.264文件如下:
在H264码流中,都是以"0x00 0x00 0x01"或者"0x00 0x00 0x00 0x01"为起始码的,找到起始码之后,使用起始码之后的第一个字节的低 5 位判断是否为 7(sps)或者 8(pps), 及 data[4] & 0x1f == 7 || data[4] & 0x1f == 8。然后对获取的 nal 去掉开始码之后进行 base64 编码,得到的信息就可以用于 sdp。 sps和pps需要用逗号分隔开来。
上图中,00 00 00 01是一个NALU的起始标志。后面的第一个字节,0x67,是NALU的类型,type &0x1f0x7表示这个NALU是SPS,type &0x1f0x8表示是PPS。
NAL Header:
NALU类型(5bit)、重要性指示位(2bit)、禁止位(1bit):第1位为禁位,默认固定为0,如果接收到的为1,那么就需要丢弃该单元;第2-3位表示重要性,00表示最不重要,11表示最重要,我们可以在解码来不及的情况下舍弃一些不重要的单元,比如在网传的时候,如果网络卡,那么就需要适当丢掉一些数据,丢掉哪些数据就是根据重要性来进行选择;第4-8位用来表示NALU的类型
- 0 没有定义
-
- 1-23 NAL单元 单个 NAL 单元包
-
- 24 STAP-A 单一时间的组合
-
- 25 STAP-B 单一时间的组包
-
- 26 MTAP16 多个时间的合包
-
- 27 MTPA24 多个事件组合包
-
- 28 FU-A 一帧单包传输
-
- 29 FU-B 一帧分多包传输
-
- 30-31 没有定义
h264仅用1-23,24以后的用在RTP H264负载类型头中
- 30-31 没有定义
RBSP + RBSP trailing bits + SODE = RBSP
NAL header(1byte) + RBSP = NALU