You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
7.9 KiB

/*
* NearOptimalAdaptationLogic.cpp
*****************************************************************************
* Copyright (C) 2017 - VideoLAN Authors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "NearOptimalAdaptationLogic.hpp"
#include "Representationselectors.hpp"
#include "../playlist/BaseAdaptationSet.h"
#include "../playlist/BaseRepresentation.h"
#include "../playlist/BasePeriod.h"
#include "../http/Chunk.h"
#include "../tools/Debug.hpp"
#include <cmath>
using namespace adaptive::logic;
using namespace adaptive;
/*
* Multi stream version of BOLA: Near-Optimal Bitrate Adaptation for Online Videos
* http://arxiv.org/abs/1601.06748
*/
#define minimumBufferS VLC_TICK_FROM_SEC(6) /* Qmin */
#define bufferTargetS VLC_TICK_FROM_SEC(30) /* Qmax */
NearOptimalContext::NearOptimalContext()
: buffering_min( minimumBufferS )
, buffering_level( 0 )
, buffering_target( bufferTargetS )
, last_download_rate( 0 )
{ }
NearOptimalAdaptationLogic::NearOptimalAdaptationLogic(vlc_object_t *obj)
: AbstractAdaptationLogic(obj)
, currentBps( 0 )
, usedBps( 0 )
{
vlc_mutex_init(&lock);
}
NearOptimalAdaptationLogic::~NearOptimalAdaptationLogic()
{
}
BaseRepresentation *
NearOptimalAdaptationLogic::getNextQualityIndex( BaseAdaptationSet *adaptSet, RepresentationSelector &selector,
float gammaP, float VD, float Q )
{
BaseRepresentation *ret = NULL;
BaseRepresentation *prev = NULL;
float argmax;
for(BaseRepresentation *rep = selector.lowest(adaptSet);
rep && rep != prev; rep = selector.higher(adaptSet, rep))
{
float arg = ( VD * (getUtility(rep) + gammaP) - Q ) / rep->getBandwidth();
if(ret == NULL || argmax <= arg)
{
ret = rep;
argmax = arg;
}
prev = rep;
}
return ret;
}
BaseRepresentation *NearOptimalAdaptationLogic::getNextRepresentation(BaseAdaptationSet *adaptSet, BaseRepresentation *prevRep)
{
RepresentationSelector selector(maxwidth, maxheight);
const float umin = getUtility(selector.lowest(adaptSet));
const float umax = getUtility(selector.highest(adaptSet));
vlc_mutex_lock(&lock);
std::map<ID, NearOptimalContext>::iterator it = streams.find(adaptSet->getID());
if(it == streams.end())
{
vlc_mutex_unlock(&lock);
return selector.lowest(adaptSet);
}
NearOptimalContext ctxcopy = (*it).second;
const unsigned bps = getAvailableBw(currentBps, prevRep);
vlc_mutex_unlock(&lock);
const float gammaP = 1.0 + (umax - umin) / ((float)ctxcopy.buffering_target / ctxcopy.buffering_min - 1.0);
const float Vd = (secf_from_vlc_tick(ctxcopy.buffering_min) - 1.0) / (umin + gammaP);
BaseRepresentation *m;
if(prevRep == NULL) /* Starting */
{
m = selector.select(adaptSet, bps);
}
else
{
/* noted m* */
m = getNextQualityIndex(adaptSet, selector, gammaP - umin /* umin == Sm, utility = std::log(S/Sm) */,
Vd, secf_from_vlc_tick(ctxcopy.buffering_level));
if(m->getBandwidth() < prevRep->getBandwidth()) /* m*[n] < m*[n-1] */
{
BaseRepresentation *mp = selector.select(adaptSet, bps); /* m' */
if(mp->getBandwidth() <= m->getBandwidth())
{
mp = m;
}
else if(mp->getBandwidth() > prevRep->getBandwidth())
{
mp = prevRep;
}
else
{
mp = selector.lower(adaptSet, mp);
}
m = mp;
}
}
BwDebug( msg_Info(p_obj, "buffering level %.2f% rep %ld kBps %zu kBps",
(float) 100 * ctxcopy.buffering_level / ctxcopy.buffering_target, m->getBandwidth()/8000, bps / 8000); );
return m;
}
float NearOptimalAdaptationLogic::getUtility(const BaseRepresentation *rep)
{
float ret;
std::map<uint64_t, float>::iterator it = utilities.find(rep->getBandwidth());
if(it == utilities.end())
{
ret = std::log((float)rep->getBandwidth());
utilities.insert(std::pair<uint64_t, float>(rep->getBandwidth(), ret));
}
else ret = (*it).second;
return ret;
}
unsigned NearOptimalAdaptationLogic::getAvailableBw(unsigned i_bw, const BaseRepresentation *curRep) const
{
unsigned i_remain = i_bw;
if(i_remain > usedBps)
i_remain -= usedBps;
else
i_remain = 0;
if(curRep)
i_remain += curRep->getBandwidth();
return i_remain > i_bw ? i_remain : i_bw;
}
unsigned NearOptimalAdaptationLogic::getMaxCurrentBw() const
{
unsigned i_max_bitrate = 0;
for(std::map<ID, NearOptimalContext>::const_iterator it = streams.begin();
it != streams.end(); ++it)
i_max_bitrate = std::max(i_max_bitrate, ((*it).second).last_download_rate);
return i_max_bitrate;
}
void NearOptimalAdaptationLogic::updateDownloadRate(const ID &id, size_t dlsize, vlc_tick_t time)
{
vlc_mutex_lock(&lock);
std::map<ID, NearOptimalContext>::iterator it = streams.find(id);
if(it != streams.end())
{
NearOptimalContext &ctx = (*it).second;
ctx.last_download_rate = ctx.average.push(CLOCK_FREQ * dlsize * 8 / time);
}
currentBps = getMaxCurrentBw();
vlc_mutex_unlock(&lock);
}
void NearOptimalAdaptationLogic::trackerEvent(const SegmentTrackerEvent &event)
{
switch(event.type)
{
case SegmentTrackerEvent::SWITCHING:
{
vlc_mutex_lock(&lock);
if(event.u.switching.prev)
usedBps -= event.u.switching.prev->getBandwidth();
if(event.u.switching.next)
usedBps += event.u.switching.next->getBandwidth();
BwDebug(msg_Info(p_obj, "New total bandwidth usage %zu kBps", (usedBps / 8000)));
vlc_mutex_unlock(&lock);
}
break;
case SegmentTrackerEvent::BUFFERING_STATE:
{
const ID &id = *event.u.buffering.id;
vlc_mutex_lock(&lock);
if(event.u.buffering.enabled)
{
if(streams.find(id) == streams.end())
{
NearOptimalContext ctx;
streams.insert(std::pair<ID, NearOptimalContext>(id, ctx));
}
}
else
{
std::map<ID, NearOptimalContext>::iterator it = streams.find(id);
if(it != streams.end())
streams.erase(it);
}
vlc_mutex_unlock(&lock);
BwDebug(msg_Info(p_obj, "Stream %s is now known %sactive", id.str().c_str(),
(event.u.buffering.enabled) ? "" : "in"));
}
break;
case SegmentTrackerEvent::BUFFERING_LEVEL_CHANGE:
{
const ID &id = *event.u.buffering.id;
vlc_mutex_lock(&lock);
NearOptimalContext &ctx = streams[id];
ctx.buffering_level = event.u.buffering_level.current;
ctx.buffering_target = event.u.buffering_level.target;
vlc_mutex_unlock(&lock);
}
break;
default:
break;
}
}