网易云音乐在视频播放优化上的实践

从最开始的MV,到后来的短视频,再到现在的直播,网易云音乐在视频业务上不断的进行尝试和扩展。实践中,我们不得不面对很多功能和性能上的问题,通过不断的思考、迭代和重构,持续的去解决各种问题,不断的优化播放性能,给用户提供最优的播放体验。本文主要来介绍网易云音乐是如何解决开发中面临的各种问题以及怎样去提升用户体验的。

本文内容主要可以分为以下三部分:

  • 视频功能开发中我们遇到了哪些痛点
  • 我们是怎么来解决这些痛点的
  • 我们下一步的计划什么

一、背景

1、痛点

我们讲做视频优化,到底是要优化什么,这就要看我们遇到了哪些问题,有哪些痛点需要解决。整体看来,视频的优化从下面几个方面切入:首帧时间、带宽、播放错误、缓存、流畅性等。

  • 首帧时间:用户从发起网络请求到视频第一帧渲染到屏幕上的时间,视频播放快慢最直观的指标。
  • 带宽:不仅需要为用户节省流量,也需要考虑节省云音乐的带宽,毕竟都是真金白银买来的。
  • 播放错误:播放错误对于用户体验的影响不言而喻,对用户活跃和用户留存都会产生不利影响。在降低播放错误率方面,我们也做了很多的努力。
  • 缓存:缓存的主要目的也是提升播放的体验,能够让用户像播放本地视频一样播放在线视频,这个算是投入产出比比较高的一项优化了。
  • 流畅性:既包含页面滑动的流畅性也包括播放的流畅性,尤其对于直播,流畅的体验显得尤为重要。

2、原生播放器的局限

Android本身提供了MediaPlayer来播放媒体文件。通常只需要使用原生播放器,通过一些简单的接口调用就可以实现视频播放功能。

但是,实践中我们发现原生播放器有很大的局限性。

从客观方面,原生播放器的兼容性比较差,由于Android的碎片化、不同厂商的ROM定制、芯片的差异等原因,播放失败、花屏、黑屏等问题层出不穷,并且当问题出现的时候,我们通常束手无策。另外,播放出错的时候,播放器提供的错误信息也不够明确,很多时候很难清楚的知道到底是因为什么原因导致的播放错误。因此,无论从播放问题的排查还是处理上,原生播放器都有其局限性。

主观方面来看,原生播放器的灵活性有限。播放器数据的请求是由播放器内部发起的,我们只是提供了一个url,而不能控制数据的请求过程。如果我们有特殊需求(比如需要网络请求走代理服务器),需要去控制数据请求过程,就比较麻烦了。另外,如果需要实现一些缓存相关的功能,比如视频的预加载、边播边存等功能,也是无法直接实现的。

为了解决以上这些问题,云音乐放弃了原生播放器,而使用了自研播放器。

二、实践

下面会基于之前提到的痛点和局限性,展开来讲解网易云音乐在视频播放体验的优化上所做的一些工作。由于点播和直播技术在具体需求和细节上会有一些差异,我们会分开两部分来讨论。


1、点播

下面先来讲讲点播的问题。首先我们会介绍在自定义播放器的基础上做了哪些优化,这些优化主要围绕节省带宽和降低首帧时间来展开。然后,我们来讲讲在视频播放的错误率方面所做的一些优化和尝试。最后,会简单介绍一下网络优化对视频播放的影响。

1)DataSource

自研播放器给云音乐的功能开发带来了很大的灵活性,许多功能都可以根据需要进行定制。首先要说的就是DataSource功能。

通常我们给播放器提供数据都是直接设置一个url,由播放器去请求数据。而为了能够对数据请求过程进行控制,我们改为给播放器提供DataSource。由DataSource负责数据的请求过程,播放器只需要从DataSource读取字节数据,并不需要关心数据的具体来源,数据可以来自网络、本地文件或其他任何来源,如下图。


