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.
394 lines
14 KiB
394 lines
14 KiB
/*
|
|
* BufferingLogic.cpp
|
|
*****************************************************************************
|
|
* Copyright (C) 2014 - 2020 VideoLabs, VideoLAN and VLC 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 "BufferingLogic.hpp"
|
|
#include "../playlist/BaseRepresentation.h"
|
|
#include "../playlist/BasePeriod.h"
|
|
#include "../playlist/BasePlaylist.hpp"
|
|
#include "../playlist/SegmentTemplate.h"
|
|
#include "../playlist/SegmentTimeline.h"
|
|
#include "../playlist/SegmentList.h"
|
|
#include "../playlist/SegmentBase.h"
|
|
|
|
#include <limits>
|
|
#include <cassert>
|
|
|
|
using namespace adaptive;
|
|
using namespace adaptive::playlist;
|
|
using namespace adaptive::logic;
|
|
|
|
const vlc_tick_t AbstractBufferingLogic::BUFFERING_LOWEST_LIMIT = VLC_TICK_FROM_SEC(2);
|
|
const vlc_tick_t AbstractBufferingLogic::DEFAULT_MIN_BUFFERING = VLC_TICK_FROM_SEC(6);
|
|
const vlc_tick_t AbstractBufferingLogic::DEFAULT_MAX_BUFFERING = VLC_TICK_FROM_SEC(30);
|
|
const vlc_tick_t AbstractBufferingLogic::DEFAULT_LIVE_BUFFERING = VLC_TICK_FROM_SEC(15);
|
|
|
|
AbstractBufferingLogic::AbstractBufferingLogic()
|
|
{
|
|
userMinBuffering = 0;
|
|
userMaxBuffering = 0;
|
|
userLiveDelay = 0;
|
|
}
|
|
|
|
void AbstractBufferingLogic::setLowDelay(bool b)
|
|
{
|
|
userLowLatency = b;
|
|
}
|
|
|
|
void AbstractBufferingLogic::setUserMinBuffering(vlc_tick_t v)
|
|
{
|
|
userMinBuffering = v;
|
|
}
|
|
|
|
void AbstractBufferingLogic::setUserMaxBuffering(vlc_tick_t v)
|
|
{
|
|
userMaxBuffering = v;
|
|
}
|
|
|
|
void AbstractBufferingLogic::setUserLiveDelay(vlc_tick_t v)
|
|
{
|
|
userLiveDelay = v;
|
|
}
|
|
|
|
/* Try to never buffer up to really end */
|
|
/* Enforce no overlap for demuxers segments 3.0.0 */
|
|
/* FIXME: check duration instead ? */
|
|
const unsigned DefaultBufferingLogic::SAFETY_BUFFERING_EDGE_OFFSET = 1;
|
|
const unsigned DefaultBufferingLogic::SAFETY_EXPURGING_OFFSET = 2;
|
|
|
|
DefaultBufferingLogic::DefaultBufferingLogic()
|
|
: AbstractBufferingLogic()
|
|
{
|
|
|
|
}
|
|
|
|
uint64_t DefaultBufferingLogic::getStartSegmentNumber(BaseRepresentation *rep) const
|
|
{
|
|
if(rep->getPlaylist()->isLive())
|
|
return getLiveStartSegmentNumber(rep);
|
|
|
|
const AbstractSegmentBaseType *profile = rep->inheritSegmentProfile();
|
|
if(!profile)
|
|
return 0;
|
|
uint64_t num = profile->getStartSegmentNumber();
|
|
vlc_tick_t offset = rep->getPlaylist()->presentationStartOffset.Get();
|
|
if(offset > 0)
|
|
{
|
|
vlc_tick_t startTime, duration;
|
|
if(profile->getPlaybackTimeDurationBySegmentNumber(num, &startTime, &duration))
|
|
profile->getSegmentNumberByTime(startTime + offset, &num);
|
|
}
|
|
return num;
|
|
}
|
|
|
|
vlc_tick_t DefaultBufferingLogic::getMinBuffering(const BasePlaylist *p) const
|
|
{
|
|
if(isLowLatency(p))
|
|
return BUFFERING_LOWEST_LIMIT;
|
|
|
|
vlc_tick_t buffering = userMinBuffering ? userMinBuffering
|
|
: DEFAULT_MIN_BUFFERING;
|
|
if(p->getMinBuffering())
|
|
buffering = std::max(buffering, p->getMinBuffering());
|
|
return std::max(buffering, BUFFERING_LOWEST_LIMIT);
|
|
}
|
|
|
|
vlc_tick_t DefaultBufferingLogic::getMaxBuffering(const BasePlaylist *p) const
|
|
{
|
|
if(isLowLatency(p))
|
|
return getMinBuffering(p);
|
|
|
|
vlc_tick_t buffering = userMaxBuffering ? userMaxBuffering
|
|
: DEFAULT_MAX_BUFFERING;
|
|
if(p->isLive())
|
|
buffering = std::min(buffering, getLiveDelay(p));
|
|
if(p->getMaxBuffering())
|
|
buffering = std::min(buffering, p->getMaxBuffering());
|
|
return std::max(buffering, getMinBuffering(p));
|
|
}
|
|
|
|
vlc_tick_t DefaultBufferingLogic::getLiveDelay(const BasePlaylist *p) const
|
|
{
|
|
if(isLowLatency(p))
|
|
return getMinBuffering(p);
|
|
vlc_tick_t delay = userLiveDelay ? userLiveDelay
|
|
: DEFAULT_LIVE_BUFFERING;
|
|
if(p->suggestedPresentationDelay.Get())
|
|
delay = p->suggestedPresentationDelay.Get();
|
|
else if(p->presentationStartOffset.Get())
|
|
delay = p->presentationStartOffset.Get();
|
|
if(p->timeShiftBufferDepth.Get())
|
|
delay = std::min(delay, p->timeShiftBufferDepth.Get());
|
|
return std::max(delay, getMinBuffering(p));
|
|
}
|
|
|
|
vlc_tick_t DefaultBufferingLogic::getStableBuffering(const BasePlaylist *p) const
|
|
{
|
|
vlc_tick_t min = getMinBuffering(p);
|
|
if(isLowLatency(p))
|
|
return min;
|
|
if(p->isLive())
|
|
return std::max(min, getLiveDelay(p) * 6/10);
|
|
vlc_tick_t max = getMaxBuffering(p);
|
|
return std::min(getMinBuffering(p) * 2, max);
|
|
}
|
|
|
|
uint64_t DefaultBufferingLogic::getLiveStartSegmentNumber(BaseRepresentation *rep) const
|
|
{
|
|
BasePlaylist *playlist = rep->getPlaylist();
|
|
|
|
/* Get buffering offset min <= max <= live delay */
|
|
vlc_tick_t i_buffering = getBufferingOffset(playlist);
|
|
|
|
SegmentList *segmentList = rep->inheritSegmentList();
|
|
SegmentBase *segmentBase = rep->inheritSegmentBase();
|
|
SegmentTemplate *mediaSegmentTemplate = rep->inheritSegmentTemplate();
|
|
|
|
SegmentTimeline *timeline;
|
|
if(mediaSegmentTemplate)
|
|
timeline = mediaSegmentTemplate->inheritSegmentTimeline();
|
|
else if(segmentList)
|
|
timeline = segmentList->inheritSegmentTimeline();
|
|
else
|
|
timeline = nullptr;
|
|
|
|
if(timeline)
|
|
{
|
|
uint64_t start = 0;
|
|
const Timescale timescale = timeline->inheritTimescale();
|
|
|
|
uint64_t safeMinElementNumber = timeline->minElementNumber();
|
|
uint64_t safeMaxElementNumber = timeline->maxElementNumber();
|
|
stime_t safeedgetime, safestarttime, duration;
|
|
for(unsigned i=0; i<SAFETY_BUFFERING_EDGE_OFFSET; i++)
|
|
{
|
|
if(safeMinElementNumber == safeMaxElementNumber)
|
|
break;
|
|
safeMaxElementNumber--;
|
|
}
|
|
bool b_ret = timeline->getScaledPlaybackTimeDurationBySegmentNumber(safeMaxElementNumber,
|
|
&safeedgetime, &duration);
|
|
if(unlikely(!b_ret))
|
|
return 0;
|
|
safeedgetime += duration - 1;
|
|
|
|
for(unsigned i=0; i<SAFETY_EXPURGING_OFFSET; i++)
|
|
{
|
|
if(safeMinElementNumber + 1 >= safeMaxElementNumber)
|
|
break;
|
|
safeMinElementNumber++;
|
|
}
|
|
b_ret = timeline->getScaledPlaybackTimeDurationBySegmentNumber(safeMinElementNumber,
|
|
&safestarttime, &duration);
|
|
if(unlikely(!b_ret))
|
|
return 0;
|
|
|
|
if(playlist->timeShiftBufferDepth.Get())
|
|
{
|
|
stime_t edgetime;
|
|
b_ret = timeline->getScaledPlaybackTimeDurationBySegmentNumber(timeline->maxElementNumber(),
|
|
&edgetime, &duration);
|
|
if(unlikely(!b_ret))
|
|
return 0;
|
|
edgetime += duration - 1;
|
|
stime_t timeshiftdepth = timescale.ToScaled(playlist->timeShiftBufferDepth.Get());
|
|
if(safestarttime + timeshiftdepth < edgetime)
|
|
{
|
|
safestarttime = edgetime - timeshiftdepth;
|
|
safeMinElementNumber = timeline->getElementNumberByScaledPlaybackTime(safestarttime);
|
|
}
|
|
}
|
|
assert(safestarttime<=safeedgetime);
|
|
|
|
stime_t starttime;
|
|
if(safeedgetime - safestarttime > timescale.ToScaled(i_buffering))
|
|
starttime = safeedgetime - timescale.ToScaled(i_buffering);
|
|
else
|
|
starttime = safestarttime;
|
|
|
|
start = timeline->getElementNumberByScaledPlaybackTime(starttime);
|
|
assert(start >= timeline->minElementNumber());
|
|
assert(start >= safeMinElementNumber);
|
|
assert(start <= timeline->maxElementNumber());
|
|
assert(start <= safeMaxElementNumber);
|
|
|
|
return start;
|
|
}
|
|
else if(mediaSegmentTemplate)
|
|
{
|
|
/* Else compute, current time and timeshiftdepth based */
|
|
uint64_t start = 0;
|
|
stime_t scaledduration = mediaSegmentTemplate->inheritDuration();
|
|
if(scaledduration)
|
|
{
|
|
/* Compute playback offset and effective finished segment from wall time */
|
|
vlc_tick_t now = vlc_tick_from_sec(time(nullptr));
|
|
vlc_tick_t playbacktime = now - i_buffering;
|
|
vlc_tick_t minavailtime = playlist->availabilityStartTime.Get() + rep->getPeriodStart();
|
|
const uint64_t startnumber = mediaSegmentTemplate->inheritStartNumber();
|
|
const Timescale timescale = mediaSegmentTemplate->inheritTimescale();
|
|
if(!timescale)
|
|
return startnumber;
|
|
const vlc_tick_t duration = timescale.ToTime(scaledduration);
|
|
if(!duration)
|
|
return startnumber;
|
|
|
|
/* restrict to DVR window */
|
|
if(playlist->timeShiftBufferDepth.Get())
|
|
{
|
|
vlc_tick_t elapsed = now - minavailtime;
|
|
elapsed = elapsed - (elapsed % duration); /* align to last segment */
|
|
vlc_tick_t alignednow = minavailtime + elapsed;
|
|
if(playlist->timeShiftBufferDepth.Get() < elapsed)
|
|
minavailtime = alignednow - playlist->timeShiftBufferDepth.Get();
|
|
|
|
if(playbacktime < minavailtime)
|
|
playbacktime = minavailtime;
|
|
}
|
|
/* Get completed segment containing the time ref */
|
|
start = mediaSegmentTemplate->getLiveTemplateNumber(playbacktime);
|
|
if (unlikely(start < startnumber))
|
|
{
|
|
assert(startnumber > start); /* blame getLiveTemplateNumber() */
|
|
start = startnumber;
|
|
}
|
|
|
|
const uint64_t max_safety_offset = playbacktime - minavailtime / duration;
|
|
const uint64_t safety_offset = std::min((uint64_t)SAFETY_BUFFERING_EDGE_OFFSET,
|
|
max_safety_offset);
|
|
if(startnumber + safety_offset <= start)
|
|
start -= safety_offset;
|
|
else
|
|
start = startnumber;
|
|
|
|
return start;
|
|
}
|
|
}
|
|
else if (segmentList && !segmentList->getSegments().empty())
|
|
{
|
|
const Timescale timescale = segmentList->inheritTimescale();
|
|
const std::vector<Segment *> &list = segmentList->getSegments();
|
|
const ISegment *back = list.back();
|
|
|
|
/* working around HLS discontinuities by using durations */
|
|
stime_t totallistduration = 0;
|
|
for(auto it = list.begin(); it != list.end(); ++it)
|
|
totallistduration += (*it)->duration.Get();
|
|
|
|
/* Apply timeshift restrictions */
|
|
stime_t availableduration;
|
|
if(playlist->timeShiftBufferDepth.Get())
|
|
{
|
|
availableduration = std::min(totallistduration,
|
|
timescale.ToScaled(playlist->timeShiftBufferDepth.Get()));
|
|
}
|
|
else availableduration = totallistduration;
|
|
|
|
uint64_t availableliststartnumber = list.front()->getSequenceNumber();
|
|
if(totallistduration != availableduration)
|
|
{
|
|
stime_t offset = totallistduration - availableduration;
|
|
for(auto it = list.begin(); it != list.end(); ++it)
|
|
{
|
|
availableliststartnumber = (*it)->getSequenceNumber();
|
|
if(offset < (*it)->duration.Get())
|
|
break;
|
|
offset -= (*it)->duration.Get();
|
|
}
|
|
}
|
|
|
|
uint64_t safeedgenumber = back->getSequenceNumber() -
|
|
std::min((uint64_t)list.size() - 1,
|
|
(uint64_t)SAFETY_BUFFERING_EDGE_OFFSET);
|
|
uint64_t safestartnumber = availableliststartnumber;
|
|
|
|
for(unsigned i=0; i<SAFETY_EXPURGING_OFFSET; i++)
|
|
{
|
|
if(safestartnumber + 1 >= safeedgenumber)
|
|
break;
|
|
safestartnumber++;
|
|
}
|
|
|
|
stime_t maxbufferizable = 0;
|
|
stime_t safeedgeduration = 0;
|
|
for(auto it = list.begin(); it != list.end(); ++it)
|
|
{
|
|
if((*it)->getSequenceNumber() < safestartnumber)
|
|
continue;
|
|
if((*it)->getSequenceNumber() <= safeedgenumber)
|
|
maxbufferizable += (*it)->duration.Get();
|
|
else
|
|
safeedgeduration += (*it)->duration.Get();
|
|
}
|
|
|
|
stime_t tobuffer = std::min(maxbufferizable, timescale.ToScaled(i_buffering));
|
|
stime_t skipduration = totallistduration - safeedgeduration - tobuffer ;
|
|
uint64_t start = safestartnumber;
|
|
for(auto it = list.begin(); it != list.end(); ++it)
|
|
{
|
|
start = (*it)->getSequenceNumber();
|
|
if((*it)->duration.Get() > skipduration)
|
|
break;
|
|
skipduration -= (*it)->duration.Get();
|
|
}
|
|
|
|
return start;
|
|
}
|
|
else if(segmentBase)
|
|
{
|
|
const std::vector<Segment *> &list = segmentBase->subSegments();
|
|
if(!list.empty())
|
|
return segmentBase->getSequenceNumber();
|
|
|
|
const Timescale timescale = rep->inheritTimescale();
|
|
if(!timeline->isValid())
|
|
return std::numeric_limits<uint64_t>::max();
|
|
const Segment *back = list.back();
|
|
const stime_t bufferingstart = back->startTime.Get() + back->duration.Get() -
|
|
timescale.ToScaled(i_buffering);
|
|
|
|
uint64_t start = AbstractSegmentBaseType::findSegmentNumberByScaledTime(list, bufferingstart);
|
|
if(start == std::numeric_limits<uint64_t>::max())
|
|
return list.front()->getSequenceNumber();
|
|
|
|
if(segmentBase->getSequenceNumber() + SAFETY_BUFFERING_EDGE_OFFSET <= start)
|
|
start -= SAFETY_BUFFERING_EDGE_OFFSET;
|
|
else
|
|
start = segmentBase->getSequenceNumber();
|
|
|
|
return start;
|
|
}
|
|
|
|
return std::numeric_limits<uint64_t>::max();
|
|
}
|
|
|
|
vlc_tick_t DefaultBufferingLogic::getBufferingOffset(const BasePlaylist *p) const
|
|
{
|
|
return p->isLive() ? getLiveDelay(p) : getMaxBuffering(p);
|
|
}
|
|
|
|
bool DefaultBufferingLogic::isLowLatency(const BasePlaylist *p) const
|
|
{
|
|
if(userLowLatency.isSet())
|
|
return userLowLatency.value();
|
|
return p->isLowLatency();
|
|
}
|
|
|