网易新闻从0到1的短视频性能优化之路

994 阅读36分钟

本文阐述了网易新闻客户端在段视频性能方面的优化思路、过程及结果,做到了小投入高产出

本文作者:Re(网易传媒技术团队)

一、前言

在过去的2018年,短视频开始占据互联网越来越多的流量。爆红的短视频社交应用自不必说,通过短视频来获取新闻资讯、观看音乐专辑、甚至在电商平台观看商品介绍也已经成为越来越多用户的选择。

网易新闻客户端作为一个资讯类产品,也在逐渐增加视频形式的内容来满足用户的需求。而随着视频的内容的增加,视频体验也变得越来越重要。如果视频的观看给用户造成了困扰,那么就会增加流失用户的风险。

为此我们在过去的一年中对视频的性能进行了持续的优化,虽然期间遇到过很多困难,但也因此收获了宝贵的经验。我们希望将这个过程中的经验和思考分享出来,一方面是作为一个阶段性的总结,另一方面也希望能对大家有所帮助和启发。

在这篇文章中,我们将围绕以下几点来分享我们的经验与思考

  • 如何明确优化的方向与重点

  • 客户端上有哪些提升性能的手段

  • 如何结合具体情况进行技术选型

  • 客户端外我们做了哪些优化

  • 数据导向的重要性及对我们的帮助

二、明确方向

短视频作为网易新闻中的一个功能,是重点但不是全部。在优化上我们没有不停试错的成本,所以抓住重点与方向很重要,这样才能在有限的成本中达到最好的效果,一旦跑偏那么会浪费大量的时间和人力。

流程分析

系统地了解视频的播放流程是有助于我们把握方向的,站在一个全局的角度对视频播放进行分析也更容易发现其中的问题。所以我们先将需要优化的过程拆解成几个独立的过程,分析这些过程各自的特点,然后结合我们的目标和条件去进行选择性的优化。

生产流程

在了解视频的播放流程前,我们可以先看看视频是如何生成的。

  • 数字化

    曾经广泛流行的模拟摄像机输出的是模拟信号,因为计算机只能处理0和1,所以需要将模拟信号转换为数字信号,这个过程就叫做数字化。虽然现在数字摄像机和手机已经成为主流,可以直接输出数字信号,但是这些设备内部的光感元件本质上输出的还是模拟信号,所以这个过程还是存在的,只是绝大多数情况下我们不需要去关心它。

  • 编码

    原始数字视频的数据量是相当大的。如果不进行压缩,通过现有的网络传输能力是难以流畅的在线播放的,即使是存储也会变得非常不便。所以我们需要通过压缩来高效的存储数字视频的数据,这个过程就叫做编码。经常看到的 H264(AVC)、H265(HEVC)就是常见的视频编码,AAC 则是一种常见的音频编码。

  • 封装

    我们通常意义上的“视频”,其实包含了视频数据和音频数据,有时还会包含多音轨或者字幕。所以我们需要一个容器能够将视频和音频等数据打包传输存储,并且能够保证它们在时间上的同步。将以上这些元素打包进一个容器中的过程就叫做封装,而我们经常提到的 MP4、MKV、TS 等等这些其实都是一种封装格式。

  • 协议

    这里的流媒体协议指的是 HLS 、DASH 这些基于 HTTP 的动态自适应协议。在直播风头过去后,这些流媒体协议仍然活跃在很多点播场景中。并不是所有的视频都需要并且可以通过流媒体协议来下发,但是这些流媒体协议的动态自适应等特点在一些场景下可以帮助我们获得更大的优化空间。

播放流程

在了解了视频的生产流程后,再看播放流程就相对简单了。如果说视频的生产是将数据一步步封装组合的话,那么视频的播放就是将数据一步步拆解还原。