通过DataSource,我们可以在给播放器提供数据的同时,将数据缓存到本地文件中。当再次播放同一视频的时候,只需要从本地读取文件,而不再需要请求网络。通过给播放器设置DataSource的方式,就可以实现边播边下载的功能。

我们注意到,如果视频播放过程中用户有多次的拖动,缓存的文件就是带有空洞的。再次播放此视频的时候,如果读取到已缓存的数据,DataSource直接返回给播放器,如果读取的数据缓存里没有,就需要阻塞读取线程,通知下载线程去下载这部分数据,等数据下载完成再次唤醒读取线程将数据返回给播放器。这里就需要处理好文件的分段下载和两个线程的同步的问题。

2)按需加载

想象一个场景:很多情况下,用户观看一个视频,发现不感兴趣,就去看下面的内容了。如果用户只看了十几秒的视频,在网速足够快的情况下,播放器可能已经完整的缓存整个视频。这样不仅浪费了用户大量的流量,也会浪费我们的带宽资源。为了解决这个问题,需要对视频进行按需加载。

首先需要提供两个阈值,阈值A表示每次缓存数据的长度,阈值B表示本地缓存还剩多少未播放的时候再次触发网络数据的请求。当播放器读取数据,我们首先请求A字节的数据,后续的播放器数据读取都不触发新的网络读取,直到本地缓存的数据只剩B字节,则再次触发新的网络数据读取,长度依然为A字节,这时候,本地缓存数据的长度就达到了2A字节,后续的处理都是类似的。

这样的方案解决了资源浪费的问题,同时也可以保证不会因为非网络原因导致本地数据不足而引起播放卡顿。

3)Feed流优化

现在视频Feed流已经变的很普遍,基本上大部分带有视频功能的App都会提供Feed流功能。我们知道每次的视频播放都需要DNS、TCP连接的建立、视频头部探测,下载和解析等步骤才能最终将视频画面呈现在屏幕上,这整个过程时间累加起来就可能会达到几秒钟。为了能够提高Feed流中视频的首帧渲染速度,我们考虑使用预缓存的方式来优化视频Feed流。

具体的实现方式是播放一个视频的时候,主动去预加载Feed流中后续的若干个视频文件,每个视频都只下载固定大小的数据,比如1M。这样在播放下一个视频的时候,直接读取下载到本地的数据,进行视频帧的解析和展示,而省去了其他一系列请求和下载过程,极大的提高了视频首帧的渲染速度。理论上,这种情况下首帧的渲染时间和本地视频没有什么差别。

关于视频预加载的技术实现,我们无法直接通过播放器去缓存数据,只能尝试另外的实现方式。云音乐使用的是socket server的方案,首先建立本地的socket server,监听local host和指定端口的请求。每次数据的请求都发给local host,由 socket server来代理视频数据的请求,请求到的数据不返回给播放器,而是直接写入到文件缓存中。

4) moov优化

开始讲解这个问题之前,我们先来看一下mp4文件的组成。mp4文件主要由ftyp、mdat、moov三部分组成,ftyp记录编码格式之类的信息,moov则如同检索表一样,记录了每一帧对应的数据在哪里,mdat记录视频的数据信息。播放器在播放视频的时候需要先拿到ftyp和moov信息,才能继续往下解析视频数据,开始视频的播放。

而在实际中,我们发现有些视频的加载速度比其他视频要慢很多。通过排查,我们发现,原生播放器在开始播放这些视频的时候都会发起不只一次的网络请求,而且请求的range不仅在文件开头,也可能会请求文件尾部。因此,我们猜测播放器对文件进行探测的时候,没有得到需要的配置信息,头部读取了超过一定的数据,就去探测文件尾部。通过将文件下载下来,通过工具查看也验证了我们的猜测。

既然知道了原因,这个问题的处理方式也很明确。只需要将这些文件处理一下,将moov移到mdat的前面就可以了。

5)播放错误率

前面提到我们使用自研播放器的目的之一,是解决播放错误问题。

