ExoPlayer Talk 01 缓存策略分析与优化

操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Android studio 2.3.3 | ExoPlayer r2.5.1



使用 ExoPlayer已经有一段时间了,对播放器的整体架构设计 到 具体实现 佩服至极,特别建议开发播放器的同学有机会一定要看看,相信会受益匪浅。这次分享的内容主要关于缓存策略优化。

Default Buffer Policy



Google ExoPlayer提供了默认的AV数据的缓存策略,并通过 DefaultLoadControl 组件实现。该加载器组件本身没有问题,只不过在一些情景下,这种默认缓存策略,会减损"缓存"本身的效果。在 DefaultLoadControl中有如下代码片段:

  @Override    public boolean shouldContinueLoading(long bufferedDurationUs)
  {
      ...
      isBuffering = bufferTimeState == BELOW_LOW_WATERMARK || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached);

      ...return isBuffering;
  }

该函数由于播放器调用,以确定是否应该继续加载缓存AV数据。

/**
   * The default minimum duration of media that the player will attempt to ensure is buffered at all
   * times, in milliseconds.
   */
  public static final int DEFAULT_MIN_BUFFER_MS = 15000;

  /**
   * The default maximum duration of media that the player will attempt to buffer, in milliseconds.
   */
  public static final int DEFAULT_MAX_BUFFER_MS = 30000;

  /**
   * The default duration of media that must be buffered for playback to start or resume following a
   * user action such as a seek, in milliseconds.
   */
  public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500;

  /**
   * The default duration of media that must be buffered for playback to resume after a rebuffer,
   * in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user
   * action.
   */
  public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS  = 5000;

DEFAULT_MIN_BUFFER_MS  常量定义了播放器触发加载AV数据的时机,即当前缓冲区AV数据 duration time 小于15秒。

DEFAULT_MAX_BUFFER_MS 常量定义了播放器进行加载AV数据的上限,即当前缓冲区AV数据 duration time 小于30秒。

DEFAULT_BUFFER_FOR_PLAYBACK_MS 常量定义了播放器播放AV数据的条件,即缓冲区必须满足AV数据 duration time 不小于2.5秒。

DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS 常量定义了播放器从 REBUFFER状态(REBUFFER是由于运行时缓冲区耗尽触发导致) 恢复为可播放状态后可播放AV数据的条件,即缓冲区必须满足AV数据 duration time 不小于5秒。

所以根据以上代码了解,前两个参数用于描述加载时序,后两个参数用于描述是否有足够的缓冲数据来播放。

我们可以概述默认加载器组件会按照如下方式工作:

  1. 加载组件持续加载,直到缓冲AV数据大于30秒,停止加载,进入等待状态。
  2. 当缓冲AV数据小于15秒时,加载器重新执行加载逻辑。
  3. 播放器根据当前缓冲区AV数据 duration time 控制是否播放。

那么问题来了,这样设计的目的是什么?很难想到一个情景应用该策略,换句话说是否可以自定义修改15秒30秒这样的数值呢?

What about Buffering?


There are arguments that mobile carriers prefer this kind of traffic pattern over their networks (i.e. bursts rather than drip-feeding). Which is an important consideration given ExoPlayer is used by some very popular services. It may also be more battery efficient.

Whether these arguments are still valid is something we should probably take another look at fairly soon, since the information we used when making this decision is 3-4 years old now. We should also figure out whether we should adjust the policy dynamically based on network type (e.g. even if the arguments are still valid, they may only hold for mobile networks and not for WiFi).

关于此问题已经有人问询过,其中一个ExoPlayer开发人员给出这样的答复,大致意为,目前主流移动运营商将作为ExoPlayer的相关实现重要考虑对象,有证据表明运营商们更倾向于这种被称为 bursts 的流量模式,而不是 drip-feeding 类的流量模式。除此之外也会提升电池使用效率。无论这些证据是否有效,很快会再次的了解一下,因为做出这个决定所参考的是3-4年前的信息了。还应该确定是否有必要根据网络类型动态调整策略(即使这些参数仍然使用,它们只适用于移动网络,而不是WiFi)。

尽现在使用的默认缓存策略比较通用,但不可能满足所有的情况。为了更好的点播体验,增加缓冲数据 duration time 为1分钟或者更久,接下来我们进一步分析ExoPlayer默认缓存策略的实现原理,并在最后给出一般性的优化例子。

Water Marks



在默认的缓存策略实现中,有一个 water marks 的概念,类似水桶盛水过程中变化的"水位 ",具体代码为:

  private static final int ABOVE_HIGH_WATERMARK = 0;
  private static final int BETWEEN_WATERMARKS = 1;
  private static final int BELOW_LOW_WATERMARK = 2;

三个级别的水位与前面提到的四个常量的关系如图所示:

参考完整的 getBufferTimeState() 与 shouldContinueLoad() 函数,其中 getBufferTimeState() 根据当前的缓存AV数据的 duration time 来判断处于哪个水位。

