本文为学习笔记,并非原创。原文链接,原文发表于2021-04-06
背景介绍
「零首帧」并不是真的0毫秒启播,而是用户几乎感知不到有首帧时间的存在,在我们的播放质量埋点中对应小于100ms以内的首帧时间。
在现实业务中,有些场景是无法使用所有的优化条件的,比如在随机播放的场景不能进行预加载、某些场景不适合使用播放器复用技术等。
首帧的构成
从上图可以看出首帧时间主要包含这么几个部分: 获取视频播放链接,网络建连,下载视频头部数据,音视频解码和渲染。
通用的首帧优化方法
获取播放地址
优化方案:播放地址随feed下发。
通常而言,视频资源会有唯一标识 video id , 在点播的服务端会有一个根据 video id 信息获取播放链接的服务,如果 app server 端能够调用 vod 服务生成播放地址,然后将播放地址随 feed 流一起下发,则省去了客户端的一次网络请求耗时。
网络建连
优化方案:预解析。预连接&连接复用。避免TLS握手。
拿到播放地址,播放器与DNS建连,首先会做DNS解析。为了防止DNS劫持,大部分客户单会做HTTPDNS,这会涉及到一个网络耗时。可以采取DNS预解析的策略,例如在APP启动时,就可以下发APP可能用到的域名,客户端可以提前对这些域名做解析并缓存。
在HTTP 1.1中支持连接复用,因此我们可以预先创建几个与CDN的socket连接,然后在播放时直接复用连接即可。
HTTPS比HTTP多了TLS握手过程,多了两个RTT。通过TLS False Start加上session复用,可以实现0RTT。
音视频首包
优化方案:减少 probe、moov 位置。
在播放器与CDN完成建连后,播放器就开始下载视频文件。首先播放器会尝试探测视频文件的格式、编码等信息。如果视频经过服务器统一转码,则可以略过这个步骤。
mp4 视频文件,有一个 moov box,这里面会存储音视频流 track 信息比如解码信息、以及音视频帧与文件对应的关系(用于 seek ),因此通常播放器都会先下载 moov 的数据。而 moov 的位置则会对起播造成一定的影响。举个例子,如果 moov 在文件尾部,当下载了视频前面部分数据,发现 moov 没找到,就去尾部查找 moov,这样就又多了两次网络请求。对于这个问题,我们可以通过转码将 moov 挪到视频文件的头部,从而缩短首帧耗时。
音视频解码
优化方案:解码器异步初始化、解码器复用。
通常情况下,在播放器读取到视频数据,拿到视频的解码信息后就可以开始创建解码器解码了。不过解码器创建这个过程,尤其是在 Android 平台上 MediaCodec 的创建是一个比较耗时的操作。这里我主要介绍两个优化: 解码器异步初始化和复用。如果 app server 提前把视频的解码信息传递给播放器,那么播放器就可以在建连的同时去异步初始化解码器,这样就可以减小硬解创建耗时的影响。而解码器复用则可以完全消除这个耗时,顺着这个思路,我们可以做播放器线程的复用甚至整个播放器的复用,这些方法都可以大幅优化首帧耗时。
起播水位
首帧解析后就立即播放,来做到极致首帧,但是会引起卡顿增多,尤其是起播后的1~3秒卡顿增加。
缓存一定数据再起播,可以减少卡顿,而且对首帧影响不大。
预加载
需要考虑:
- 什么时候预加载?
对于15s的短视频,可以等当前视频加载完成后就预加载下一条。对于时长超过1min的视频,需要考虑当前播放视频的可用缓存、当前网络的下载速度、当前视频的码率以及即将预加载视频的码率、并行预加载数量,通过这些数据我们能够构建一个模型去预测接下来视频播放的卡顿状况,如果大概率是不会发生卡顿,则可以开启预加载,反之则不启用或者暂停预加载。
- 预加载多少内容?
一个粗略的估算方法是 moov 大小加上视频的平均码率 * 预加载时长,这样就可以通过服务端下发 moov 头大小及视频的平均码率,然后在 app 端上通过实验去调整预加载时长参数,进而调整预加载大小。
- 并行加载数量。
预渲染
预加载只能将网络请求的耗时消除掉,但播放器还是需要经历解复用、解码、渲染的步骤,在中低端机器上有200ms以上的耗时。将视频的首帧提前渲染好而不播放。
举个例子,在小视频场景上,当滑动视频卡片时,就已经开始启动预渲染,在卡片滑动过程中,视频的首帧很可能就已经通过预渲染加载出来,这样当卡片滑到中央时,则直接启动播放,这时候用户基本上感受不到视频的加载。
Comments
comments powered by Disqus