Commit 50fc01a7 authored by Francois Cartegnie's avatar Francois Cartegnie

stream_filter: smooth: rewrite bw adaptation algorithm

BW measurement must be per stream as receive window/transfert
rate will differ relatively to chunk size. There's no real
way to get an accurate Max BW with such small files, but it
is still accurate to predict the BW for a download of same
size.

A validation cursor is introduced to qualify bitrates.
Cursor evolves within a window of summed download times.
Adds duration to window if > bitrate, and opposite.
  -PROBE_TIME << cursor << PROBE_TIME
Negative values makes harder to select previously unqualified
bitrates.
Stream quality will rank up quality only if next candidate has
filled its own window.
Stream reselection only occurs when cursor <= 0.
In that way, we can tolerate temporary bitrate lowering or increase
and avoid the hiccup reselection problems we had until now.
parent 3843d77d
......@@ -206,39 +206,91 @@ static int sms_Download( stream_t *s, chunk_t *chunk, char *url )
}
#ifdef DISABLE_BANDWIDTH_ADAPTATION
static const quality_level_t *
BandwidthAdaptation( stream_t *s, sms_stream_t *sms, uint64_t bandwidth )
static quality_level_t *
BandwidthAdaptation( stream_t *s, sms_stream_t *sms,
uint64_t obw, uint64_t i_duration,
bool b_starved )
{
VLC_UNUSED(bandwidth);
VLC_UNUSED(obw);
VLC_UNUSED(s);
VLC_UNUSED(i_duration);
VLC_UNUSED(b_starved);
return sms->current_qlvl;
}
#else
static const quality_level_t *
BandwidthAdaptation( stream_t *s, sms_stream_t *sms, uint64_t bandwidth )
static quality_level_t *
BandwidthAdaptation( stream_t *s, sms_stream_t *sms,
uint64_t obw, uint64_t i_duration,
bool b_starved )
{
if( sms->type != VIDEO_ES )
return sms->current_qlvl;
quality_level_t *ret = NULL;
uint64_t bw_candidate = 0;
const quality_level_t *ret = sms->current_qlvl;
assert( sms->current_qlvl );
if ( sms->qlevels.i_size < 2 )
return sms->qlevels.p_elems[0];
if ( b_starved )
{
//TODO: do something on starvation post first buffering
// s->p_sys->i_probe_length *= 2;
}
/* PASS 1 */
quality_level_t *lowest = sms->qlevels.p_elems[0];
FOREACH_ARRAY( quality_level_t *qlevel, sms->qlevels );
if ( qlevel->Bitrate >= obw )
{
qlevel->i_validation_length -= i_duration;
qlevel->i_validation_length = __MAX(qlevel->i_validation_length, - s->p_sys->i_probe_length);
}
else
{
qlevel->i_validation_length += i_duration;
qlevel->i_validation_length = __MIN(qlevel->i_validation_length, s->p_sys->i_probe_length);
}
if ( qlevel->Bitrate < lowest->Bitrate )
lowest = qlevel;
FOREACH_END();
FOREACH_ARRAY( const quality_level_t *qlevel, sms->qlevels );
if( unlikely( !qlevel ) )
/* PASS 2 */
if ( sms->current_qlvl->i_validation_length == s->p_sys->i_probe_length )
{
msg_Err( s, "Could no get %uth quality level", fe_idx );
/* might upgrade */
ret = sms->current_qlvl;
}
else if ( sms->current_qlvl->i_validation_length >= 0 )
{
/* do nothing */
ret = sms->current_qlvl;
msg_Dbg( s, "bw current:%uKB/s avg:%"PRIu64"KB/s qualified %"PRId64"%%",
(ret->Bitrate) / (8 * 1024),
obw / (8 * 1024),
( ret->i_validation_length*1000 / s->p_sys->i_probe_length ) /10 );
return ret;
}
else
{
/* downgrading */
ret = lowest;
}
if( qlevel->Bitrate < (bandwidth - bandwidth / 3) &&
qlevel->Bitrate > bw_candidate )
/* might upgrade */
FOREACH_ARRAY( quality_level_t *qlevel, sms->qlevels );
if( qlevel->Bitrate <= obw &&
ret->Bitrate <= qlevel->Bitrate &&
qlevel->i_validation_length >= 0 &&
qlevel->i_validation_length >= ret->i_validation_length )
{
bw_candidate = qlevel->Bitrate;
ret = qlevel;
}
FOREACH_END();
msg_Dbg( s, "bw reselected:%uKB/s avg:%"PRIu64"KB/s qualified %"PRId64"%%",
(ret->Bitrate) / (8 * 1024),
obw / (8 * 1024),
( ret->i_validation_length*1000 / s->p_sys->i_probe_length ) /10 );
return ret;
}
#endif
......@@ -383,8 +435,7 @@ static int build_smoo_box( stream_t *s, uint8_t *smoo_box )
((uint32_t *)stra_box)[13] = bswap32( sms->timescale );
((uint64_t *)stra_box)[7] = bswap64( p_sys->vod_duration );
const quality_level_t * qlvl = sms->current_qlvl;
const quality_level_t *qlvl = sms->current_qlvl;
if ( qlvl )
{
FourCC = qlvl->FourCC ? qlvl->FourCC : sms->default_FourCC;
......@@ -473,13 +524,12 @@ static int Download( stream_t *s, sms_stream_t *sms )
}
/* sanity check - can we download this chunk on time? */
uint64_t avg_bw = sms_queue_avg( p_sys->download.bws );
if( (avg_bw > 0) && (sms->current_qlvl->Bitrate > 0) )
if( (sms->i_obw > 0) && (sms->current_qlvl->Bitrate > 0) )
{
/* duration in ms */
unsigned chunk_duration = chunk->duration * 1000 / sms->timescale;
uint64_t size = chunk_duration * sms->current_qlvl->Bitrate / 1000; /* bits */
unsigned estimated = size * 1000 / avg_bw;
unsigned estimated = size * 1000 / sms->i_obw;
if( estimated > chunk_duration )
{
msg_Warn( s,"downloading of chunk @%"PRIu64" would take %d ms, "
......@@ -512,49 +562,53 @@ static int Download( stream_t *s, sms_stream_t *sms )
msg_Info( s, "downloaded chunk @%"PRIu64" from stream %s at quality %u",
chunk->start_time, sms->name, sms->current_qlvl->Bitrate );
unsigned dur_ms = __MAX( 1, duration / 1000 );
uint64_t bw = chunk->size * 8 * 1000 / dur_ms; /* bits / s */
if( sms_queue_put( p_sys->download.bws, bw ) != VLC_SUCCESS )
return VLC_EGENERIC;
avg_bw = sms_queue_avg( p_sys->download.bws );
if( sms->type != VIDEO_ES ) /* FIXME: hanle adaptation for audio */
return VLC_SUCCESS;
if (likely( duration ))
bw_stats_put( sms, chunk->size * 8 * CLOCK_FREQ / duration ); /* bits / s */
/* Track could get disabled in mp4 demux if we trigger adaption too soon.
And we don't need adaptation on last chunk */
if( sms->p_chunks == NULL || sms->p_chunks == sms->p_lastchunk )
return VLC_SUCCESS;
const quality_level_t *new_qlevel = BandwidthAdaptation( s, sms, avg_bw );
assert(new_qlevel);
bool b_starved = false;
vlc_mutex_lock( &p_sys->playback.lock );
if ( p_sys->playback.init.p_datachunk == NULL ) /* Don't chain/nest reinits */
if ( &p_sys->playback.b_underrun )
{
const quality_level_t *prev_qlevel = sms->current_qlvl;
if( new_qlevel->Bitrate != sms->current_qlvl->Bitrate )
{
msg_Warn( s, "detected %s bandwidth (%u) stream",
(new_qlevel->Bitrate >= sms->current_qlvl->Bitrate) ? "faster" : "lower",
new_qlevel->Bitrate );
sms->current_qlvl = new_qlevel;
}
p_sys->playback.b_underrun = false;
bw_stats_underrun( sms );
b_starved = true;
}
vlc_mutex_unlock( &p_sys->playback.lock );
quality_level_t *new_qlevel = BandwidthAdaptation( s, sms, sms->i_obw,
duration, b_starved );
assert(new_qlevel);
/* FIXME: Why no reinit/new extra bytes with different qlevel ???
Probably broken with some codecs */
if( sms->qlevels.i_size < 2 )
{
assert(new_qlevel == sms->current_qlvl);
return VLC_SUCCESS;
}
if( new_qlevel->MaxWidth != prev_qlevel->MaxWidth ||
new_qlevel->MaxHeight != prev_qlevel->MaxHeight )
vlc_mutex_lock( &p_sys->playback.lock );
if ( p_sys->playback.init.p_datachunk == NULL && /* Don't chain/nest reinits */
new_qlevel != sms->current_qlvl )
{
msg_Warn( s, "detected %s bandwidth (%u) stream",
(new_qlevel->Bitrate >= sms->current_qlvl->Bitrate) ? "faster" : "lower",
new_qlevel->Bitrate );
quality_level_t *qlvl_backup = sms->current_qlvl;
sms->current_qlvl = new_qlevel;
chunk_t *new_init_ck = build_init_chunk( s );
if( new_init_ck )
{
chunk_t *new_init_ck = build_init_chunk( s );
if( new_init_ck )
{
p_sys->playback.init.p_datachunk = new_init_ck;
p_sys->playback.init.p_startchunk = chunk->p_next; /* to send before that one */
assert( chunk->p_next && chunk != sms->p_lastchunk );
}
p_sys->playback.init.p_datachunk = new_init_ck;
p_sys->playback.init.p_startchunk = chunk->p_next; /* to send before that one */
assert( chunk->p_next && chunk != sms->p_lastchunk );
}
else
sms->current_qlvl = qlvl_backup;
}
vlc_mutex_unlock( &p_sys->playback.lock );
......@@ -638,12 +692,6 @@ void* sms_Thread( void *p_this )
int canc = vlc_savecancel();
/* We compute the average bandwidth of the 4 last downloaded
* chunks, but feel free to replace '4' by whatever you wish */
p_sys->download.bws = sms_queue_init( 4 );
if( !p_sys->download.bws )
goto cancel;
chunk_t *init_ck = build_init_chunk( s );
if( !init_ck )
goto cancel;
......@@ -666,11 +714,15 @@ void* sms_Thread( void *p_this )
vlc_mutex_lock( &sms->chunks_lock );
if ( sms->p_nextdownload )
{
mtime_t duration = mdate();
if( Download( s, sms ) != VLC_SUCCESS )
{
vlc_mutex_unlock( &sms->chunks_lock );
goto cancel;
}
duration = mdate() - duration;
if (likely( duration ))
bw_stats_put( sms, sms->p_nextdownload->size * 8 * CLOCK_FREQ / duration ); /* bits / s */
sms->p_nextdownload = sms->p_nextdownload->p_next;
}
vlc_mutex_unlock( &sms->chunks_lock );
......
......@@ -442,7 +442,6 @@ static void SysCleanup( stream_sys_t *p_sys )
ARRAY_RESET( p_sys->sms_selected );
if ( p_sys->playback.init.p_datachunk )
chunk_Free( p_sys->playback.init.p_datachunk );
sms_queue_free( p_sys->download.bws );
free( p_sys->download.base_url );
}
......@@ -532,6 +531,7 @@ static int Open( vlc_object_t *p_this )
}
p_sys->playback.toffset = p_sys->p_current_stream->p_playback->start_time;
p_sys->playback.next_chunk_offset = CHUNK_OFFSET_UNSET;
p_sys->i_probe_length = SMS_PROBE_LENGTH;
vlc_mutex_init( &p_sys->lock );
vlc_cond_init( &p_sys->download.wait );
......@@ -693,6 +693,9 @@ static chunk_t *get_chunk( stream_t *s, const bool wait, bool *pb_isinit )
}
msg_Dbg( s, "get_chunk is waiting for chunk %ld !!!", p_chunk->start_time );
vlc_mutex_lock( &p_sys->playback.lock );
p_sys->playback.b_underrun = true;
vlc_mutex_unlock( &p_sys->playback.lock );
vlc_cond_timedwait( &p_sys->playback.wait,
&p_sys->p_current_stream->chunks_lock, mdate() + CLOCK_FREQ/2 );
}
......
......@@ -33,21 +33,10 @@
//#define DISABLE_BANDWIDTH_ADAPTATION
typedef struct item_s
{
uint64_t value;
struct item_s *next;
} item_t;
typedef struct sms_queue_s
{
unsigned length;
item_t *first;
} sms_queue_t;
#define CHUNK_OFFSET_UNSET 0
#define CHUNK_OFFSET_0 1
#define SMS_BW_SHORTSTATS 4
#define SMS_PROBE_LENGTH (CLOCK_FREQ * 2)
typedef struct chunk_s chunk_t;
struct chunk_s
......@@ -83,7 +72,7 @@ typedef struct quality_level_s
unsigned nBlockAlign;
char *CodecPrivateData; /* hex encoded string */
DECL_ARRAY(custom_attrs_t *) custom_attrs;
int64_t i_validation_length; /* how long did we experience that bitrate */
} quality_level_t;
typedef struct sms_stream_s
......@@ -102,7 +91,10 @@ typedef struct sms_stream_s
char *name;
char *url_template;
int type;
const quality_level_t *current_qlvl; /* current quality level for Download() */
quality_level_t *current_qlvl; /* current quality level for Download() */
uint64_t rgi_bw[SMS_BW_SHORTSTATS]; /* Measured bandwidths of the N last chunks */
uint64_t i_obw; /* Overwall bandwidth average */
unsigned int i_obw_samples; /* used to compute overall incrementally */
} sms_stream_t;
struct stream_sys_t
......@@ -113,6 +105,7 @@ struct stream_sys_t
uint64_t vod_duration; /* total duration of the VOD media (seconds / TimeScale) */
uint64_t time_pos;
unsigned timescale;
int64_t i_probe_length; /* min duration before upgrading resolution */
/* Download */
struct
......@@ -120,7 +113,6 @@ struct stream_sys_t
char *base_url; /* URL common part for chunks */
unsigned lookahead_count;/* max number of fragments ahead on server on live streaming */
vlc_thread_t thread; /* SMS chunk download thread */
sms_queue_t *bws; /* Measured bandwidths of the N last chunks */
vlc_cond_t wait; /* some condition to wait on */
} download;
......@@ -140,6 +132,7 @@ struct stream_sys_t
} init;
vlc_mutex_t lock;
vlc_cond_t wait; /* some condition to wait on */
bool b_underrun; /* did we ran out of data recently */
} playback;
/* state */
......@@ -182,10 +175,9 @@ struct stream_sys_t
#define SMS_GET_SELECTED_ST( cat ) \
sms_get_stream_by_cat( p_sys, cat )
void sms_queue_free( sms_queue_t* );
sms_queue_t *sms_queue_init( const unsigned int );
int sms_queue_put( sms_queue_t *, const uint64_t );
uint64_t sms_queue_avg( sms_queue_t *);
void bw_stats_put( sms_stream_t *, const uint64_t );
uint64_t bw_stats_avg( sms_stream_t * );
void bw_stats_underrun( sms_stream_t * );
void* sms_Thread( void *);
quality_level_t * ql_New( void );
void ql_Free( quality_level_t *);
......
......@@ -152,74 +152,42 @@ void sms_Free( sms_stream_t *sms )
free( sms );
}
sms_queue_t *sms_queue_init( const unsigned length )
void bw_stats_put( sms_stream_t *sms, const uint64_t bw )
{
sms_queue_t *ret = malloc( sizeof( sms_queue_t ) );
if( unlikely( !ret ) )
return NULL;
ret->length = length;
ret->first = NULL;
return ret;
}
void sms_queue_free( sms_queue_t* queue )
{
item_t *item = queue->first, *next = NULL;
while( item )
/* overall bw update */
if ( bw >= sms->i_obw )
sms->i_obw = sms->i_obw + ( bw - sms->i_obw ) /
(sms->i_obw_samples + 1);
else
sms->i_obw = sms->i_obw - ( sms->i_obw - bw ) /
(sms->i_obw_samples + 1);
sms->i_obw_samples++;
/* limited history bw stats update */
if ( sms->rgi_bw[0] == 0 )
{ /* first stats */
for( int i=0; i<SMS_BW_SHORTSTATS; i++ )
sms->rgi_bw[i] = bw;
}
else
{
next = item->next;
free( item );
item = next;
memmove( sms->rgi_bw, &sms->rgi_bw[1],
sizeof(sms->rgi_bw[0]) * (SMS_BW_SHORTSTATS - 1) );
sms->rgi_bw[SMS_BW_SHORTSTATS - 1] = bw;
}
free( queue );
}
int sms_queue_put( sms_queue_t *queue, const uint64_t value )
uint64_t bw_stats_avg( sms_stream_t *sms )
{
/* Remove the last (and oldest) item */
item_t *item, *prev = NULL;
unsigned int count = 0;
for( item = queue->first; item != NULL; item = item->next )
{
count++;
if( count == queue->length )
{
FREENULL( item );
if( prev ) prev->next = NULL;
break;
}
else
prev = item;
}
/* Now insert the new item */
item_t *new = malloc( sizeof( item_t ) );
if( unlikely( !new ) )
return VLC_ENOMEM;
new->value = value;
new->next = queue->first;
queue->first = new;
return VLC_SUCCESS;
uint64_t sum = sms->rgi_bw[0];
for( int i=1; i<SMS_BW_SHORTSTATS; i++ )
sum += sms->rgi_bw[i];
return sum / SMS_BW_SHORTSTATS;
}
uint64_t sms_queue_avg( sms_queue_t *queue )
void bw_stats_underrun( sms_stream_t *sms )
{
item_t *last = queue->first;
if( last == NULL )
return 0;
uint64_t sum = queue->first->value;
for( unsigned int i = 0; queue->length && i < queue->length - 1; i++ )
{
if( last )
{
last = last->next;
if( last )
sum += last->value;
}
}
return sum / queue->length;
sms->i_obw = bw_stats_avg( sms );
sms->i_obw_samples = SMS_BW_SHORTSTATS;
}
sms_stream_t * sms_get_stream_by_cat( stream_sys_t *p_sys, int i_cat )
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment