短视频宝贝=慢?阿里巴巴工程师这样秒开短视频

前言

随着短视频兴起,各大APP中短视频随处可见,feeds流、详情页等等。怎样让用户有一个好的视频观看体验显得愈来愈重要了。大部分feeds里面滑动观看视频的时候,有明显的等待感,体验不是很好。针对这个问题咱们展开了一波优化,目标是:视频播放秒开,视频播放体验良好。无图无真相,上个对比图,左边是优化以前的,右边是优化以后的:html

问题分析

视频格式的选择git

在正式分析问题以前有必要说明下:咱们如今首页的视频,都是320p H.264编码的mp4视频。github

  • H.264 & H.265算法

    H.264也称做MPEG-4AVC(Advanced Video Codec,高级视频编码),是一种视频压缩标准,同时也是一种被普遍使用的高精度视频的录制、压缩和发布格式。H.264因其是蓝光光盘的一种编解码标准而著名,全部蓝光播放器都必须能解码H.264。H.264相较于之前的编码标准有着一些新特性,如多参考帧的运动补偿、变块尺寸运动补偿、帧内预测编码等,经过利用这些新特性,H.264比其余编码标准有着更高的视频质量和更低的码率.
    
    H.265/HEVC的编码架构大体上和H.264/AVC的架构类似,也主要包含:帧内预测(intra prediction)、帧间预测(inter prediction)、转换 (transform)、量化 (quantization)、去区块滤波器(deblocking filter)、熵编码(entropy coding)等模块。但在HEVC编码架构中,总体被分为了三个基本单位,分别是:编码单位(coding unit,CU)、预测单位(predict unit,PU) 和转换单位(transform unit,TU )。
    
    总的来讲H.265压缩效率更高,传输码率更低,视频画质更优。看起来使用H.265彷佛是很明智的选择,
    但咱们这里选择的是H.264。缘由是:H.264支持的机型范围更为普遍。    
    
    PS:闲鱼H.265视频在宝贝详情页会在近期上线,敬请关注体验!
  • TS & FLV & MP4

TS是日本高清摄像机拍摄下进行的封装格式,全称为MPEG2-TS。TS即"Transport Stream"的缩写。MPEG2-TS格式的特色就是要求从视频流的任一片断开始都是能够独立解码的。下述命令能够把mp4转换成ts格式,从结果来看ts文件(4.3MB)比mp4文件(3.9MB)大10%左右。浏览器

ffmpeg -i input.mp4 -c copy output.ts

FLV是FLASH VIDEO的简称,FLV流媒体格式是随着Flash MX的推出发展而来的视频格式。因为它造成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等问题。FLV只支持一个音频流、一个视频流,不能在一个文件里包含多路音频流。音频采样率不支持48k,视频编码不支持H.265。相同编码格式下,文件大小和mp4几乎没有区别。缓存

ffmpeg -i input.mp4 -c copy output.flv

MP4是为你们所熟知的一种视频封装格式,MP4或称MPEG-4第14部分是一种标准的数字多媒体容器格式。MPEG-4第14部分的扩充名为.mp4,以存储数字音频及数字视频为主,但也能够存储字幕和静止图像。因其可容纳支持比特流的视频流,MP4能够在网络传输时使用流式传输。其兼容性很好,几乎全部的移动设备都支持,并且还能在浏览器、桌面系统进行播放。综合上面几个封装格式的特色,咱们的最终选择是MP4.服务器

播放流程

一个视频在客户端的播放流程是怎么样的?播放首开慢耗时在什么地方?耗时点是否可以快速低成本的解决?了解视频的播放流程有助于找到问题的突破口。视频从加载到播放能够分为三个阶段:网络

  • 读取(IO):“获取” 内容 -> 从 “本地” or “服务器” 上获取
  • 解析(Parser):“理解” 内容 -> 参考 “格式&协议” 来 “理解” 内容
  • 渲染(Render):“展现” 内容 -> 经过扬声器/屏幕来 “展现” 内容

能够看出内容获取从“服务器”改成“本地”,这样会节省很大一部分时间,并且成本很低,是一个很好的切入点。事实也是如此,咱们的优化正是围绕此点展开。架构

PS: 咱们使用的网络库,播放器都是集团内部的,自己作了不少优化。本文不涉及网络协议,播放器方面的优化讨论。编辑器

技术方案

鉴于上面的分析,咱们要作的工做是:把mp4文件提早缓存一部分,到feeds滑动要播放的时候,播放本地的mp4文件。因为用户可能继续观看视频,因此本地的数据播放完后,须要从网络下载数据进行播放。这里须要解决两个问题:

  • 应该提早下载多少数据
  • 缓存数据播放完成后该怎么切换到网络数据

MOOV BOX的位置

对于第一个问题,咱们不得不分析一下mp4的文件结构,看看咱们应该下载多少数据量合适。MP4是由不少Box 组成的,Box里面能够嵌套Box:

这里不详细介绍MP4的格式信息。可是能够看出moov box对播放很关键,它提供的信息如:宽高、时长、码率、编码格式、帧列表、关键帧列表等等。播放器没有获取到moov box是没办法进行播放的。因此下载的数据应该要包含moov box再加上几十帧的数据。

作了一个简单的计算:闲鱼短视频通常最长是30s,feeds里面的分辨率是320p,码率是1141kb/s,ftyp+moov这个视频的数据量在31kb左右(打开文件能够看出mdat是从31754byte的位置开始的),因此,头部信息+10帧的数据大约是:(31kb + 1141kb/3)/8 = 51KB

Proxy

第二个问题:缓存数据播放完成后该怎么切换到网络数据呢?在本地数据播放完成以后,设置一个网络地址给播放器,告诉播放器下载的offset是多少,而后继续从网络下载数据播放。这样看起来可行,可是须要播放器提供支持:本地数据播放完成的回调;设置网络url并支持offset。另外,服务端须要支持range参数,并且切换到网络播放的时候须要新创建网络链接,极可能会形成卡顿。

最终,咱们选择了proxy的方式,把proxy做为中间人,负责预加载数据、给播放器提供数据,切换逻辑在proxy里面来完成。未加入proxy以前流程是这样的:

加入了proxy以后流程是这样的:

这样作的好处很明显,咱们能够在proxy里面作不少事情:例如本地文件缓存数据和网络数据的切换工做。甚至是和CDN使用其它的协议进行通讯。咱们这里假定预加载工做已经完成,看看播放器是怎么和proxy进行交互的。播放的时候会用Proxy提供的一个localhost的url进行播放,这样代理服务器会收到网络请求,把本地预加载的数据返回给播放器。播放器彻底感知不到proxy模块、预加载模块的存在。播放器、预加载模块都是Proxy的client,调用逻辑都是同样。图示说明以下:

下面逐步解释一下,数据的加载过程:

  • Client发起http请求获取数据,箭头1所示
  • 文件缓存若是存在所请求的数据则直接返回数据,箭头2所示
  • 若本地文件缓存数据不够,则发起网络请求,向CDN请求数据,箭头3所示
  • 获取网络数据,写入文件缓存,箭头4所示
  • 返回请求的数据给Client,箭头2所示

实现模块

预加载模块

肯定了技术方案后,预加载模块仍是有不少工做要作的。在列表网络数据解析完成后会触发视频预加载,首先会根据url生成md5值,而后去查看这个md5值对应的任务是否存在,若是存在则不会重复提交。生成任务后会提交到线程池,在后台线程进行处理。网络从Wifi切换到3G的时候,会把任务取消,防止消耗用户的数据流量。

预加载任务在线程池执行的时候,其流程是这样的:首先会获取一个本地代理的url。而后发起http请求。Proxy会收到http请求进行处理,开始作真正的数据预加载工做。预加载模块读取到指定的数据量后终止。到此,预加载的任务就已完成。流程图以下所示:

在用户快速滑动的时候,怎么能保证视频还能继续秒开呢?预加载模块对于每个任务都会维护一个状态机,在Fling的时候会把划过的任务暂停下,把最新要显示的任务优先级提升,让其优先执行。

Proxy模块

Proxy内部有个local的httpServer负责拦截播放器和预加载模块的http请求。client在请求时会带入CDN的url,在本地缓存数据没有的时候会去CDN获取新鲜数据。由于有多个地方向Proxy请求数据,因此用线程池来处理多个client的链接颇有必要,这样多个client能够并行,不会由于前面有client在请求而阻塞。文件缓存使用LruDiskCache,在超过指定文件大小后,老的缓存文件会删除,这是一个在使用文件缓存时很容易忽视的问题。因为咱们的场景视频是连续播放的,不存在seek的状况,因此文件缓存相对比较简单,不用考虑文件分段的状况。Proxy内部对于同一个url会映射到一个client,若是预加载和播放同时进行,数据只会有一份,不会去重复下载数据。再来一个Proxy内部构造示意图:

遇到的问题

在测试中发现,有的视频仍是会播放很慢,仔细查看本地的确缓存了指望的数据大小,可是播放的时候仍是有较长的等待时间,这种视频有个特色:moov box在尾部。对于moov在尾部的视频,是整个文件都下载完成后才进行播放的,缘由是moov box里面存了不少关键信息,前面分析mp4格式的时候有提到。对于这个问题有两个解法:

  • 解法一:

服务端在进行转码的时候保证moov的头部在前面,发现moov位置不正确的视频服务端进行订正。

PS:查看moov在文件中的位置能够用hex文本编辑器打开,按字符搜索moov所在的位置便可,MAC上面还可使用MediaParser , 另外还能够用ffmpeg命令生成moov在头部或者尾部的mp4文件。

例如:

 从1.mp4 copy一个文件,使其moov头在尾部
ffmpeg -i 1.mp4 -c copy -f mp4 output.mp4
从1.mp4 copy一个文件,使其moov头在头部:
ffmpeg -i 1.mp4 -c copy -f mp4 -movflags faststart output2.mp4
  • 解法二

不用修改moov box的位置,而是在播放端进行处理,播放端须要检测流信息,若是moov前面没有,就去请求文件的尾部信息。具体就是:发起 HTTP MP4 请求,读取响应 body 的开头,若是发现 moov 在开头,就接着往下读mdat。若是发现开头没有,先读到 mdat,立刻 RESET 这个链接,而后经过 Range 头读取文件末尾数据,由于前面一个 HTTP 请求已经获取到了 Content-Length ,知道了 MP4 文件的整个大小,经过 Range 头读取部分文件尾部数据也是能够的。示意图以下

这个方案的缺点是:对于moov box在尾部的视频会多两次http connection。

结语

本文介绍了常见的视频编码格式,视频封装格式,介绍了moov头信息对于视频播放的影响。随着对于播放流程的分析,咱们找到了问题的切入点。简单说就是围绕着数据预加载展开,把网络请求数据的工做提早完成,播放的时候直接从缓存读取,并且后续的视频回看都是从缓存读取,不只解决了视频初始化播放慢的问题,还解决了播放缓存问题,能够说是一举两得。Proxy是这个方案的核心思想,本地localhost的url是一个关键纽带,视频预加载模块和播放器模块解耦完全,换了播放器照样可使用。到此为止,视频feeds秒开优化就已完成。上线后的数据来看视频打开速度在800ms左右。

回过头来,或许咱们还能够更进一步,能够对预加载收到的数据进行验证,确保缓存了准确的信息,而不是固定的数值。还能够进行更加深度的优化,让用户观看视频的体验更加顺滑。

参考文献

*  [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache)
*  [视频的封装格式和编码格式](https://www.jianshu.com/p/8034fa1ed682)
*  [播放器技术分享(1):架构设计](http://blog.51cto.com/ticktick/2324928?source=dra)
*  [MP4文件格式的解析,以及MP4文件的分割算法](https://cloud.tencent.com/developer/article/1120604)
*  [从天猫某活动视频3次请求提及](https://juejin.im/post/5c0e0f75e51d45410c5e1aea)
*  [视音频编解码学习工程:FLV封装格式分析器]https://blog.csdn.net/leixiaohua1020/article/details/17934487
*  https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10_1.pdf
*  https://baike.baidu.com/item/flv
*  https://standards.iso.org/ittf/PubliclyAvailableStandards/index.html



本文做者:闲鱼技术-邻云

阅读原文

本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索