即使使用了自研播放器,依然需要面临软解和硬解选择的问题。软解兼容性比较好,可以解决掉大部分我们开始提到的解码错误问题,但软解的解码效率比硬解要低,而且会占用CPU资源,可能会导致在性能比较差的机器上播放卡顿。硬解码直接使用GPU解码,解码效率高,但是兼容性较差,原生播放器就是因为用硬解才会有很多播放错误问题。

既然软解和硬解都不是完美的,那我们就需要有个策略来尽可能的利用两者的优势来降低播放错误。大部分情况下,低分辨率视频使用软解,机器的压力并不会很大,反倒是使用硬解的话,可能会存在兼容性问题;高分辨率的视频如果使用软解的话,有些性能比较差的手机可能会出现卡顿或音画不同步的现象。结合这些情况,在实际中,我们使用了这样的策略,大于等于720P的视频使用硬解,小于720P的视频使用软解。

这样处理可以一定程度上降低播放错误率。但即使是这样,我们也不能保证所有的机器播放都没有问题。有些手机即使播放低分辨率的视频,解码性能也可能无法流畅的播放;对于高分辨率的视频,依然会存在兼容性问题导致的播放失败。为了给用户提供更大的自由度,让用户可以选择适合自己手机的解码方式,我们在设置中增加了视频解码模式的选择功能。

通过不断的探索和优化,云音乐的播放错误率也得到显著下降。下图展示了某几个版本视频播放错误率的变化。

6)网络优化

对于播放体验和播放错误的改进,网络的优化也是不可或缺的一部分。

首先,域名解析的耗时本身会影响视频首帧时间,也可能会由于解析失败导致视频播放出错。因此,云音乐使用了HttpDns来代替默认的域名解析,并且使用了批量域名预取,预埋一些默认的域名地址,当域名解析出错时,可以通过默认ip访问网络。

其次,对于视频数据的请求,需要先建立tcp连接,为了能够避免tcp连接建立的时间消耗,底层网络库里启用了tcp连接复用,在域名收敛的情况下,可以通过复用tcp连接来减少视频请求的时间。从而提升视频首屏速度。

最后,弱网也是我们无法避开的问题。弱网环境会存在请求超时,针对这种情况,我们会尝试多次重连。而且在网络库底层,在超时的情况下,也会超时连接其他ip。另外,对于多分辨率视频的情况,如果明确知道网络比较差的话,可以尝试提示用户切换到低分辨率来播放。

2、直播

云音乐在最近的版本中加入了直播功能。虽然直播功能只是刚刚起步,我们也做了一些尝试来提高用户体验,力求在同类产品中能够做到最好。下面首先会讲一下云音乐在直播架构上的设计思路,然后介绍在首帧优化和滑动流畅性方面的一些策略。

1)多进程

对于视频播放器,通常的实现方式是在SurfaceView或TextureView中包含一个MediaPlayer,控制逻辑也都直接写在View中,而且通常一个View与一个MediaPlayer绑定。在此基础上,我们首先对播放器做了进程隔离,播放器放到单独进程,主进程和播放器进程通过binder进行通信,然后对播放器进行复用,多个View绑定到同一个Player,然后通过Manager进行统一管理和调度。这样做主要有以下优点:复用播放器,节省资源;播放器独立进程,减少主进程内存的占用;进程隔离,播放进程崩溃后可以通过主进程恢复。当然,这样需要增加一些额外的控制逻辑和程序的复杂度。

2)首帧优化

首帧时间,即从开始播放到视频第一帧渲染到屏幕上的时间。要对首帧耗时进行优化,我们需要搞清楚这其中包含哪些步骤。这个过程依次分为以下几步:

1. DNS解析,通常一个App内视频资源的域名是固定的一个或几个,所以只有第一次播放需要请求域名服务器解析域名,后面都只需要直接到缓存里面去取就可以了。

2. 域名解析完之后,首先要发起TCP连接,这个时间受网络状况影响。

3. 然后发起Http请求,等待Http数据的返回,这个也会受到网络情况的影响。