我们将客户端获取视频数据的过程也加进来,那么一个视频在手机上播放会经历以下这些步骤。

  • 读取

    获取视频的数据,数据的来源可以是网络、磁盘或者内存。

  • 缓冲

    一般情况下,读取的速度是大于播放器消费的速度的,我们可以将已经读取但播放器尚未消费的数据存储在内存中,这样当 I/O 速度有所波动时可以一定程度上避免对观看体验的影响。这个过程就叫做缓冲。

  • 解协议

    如果我们读取的是流媒体协议,比如 HLS、DASH 这种,那么我们还需要进行解协议的操作。按照索引文件的规则去读取相应的视频文件,但如果我们读取的已经是 MP4 这种容器文件的话,那么就不存在这一步了。

  • 解封装

    视频、音频等数据都封装在了容器文件中,因此要想解析视频和音频就需要将它们从封装容器中提取出来。按照封装格式的规则进行数据提取的过程可以称作解封装。

  • 解码

    在解封装后我们拿到了视频和音频的编码数据,接下来需要将它们解码还原成视频和音频的原始数据。

  • 渲染

    最后我们就可以将视频和音频进行输出从而进行观看了。

在了解了基本的播放流程后,就需要结合目标进行具体分析,看看在这个播放的流程中哪些过程是值得我们重点关注的。

目标分析

在网易新闻客户端上我们是以秒开率、卡顿率、失败率三个指标来衡量性能。

秒开率

秒开率的优化重点和首屏平均时间是不一样的。秒开率不仅仅要求的是速度,也衡量着播放器的稳定性。

当整体水平提升到一定阶段后,首屏平均时间关注的仍然是中坚用户,因为他们的占比最大,优化的效果对于均值的提升最明显;但秒开率更需要关注的是那些性能较差的用户,因为只有这部分的用户性能提升了,秒开率才能提高。

所以对于秒开率来说,“读取”阶段最为关键。数据默认情况下都是从网络读取,而网络的时间是非常不可控的。恶劣的网络环境下加载时间很容易就会超过1秒,因此这里有着很大的优化空间。

卡顿率

卡顿率的计算方式虽然有很多种,但是衡量的东西都是一样的。如果我们的带宽跟不上码率,又没有提前在内存中缓冲到足够的数据的话,就会造成卡顿的升高。数据的读取和缓冲的策略会对卡顿有着较明显的影响。

失败率

视频播放失败的原因则有很多,可能是网络上的错误,也可能是本地缓存文件的异常,又或者硬件解码失败等等。除了缓冲,理论上每个步骤都有可能导致播放失败。

综合上面的分析,我们可以大致把重点与方向锁定在读取与缓冲阶段,而其余阶段则是当我们需要优化失败率或者遇到瓶颈时需要考虑的。

三、技术实现

在确定了重点与方向后,接下来就是方案了。要想提高秒开率,首先想到的会是预加载。

预加载

预加载作为最常见的优化手段,可以在网络畅通、流量充裕的情况下提前将数据缓存到本地,有效避免弱网时加载视频的等待。在网易新闻客户端上,预加载的各种策略可以为我们带来接近 10% 的秒开率绝对值的提升,可以说起到了非常重要的作用。

不过虽然预加载简单好用,在实现的过程中还是存在着很多我们需要注意的细节,如果不加以注意不仅会让效果大打折扣,还会带来很多问题与麻烦。

控制预加载的大小

不同于文章的文本数据,视频中有视频数据、音频数据、元数据等信息,要大的多。

举个例子来说的话,假设我们现在希望预加载一个视频时长 34 秒且清晰度较好,固定码率是 800Kbps 左右,那么这个视频的大小就是 800Kbps / 8 * 34 / 1024 = 3.4M。消耗 3.4M 流量去加载一个不一定会被消费的内容是很浪费的,更何况实际使用中会发很多预加载请求,这个消耗将是巨大的。

磁盘消耗

