视频要播放它确定是有视频数据,把视频数据放到编码器,而后编码器把这个视频数据解码出来,解成图片,而后播放到显示器上,这是一个基本的播放流程。通常来说,你们如今主流的用H.264编码。对于H.264编码来讲,咱们会有三个不一样的帧,所谓帧是什么呢?就是你看到的每个图像。咱们看到动态的视频,你们知道电影最开始用胶片拍的时候,每秒是25帧,是每秒25个图片在切换。对于H.264来说,咱们常见的有I帧,P帧,和B帧。git
1.I帧,I-Frame也有人会叫Inter Frame,那么它的意义是什么?算法
它是一个自描述帧,你能够理解为它就相似一个jpg图片,它里头全部的数据,你解出来以后,它就是一整张图片。缓存
无其余帧引用,它不须要去作前置和后置的引用。服务器
它压缩比是最小的,由于它要包括整个图片全部的数据在里头。网络
2.P帧,P-Frame也就是说预测帧,它的预测帧是怎么回事呢?你们有没有用过版本管理软件,好比git或SVN,这样可能你们会比较好理解,P帧就是保留变的部分,不变的部分你去上一个或者几个帧里面找就行。P帧只是负责向前引用,也就是任何一个P帧,它只看它往前的这些帧的数据。P帧的好处是什么呢?由于它只存一些变化信息,因此它大概的压缩比是I帧的50%。这个数据哪来的?你们能够去翻一下维基百科,那里会有一些介绍。数据结构
3.B帧,B-Frame,先后双向引用预测。运维
B帧比较特别,它要引用前面P帧某一部分的图像数据同时B帧后面的数据也会引用,这个是B帧的特色,它要引用前面的数据,也要引用后面的数据。那么它的优点就是压缩比比P帧还大,大概是I帧的25%,也就是咱们B帧用的特别多的话,它会把视频的大小降的比较低,由于它的压缩比更大一些。ide
I帧,B帧,P帧它是怎么组成一个视频流呢?咱们管这个东西叫Group Of Picture,简称叫GoP。编码
视频解码器,看到GoP它是怎么放呢?那很简单,编码器会有一个缓冲,而后它会保留从I帧开始,固然如今说是I帧,其实这个I帧还有个特殊的类型。从I帧开始,他会把数据缓存到解码器的Buffer里,当他遇到下一个P帧,或者再下一个B帧的时候,它会从它Buffer里找到它以前引用的那个帧,而后把这个数据解出来,最终播放到显示器上。那么缓冲区开始的第一个帧确定是I帧,这个毋庸置疑。另外还有一个比较特别的点,B帧和P帧并不会只引用当前GoP里的帧,他可能会往前去引用上一个GoP中的帧。既然它有这么一个特性的话,咱们何时去清空这个缓冲区呢?这里就要介绍一个新的概念,叫IDR帧。spa
IDR帧是I帧,但I帧并不必定是IDR帧,所谓IDR帧是什么?它就是拿到这个帧以后,播放器能够直接从这个帧开始日后播放,它保证后面的P帧和B帧的引用不会跨越这个IDR帧,那么看到IDR帧,编码器就能够把当前的Buffer清空,从当前这IDR帧开始解码往Buffer里边放,后续帧就能够从Buffer里的数据引用,而后解码,也就是说编码器能够从任何一个IDR帧开始解码。你们能够联想到,当我播放一个视频文件的时候,我能够拖动,可是我拖动的任何一个点,它确定是一个IDR帧,固然它也是I帧,可是并不必定说每个I帧我都能让它做为一个拖动的点。
IDR帧有时也有它不太学术的叫法:关键帧。在作编解码程序的时候,咱们可能会看到FFmpeg的数据结构里会标着PTS和DTS,那么PTS和DTS是什么呢?
PTS,Presentation Time Stamp也就说这个帧何时会放在显示器上;DTS就是Decode Time Stamp,就是说这个帧何时被放在编码器去解。那么若是全是I帧和P帧,PTS和DTS都是单调递增的,那么若是咱们有B帧,会出现什么状况?由于你们都知道,对于B帧来说,它会引用前面的帧和后面的帧。
咱们看这个例子,就是当B帧进来的时候,由于它要引用后面的P帧,也要引用前面的I帧,能够看到DTS的顺序,一三四二,而后PTS顺序,一二三四。B帧它会根据它编码时候的特性,它会自动的把它的DTS时间戳日后挪,把它引用的帧先放到前面去,等它引用的帧解完了,数据解完了以后,才会把B帧去解,不然的话,我先把B帧放进去,它引用后面的P帧,P帧的数据尚未,B帧解不出来。因此说这点上给你们讲一下,B帧,P帧,I帧它们整个放在一个视频流里面,它的解码顺序和编码顺序,固然后续的话,咱们可能会根据这个有一些开放性的思考。
对于直播来说,它是一个流,它不像点播,你们都从0秒开始,任何一个视频文件,0秒第一个帧确定都是关键帧。那么对于直播来说,我是一个随机的时间点接到这个视频流进行播放,那么我接入的这个时间点的帧有可能拿到的第一个帧的数据是I帧,也有多是B帧,也有多是P帧。这是一个随机的。在这种状况下,咱们大几率会出现一个黑屏的状态。由于我拿到的是个P帧,对于P帧来说,解码器面那个Buffer是空的,它不知道这个P帧如何进行解码,因此它只能丢弃这个帧。
对于直播来说,我一秒钟的帧数是固定的,只能等到我下一个关键帧到来的时候,我才能开始去播放。固然正好赶巧了的话,接入那瞬间获得的数据正好是个I帧。就能够达到秒开的效果。
实际上是在cache服务器上,它会去预先解一下这个帧,而后去看它究竟是个I帧,仍是个B帧,仍是个P帧,当它发现是I帧的时候,它会放在它的程序的内存里头,当你每一次打开这个视频流的时候,cache服务器会把内存中的I帧发送给客户端好比当前播放到了P帧,那我把P帧前面的I帧和P帧全波放到cache的内存里,而后当客户端接入以后先把内存里的数据发送给客户端解码器,而后再从这个B帧日后给。对于这个解码器来说,它很舒服,它接到第一个数据流的第一个包确定是I帧,那么它就能够直接播放了。
没有什么好的方法,任何人作这种事时候都是一个笨的方法,就是去看文档,没有什么捷径。你们能够翻阅FLV视频文件格式文档,它会告诉你package里头,它任何一个Video里的package有一个Video TagHeader,对它是有一个Frame Type,Frame Type若是把它解出来,它是1的时候,它管这个叫key Frame,我们看后面这个AVC,a seekable Frame你能够理解为它是个IDR帧,它并不必定就是I帧。
开放性的解决问题,GoP Cache是从当前的这个GoP帧开仍是从上一个GoP开始?
这个问题比较有意思的是我从上一个GoP放的话,我拿过来的确定是直接能够放了。由于有时我也预见过一些比较特殊的编码,会致使我从当前这个GoP的第一个I帧拿播放器放出来,我这样可能会提升编码器的兼容性,可是它会有一个问题,就是若是GoP开的特别大的话,那么个人延时天然而然就会上去。由于我上一个GoP若是是十秒,等于说我拿的是十秒以前的数据,也就是你看到的是10秒以前他说的话,他作得表情,他作的动做。那么我从当前这个GoP开始,它确定是有必定的延迟,可是不会大到超过你整个GoP。对于视频直播来说,咱们GoP size多少合适,换一句话讲若是GoP size设成0,全部都是I帧,不存在任何GoP cache问题,可是码率来说会很高,由于全部的都是I帧,咱们知道它压缩比会比较低,那么反过来,就是我须要设一个GoP的Size是多少呢?
通常来说,对于手机直播,一到两秒多是比较合适的,由于它自己的GoP时间也不会很长,我这边缓冲,一旦出现问题大概一到两秒这个视频也能出来。有一个不太好的地方就是它码率会稍微高一些,也就说一样的东西,若是我把GoP改为十秒,我多是500K,可是我改为一秒,有可能变成一个六七百K的样子,这个仍是跟编码有关系,具体的比例是多少,可能跟实际相关。
另外若是是点播的话,不关心首屏打开时间,只要是客户端下来速度快,CDN给力,那么我可能要求更小的范围。告诉你们一个实践过程当中得出的结果,你们用过OBS?好比说作主播的话,你们用OBS会比较多,OBS它有一个问题就是它默认的话,若是你不调它的特性,GoP就是10秒,10秒的意思就是说GoP size。若是比较点背的话,看的是10秒以前的,若是是比较大的话,它的码率,码流的大小会小点,可是延迟会稍微高一些,CDN开了几个cache,有些状况下,咱们也能够作些转码,强行把它的GoP size压小,整个CDN层面上加一个转码的话,它可能会增高这个延迟,这块一个开放性问题,你们能够根据本身的场景去思考,这个GoP Size配成多大比较合适。
我有时候在搜相关的资料,也有作直播的不用B帧,因此这一块我并无什么结论,就是说给你们一个点,让你们去想想。鉴于B帧以前看的DTS和PTS的PPT,也知道B帧在解码的时候,它是要打乱每个帧传入解码器的顺序,若是丢包或者一些特殊状况,它可能会影响解码器的运行的特色
我不知道有多少人据说过这个事,就是1943年之前,二战的时候,由于英国被德国打的都已经找不着北了,他但愿美国在后面支持英国,而后去作一些物资上的支持,那么就会有不少的商船,运输船把它的物资源源不断的从美国送到英国,德国人反制方式是什么?它的狼群潜艇在大西洋里逛。对于军事学家来说,他们可能想不出来一些什么好的办法,他们就请来一些数学家问我这个商船怎么开比较合适,能保证一个是个人损失最小,另一个我物资运送最大,碰到狼群的次数越低。这些数学家根据几率统计的几率论,而后得出一个结论,就是必定数量的船队编队规模越小,编次流越多与敌人相遇的几率就越大,换句话来说,就是我这个船队确定是攒足了,好比说我原来有一百艘船的货运量,那么我每一次发一艘船过去,那么我可能遇到狼群的几率会大一些,可是我把这一百艘船变成一个编队,而后我把个人护航编队作到足够好,我把我这一群送过去,我碰到的狼群几率很低的状况下,我可能效率更高一些。
咱们从这个数学故事发散去考虑一个问题,咱们把视频的数据保管好,运维的数据保管好,当成咱们的运输舰,把网络走动,丢包,咱们想成是一个狼群,就是德国的潜艇在整个大西洋上跑。咱们就能够根据这个数学结论去考虑一个特殊的点,咱们的发包节奏是什么样的。
由于我发一个包,好比说我发出一个数据包过去,你能够理解为,我从美国发了一批货运船,带了一行护行舰队去往英国,那么咱们究竟是有数据就往外发,仍是咱们攒一波数据以后往外发?这个就我刚才说的,你发包频率应该是有讲究的,看怎么去发比较合适。
另外,可不能够换一种思路,使用相似TCP慢起动这样的算法,我最开始为了保证首屏时间,我以最快的发包速率往上发,等我发到必定的程度的时候,换成慢包率发送,把时间拉长一点,拉倒一个能够接受的范围,而后逐渐的去调整这个东西,固然这多是比较传统的,朴素的方式。最终的效果呢你们还得本身去根据这个特性本身想,这也是一个开放性的问题。