4. 数据返回之后,播放器首先要对视频格式进行探测,而且需要足够的数据量才能探测成功。

5. 格式探测完也是不能直接播放的,播放器需要拿到足够多的数据缓存才能开始播放视频,开始播放的缓存量有一个阈值是可以设置的。

6. 最后一步就是对视频数据的解码,这个耗时由播放器本身的性能决定。

通过上面的分析,首帧耗时主要由DNS耗时+TCP连接耗时+Http请求耗时+视频探测耗时+buffer缓存耗时+播放器解析耗时累加而成。其实,从整个过程来看,App层面可以做的并不多。我们考虑从其他角度来优化首帧时间。

云音乐的直播间采用的是上下滑动切换,可以考虑在滑动的时候提前启动数据的加载过程。

首先,我们需要在打开直播间的时候就直接将直播地址的列表传进去,直播间切换的时候就可以直接开始播放,而不需要等待接口返回了。

其次,考虑将视频数据的请求时间尽可能的提前,就还需要一定程度的预加载。我们考虑设计一个播放器池,包含三个播放器实例A、B、C。当前使用播放器B播放视频,当直播间开始滑动,滑动距离达到一个阈值,就认为已经切换到了下一个房间,可以提前取下一个播放器A(或C)加载数据。等到滑动完全停止,B停止播放,A(或C)如果已经加载完数据进入prepared状态则可以直接播放,否则就继续数据的加载过程。这样就充分利用了滑动切换的这个时间来减少了视频首帧的渲染时间。

我们对云音乐直播的首帧数据和两个竞品进行了对比,在相同网络、同一台机器下的数据如下:


3)滑动流畅性

云音乐的直播是使用上下滑动的切换方式,提到滑动,流畅性就是一个比较重要的参数。通常的方案是使用多个item,上下滑动的时候直接切换不同的item来实现页面的切换。考虑到由于每个item中包含的View比较多,滑动中大量View的重绘会导致性能低下。我们就使用了View复用的方案,底层用一个RecyclerView用于提供上下滑动的功能,内部仅包含简单的View,而在RecyclerView的上面盖一个播放页的Fragment,RecyclerView上下滑动的时候同步移动Fragment的位置,当滑动结束,再将Fragment位置恢复,并重新填充下一个直播间的数据。

我们对云音乐直播和两个竞品的GPU呈现模式的数据进行了对比。可以看出,云音乐的流畅性明显优于另外两个竞品。

三、展望

除了上面所提到的功能和优化点,我们也在思考当前还有哪些是我们欠缺或者做的不到位的。我们发现网易云音乐当前主要有两个问题亟待解决和完善。

首先,目前云音乐的两个App都有视频播放功能,它们有很多类似的地方,但是由于历史原因,无论是播放框架还是具体播放逻辑上,它们都有一些差异。所以,近期的目标是对两个App的播放器框架层进行统一,对上提供统一的接口封装,对下提供对播放器sdk的可插拔设计,对内实现多业务多场景的一致性的支持。最终目标是能够实现一个可复用、易扩展、功能强大的视频播放组件。

其次,云音乐缺少一个能够完整展示视频播放情况的监控平台,这个平台包含但不限于播放的卡顿率、错误率、错误类型、首帧时间、播放状态等等数据内容。目前我们只能根据某些用户的反馈来解决个别问题,并不能明确的知道某一类问题是否得到了彻底解决,这样的问题处理方式有很大的局限性。另外,我们缺乏一个对视频播放情况全面系统的认识,需要有一个数据告诉我们存在什么问题,问题解决的效果怎么样。因此,这样的一个数据监控平台的建立对于我们进一步去提升用户体验是至关重要的。

四、总结

以上介绍了网易云音乐在视频点播和直播上的一些思考和设计,以及在用户体验优化上所做的一些尝试。播放体验的优化是一个持续探索的过程,我们后面也会在这方面做出更多的努力和尝试。后续云音乐也会在播放框架的统一、视频播数据的监控等方面做进一步的改进和优化。

编辑于 2019-01-31