过多的数据首先会占用到大量磁盘的空间。如果预加载的文件在磁盘中占用太多空间会直接影响到用户的手机使用,但如果占用的空间较少又会导致磁盘频繁的删除和写入。

流量消耗

大量的数据在流量上也会是一笔很大的开销。对于用户来说,预加载会造成他们的流量浪费,所以我们应该在 WiFi条件下才开启预加载。对于公司来说数据量过多同样会导致成本升高,这是因为很多时候我们都会借助商用 CDN 来提升性能,而 CDN 是按流量计费,所以流量的浪费就等于资金的浪费。

合适的大小

要避免这些问题,最好的办法还是控制每个视频预加载的数据量在一个合适的大小。这个大小的值取决于视频的起播缓冲量,简单来说就是需要加载多少时间的视频数据后才能渲染出首屏画面。我们的预加载至少要大于这个起播缓冲量才能发挥作用

如果一个播放器需要5s的数据才能起播,那么我们的预加载数据量就需要至少能够播放5s的视频才行。否则当点击视频播放时,预加载的数据量不够起播,那么还是得去网络继续下载数据,就又会受到网络环境的影响了。

对于不同时长、不同编码、不同播放器播放的视频,这个需要下载的大小是不一样的。我们可以在后台提前计算好这个大小,通过接口告诉客户端

切换播放器的输入流

既然要做到部分数据的预加载,那么就不可避免的要涉及到流的切换。假设一个视频总长度34秒,我们预加载了前2秒,那么我们需要完成的任务就是前2秒的视频去读取本地文件,而后32秒的数据去通过网络请求。

很多播放器都不支持在一次播放内进行流的切换,所以我们需要自己去实现这个逻辑。但很多时候播放器为了保证自身的稳定性都会进行封装,不要说解码、渲染等过程,即使是读取过程也没有暴露给外界。最常见的就是 Android 和 iOS 平台上的系统播放器了,它们虽然使用简单方便,但是不易扩展,内部对于我们来说基本上就是一个黑盒,从读取到渲染都是在播放器内部完成的。

如果我们无法去定制播放器的读取流程,那么切换播放器的输入流就无从谈起,预加载也就无法落地。这时摆在我们面前的只有两条路,一条是选择一个可以支持定制读取流程的播放器,另一条则是对播放器进行“欺骗”的本地代理了。

本地代理

通过本地代理我们可以让播放器以为自己一直在读取“网络数据”,但是这个“网络数据”实际上是我们的本地代理服务器提供的。我们可以在代理服务器处进行缓存、切换流等逻辑,这样就可以在不改变播放器行为的前提下改变播放器的输出结果。

要实现本地代理我们需要在客户端本地建立服务器,监听 localhost 的指定端口,并将所有的视频请求都指向这个端口。这样本地代理服务器就可以拦截客户端的请求从而进行定制和改造了。

本地代理的优势在于对原有代码几乎是无入侵的,需要做的仅仅是将原本要传给播放器的 url 包装成指向本地代理服务器的 url 就行了。但是这个方案同样存在着缺点,我们需要处理许多细节问题,比如代理服务器中断后的端口更新、拖拽造成的 range 请求的特殊处理、缓存文件存在但内容损坏时的容错等等,稍有不慎可能就会造成视频的播放失败等问题。

提升预加载的命中率

光是实现预加载是不够的,我们还需要让预加载发挥出最大价值。提高预加载的命中率可以不仅能提升性能,还能降低成本

要实现这个目标我们需要了解用户在当前页面接下来可能会消费什么内容,而这个预测是需要建立在数据的基础之上的。不同产品、不同业务下用户的行为都可能有所不同,因此我们需要结合数据在具体场景下去具体分析。

比如网易新闻最一开始有两个入口可以进入视频播放页。分别是新闻栏目和视频栏目。最一开始我们想当然的在视频栏目进行预加载,这样进入视频播放页的时候就可以直接使用已加载的数据,但是上线后效果并不理想。 分析过数据后我们发现进入视频播放页的大部分流量其实都来自新闻栏目,而视频栏目进入的占比则很少。而在增加了新闻栏目的预加载后,数据确实得到了明显的提升。

类似的场景还有很多,比如保持预加载的顺序和自动播放的顺序相同、优先下载热门的视频等等。任何时候我们都应该优先预加载用户最可能播放的内容

降低预加载的副作用

虽然预加载带来的好处显而易见,但是如果处理不好也会带来一些麻烦。

处理播放器和预加载请求的冲突

首先预加载作为一个单独的网络请求,是有可能跟播放器的网络请求冲突的。而冲突的结果,如果是不同的视频,则会抢占带宽;如果是相同的视频,根据你的实现,可能会没命中缓存或者发生异常。

因此我们需要做的是当播放器加载视频时停掉预加载的请求,并去判断是否有缓存文件。当播放器空闲时,再继续队列中的预加载。

限制预加载的资源占用

如果预加载的并发数如果过多,会占用过多带宽和系统资源,影响到其他的业务和功能。因此由一个单线程依次执行预加载队列中的任务是比较合适的。

让预加载请求和页面生命周期绑定

当退出页面时,我们应该停止并清空队列中属于这个页面的请求,因为这些视频很可能已经没有机会被播放了。

如果我们只是暂时离开了一个页面,那么挂起这个页面发起的预加载请求是比较合适的。因为很可能我们进入的页面会有新的预加载请求,继续之前页面的请求会让我们新的预加载请求无法第一时间开始执行。

除此以外还有很多细节的问题我们可以进行处理,这些细节虽然提升有限,但是积少成多也能有显著的效果。

缓存

预加载本质上就是一种提前的缓存,只是其写数据的时机和逻辑可以不依赖于播放器。而缓存虽然与播放器息息相关,但是比预加载要更加泛用,好的缓存机制可以带来续播、重播等场景的体验提升。

在优化缓存时我们可以参考其他的缓存机制,比如图片缓存等等。它们之间既有相似之处也有差异和不同,我们可以对于其中相似的部分可以进行借鉴,而对于不同的部分进行优化改进。

缓存的切片

图片缓存中我们通常会以url作为key,将完整的图片数据作为value保存起来。当所占空间到达上限时通过一些淘汰算法进行部分缓存的清除,比如常用的LRU。但如果我们在使用MP4视频时仍然使用这种方式缓存,就可能遇到一些我们不希望出现的场景。

现在用户打开了一个已经缓存过的视频,在观看了几秒钟后就关闭了,这种情况总是很常见,比如这是一个信息流中自动播放的视频。这个时候视频的最近使用时间被更新了,但其实它是一个用户不感兴趣的内容。 根据缓存策略,这个文件的清除顺序会被往后排。前几秒因为被播放过,往后被清除是可以被接受的,但是后几分钟没有看过的内容,因为跟前几秒属于同一个文件,就也被往后清除,显然对其他文件不太公平。这会影响到其他缓存文件的存活时间。

所以我们应该将本地的缓存文件切片,在缓存的key中增加一个时间片段维度来区分同一个视频的不同时间片段,让真正被最近使用的文件才最晚被清除。这可以让视频获得更细的淘汰粒度,从而提高缓存的命中率

每个切片的大小没有严格的要求,但是都有一个统一的上限,比如最多是500KB。这会带来另一个好处就是在处理 seek 时逻辑会变得简单。当发生拖拽时我们只需要结束当前文件的写入,新创建一个文件进行顺序写入操作就行了。

建立多级缓存模型

图片缓存的另一个特点,则是我们经常会用到多级缓存模型。最基础的三级缓存根据IO速度的不同,分为内存、磁盘、网络。有时候针对业务特点,可能会发展出四级等模型,根据重要性再次进行区分。

