简介

带宽预测是播放器实现码率自适应的技术基础。只有对当前的带宽预测的足够准确,才能够选择出当前场景下最优的码率进行播放。下面分别介绍下ijkplayer、exoplayer及VLC的带宽预测方法。

ijkplayer

ijkplayer的带宽预测代码位于ijksdl_timer.c中,核心思想是对过去时间点的网速进行采样,使用过去所有采样点的加权均值对当前带宽进行预测。其核心结构体为SDL_SpeedSampler2:

typedef struct SDL_SpeedSampler2

{

    int64_t sample_range;//采样点的时间范围,初始化时定义,后续不会修改,默认值是2000ms

    int64_t last_profile_tick;//上一次采样的时间

    int64_t last_profile_duration;//之前所有采样下载时间的加权均值

    int64_t last_profile_quantity;//之前所有采样下载数据量的加权均值

    int64_t last_profile_speed;//上一次采样时的平均带宽

} SDL_SpeedSampler2;

SDL_SpeedSampler2结构体的last_profile_duration和last_profile_quantity变量比较难理解,需要结合代码逻辑才能更好地解释,首先看下增加采样点的代码:

//函数参数只有两个,分别是采样结构体的指针和本次采样下载的数据量

int64_t SDL_SpeedSampler2Add(SDL_SpeedSampler2 *sampler, int quantity)

{

    if (quantity < 0)

        return 0;

    int64_t sample_range  = sampler->sample_range;

    int64_t last_tick     = sampler->last_profile_tick;

    int64_t last_duration = sampler->last_profile_duration;

    int64_t last_quantity = sampler->last_profile_quantity;

    int64_t now           = (int64_t)SDL_GetTickHR();

    //这里使用两次采样的间隔作为本次采样的下载时间

    int64_t elapsed       = (int64_t)llabs(now - last_tick);

    //如果两次采样的间隔时间超过了sample_range,那么之前的采样信息便不再置信,因此需要重新统计采样信息,将本次采样作为第一次采样

    if (elapsed < 0 || elapsed >= sample_range) {

        // overflow, reset to initialized state

        sampler->last_profile_tick     = now;

        sampler->last_profile_duration = sample_range;

        sampler->last_profile_quantity = quantity;

        sampler->last_profile_speed    = quantity * 1000 / sample_range;

        return sampler->last_profile_speed;

    }

    //这里就是加权算法的部分,这里的last_quantity和last_duration都是从0开始增长的,即new_quantity和new_duration也是从0开始增长的。当new_duration小于sample_range时,

    //即第一个采样点与最后一个采样点的时间差在sample_range范围内时,new_duration和new_quantity的权重都还是1,即未经过加权;一旦new_duration大于sample_range后,new_duration

    //的值就会被固定为sample_range,new_quantity则按比例进行缩小,这也就变相地实现了,越靠前的采样点,随着时间的推移,所占的比重会越来越小。

    int64_t new_quantity = last_quantity + quantity;

    int64_t new_duration = last_duration + elapsed;

    if (new_duration > sample_range) {

        new_quantity = new_quantity * sample_range / new_duration;

        new_duration = sample_range;

    }

    //这部分逻辑比较简单了,更新变量然后算出当前平均带宽。

    sampler->last_profile_tick     = now;

    sampler->last_profile_duration = new_duration;

    sampler->last_profile_quantity = new_quantity;

    if (new_duration > 0)

        sampler->last_profile_speed = new_quantity * 1000 / new_duration;

    return sampler->last_profile_speed;

}

上面介绍了采样的过程,预测的过程就十分简单了,和采样的流程基本一致:

int64_t SDL_SpeedSampler2GetSpeed(SDL_SpeedSampler2 *sampler)

