标签:
目前几乎所有的视频点播网站全部采用HTTP协议传输数据。因为相对于诸如RTMP等协议来说,HTTP协议是无状态的,数据传输完毕就断开连接,这样服务器就可以腾出资源来服务更多的用户。而RTMP则会在用户播放期间一直维护一个连接,这样服务器的负载就非常有限。而且HTTP服务器,CDN等都已经是非常成熟的技术,成本低性能好。另外HTTP的请求可以直接使用浏览器Cookie,容易和网站业务打通。最后,HTTP还能使用浏览器缓存,这算优点也算缺点,优点是请求同样的资源可以直接从缓存中取,缺点是安全性差了点。
HTTP拥有更好的性能,但是没法传输太实时性的东西,否则性能还不如RTMP,比如视频聊天,直播这些。
有时候我们被访问的视频可能需要做一些限制,比如防盗链,视频收费等等。如果采用HTTP协议的话,传统的鉴权方式就足够,Cookie里带token什么的判断是否有权限访问视频资源,细节我就不说了。唯一的问题是一旦用户有权限访问视频,就有可能把视频下载下来用作他用。
为了让HTTP能服务更多的用户,同时维护更少的连接,我们需要传输尽快完毕。但这是我们分段的理由吗?不是,因为无论分段不分段,一个用户加载完全部的视频数据对服务器占用时间是一定的(假设传输速度一定),甚至会多占用很多创建连接和销毁连接的资源。
但是我们看到各大视频网站实际上都是有对视频分段的,这里我就谈谈视频分段的好处。
我们在Flash端如何播放视频很大程度上受NetStream提供的功能所限。所以这里大致介绍下NetStream提供的功能和一些限制,这也是为什么后面程序要这么设计的原因。
直接播放单个视频文件的方式我就不说了,我这里介绍的是如何像播一个完整文件一样播放经过分段的视频。这个方案有些许瑕疵,后续的方案都是基于这个方案进行优化的。
服务器我们采用上面提到的第一种最简单的静态分段。并且在视频开始播放前我们会拿到一个包含视频分段的开始时间,结束时间,以及分段地址的列表,还有个总的视频metadata信息。
当视频列表加载完毕后就可以开始依次通过NetStream加载播放各个视频分片了,每个分片用一个NetStream实例控制。如图所示。
我们可以设定一个最大缓冲距离,结合当前播放进度,算出一个允许缓冲位置,在这个允许缓冲位置之内的切片都可以依次开始加载,开始加载的时候暂停住不播放。当一个切片开始加载之后是不会停止的,所以实际缓冲进度可能会大于允许缓冲位置。
当一个切片播放完毕之后不要急着把它关掉,它可能需要留着供后续的seek使用。紧接着,我们把下一个分片执行resume方法来让他播放。这样多个分片按照顺序播放,对外界来说就像播放一个完整的视频一样。
这种结构下,若外界需要对视频进行seek操作,可以分三种情况:
所以我们可以看到,静态分片方式的在seek的处理还是还是有很多不足的,对未加载部分内容的seek都不能做到非常精确。不过如果将切片切得比较短小的话这个问题可以有所改善,但是还会带来另外的问题,这个问题我后面讲。另外我们可以再静态分片的基础上引入了start参数,也就是上文提到的“静态分片+start参数”类型服务器。
引入了start参数后对上面的2、3两种seek情况进行了改进:
如此以来在任何情况下seek都可以精确到关键帧,缺点是把正在加载的切片关掉会造成数据浪费。从切片中间开始加载也会造成一个切片内容不完整。下次seek的时候如果不巧是在这个切片start位置之前,就需要重新加载该切片。这些都会造成数据浪费。好在一般用户不会吃饱了没事儿seek来seek去。
从上文的几个策略可以看出,如果视频分得越短小,无论对seek的精确度,还是数据浪费情况都是有好处的,但是这带来的一个问题是需要实例化更多的NetStream来维护切片。另外对于时长较长的视频来说,NetStream的数量也会变得很多。但实际上NetStream能同时开启的连接数量是有限的,这不是内存问题,而是Flash提供的连接数有限。超过了这个限制NetStream就没办法正常工作了,而且也不报错。这个限制在不同浏览器下还不一样,我怀疑这和浏览器底层有关。
所以为了限制NetStream的数量,我们需要设计一个NetStream连接池来管理所有的NetStream。连接池上限不能小于最大缓冲举例可能加载的最多分片数,否则逻辑上就是有问题滴。
我们可以从连接池中取得一个新的NetStream来使用(这个NetStream可能是别的NetStream关闭后的,不过你可以把它当新的用)当连接池数量满的时候,他就会自动把一些老的处于连接状态的NetStream关闭掉。这个淘汰原则是基于空间局部性原理的,也就是说和当前播放头位置距离最远的切片应该首先被关掉(处于最大缓冲距离之内的切片不能关闭)。因为根据概率统计发现大部分的seek都出现在播放头附近(可能为了找什么情节)。
通过多个NetStream切换的方式播放视频,在切换的时候会出现不明显的爆音,但是仔细听还是能够发现。这也是我在上文中提到的文件分割得太短小出现的另外一个问题,爆音太频繁了,可能影响视频观看。
所以要从根本上解决这个问题,我们就要放弃NetStream切换的方式,转用数据生成模式。数据生成模式可以把请求的切片做得很小(但也不要太小,否则服务器性能降低)。切片做小的一个好处是请求更快的完成,那么请求被打断的几率就会降低,当请求完成之后,下次请求同样的资源就能从浏览器缓冲中取。所以小切片更容易被缓存。而上文中的小切片产生的问题在这里不复存在。如图所示。
我们根据播放头的位置,往后加载分片数据,直到最大缓冲距离,这和前面提到的方式类似。而后我们把这些加载的二进制数据保存在内存中。从播放后往后一定的距离(我们称作NS缓冲长度),如果有分片进入,那么就把它appendBytes到用于播放的NetStream中。图中所示的蓝色部分就是保存在内存中的数据,它也有前面提到的连接池类似的淘汰机制用于控制内存总大小。被从内存中释放掉的数据,我们可以在浏览器缓存中找到(因为已经加载过了),如果要使用的话,我们可以像请求服务端数据一样的方式快速请求到这些数据(当然比从内存中慢一些)。图中白色方框的是还未加载过的数据,他们在服务器上等待加载。如图所示就是数据的三级查询。
如果用户进行seek:
如果分片数据较大,seek的位置在分片中间,那么也可以从分片中间开始加载,这样可以从逻辑上把一个分片分为了两个。
数据生成模式从本质上保证了播放质量,杜绝了数据浪费,保证了seek精确度,服务器实现上也异常简单,真是视频播放首选!
这种方式需要服务器做实时分片并分发到CDN。比如服务器从直播数据源里把30秒的视频数据打包成一个数据包分发到CDN上,所以理论上直播至少会延迟30秒。不过对于实时性不是特别强的直播,这种方式的负载能力会更好。
传统的长连接方式直播,需要客户端和服务器一直保持连接,服务器需要维护每个客户端的连接,但实际上传输30秒的视频数据只需要1秒,所以如果采用HTTP的方式,因为传输完毕就可以服务别人了,所以理论上维护连接的效率可以提高30倍。
这里我们要求服务器提供一个视频地址列表,列表里提供了最新的N个视频分片地址。这样客户端通过轮询这个视频列表就能让客户端和直播保持同步。
如图所示客户端维护着一个切片列表队列,通过轮询服务器,我们把最新的视频地址添加到队列中,而播放模块则从队列中取出最老的切片地址加载播放。
如果用户网络较差,那么播放就会卡顿,所以从队列中取出切片地址的频率就会降低,队列会越来越长。队列越长说明视频播放的延迟越大。
所以当队列长于某一个临界值时(我们设定的),我们就把队列清空到只剩一个最近的地址,直到下一次这个地址被取出时,才允许队列继续变长。这个队列清空的操作实际上是对因播放卡顿引起的延迟做了矫正,让直播不要延迟得太厉害。
标签:
原文地址:http://blog.csdn.net/u014723529/article/details/50589013