那么视频是不是也可以借鉴同样的思路呢。前面我们已经分析过视频的大小,将视频完整的放在内存中肯定不现实,但跟预加载一样只加载前几秒的数据还是可行的。

首屏数据

通过前面的预加载和缓存切片,我们至少可以将一个完整的视频文件分为首屏数据和内容数据。

首屏数据即预加载下载的数据,即大于或等于起播时间长度的数据。 内容数据则是除首屏数据以外的所有数据,当然内容数据也是可以再切分的,这里我们先简单地将内容数据当作一个整体看待。

三级缓存

将首屏数据放到内存中可以获得更快的IO速度,在遇到磁盘性能较差的手机或者磁盘IO密集的环境时也能将波动尽可能降低。

视频的秒开很大程度上取决与首屏数据的读取速度,因此稳定快速的首屏数据获取可以进一步保证视频的秒开。

四级缓存

如果我们磁盘上的淘汰策略是LRU,则会将最近最少使用的文件切片清除。 但在很多情况下,即使某个首屏数据没有被使用过,它的重要性也可能比使用过的内容数据更高。因为用户看过的视频不一定会去看,但是页面上看到的未播放过的视频是有可能会去点击的。

基于这种特点我们可以换一种淘汰算法,比如跟LRU策略相反,将播放过的视频优先淘汰,因为可能我们会认为用户对于看过的视频不会再感兴趣。但这种场景是不能一概而论的,比如重播场景这种方式就明显不适用,所以具体的策略还得根据数据分析来进行决定。

如果我们不想去考虑那些复杂的问题仍然使用LRU的话,在磁盘层再分出一级缓存出来演化成四级缓存是另外一种方案。

这里的二三级缓存虽然没有速度的差异,但是在优先级上是有区分的。当磁盘空间不足时,会优先清空三级缓存中的内容。首屏数据得到保留,从而可以为下次的播放提供秒开的条件。

不论几级缓存,其目的都是一样的,都是为最重要的数据提供尽可能快的IO速度

缓冲控制

在多级缓存模型中,可以通过增加了内存缓存来获取更快的IO速度。而在播放器内部其实也存在着一个基于内存的缓存,虽然它只能缓存播放器正在加载的视频,但是保证了视频播放的流畅性。我们将这个播放器内部的内存缓存称为缓冲。

缓冲的控制在优化中同样起到了举足轻重的作用。网易新闻客户端早期使用的是系统播放器,在Android 6.0 以下的系统播放器默认的起播缓冲量是5秒数据,这个时间在弱网下会大大增加首屏的时间,在替换播放器并优化了这个时间后我们的秒开率直接提升到了 80%。在卡顿率上,各种缓冲策略的调整帮助我们降低了1%的绝对值,相当于卡顿次数减少了30%。所以,缓冲的控制是我们不能忽视的。

让播放器能第一时间播放

单纯的降低起播缓冲量比如从5s降到1s已经可以看到明显的效果了,但是我们可以将这个值计算的再精细些。

现在绝大部分的视频编码格式都是 H264,在 H264 的传输单元中包含三种帧,其中I帧具备完整的图像信息可以独立解码,P帧需要参考之前已经处理的帧,B帧则既需要参考之前的帧,也需要参考后面的帧。两个I帧之间的图像序列称为一个GOP(Group of Pictures)。

如果我们可以在转码时保证视频的第一帧是I帧,那么一个I帧的数据加上当前视频格式的元数据,这些就是我们起播视频所需要的最小大小。

这个大小按照我们之前说的可以在后台提前计算好,然后下发给客户端,播放器在播放前配置好,从而保证视频的第一时间渲染。

两个起播缓冲量

“起播的缓冲量越低,我们就可以越早的渲染画面,优化效果就越好”,这句话并不是完全正确的。

首先,当这个时间小于I帧的数据时,再小也是没有效果的,因为解码器没法还原出一个完整的画面。