{

    int64_t sample_range  = sampler->sample_range;

    int64_t last_tick     = sampler->last_profile_tick;

    int64_t last_quantity = sampler->last_profile_quantity;

    int64_t last_duration = sampler->last_profile_duration;

    int64_t now           = (int64_t)SDL_GetTickHR();

    int64_t elapsed       = (int64_t)llabs(now - last_tick);

    if (elapsed < 0 || elapsed >= sample_range)

        return 0;

    //这里存在一个问题,就是elapsed是已知的,因此可以直接加到duration里,但是这段时间下载的数据量quantity是未知的,因此new_quantity是比实际值要小的,最终导致预测的带宽也要比

    //实际值小一点,且elapsed越大,这个误差会越明显

    int64_t new_quantity = last_quantity;

    int64_t new_duration = last_duration + elapsed;

    if (new_duration > sample_range) {

        new_quantity = new_quantity * sample_range / new_duration;

        new_duration = sample_range;

    }

    if (new_duration <= 0)

        return 0;

    return new_quantity * 1000 / new_duration;

}

总结:ijkplayer巧妙地使用一个结构体保存了”所有“采样点的信息,并且实现了带宽的加权平均预测,这种方法相对于单独记录每个采样点信息的方式会比较省内存,但是一些较老的采样点是否仍应该被计算进来则有待商榷。(其实就是指数移动平均)

exoplayer

exoplayer同样使用采样的方式进行带宽预测,其代码位于SlidingPercentile.java中,使用了滑动窗口的方式限制了采样点的数量,先看下该类的主要成员变量:

//重载了两种COMPARATOR,分别比较采样点的index和value

private static final Comparator<Sample> INDEX_COMPARATOR = (a, b) -> a.index - b.index;

private static final Comparator<Sample> VALUE_COMPARATOR =

    (a, b) -> Float.compare(a.value, b.value);

//排序方式,按照index排序或按照value排序

private static final int SORT_ORDER_NONE = -1;

private static final int SORT_ORDER_BY_VALUE = 0;

private static final int SORT_ORDER_BY_INDEX = 1;

//回收区的最大数量

private static final int MAX_RECYCLED_SAMPLES = 5;

//最大权重,即滑动窗口的大小

private final int maxWeight;

//采样点列表

private final ArrayList<Sample> samples;

//回收区采样点列表,这里为了避免采样点的频繁创建与销毁,新增了回收区

private final Sample[] recycledSamples;

//当前排序方式

private int currentSortOrder;

//下一个采样点的index

private int nextSampleIndex;

//当前权重和

private int totalWeight;

//已回收的采样点数量

private int recycledSampleCount;

采样点的结构比较简单,仅包含三个成员变量:

private static class Sample {

  public int index;

  //当前采样点的权重,为下载数据量的平方根

  public int weight;

  //当前采样点的下载速度

  public float value;

}

然后看下新的采样点是如何加入的:

//参数有两个,分别是采样点的权重和下载速度

