/* * 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 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 (CLOCK_FREQ * 6) /* Qmin */ #define bufferTargetS (CLOCK_FREQ * 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 ) , p_obj( obj ) { vlc_mutex_init(&lock); } NearOptimalAdaptationLogic::~NearOptimalAdaptationLogic() { vlc_mutex_destroy(&lock); } BaseRepresentation * NearOptimalAdaptationLogic::getNextQualityIndex( BaseAdaptationSet *adaptSet, RepresentationSelector &selector, float gammaP, vlc_tick_t VD, vlc_tick_t Q ) { BaseRepresentation *ret = nullptr; BaseRepresentation *prev = nullptr; float argmax = 0; 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 == nullptr || argmax <= arg) { ret = rep; argmax = arg; } prev = rep; } return ret; } BaseRepresentation *NearOptimalAdaptationLogic::getNextRepresentation(BaseAdaptationSet *adaptSet, BaseRepresentation *prevRep) { RepresentationSelector selector(maxwidth, maxheight); BaseRepresentation *lowest = selector.lowest(adaptSet); BaseRepresentation *highest = selector.highest(adaptSet); if(lowest == nullptr || highest == nullptr) return nullptr; if(lowest == highest) return lowest; const float umin = getUtility(lowest); const float umax = getUtility(highest); vlc_mutex_lock(&lock); std::map::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 = ((float)ctxcopy.buffering_min / CLOCK_FREQ - 1.0) / (umin + gammaP); BaseRepresentation *m; if(prevRep == nullptr) /* Starting */ { m = selector.select(adaptSet, bps); if(m == lowest) { /* Handle HLS specific cases where the lowest is audio only. Try to pick first A+V */ BaseRepresentation *n = selector.higher(adaptSet, m); if(m != n && m->getCodecs().size() == 1 && n->getCodecs().size() > 1) m = n; } } else { /* noted m* */ m = getNextQualityIndex(adaptSet, selector, gammaP - umin /* umin == Sm, utility = std::log(S/Sm) */, Vd, (float)ctxcopy.buffering_level / CLOCK_FREQ); 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::iterator it = utilities.find(rep->getBandwidth()); if(it == utilities.end()) { ret = std::log((float)rep->getBandwidth()); utilities.insert(std::pair(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::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, mtime_t) { vlc_mutex_lock(&lock); std::map::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 TrackerEvent &ev) { switch(ev.getType()) { case TrackerEvent::Type::RepresentationSwitch: { const RepresentationSwitchEvent &event = static_cast(ev); vlc_mutex_lock(&lock); if(event.prev) usedBps -= event.prev->getBandwidth(); if(event.next) usedBps += event.next->getBandwidth(); BwDebug(msg_Info(p_obj, "New total bandwidth usage %zu kBps", (usedBps / 8000))); vlc_mutex_unlock(&lock); } break; case TrackerEvent::Type::BufferingStateUpdate: { const BufferingStateUpdatedEvent &event = static_cast(ev); const ID &id = *event.id; vlc_mutex_lock(&lock); if(event.enabled) { if(streams.find(id) == streams.end()) { NearOptimalContext ctx; streams.insert(std::pair(id, ctx)); } } else { std::map::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.enabled) ? "" : "in")); } break; case TrackerEvent::Type::BufferingLevelChange: { const BufferingLevelChangedEvent &event = static_cast(ev); const ID &id = *event.id; vlc_mutex_lock(&lock); NearOptimalContext &ctx = streams[id]; ctx.buffering_level = event.current; ctx.buffering_target = event.target; vlc_mutex_unlock(&lock); } break; default: break; } }