private int getBufferTimeState(long bufferedDurationUs)
{
    return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK
        : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS);
}
@Override
  public boolean shouldContinueLoading(long bufferedDurationUs)
  {
      int bufferTimeState = getBufferTimeState(bufferedDurationUs);
      boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
      boolean wasBuffering = isBuffering;

      isBuffering = bufferTimeState == BELOW_LOW_WATERMARK || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached);

      if (priorityTaskManager != null && isBuffering != wasBuffering)
      {
          if (isBuffering)
          {
            priorityTaskManager.add(C.PRIORITY_PLAYBACK);
          }
          else
          {
            priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
          }
      }

      return isBuffering;
  }

一个典型的加载行为经过 A、B、C 三个阶段:

Pass A

起始阶段,加载器开始连续的加载AV数据,一直达到或者超过 maxBufferUs 水位为止,需要注意的是这个时候时间线为停顿在 t1 ,如下图所示:

Pass B

t1 时间后,播放器消费缓冲区AV数据,但 isBuffering = false 并且 bufferTimeState == BETWEEN_WATERMARK,所以 shouldContinueLoading() 仍然返回 false,即不需要加载AV数据,时间线停顿在 t2 ,如下图所示:

Pass C

来到最后一个阶段,当播放器持续消费缓冲区AV数据,直到水位低于 minBufferUs ,即 bufferTimeState == BELOW_LOW_WATERMARK 时候,我们恢复加载程序,时间线停顿在 t3 ,如下图所示:

作为一个小节,通过三个阶段的图示我们了解到,从 t1t3 之间区间内,加载器没有做任何加载操作。因此会遇到这种情景,某时刻缓冲区中只有仅仅15秒的缓冲数据。

除此之外,对于缓冲区大小也是有限制的,一般来说当网络状况良好时,一般都可以缓存 15 到 30 秒的AV数据,换句话说,有可能根据需求扩展缓冲区大小。

How to customize the buffer?



应用前面提到的 drip - feeding 滴灌方式,移除缓冲区的上线限制,代码如下:

isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
                || (bufferTimeState == BETWEEN_WATERMARKS
            /*
             * commented below line to achieve drip-feeding method for better caching. once you are below maxBufferUs, do fetch immediately.
             */
            /* && isBuffering */
                && !targetBufferSizeReached);

同时扩大 maxBufferUsminBufferUs

/**
 * To increase buffer time and size.
 */
public static int BUFFER_SCALE_UP_FACTOR = 4;
....
minBufferUs = BUFFER_SCALE_UP_FACTOR * minBufferMs * 1000L;
maxBufferUs = BUFFER_SCALE_UP_FACTOR * maxBufferMs * 1000L;
...

可以在 shouldContinueLoading() 函数下面添加日志,验证修改前后的不同表现。

Log.d(CustomLoadControl.class.getSimpleName(), "current buffer durationUs: " + bufferedDurationUs + ",max bufferUs: " + maxBufferUs + ", min bufferUs: " + minBufferUs + " shouldContinueLoading: " + isBuffering);

修改之前的LOG如下,可以观测到只有第一次水位达到 maxBufferUs ,之后的缓存策略一直维持在 minBufferUs

D/DefaultLoadControl:    current    buffer    durationUs:    0,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    981333,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    2154666,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    3136000,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
...
D/DefaultLoadControl:    current    buffer    durationUs:    15160125,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    15973479,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15973479,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15963667,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
...
D/DefaultLoadControl:    current    buffer    durationUs:    15003688,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    14993604,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    15975896,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15975896,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15964834,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
...
D/DefaultLoadControl:    current    buffer    durationUs:    15005417,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    14994542,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    15891750,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15891750,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15880042,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
...
D/DefaultLoadControl:    current    buffer    durationUs:    15003708,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    14992667,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    15601000,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15601000,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    15588708,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
...
D/DefaultLoadControl:    current    buffer    durationUs:    15004458,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE
D/DefaultLoadControl:    current    buffer    durationUs:    14993416,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    TRUE
D/DefaultLoadControl:    current    buffer    durationUs:    16081313,max    bufferUs:    30000000,    min    bufferUs:    15000000    shouldContinueLoading:    FALSE

修改之后,缓冲区扩大,持续加载,维持在 maxBufferUs

D/CustomControl:    current    buffer    durationUs:          0,max bufferUs:120000000,    min    bufferUs:60000000    shouldContinueLoading:    TRUE
D/CustomControl:    current    buffer    durationUs:     981333,max bufferUs:120000000,    min    bufferUs:60000000    shouldContinueLoading:    TRUE
D/CustomControl:    current    buffer    durationUs:    2154666,max bufferUs:120000000,    min    bufferUs:60000000    shouldContinueLoading:    TRUE
...
D/CustomControl:    current    buffer    durationUs:    53194146,max bufferUs:120000000,    min    bufferUs:60000000    shouldContinueLoading:    TRUE
D/CustomControl:    current    buffer    durationUs:    54319750,max bufferUs:120000000,    min    bufferUs:60000000    shouldContinueLoading:    TRUE
D/CustomControl:    current    buffer    durationUs:    55313834,max bufferUs:120000000,    min    bufferUs:60000000    shouldContinueLoading:    TRUE

Ok,本次分享的内容就到这里了,以上内容如有不对之处,请多多指正。