public void addSample(int weight, float value) {

  //确保采样点是按照index即时间顺序排序的

  ensureSortedByIndex();

  //如果回收区有采样点,直接复用即可;如果没有,则需要新建,这样能够一定程度地避免内存频繁申请与释放

  Sample newSample =

      recycledSampleCount > 0 ? recycledSamples[--recycledSampleCount] : new Sample();

  //相关信息的更新

  newSample.index = nextSampleIndex++;

  newSample.weight = weight;

  newSample.value = value;

  samples.add(newSample);

  totalWeight += weight;

  //这里是实现滑动窗口的部分,如果当前总权重超过了最大权重,就需要滑动窗口,保持窗口内的权重恒等于最大权重

  while (totalWeight > maxWeight) {

    //计算totalWeight与maxWeight的差值excessWeight

    int excessWeight = totalWeight - maxWeight;

    Sample oldestSample = samples.get(0);

    //如果最老采样点的权重小于excessWeight,直接删除

    if (oldestSample.weight <= excessWeight) {

      totalWeight -= oldestSample.weight;

      samples.remove(0);

      if (recycledSampleCount < MAX_RECYCLED_SAMPLES) {

        recycledSamples[recycledSampleCount++] = oldestSample;

      }

    //如果最老采样点的权重大于excessWeight,该点的权重减掉excessWeight

    else {

      oldestSample.weight -= excessWeight;

      totalWeight -= excessWeight;

    }

  }

}

最后看下预测部分的逻辑:

//只有一个参数,取值范围是(0,1]

public float getPercentile(float percentile) {

  //将采样点按照下载速度进行排序

  ensureSortedByValue();

  //算出要取的权重值desiredWeight

  float desiredWeight = percentile * totalWeight;

  int accumulatedWeight = 0;

  //遍历采样点,找到累加权重值大于等于desiredWeight的采样点,并返回该采样点的下载速度

  for (int i = 0; i < samples.size(); i++) {

    Sample currentSample = samples.get(i);

    accumulatedWeight += currentSample.weight;

    if (accumulatedWeight >= desiredWeight) {

      return currentSample.value;

    }

  }

  // Clamp to maximum value or NaN if no values.

  return samples.isEmpty() ? Float.NaN : samples.get(samples.size() - 1).value;

}

总结:exoplayer的核心思想就是使用滑动窗口对带宽进行预测。个人认为目前的计算方式有两点问题:1、目前的滑动窗口大小取决于数据量的大小,不能很好地确保采样点间的时间相关性;2、最终预测时将采样点列表按照下载速度排序,再一次弱化了时间相关性,暂时只能理解为exoplayer选择了较为保守的中值策略。

VLC

VLC的带宽预测同样使用了滑动窗口的思想,其主要逻辑位于MovingAverage.hpp中:

   class MovingAverage

   {

       public:

           MovingAverage(unsigned = 10);

           T push(T);

       private:

           //保存了采样点带宽的列表

           std::list<T> values;

           //上一个被丢弃的采样点的带宽

           T previous;

           //列表中采样点的最大数量

           unsigned maxobs;

           //带宽均值

           T avg;

   };

   template <class T>

   T MovingAverage<T>::push(T v)

   {

       //滑动窗口的逻辑

       if(values.size() >= maxobs)

       {

           previous = values.front();

           values.pop_front();

       }

       values.push_back(v);

       /* compute for deltamax */

       T omin = *std::min_element(values.begin(), values.end());

       T omax = *std::max_element(values.begin(), values.end());

       //diffsums保存了列表中每一个采样点和其上一个采样点的带宽差值的绝对值之和

       //列表中第一个采样点的上一个采样点即previous

       MovingAverageSum<T> diffsums = std::for_each(values.begin(), values.end(),

                                                    MovingAverageSum<T>(previous));

       /* Vertical Horizontal Filter / Moving Average

        *

        * stability during observation window alters the alpha parameter

        * and then defines how fast we adapt */

       //这里用列表中最大带宽和最小带宽作差取得deltamax,然后用deltamax除以diffsums作为权重

       //代码里没有给出这种算法的出处,大体可以理解为这是一种衡量网络波动的算法?

       const T deltamax = omax - omin;

       double alpha = (diffsums.sum) ? 0.33 * ((double)deltamax / diffsums.sum) : 0.5;

       avg = alpha * avg + (1.0 - alpha) * (*values.rbegin());

       return avg;

   }

总结:VLC同样使用滑动窗口对网络带宽进行预测,预测算法中侧重于网络波动的影响,也淡化了时间的概念。(改进的指数移动平均)

播放器网络带宽预测方法相关推荐

  1. 小米路由器怎么连接无盘服务器,播放器+服务器的方法瞬间玩转小米路由方法图文介绍...

    "厨具":小米路由及其外接硬盘.安卓手机.威动播放器(VidOn Player).威动服务器(VidOn Server) "食材":冰雪奇缘.生活大爆炸 用两种 ...

  2. 一种多功能语音识别技术和音乐播放器相结合的方法

    一种多功能语音识别技术和音乐播放器相结合的方法 [专利摘要]本发明一种适用于一种多功能语音识别技术和音乐播放器相结合的方法,本发明将语音识别用于音乐播放器上.将音乐播放器改为语音控制型的,可以减少硬件 ...

  3. html做全景视频播放器,一种全景视频播放方法及播放器的制造方法

    一种全景视频播放方法及播放器的制造方法 [技术领域] [0001]本发明涉及视频播放领域,尤其涉及一种全景视频播放方法及播放器. [背景技术] [0002]随着近年来视频拍摄技术的发展,全景图片.全景 ...

  4. Ubuntu 下 FireFox( 火狐 )无法使用HTML5播放器的解决方法

    Ubuntu 下 FireFox( 火狐 )无法使用HTML5播放器的解决方法: ubuntu自带的火狐没有视频播放器,可以不用下载flash(即将不支持),现在国内许多视频网站(B站等)都支持HTM ...

  5. kodi pvr 不能安装_「家庭影音串流」电视最强播放器KODI使用方法

    本文作者:空翻的帕兹 文章适用电脑手机等全平台设备,在用户没有nas的情况下如何使用串流 电视最强播放器KODI Kodi是由XBMC基金會開發的開源媒體播放器,原名XBMC(最後一個以XBMC命名的 ...

  6. qq播放器免费的方法

    没有网哪有痛  没有爱哪有痛  当网成为痛的根由  当爱成为一种毒素  我无法借掉对网的迷惑  也无法逃避这爱的旋涡  第一步:登录你的QQ空间(http://Q-zone.qq.com/web),首 ...

  7. win10系统专用DVD播放器安装的方法--win7w.com

    win10系统性能稳定受到广大用户的喜爱,但也不尽完美,在使用过程中,难免会遇到win10系统专用DVD播放器安装的问题.碰到win10系统专用DVD播放器安装的问题.很多用户不知道win10系统专用 ...

  8. windows media player控件播放器属性及方法使用

    wmp 9.0控件常见属性和方法 [基本属性] URL:String; 指定媒体位置,本机或网络地址 ui Mode:String; 播放器界面模式,可为Full, Mini, None, Invis ...

  9. android调用音乐播放器,三种方法

    小弟想请问一下,如何在自己写的程序中调用系统的音乐播放器呢. 我在google上搜索了,主要是有两种方法,但是都不是我想要的. 第一种是,使用mp3音乐文件的uri,和intent,进行调用,但是这种 ...

最新文章

  1. 深度学习的分布式训练--数据并行和模型并行
  2. R语言构建logistic回归模型并评估模型:计算混淆矩阵、并基于混淆矩阵计算Accuray、Precision、Recall(sensitivity)、F1、Specificity指标
  3. java使用数据库连接池连接MySQL/MariaDB--DBCP2
  4. Win 7 隐藏小功能——屏幕录制
  5. Confluence 6 针对 'unmigrated-wiki-markup' 宏重新尝试合并
  6. 幼儿园 php,input.php
  7. 基础算法 —— 高精度计算
  8. Python里面使用的容器
  9. php练习——打印半金字塔、金字塔、空心金字塔、菱形、空心菱形
  10. android移植jdk,重装windows后移植jdk和Android Studio
  11. 基于centos的FasfDFS安装配置
  12. 利用predis操作redis方法大全
  13. python能不能自动写代码_微软最强 Python 自动化工具开源了!不用写一行代码!...
  14. 华为WATCH D血压管理计划怎么用
  15. 整数幂C语言1005,zzuli1005: 整数幂 - 菜鸟头头
  16. Helio P10 (MT6755)
  17. HDU - 5894 hannnnah_j’s Biological Test 组合数(插板法)
  18. 影院电影售票管理系统
  19. jmeter压测指南
  20. python爬取《你好, 李焕英》豆瓣评论数据

热门文章

  1. 鼎捷T100 Linux基础篇
  2. 杨辉三角形(超级简单的Python实现方法)
  3. 小视频系统源码H5 直播起航
  4. 计算机云教室管理制度,《中云中学专用教室管理制度》一.doc
  5. GUI-Guider LVGL 如何切换界面
  6. ROI Pooling(感兴趣区域池化)
  7. bettercap 安装使用笔记
  8. List中remove()方法的注意事项
  9. uniapp实现微信支付、支付宝支付
  10. c语言打开当前目录下的文件_干货||嵌入式Linux下的C编程知识要点总结