其次,起播缓冲量在不同场景下起到的作用是不一样的。当首次播放时,较低起播缓冲量的缓冲量可以帮助我们快速打开视频。但当缓冲区见底重新加载到足够数据时,立即播放反而可能会给用户更差的体验。想象一下当你在高铁上打开手机想通过视频来打发时间,却发现视频播一帧卡一下,这种感觉是很难受的。

如果我们将起播缓冲量分为首屏起播缓冲量和卡顿起播缓冲量的话,那么前者大小可以是我们之前分析的首个I帧数据,而后者的配置则应该结合具体的用户场景进行分析。

缓冲区的限制

在网易新闻中有着很多自动播放的场景来帮助用户更低成本的观看视频,但是这不一定是对所有用户都是有帮助的。虽然提供了关闭自动播放的选项,仍然会出现自动播放了用户不感兴趣内容的场景,当出现这种情况时用户都会选择手动关闭掉视频。

试想一下如果我们的缓冲区没有上限,发生上述场景时很可能在用户思考的短短几秒内就已经加载完了完整的视频。随后用户关闭它并且不再观看了,那么后续下载的那些数据都将成为浪费。

因此我们不能在播放后就无止境的加载,而应该在加载到了足够数据后暂停缓冲,当缓冲区的数据快要不足时在重新开启这个任务。我们可以将这个停的节点作为缓冲区的上限,将重启缓冲任务的节点作为缓冲区的下限。

缓冲模型

结合上面的分析,我们了解到有四个节点是我们比较关心的,通过这四个节点,可以组成一个缓冲模型。

首屏起播缓冲量表示首次播放时只有到达这个数据量时才能开始渲染。卡顿起播缓冲量表示进入缓冲区数据见底导致画面停止,重新加载到足够数据允许播放时的缓冲量。缓冲区的上下限则分别定义了缓冲该何时停止和重新开始。

缓冲的自适应

在前面我们说到以I帧的数据量进行首屏起播,这是最理想的情况。但是并不是所有用户在这个最理想的配置下都能获得最理想的体验。

当弱网环境下,我们很快的加载出了I帧的数据,渲染出了画面,但是画面很快就又停住了。这是因为我们的带宽跟不上码率,所以后续的数据还没有下载下来,随后用户就会开始播一帧卡一下的场景了。

这时候我们需要对不同网络状态下的播放进行针对性的调整。我们可以让上面模型中的参数变得更加智能。比如当带宽较低时,我们可以适当的延长首屏的起播缓冲量、当频繁发生卡顿时,我们需要适当增大卡顿的起播缓冲量等等。

了解了方案后剩下的就是实现了。对于一些播放器来讲,这些可能只是一个简单的参数配置问题;对于另一些播放器来讲,可能需要自己去实现这个模型;但还有那么一部分播放器,你拿着优化方案却根本无从下手,因为它们根本不支持定制。如果你是最后这种情况,那么或许你应该重新考虑技术的选型了。

四、技术选型

技术选型是相当重要的。它不仅决定了优化的上限,同时也影响着方案的实现

之所以放在后面说,是因为如果一上来就讲,未免会显得有些空洞。如果没有实际遇到问题,很难能体会到不同技术上的差异和特点。但是如果你完整的阅读了前面的内容,应该已经能够理解这一点了。

播放器的选择

在上面的优化中,感受最明显的就是播放器的选择了。如果我们的需求超出了播放器的能力范围,那么任何优化做起来都会变的很困难。所以我们先来看看播放器的选型。 客户端上可以使用的播放器有很多,我们可以大致分为以下三类

理论上讲,越偏底层的播放器,上限也就越高,但是要承担的风险也就越大。 所以在选择时我们应该结合自己的目标和条件,进行合适的选择。

网易新闻的播放器