时间: 2024-11-08 17:43:15

ExoPlayer Talk 01 缓存策略分析与优化的相关文章

图片缓存策略

图片缓存策略 1.图片缓存策略分析 从网络上加载一张图,然后把它显示到UI上是个很简单的事情.当图片变多时,处理起来就有些麻烦了,很典型的应用场景,如ListView,GridView或者ViePager等.我们既需要保证用户看到更多的图片,以免屏幕出现大面积的空白,又要保证内存能Hold住. GC会自动释放一个没有强引用的图片或者View,这本来是个好事情,但为了让用户来回滚动时还能快速加载老图片,通常会使用图片缓存. 下面分别讨论下,通过使用Memory Cache和Disk Cache来增

iOS网络加载图片缓存策略之ASIDownloadCache缓存优化

在我们实际工程中,很多情况需要从网络上加载图片,然后将图片在imageview中显示出来,但每次都要从网络上请求,会严重影响用户体验,为了不是每次显示都需要从网上下载数据,希望将图片放到本地缓存,因此我们需要一个好的的缓存策略,今天我将我在项目工程中的实际经验分享给大家,我这里主要介绍一下强大的ASIHTTPRequest的缓存策略,以及使用方法. AD: 在我们实际工程中,很多情况需要从网络上加载图片,然后将图片在imageview中显示出来,但每次都要从网络上请求,会严重影响用户体验,为了不

Universal-Image-Loader源码分析,及常用的缓存策略

讲到图片请求,主要涉及到网络请求,内存缓存,硬盘缓存等原理和4大引用的问题,概括起来主要有以下几个内容: 原理示意图 主体有三个,分别是UI,缓存模块和数据源(网络).它们之间的关系如下: ① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap. ② 内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据.否则执行第三步. ③ 硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件. ④ 如果有对应文件,使用BitmapFactory.deco

tomcat压缩优化和缓存策略

tomcat压缩内容 tomcat的压缩优化就是将返回的html页面等内容经过压缩,压缩成gzip格式之后,发送给浏览器,浏览器在本地解压缩的过程. 对于页面量信息大或者带宽小的情况下用压缩方式还是蛮适用的. 开启tomcat进行压缩的设置 将 1 2 3 4 <Connectorport="8080"  protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="844

【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李志刚 导语 Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必须要掌握的知识,但最近我遇到了几个缓存头设置相关的题目,发现有好几道题答错了,有的甚至在知道了正确答案后依然不明白其原因,可谓相当的郁闷呢!!为了确认下是否只是自己理解不深,我特意请教了其他几位小伙

【转载】HTTP 缓存的四种风味与缓存策略

原文地址:https://segmentfault.com/a/1190000006689795 HTTP Cache 通过网络获取内容既缓慢,成本又高:大的响应需要在客户端和服务器之间进行多次往返通信,这拖延了浏览器可以使用和处理内容的时间,同时也增加了访问者的数据成本.因此,缓存和重用以前获取的资源的能力成为优化性能很关键的一个方面.每个浏览器都实现了 HTTP 缓存! 我们所要做的就是,确保每个服务器响应都提供正确的 HTTP 头指令,以指导浏览器何时可以缓存响应以及可以缓存多久.服务器在

彻底弄懂 HTTP 缓存机制 —— 基于缓存策略三要素分解法

导语 Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必须要掌握的知识,但最近我遇到了几个缓存头设置相关的题目,发现有好几道题答错了,有的甚至在知道了正确答案后依然不明白其原因,可谓相当的郁闷呢!!为了确认下是否只是自己理解不深,我特意请教了其他几位小伙伴,发现情况也或多或少和我类似. 为了不给大家卖关子,下面我贴出2道题,大家可以尝试解答下: 以下为 page.html 内容: <!DOCTYPE html><html xmlns="h

中小型网站架构分析及优化

先看网站架构图: 以上网站架构广泛运用中大型网站中,本文从架构每一层分析所用主流技术和解决手段,有助于初入网站运维朋友们,进一步对网站架构认识,从而自己形成一套架构概念. 第一层:CDN 国内网络分布主要南电信北联通,造成跨地区访问延迟大问题,对于有一定访问量网站来说,增加CDN(内容分发网络)层可有效改善此现象,也是网站加速的最好选择.CDN把网站页面缓存到全国分布的节点上,用户访问时从最近的机房获取数据,这样大大减少网络访问的路径.如果想自己搭建CDN,不建议这么做,因为什么呢?其实说白了,

linux服务器的性能分析与优化(十三)

[教程主题]:1.linux服务器的性能分析与优化 [主要内容] [1]影响Linux服务器性能的因素 操作系统级 Ø CPU 目前大部分CPU在同一时间只能运行一个线程,超线程的处理器可以在同一时间处理多个线程,因此可以利用超线程特性提高系统性能. 在linux系统下只有运行SMP内核才能支持超线程,但是安装的CPu数量越多,从超线程获得的性能提升越少. 另外linux内核会将多核的处理器当做多个单独的CPU来识别,例如,两个4核的CPU会被当成8个单个CPU,从性能角度讲,两个4核的CPU整