视频⽂件封装格式
封装格式(也叫容器),就是将已经编码压缩好的视频轨和⾳频轨按照⼀定的格式放到⼀个⽂件中,也就是说仅仅 是⼀个外壳,或者⼤家把它当成⼀个放视频轨和⾳频轨的⽂件夹也可以。说得通俗点,视频轨相当于饭,⽽⾳频轨 相当于菜,封装格式就是⼀个碗,或者⼀个锅,⽤来盛放饭菜的容器。 下⾯是⼏种常⽤的 视频⽂件后缀类型 与其相对应的 封装格式。
H264概述
H264压缩技术主要采⽤了以下⼏种⽅法对视频数据进⾏压缩。包括:
- 帧内预测压缩,解决的是空域数据冗余问题。
- 帧间预 测压缩(运动估计与补偿),解决的是时域数据冗余问题。
- 整数离散余弦变换(DCT),将空间上的相关性变为频域上⽆关的数据然后进⾏量化。
经过压缩后的帧分为:I帧,P帧和B帧:
- I帧:关键帧,采⽤帧内压缩技术。
- P帧:向前参考帧,在压缩时,只参考前⾯已经处理的帧。采⽤帧间压缩技术。
- B帧:双向参考帧,在压缩时,它即参考前⽽的帧,⼜参考它后⾯的帧。采⽤帧间压缩技术。
除了I/P/B帧外,还有图像序列GOP。
GOP:两个I帧之间是⼀个图像序列,在⼀个图像序列中只有⼀个I帧。如下图所示:
H264压缩技术
H264的基本原理其实⾮常简单,下我们就简单的描述⼀下H264压缩数据的过程。通过摄像头采集到的视频帧(按 每秒 30 帧算),被送到 H264 编码器的缓冲区中。编码器先要为每⼀幅图⽚划分宏块。
以下⾯这张图为例:
划分宏块
H264默认是使⽤ 16X16 ⼤⼩的区域作为⼀个宏块,也可以划分成 8X8 ⼤⼩。
- 划分好宏块后,计算宏块的象素值。
以此类推,计算⼀幅图像中每个宏块的像素值,所有宏块都处理完后如下⾯的样⼦。
划分⼦块
H264对⽐较平坦的图像使⽤ 16X16 ⼤⼩的宏块。但为了更⾼的压缩率,还可以在 16X16 的宏块上更划分出更⼩的⼦块。⼦块的⼤⼩可以是 8X16、 16X8、 8X8、 4X8、 8X4、 4X4⾮常的灵活。
上幅图中,红框内的 16X16 宏块中⼤部分是蓝⾊背景,⽽三只鹰的部分图像被划在了该宏块内,为了更好的处理三只鹰的部分图像,H264就在 16X16 的宏块内⼜划分出了多个⼦块。
这样再经过帧内压缩,可以得到更⾼效的数据。下图是分别使⽤mpeg-2和H264对上⾯宏块进⾏压缩后的结果。其中左半部分为MPEG-2⼦块划分后压缩的结果,右半部分为H264的⼦块划压缩后的结果,可以看出H264的划分 ⽅法更具优势。
宏块划分好后,就可以对H264编码器缓存中的所有图⽚进⾏分组了。
帧分组
对于视频数据主要有两类数据冗余,⼀类是时间上的数据冗余,另⼀类是空间上的数据冗余。其中时间上的数据冗余是最⼤的。下⾯我们就先来说说视频数据时间上的冗余问题。
为什么说时间上的冗余是最⼤的呢?假设摄像头每秒抓取30帧,这30帧的数据⼤部分情况下都是相关联的。也有可能不⽌30帧的的数据,可能⼏⼗帧,上百帧的数据都是关联特别密切的。
对于这些关联特别密切的帧,其实我们只需要保存⼀帧的数据,其它帧都可以通过这⼀帧再按某种规则预测出来,所以说视频数据在时间上的冗余是最多的。
为了达到相关帧通过预测的⽅法来压缩数据,就需要将视频帧进⾏分组。那么如何判定某些帧关系密切,可以划为⼀组呢?我们来看⼀下例⼦,下⾯是捕获的⼀组运动的台球的视频帧,台球从右上⻆滚到了左下⻆。
H264编码器会按顺序,每次取出两幅相邻的帧进⾏宏块⽐较,计算两帧的相似度。如下图:
通过宏块扫描与宏块搜索可以发现这两个帧的关联度是⾮常⾼的。进⽽发现这⼀组帧的关联度都是⾮常⾼的。因 此,上⾯这⼏帧就可以划分为⼀组。其算法是:在相邻⼏幅图像画⾯中,⼀般有差别的像素只有10%以内的点,亮 度差值变化不超过2%,⽽⾊度差值的变化只有1%以内,我们认为这样的图可以分到⼀组。
在这样⼀组帧中,经过编码后,我们只保留第⼀帖的完整数据,其它帧都通过参考上⼀帧计算出来。我们称第⼀帧为IDR/I帧,其它帧我们称为P/B帧,这样编码后的数据帧组我们称为GOP。
运动估计与补偿
在H264编码器中将帧分组后,就要计算帧组内物体的运动⽮量了。还以上⾯运动的台球视频帧为例,我们来看⼀下它是如何计算运动⽮量的。
H264编码器⾸先按顺序从缓冲区头部取出两帧视频数据,然后进⾏宏块扫描。当发现其中⼀幅图⽚中有物体时,就在另⼀幅图的邻近位置(搜索窗⼝中)进⾏搜索。如果此时在另⼀幅图中找到该物体,那么就可以计算出物体的运动⽮量了。下⾯这幅图就是搜索后的台球移动的位置。
通过上图中台球位置相差,就可以计算出台图运⾏的⽅向和距离。H264依次把每⼀帧中球移动的距离和⽅向都记 录下来就成了下⾯的样⼦。
运动⽮量计算出来后,将相同部分(也就是绿⾊部分)减去,就得到了补偿数据。我们最终只需要将补偿数据进 ⾏压缩保存,以后在解码时就可以恢复原图了。压缩补偿后的数据只需要记录很少的⼀点数据。如下所示:
我们把运动⽮量与补偿称为帧间压缩技术,它解决的是视频帧在时间上的数据冗余。除了帧间压缩,帧内也要进⾏数据压缩,帧内数据压缩解决的是空间上的数据冗余。下⾯我们就来介绍⼀下帧内压缩技术。
帧内预测
⼈眼对图象都有⼀个识别度,对低频的亮度很敏感,对⾼频的亮度不太敏感。所以基于⼀些研究,可以将⼀幅图像中⼈眼不敏感的数据去除掉。这样就提出了帧内预测技术。
H264的帧内压缩与JPEG很相似。⼀幅图像被划分好宏块后,对每个宏块可以进⾏ 9 种模式的预测。找出与原图最接近的⼀种预测模式。
下⾯这幅图是对整幅图中的每个宏块进⾏预测的过程。
帧内预测后的图像与原始图像的对⽐如下:
然后,将原始图像与帧内预测后的图像相减得残差值。
再将我们之前得到的预测模式信息⼀起保存起来,这样我们就可以在解码时恢复原图了。效果如下:
经过帧内与帧间的压缩后,虽然数据有⼤幅减少,但还有优化的空间。
对残差数据做DCT
可以将残差数据做整数离散余弦变换,去掉数据的相关性,进⼀步压缩数据。如下图所示,左侧为原数据的宏块,右侧为计算出的残差数据的宏块。
将残差数据宏块数字化后如下图所示:
将残差数据宏块进⾏ DCT 转换。
去掉相关联的数据后,我们可以看出数据被进⼀步压缩了。
做完 DCT 后,还不够,还要进⾏ CABAC 进⾏⽆损压缩。
DCT原理⼤⽩话
这是第⼀帧画⾯:P1(我们的参考帧)
这是第⼆帧画⾯:P2(需要编码的帧)
从视频中截取的两张间隔1-2秒的画⾯,和实际情况类似,下⾯我们进⾏⼏次运动搜索: 这是⼀个演示程序,⿏标选中P2上任意16x16的Block,即可搜索出P1上的 BestMatch 宏块。虽然⻋辆在运动,从远到近,但是依然找到了最接近的宏块坐标。
这是⼀个演示程序,⿏标选中P2上任意16x16的Block,即可搜索出P1上的 BestMatch 宏块。虽然⻋辆在运动,从远到近,但是依然找到了最接近的宏块坐标。 搜索演示2:空中电线交叉位置(上图P1,下图P2)
搜索演示3:报刊停的⼴告海报
同样顺利在P1中找到最接近P2⾥海报的宏块位置。 图⽚全搜索:根据P1和运动⽮量数据(在P2中搜索到每⼀个 宏块在P1中最相似的位置集合)还原出来的P2',即完全⽤P1各个位置的宏块拼凑出来最像P2的图⽚P2',效果如下:
仔细观察,有些⽀离破碎对吧?肯定啊,拼凑出来的东⻄就是这样,现在我们⽤P2`和P2像素相减,得到差分图 D2 = (P2' - P2) / 2 + 0x80:
这就是之前⽀离破碎的 P2 加上误差 D2之后变成了清晰可⻅的样⼦,基本还原了原图P2。
由于D2仅仅占5KB,加上压缩过后的运动⽮量不过7KB,所以参考P1我们只需要额外 7KB的数据量就可以完整表 示P2了,⽽如果独⽴将P2⽤质量尚可的有损压缩⽅式独⽴压缩,则⾄少要去到50-60KB,这⼀下节省了差不多8倍的空间,正就是所谓运动编码的基本原理。
H264相关概念
- 序列
H264编码标准中所遵循的理论依据个⼈理解成:参照⼀段时间内相邻的图像中,像素、亮度与⾊温的差别很⼩。所以当⾯对⼀段时间内图像我们没必要去对每⼀幅图像进⾏完整⼀帧的编码,⽽是可以选取这段时间的第⼀帧图像作为完整编码,⽽下⼀幅图像可以记录与第⼀帧完整编码图像像素、亮度与⾊温等的差别即可, 以此类推循环下去。
什么叫序列呢?上述的这段时间内图像变化不⼤的图像集我们就可以称之为⼀个序列。序列可以理解为有相同特点的⼀段数据。但是如果某个图像与之前的图像变换很⼤,很难参考之前的帧来⽣成新的帧,那么就结束删⼀个序列,开始下⼀段序列。重复上⼀序列的做法,⽣成新的⼀段序列。
- 帧类型
- H264结构中,⼀个视频图像编码后的数据叫做⼀帧,⼀帧由⼀个⽚(slice)或多个⽚组成,⼀个⽚由⼀个或多个宏块(MB)组成,⼀个宏块由16x16的yuv数据组成。宏块作为H264编码的基本单位。
- H26使⽤帧内压缩和帧间压缩的⽅式提⾼编码压缩率;
- H264采⽤了独特的I帧、P帧和B帧策略来实现,连续帧之间的压缩;
- I帧
- I帧:帧内编码帧 ,I帧表示关键帧,你可以理解为这⼀帧画⾯的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画⾯)
I帧特点:
- 它是⼀个全帧压缩编码帧。它将全帧图像信息进⾏JPEG压缩编码及传输;
- 解码时仅⽤I帧的数据就可重构完整图像;
- I帧描述了图像背景和运动主体的详情;
- I帧不需要参考其他画⾯⽽⽣成;
- I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
- I帧是帧组GOP的基础帧(第⼀帧),在⼀组中只有⼀个I帧;
- I帧不需要考虑运动⽮量;
- I帧所占数据的信息量⽐较⼤。
- P帧
- P帧:前向预测编码帧。P帧表示的是这⼀帧跟之前的⼀个关键帧(或P帧)的差别,解码时需要⽤之前缓存的画⾯叠加上本帧定义的差别,⽣成最终画⾯。(也就是差别帧,P帧没有完整画⾯数据,只有与前⼀帧的画⾯差别的数据)
- 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帧的压缩⽐较⾼。
- B帧
B帧:双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别(具体⽐较复杂,有4种情况),换⾔之,要解码B帧,不仅要取得之前的缓存画⾯,还要解码之后的画⾯,通过前后画⾯的与本帧数据的叠加取得最终的画⾯。B帧压缩率⾼,但是解码时CPU会⽐较累。
B帧以前⾯的I或P帧和后⾯的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动⽮量,并取预测差值和运动⽮量传送。接收端根据运动⽮量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从⽽可得到 完整的B帧。
B帧特点
- B帧是由前⾯的I或P帧和后⾯的P帧来进⾏预测的;
- B帧传送的是它与前⾯的I或P帧和后⾯的P帧之间的预测误差及运动⽮量;
- B帧是双向预测编码帧;
- B帧压缩⽐最⾼,因为它只反映两个参考帧间运动主体的变化情况,预测⽐较准确;
- B帧不是参考帧,不会造成解码错误的扩散。
- GOP(画⾯组)
- GOP即Group of picture(图像组),指两个I帧之间的距离(下图所说的视频序列就是GOP),Referenc(参考周期)指两个P帧之间的距离,可以理解为跟序列差不多意思,就是⼀段时间内变化不⼤的图像集,⽐较说GOP为120,如果是720 p60 的话,那就是2s⼀次I帧。⼀个I帧所占⽤的字节数⼤于⼀个P帧,⼀个P帧所占⽤的字节数⼤于⼀个B帧。所以在码率不变的前提下,GOP值越⼤,P、B帧的数量会越多,平均每个I、P、B帧所占⽤的字节数就越多,也就更容易获取较好的图像质量;Reference越⼤,B帧的数量越多,同理也更容易获得较好的图像质量。
- GOP结构⼀般有两个数字,如M=3,N=12。M指定I帧和P帧之间的距离,N指定两个I帧之间的距离。上⾯的M=3,N=12,GOP结构为:IBBPBBPBBPBBI。在⼀个GOP内I frame解码不依赖任何的其它帧,p frame解码则依赖前⾯的I frame或P frame,B frame解码依赖前最近的⼀个I frame或P frame 及其后最近的⼀个Pframe。
- IDR帧(关键帧)
- IDR(Instantaneous Decoding Refresh)即时解码刷新。 在编码解码中为了⽅便,将GOP中⾸个I帧要和其他I帧区别开,把第⼀个I帧叫IDR,这样⽅便控制编码和解码流程,所以IDR帧⼀定是I帧,但I帧不⼀定是IDR帧;IDR帧的作⽤是⽴刻刷新,使错误不致传播,从IDR帧开始算新的序列开始编码。I帧有被跨帧参考的可能,IDR不会。
- I帧不⽤参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之前的帧的。IDR就不允许这样,例如:
- 其核⼼作⽤是,是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀个序列出现重⼤错误,在这⾥可以获得重新同步的机会。IDR图像之后的图像永远不会使⽤IDR之前的图像的数据来解码
H264压缩⽅式
- H264采⽤的核⼼算法是帧内压缩和帧间压缩,帧内压缩是⽣成I帧的算法,帧间压缩是⽣成B帧和P帧的算法。
- 帧内(Intraframe)压缩也称为空间压缩(Spatialcompression)。当压缩⼀帧图像时,仅考虑本帧的数据⽽不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内⼀般采⽤有损压缩算法,由于帧内压缩是编码⼀个完整的图像,所以可以独⽴的解码、显示。帧内压缩⼀般达不到很⾼的压缩,跟编码jpeg差不多。
- 帧间(Interframe)压缩的原理是:相邻⼏帧的数据有很⼤的相关性,或者说前后两帧息变化很⼩的特点。也即连续的视频其相邻帧之间具有冗余信息,根据这⼀特性,压缩相邻帧之间的冗余量就可以进⼀步提⾼压缩量,减⼩压缩⽐。帧间压缩也称为时间压缩(Temporalcompression),它通过⽐较时间轴上不同帧之间的数据进⾏压缩。帧间压缩⼀般是⽆损的。帧差值(Framedifferencing)算法是⼀种典型的时间压缩法,它通过⽐较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以⼤⼤减少数据量。
H264分层结构
- H264的主要⽬标是为了有⾼的视频压缩⽐和良好的⽹络亲和性,为了达成这两个⽬标,H264的解决⽅案是将系统框架分为两个层⾯,分别是视频编码层⾯(VCL:Video Coding Layer)和⽹络抽象层⾯(NAL:Network Coding Layer),H.264原始码流(裸流)是由⼀个接⼀个NALU组成。
- VLC层是对核⼼算法引擎、块、宏块及⽚的语法级别的定义,负责有效表示视频数据的内容,最终输出编码完的数据SODB。
- ⼀个NALU = ⼀组对应于视频编码的NALU头部信息 + ⼀个原始字节序列负荷(RBSP,Raw Byte SequencePayload).
- NAL层定义了⽚级以上的语法级别(如序列参数集和图像参数集,针对⽹络传输),负责以⽹络所要求的恰当⽅式去格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。NAL层将SODB打包成RBSP然后加上NAL头组成⼀个NALU单元。
- 在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。
SODB&RBSP
SODB:(String Of Data Bit)数据⽐特串,是编码后的原始数据;
RBSP(Raw Byte Sequence Payload): 原始字节序列载荷,即在SODB的后⾯添加了trailing bits,即⼀个bit 1和若⼲个bit 0,以便字节对⻬;
RBSP的最后⼀个字节包含SODB的最后⼏个⽐特,以及trailing bits。其中,trailing bits的第⼀个⽐特为1,其余的⽐特为0,保证字节对⻬。所以RBSP就等于,SODB在它的最后⼀个字节的最后⼀个⽐特后,紧跟值为1的1个⽐特,然后增加若⼲⽐特的0,以补⻬这个字节。
H264码流结构
核⼼总结:
- F(forbiden):禁⽌位,占⽤NAL头的第⼀个位,当禁⽌位值为1时表示语法错误;
- NRI:取00~11,似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它⽽不影响图像的回放,0~3,取值越⼤,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的⽚,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需⼤于0。
- Type:NAL单元数据类型,也就是标识该NAL单元的数据类型是哪种,占⽤NAL头的第四到第⼋个位;
参数集(Parameter sets)
参数集是携带解码参数的NAL单元,参数集对于正确解码是⾮常重要的,在⼀个有损耗的传输场景中,传输过程中⽐特列或包可能丢失或损坏,在这种⽹络环境下,参数集可以通过⾼质量的服务来发送,⽐如向前纠错机制或优先级机制。Parameter sets与其之外的句法元素之间的关系如下图所示:
⾮VCL的NAL数据类型:
- SPS(Sequence Parameter Set:序列参数集)包含⼀些通⽤的参数,⽐如Profile和Level,⽐如视频帧的尺⼨,参考帧的最⼤数量等,这些参数对整个Video Sequence或者Programme都是通⽤的。
- PPS(Picture Parameter Set:图像参数集)包含⼀些通⽤的参数,⽐如熵编码类型,有效的参考图像的数⽬和初始化参数等,这些参数可以应⽤到⼀个Video Sequence或者⼀部分编码帧。
- SEI(补充增强信息):这部分参数可作为H264的⽐特流数据⽽被传输,每⼀个SEI信息被封装成⼀个NAL单元。SEI对于解码器来说可能是有⽤的,但是对于基本的解码过程来说,并不是必须的。
- ⼀个Parameter Set在开始的时候是不活跃的,直到被激活。⼀个PPS被预先传到解码器,当在⼀个SliceHeader中涉及到的时候,就会被激活,⽽且直到⼀个不同的PPS被激活。对于SPS,当⼀个PPS涉及到它的时候,就会被激活。对于⼀个以IDR Access Unit开始的Coded Video Sequence,在整个过程中,⼀个SPS会⼀直处于活跃状态。因此,⼀个SPS可以有效的被IDR Slice激活。
VCL的NAL数据类型:
- 头信息块,包括宏块类型,量化参数,运动⽮量。这些信息是最重要的,因为离开他们,被的数据块种的码元都⽆法使⽤。该数据分块称为A类数据分块。
- 帧内编码信息数据块,称为B类数据分块。它包含帧内编码宏块类型,帧内编码系数。对应的slice来说,B类数据分块的可⽤性依赖于A类数据分块。和帧间编码信息数据块不同的是,帧内编码信息能防⽌进⼀步的偏差,因此⽐帧间编码信息更重要。
- 帧间编码信息数据块,称为C类数据分块。它包含帧间编码宏块类型,帧间编码系数。它通常是slice种最⼤的⼀部分。帧间编码信息数据块是不重要的⼀部分。它所包含的信息并不提供编解码器之间的同步。C类数据分块的可⽤性也依赖于A类数据分块,但与B类数据分块⽆关。 以上三种数据块每种分割被单独的存放在⼀个NAL单元中,因此可以被单独传输。
H264封装格式
H264码流分两种组织⽅式,⼀种是AnnexB格式,⼀种是AVCC格式。
AnnexB格式
[start code]NALU | [start code] NALU |...
这种格式⽐较常⻅,也就是我们熟悉的每个帧前⾯都有0x00 00 00 01或者0x00 00 01作为起始码。.h264⽂件就是采⽤的这种格式,每个帧前⾯都要有个起始码。SPS PPS等也作为⼀类NALU存储在这个码流中,⼀般在码流最前⾯。也就是说这种格式包含VCL 和 ⾮VCL 类型的NALU。
AVCC格式
([extradata]) | ([length] NALU) | ([length] NALU) | ...
这种模式也叫AVC1格式,没有起始码,每个帧最前⾯⼏个字节(通常4字节)是帧⻓度。这⾥的NALU⼀般没有SPS PPS等参数信息,参数信息属于额外数据extradata存在其他地⽅。
⽐如ffmpeg中解析mp4⽂件后sps pps存在streams[index]->codecpar->extradata;中。也就是说这种码流通常只包含VCL类型NALU
第1字节:version (通常0x01)
第2字节:avc profile (值同第1个sps的第2字节)
第3字节:avc compatibility (值同第1个sps的第3字节)
第4字节:avc level (值同第1个sps的第3字节)
第5字节前6位:保留全1
第5字节后2位:NALU Length 字段⼤⼩减1,通常这个值为3,即NAL码流中使⽤3+1=4字节表示NALU的⻓度
第6字节前3位:保留,全1
第6字节后5位:SPS NALU的个数,通常为1
第7字节开始后接1个或者多个SPS数据
SPS结构 [16位 SPS⻓度][SPS NALU data]
SPS数据后
第1字节:PPS的个数,通常为1
第2字节开始接1个或多个PPS数据
PPS结构 [16位 PPS⻓度][SPS NALU data]