以网易新闻举例,我们当时所处的环境有以下一些特点。

  • 在视频优化上能投入的人力非常少。并且在优化的同时还得兼顾业务需求等等其他事情,这意味大刀阔斧的底层改造很难在短期看到成果。

  • 业务迭代快。这就需要我们的播放器需要足够的稳定,有着很强的健壮性。如果经常性的 ANR 或者 Crash ,会浪费大量的时间排查与定位,大大降低业务效率。

  • 我们客户端团队中还缺少经验丰富的 C/C++ 开发。这种情况下很难在底层快速实现定制化需求,同时也会有着很大的风险,也不利于之后的工作交接和维护。

这种情况下我们在 Android 平台上选择使用 ExoPlayer。作为 Google 研发并广泛应用在国外诸多 App 上的播放器框架,ExoPlayer 有着稳定、可扩展等优点。对上述提到的很多优化项比如预加载、缓存、缓冲模型都有着很好的支持,基于 Java 的实现也能让我们高效安全的进行定制与扩展,完美契合了我们当前阶段的需求。

在 iOS 平台上我们则继续使用着系统播放器。平台的优势让 iOS 的播放表现比 Android 更加稳定,再加上系统对预加载、起播缓冲配置等特性的支持,基本能满足我们当下的需求。所以 iOS 上我们在不替换播放器的前提下进行了播放器预创建、本地代理等优化,在保证稳定性的前提下提升了性能。

这套方案虽然同样存在着一些制约,但是对于现阶段的我们来说却是最合适的。在满足我们性能需求的同时,避免了成本问题与风险问题。

选择合适的技术

除了播放器,我们还会遇到很多技术上的选择,我们需要做的是找到最切合自己业务的技术。

如果应用经常播放番剧电影这种长视频,那么使用MP4格式会因为moov信息过大而消耗太多的解析时间。这时可以考虑使用 HLS 或者 DASH 等流媒体协议下发切片文件。

如果视频播放时经常会有满屏的礼物动画,那么当动画的计算和软解码的播放器一起抢占CPU资源时就很容易在低端手机上卡顿。这个时候使用硬解码会更加的合适。

如果没有完善的兼容方案就盲目的上线H265,很可能本来以前能播放视频的用户现在都无法进行观看了。即使用户能播放出来,也可能因为解码时间变长了而成为负优化。

类似这些场景还有很多,一句话总结的话,就是技术的选型不能脱离业务的场景

五、其他的优化

我们上面讨论了很多客户端上的优化,但是说到底客户端只是整个视频播放过程中的一个环节。

除了客户端,视频的播放还会经过视频的生成、转码、CDN的调度等过程。在这些过程中存在着一些我们需要注意的地方,它们很可能会在较大程度上影响着视频的性能,但是却没有太多优化成本。

避免MP4的二次请求

MP4 文件由一个个 box 组成,其中有一个叫 moov 的 box,存储着播放 MP4 视频必不可少的信息。 在下发 MP4 资源时我们要确保这个 moov 在文件的前面。因为在很多时候,生成的 MP4 的 moov 会默认追加在文件末尾,这时如果播放器直接播放会在找不到 moov 时二次请求文件末尾,从而影响播放速度。

如果我们的视频生成来源是不可控的,在后台进行视频的统一转码是很有必要的。

切片文件的优化

如果视频资源是通过 HLS 或者 DASH 协议下发的切片文件,那么我们可以考虑在切片文件上进行优化。

默认转码下,每个切片的时长和码率都是相同的。我们可以将首个切片的时长和码率降低,从而让首屏的数据变小,获取更快的首屏打开速度。在首屏之后让切片时长和码率随着进度的增加从低到高逐渐提升,使用户能够播放体验从快速趋向于平稳。

与CDN配合的优化

预加载可以降低客户端的首屏时间,同样也可以降低 CDN 回源造成的延时。如果 CDN 提前从源站预取了热门的视频,就可以保证边缘节点第一时间提供数据。并且在我们前几个切片文件缩小后,将策略调整为预取热门视频的前几个切片文件可以在相同的存储空间下缓存更多的热门视频。

CDN 有时会因为成本问题无法保证所有的视频文件都缓存在固态硬盘上,这时我们需要优先在固态硬盘上缓存每个视频的首个切片,避免硬盘的IO速度对视频秒开的影响。前几个切片文件缩小也有助于我们在固态硬盘上存放更多的视频,更大程度的进行覆盖。

多CDN的调度

在优化早期我们的视频业务只使用一家 CDN,而我们和 CDN 又经常会同时进行一些优化或调整,所以当性能出现波动时,会出现多个变量同时存在的场景,很难去定位性能影响的原因。尤其是出现一些难以解释且难以定位的问题时,会花费相当多的时间去排查分析。于是我们选择接入多家 CDN,这不仅在一定程度上帮助了我们定位问题,在容灾等方面也让我们有所受益。

在接入多 CDN 以后,我们发现不同 CDN 的技术细节是有所差异的,对不同场景下的优化经验也不一样。比如同样的视频通过不同 CDN 下发,在不同平台、不同清晰度等环境下的数据表现会出现各有优劣的情况。根据这个特点,我们综合历史数据的表现去动态调度用户访问的 CDN 资源,从而让用户能过获得最好的体验。

除此以外的优化还有很多,相比端上的优化,客户端外的优化更多的是需要沟通与配合。当我们能够明确共同的目标、积极地互相配合、高效地沟通与解决问题,那么取得的效果将会比单方的努力要好的多。

六、数据导向

有句古话叫做“兵马未动,粮草先行”。如果将开展优化项目比作行军打仗,那么数据收集就是战争中的粮草。没有粮草战争无法进行下去,而没有数据支撑优化工作也将无法展开下去。数据可以说是优化的前提

衡量结果

没有数据支撑,我们不知道自己的优化是否起到了效果,也不知道自己的思路方向是否正确。 通过直观感受去衡量优化结果是很危险的,它很可能让你产生错误的判断,从而得出错误的结论。所以如果你打算开始做优化,进行性能的埋点上报是首先需要做的。它可以确保你的第一次优化效果也能够通过数据体现出来。

通过数据我们可以了解到哪些工作起到了作用,哪些优化又是无关紧要的。当我们抓住了重点,就可以朝着这个方向进行突破而不会跑偏。可以说数据不仅能帮助我们衡量结果,也能指导我们今后的方向。

排查问题

进行数据的上报只是第一步。网易新闻客户端在早期虽然上报了数据,但在后台只有一个简单的平台展示。条件筛选需要提前约定好、数据在显示上也会有所延时。那时候出了问题我们不能第一时间发现,多维度的分析也需要自行跑脚本去处理原始数据。

后来我们的后台小伙伴对性能平台进行了改造,支持了多维度条件的筛选,数据的实时计算等功能。在那以后,出现问题我们可以第一时间从网络状态、系统版本、渠道等各个维度进行排查,大大提高了我们的效率。

举例来说,有一天我们发现秒开率从早上7点开始就断崖式下降。我们通过多维度的筛选,定位到一秒率的下降集中在同一个视频上,而这个视频有着远远高出其他视频的播放量。通过分析这个视频,我们发现这个视频是当天的热点视频,其播放量和其他产品数据是相吻合的,而因为这个视频本身的码率较高,导致首屏加载速度较慢,从而拉低了平均的秒开率。

七、结尾

现在,网易新闻客户端短视频的秒开率已经达到了 94%,卡顿率 1.4%,失败率 0.14%。在这个过程中有过各种因素的限制,也有过各种意外的发生,但是我们都一一克服并实现了进一步的提升。我们的优化虽是从0开始,但还远远没有达到1的程度,因此我们的优化之路也将仍将继续。2019年,我们会在优化的道路上继续探索,在我们力所能及的范围内为用户带来最好的体验。