62

洞察 video 超能力系列——玩转 flv

 6 years ago
source link: https://techblog.toutiao.com/2018/08/13/untitled-52/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

qI3qqiN.jpg!web

从2016年10月(Chrome 54)开始,Chrome不再内置flash,而是改为用户第一次访问flash资源时提示安装。从Chrome62开始,不再提供“click to play的选项”,改为点击视频box后,左上方弹出

NryaAbN.png!web

这意味着,flash作为过时的标准将被新技术所取代。

前言从我们可以在网站上播放视频开始,到h5播放器们如火如荼地发展之前,使用flash一直都是web播放视频的不二之选。甚至于说得更加广泛一些,在html5成为主流之前,网站的多媒体能力,包括动画、游戏和视频一直都是adobe生态在掌控。那么随着html5标准的推出,这一切都成为了过去式。

flv与播方案flv的全程是 Flash Video,顾名思义,就是专门给flash播放器提供的播放格式,这种格式具有结构简单、清晰的优点,最早出现是为了解决flash导出的swf文件体积过大,不适合在web中播放的问题。随着flash的逐渐淘汰,新的video标签自然是不支持flv格式的,人们开始把web视频的重点放在mp4或者hls上,但是随着直播这种视频形式的火热兴起,flv迎来了新一轮的生机。

我们来横向对比一下可用的直播方案:

ZJzUfmn.jpg!web

flv播放实现·跳转·清晰度切换可以看到,flv由于其编码格式的特点,只需要一个MetaData 以及音视频Track各自的Header就可以在任意的时间点播放,极大地符合实时直播的需求,在GOP足够小的情况下甚至可以达到0延迟,可以说在现有的技术方案里,http-flv是最理想的一个,正是因此,flv依然是web播放器不可或缺的一个格式支持。

首先我们通过一张图了解一下前端播放flv的过程

eENZVnA.jpg!web

从上图可以看到,flv播放的过程其实是从flvt中提取元信息、音视频header以及数据,然后转码成fmp4盒子结构的过程,再通过MSE交给video的过程。因为flv的结构本身就是流式的,也就是说,它的数据被拆分到了很多个小的tag中,所以我们可以很方便地做数据的封装,也就是说将一段flv tag数据封装成一个独立的moof_mdat盒子对,然后就可以直接交给MSE处理,非常的方便。这里我们讨论flv是如何处理加载、跳转播放、重放及清晰度切换问题的

数据加载数据加载直播和点播两种模式,首先来聊一下点播:针对点播的数据获取,我们采用Range这个参数作为分段加载的控制参数

MRjAVvV.png!web

如上图,Range这个参数向服务器描述了希望加载 *一个flv文件的 100000 到 3987705 个字节 * 这段数据,相应的,服务器也需要能够根据Range参数正确返回这段数据。这里展示一个极简的服务端代码:

IRju2mU.jpg!web

从上述代码可以看到,服务端是先是解析range,根据range切出文件分段,然后返回一个readableStream交给前端。

再看一下直播:直播跟点播是非常不一样的数据流动结构,我们看一下简单的直播流程

Rf6RjuN.jpg!web

可以看到,直播的流程是一个 推流->服务端格式编码-> 终端播放 的一个流程。那么这里就带来了一个问题,我们不可以像点播时那样去加载数据了。因为服务端实际上保存的是一个文件流,不断有数据推到服务端,播放器也不存在缓存一段数据这样的情况,而是不断向服务器请求,只要有新的流到达服务端,播放器就要拿到这一段进行解码播放,达到推流和拉流同步进行的一个直播效果。为了实现这种效果,在播放器内,我们使用 fetch+ streamReader,简单的实现如下:

YJvAbif.jpg!web

从上述代码我们可以看到,我们通过一个reader递归地从流里面读取数据,再将数据交给解码器进行处理。关于直播的数据加载还有更加先进的websocket方式,这里不再深入探讨,有兴趣的同学可以自行查阅相关资料。

播放时跳转播放时点击进度条跳转(下称seek)是一个非常高频的操作,尤其是在长视频中。用户遇到不想看的部分,或者想重看的片段,都会点击进度条触发seek。举个例子,一段视频,可能用户实际观看的部分只占不到50%, 如果我们将整段视频加载,那剩下加载的50%就浪费掉了。所以我们要做的事情就是精确地加载用户希望播放的部分,节约流量。解决方法如下:

  1. 获取用户将要跳转到的时间点,下称seekTime根据seekTime计算出离该时间点最近的一帧的位置,称为 startPos

  2. 以预加载时间为30s 为例, 计算出seekTime + 30s 这个时间点最近的帧位置 称为 endPos

  3. 我们以 Range: startPos-endPos 为请求参数,向服务端请求这一段数据

  4. 解码播放

这里说起来简单,但是涉及到了一个跟flv格式紧密相关的问题,那就是flv的onMetaData信息里是否具有keyframes这个属性。我们上述所提到的,基于时间点计算某一帧位置的算法,是完全依赖keyframes这个属性的,它记录了flv中所有关键帧的时间点和文件偏移量,keyframes大概长这个样子:

ERNnmq6.jpg!web

times中每一个时间点都对应着同位置的fileposition的偏移量,正是基于这一点,我们可以计算需要加载的数据Range。值得注意的一点是,并不是所有flv文件都携带了keyframes头,flv文件缺失部分onMetaData属性是很常见的事情。缺失了keyframes信息,我们就没办法做跳转了,因此我们需要借助额外的工具帮我们补全flv的onMetaData。这里推荐使用yamdi,一个轻量级的工具,有兴趣的同学可以看一下它的使用。

清晰度切换清晰度切换是一个非常重要的功能,为什么重要呢,因为用户会根据网速的快慢,去选择更清晰或者更流畅的视频。假设没有这个选项,用户看视频卡顿了没法切换清晰度,就只能愤愤地关掉窗口了。那么多个清晰度的视频源我们如何做到无缝的切换呢?我们分情况来讨论一下:

点播中的清晰度切换方案 点播的切换清晰度是比较容易实现的,流程如下

ZvIFvuj.jpg!web

如图,简而言之,就是尝试加载视频B,通过从视频B的onMetaData中提取关键帧信息,推算出当前应该加载哪一个关键帧,然后加载这个位置之后的数据,直到数据加载到之后,将视频B的数据交给MSE,同时,清除掉之前视频A在buffer中的缓存。

直播中的清晰度切换方案 直播清晰度切换目前还没有发现无缝的方案,因此建议在切换时,直接重建解码器以及MSE,关于这方面的问题我们还在探究。更多内容请关注我们的开源播放器: http://h5player.bytedance.com/ 并且欢迎来我们github提issue: https://github.com/bytedance/xgplayer

a6NJZjz.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK