hlsdemux: Calculate the real bitrate and switch to the correct variant

We now calculate the actual bitrate using the download speed/size and
then switch directly to the variant that matches our bandwidth the
most.
It will also be able to handle any use case where some of the
variants are not available, and would skip them and go to the next
possible variant.

Conflicts:

	gst/hls/gsthlsdemux.c
This commit is contained in:
Youness Alaoui 2012-05-08 13:04:35 -04:00 committed by Thibault Saunier
parent bfd7a52c5d
commit 705a52a1ac
2 changed files with 73 additions and 79 deletions

View file

@ -67,7 +67,7 @@ enum
PROP_0, PROP_0,
PROP_FRAGMENTS_CACHE, PROP_FRAGMENTS_CACHE,
PROP_BITRATE_SWITCH_TOLERANCE, PROP_BITRATE_LIMIT,
PROP_LAST PROP_LAST
}; };
@ -75,7 +75,7 @@ static const float update_interval_factor[] = { 1, 0.5, 1.5, 3 };
#define DEFAULT_FRAGMENTS_CACHE 3 #define DEFAULT_FRAGMENTS_CACHE 3
#define DEFAULT_FAILED_COUNT 3 #define DEFAULT_FAILED_COUNT 3
#define DEFAULT_BITRATE_SWITCH_TOLERANCE 0.4 #define DEFAULT_BITRATE_LIMIT 0.8
/* GObject */ /* GObject */
static void gst_hls_demux_set_property (GObject * object, guint prop_id, static void gst_hls_demux_set_property (GObject * object, guint prop_id,
@ -174,12 +174,11 @@ gst_hls_demux_class_init (GstHLSDemuxClass * klass)
2, G_MAXUINT, DEFAULT_FRAGMENTS_CACHE, 2, G_MAXUINT, DEFAULT_FRAGMENTS_CACHE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_BITRATE_SWITCH_TOLERANCE, g_object_class_install_property (gobject_class, PROP_BITRATE_LIMIT,
g_param_spec_float ("bitrate-switch-tolerance", g_param_spec_float ("bitrate-limit",
"Bitrate switch tolerance", "Bitrate limit in %",
"Tolerance with respect of the fragment duration to switch to " "Limit of the available bitrate to use when switching to alternates.",
"a different bitrate if the client is too slow/fast.", 0, 1, DEFAULT_BITRATE_LIMIT,
0, 1, DEFAULT_BITRATE_SWITCH_TOLERANCE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_demux_change_state); element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_demux_change_state);
@ -219,7 +218,7 @@ gst_hls_demux_init (GstHLSDemux * demux)
/* Properties */ /* Properties */
demux->fragments_cache = DEFAULT_FRAGMENTS_CACHE; demux->fragments_cache = DEFAULT_FRAGMENTS_CACHE;
demux->bitrate_switch_tol = DEFAULT_BITRATE_SWITCH_TOLERANCE; demux->bitrate_limit = DEFAULT_BITRATE_LIMIT;
demux->queue = g_queue_new (); demux->queue = g_queue_new ();
@ -247,8 +246,8 @@ gst_hls_demux_set_property (GObject * object, guint prop_id,
case PROP_FRAGMENTS_CACHE: case PROP_FRAGMENTS_CACHE:
demux->fragments_cache = g_value_get_uint (value); demux->fragments_cache = g_value_get_uint (value);
break; break;
case PROP_BITRATE_SWITCH_TOLERANCE: case PROP_BITRATE_LIMIT:
demux->bitrate_switch_tol = g_value_get_float (value); demux->bitrate_limit = g_value_get_float (value);
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -266,8 +265,8 @@ gst_hls_demux_get_property (GObject * object, guint prop_id, GValue * value,
case PROP_FRAGMENTS_CACHE: case PROP_FRAGMENTS_CACHE:
g_value_set_uint (value, demux->fragments_cache); g_value_set_uint (value, demux->fragments_cache);
break; break;
case PROP_BITRATE_SWITCH_TOLERANCE: case PROP_BITRATE_LIMIT:
g_value_set_float (value, demux->bitrate_switch_tol); g_value_set_float (value, demux->bitrate_limit);
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -724,7 +723,6 @@ static void
gst_hls_demux_reset (GstHLSDemux * demux, gboolean dispose) gst_hls_demux_reset (GstHLSDemux * demux, gboolean dispose)
{ {
demux->need_cache = TRUE; demux->need_cache = TRUE;
demux->accumulated_delay = 0;
demux->end_of_playlist = FALSE; demux->end_of_playlist = FALSE;
demux->cancelled = FALSE; demux->cancelled = FALSE;
demux->do_typefind = TRUE; demux->do_typefind = TRUE;
@ -781,6 +779,10 @@ gst_hls_demux_updates_loop (GstHLSDemux * demux)
g_mutex_lock (&demux->updates_timed_lock); g_mutex_lock (&demux->updates_timed_lock);
GST_DEBUG_OBJECT (demux, "Started updates task"); GST_DEBUG_OBJECT (demux, "Started updates task");
while (TRUE) { while (TRUE) {
/* schedule the next update */
gst_hls_demux_schedule (demux);
/* block until the next scheduled update or the signal to quit this thread */
if (g_cond_timed_wait (GST_TASK_GET_COND (demux->updates_task), if (g_cond_timed_wait (GST_TASK_GET_COND (demux->updates_task),
&demux->updates_timed_lock, &demux->next_update)) { &demux->updates_timed_lock, &demux->next_update)) {
goto quit; goto quit;
@ -791,7 +793,6 @@ gst_hls_demux_updates_loop (GstHLSDemux * demux)
demux->client->update_failed_count++; demux->client->update_failed_count++;
if (demux->client->update_failed_count < DEFAULT_FAILED_COUNT) { if (demux->client->update_failed_count < DEFAULT_FAILED_COUNT) {
GST_WARNING_OBJECT (demux, "Could not update the playlist"); GST_WARNING_OBJECT (demux, "Could not update the playlist");
gst_hls_demux_schedule (demux);
continue; continue;
} else { } else {
GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND, GST_ELEMENT_ERROR (demux, RESOURCE, NOT_FOUND,
@ -801,9 +802,6 @@ gst_hls_demux_updates_loop (GstHLSDemux * demux)
} }
} }
/* schedule the next update */
gst_hls_demux_schedule (demux);
/* if it's a live source and the playlist couldn't be updated, there aren't /* if it's a live source and the playlist couldn't be updated, there aren't
* more fragments in the playlist, so we just wait for the next schedulled * more fragments in the playlist, so we just wait for the next schedulled
* update */ * update */
@ -831,12 +829,12 @@ gst_hls_demux_updates_loop (GstHLSDemux * demux)
} }
} else { } else {
demux->client->update_failed_count = 0; demux->client->update_failed_count = 0;
}
/* try to switch to another bitrate if needed */ /* try to switch to another bitrate if needed */
gst_hls_demux_switch_playlist (demux); gst_hls_demux_switch_playlist (demux);
} }
} }
}
quit: quit:
{ {
@ -884,9 +882,6 @@ gst_hls_demux_cache_fragments (GstHLSDemux * demux)
gst_message_new_buffering (GST_OBJECT (demux), gst_message_new_buffering (GST_OBJECT (demux),
100 * i / demux->fragments_cache)); 100 * i / demux->fragments_cache));
g_get_current_time (&demux->next_update); g_get_current_time (&demux->next_update);
g_time_val_add (&demux->next_update,
gst_m3u8_client_get_target_duration (demux->client)
/ GST_SECOND * G_USEC_PER_SEC);
if (!gst_hls_demux_get_next_fragment (demux, TRUE)) { if (!gst_hls_demux_get_next_fragment (demux, TRUE)) {
if (demux->end_of_playlist) if (demux->end_of_playlist)
break; break;
@ -969,8 +964,8 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update)
GST_M3U8_CLIENT_LOCK (demux->client); GST_M3U8_CLIENT_LOCK (demux->client);
last_sequence = last_sequence =
GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->files)-> GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->
data)->sequence; files)->data)->sequence;
if (demux->client->sequence >= last_sequence - 3) { if (demux->client->sequence >= last_sequence - 3) {
GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %d", GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %d",
@ -985,39 +980,49 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update)
} }
static gboolean static gboolean
gst_hls_demux_change_playlist (GstHLSDemux * demux, gboolean is_fast) gst_hls_demux_change_playlist (GstHLSDemux * demux, guint max_bitrate)
{ {
GList *list; GList *list, *previous_variant, *current_variant;
GList *previous_list; gint old_bandwidth, new_bandwidth;
GstStructure *s;
gint new_bandwidth;
GST_M3U8_CLIENT_LOCK (demux->client); GST_M3U8_CLIENT_LOCK (demux->client);
previous_list = demux->client->main->current_variant; current_variant = demux->client->main->current_variant;
if (is_fast) previous_variant = current_variant;
list = g_list_next (previous_list);
else /* Go to the highest possible bandwidth allowed */
list = g_list_previous (previous_list); while (GST_M3U8 (current_variant->data)->bandwidth < max_bitrate) {
list = g_list_next (current_variant);
if (!list)
break;
current_variant = list;
}
while (GST_M3U8 (current_variant->data)->bandwidth > max_bitrate) {
list = g_list_previous (current_variant);
if (!list)
break;
current_variant = list;
}
/* Don't do anything else if the playlist is the same */ /* Don't do anything else if the playlist is the same */
if (!list || list->data == demux->client->current) { if (current_variant == previous_variant) {
GST_M3U8_CLIENT_UNLOCK (demux->client); GST_M3U8_CLIENT_UNLOCK (demux->client);
return TRUE; return TRUE;
} }
demux->client->main->current_variant = list; old_bandwidth = GST_M3U8 (previous_variant->data)->bandwidth;
new_bandwidth = GST_M3U8 (current_variant->data)->bandwidth;
demux->client->main->current_variant = current_variant;
GST_M3U8_CLIENT_UNLOCK (demux->client); GST_M3U8_CLIENT_UNLOCK (demux->client);
gst_m3u8_client_set_current (demux->client, list->data); gst_m3u8_client_set_current (demux->client, current_variant->data);
GST_M3U8_CLIENT_LOCK (demux->client); GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching"
new_bandwidth = demux->client->current->bandwidth; " to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth);
GST_M3U8_CLIENT_UNLOCK (demux->client);
GST_INFO_OBJECT (demux, "Client is %s, switching to bitrate %d",
is_fast ? "fast" : "slow", new_bandwidth);
if (gst_hls_demux_update_playlist (demux, FALSE)) { if (gst_hls_demux_update_playlist (demux, FALSE)) {
GstStructure *s;
s = gst_structure_new ("playlist", s = gst_structure_new ("playlist",
"uri", G_TYPE_STRING, gst_m3u8_client_get_current_uri (demux->client), "uri", G_TYPE_STRING, gst_m3u8_client_get_current_uri (demux->client),
"bitrate", G_TYPE_INT, new_bandwidth, NULL); "bitrate", G_TYPE_INT, new_bandwidth, NULL);
@ -1026,10 +1031,15 @@ gst_hls_demux_change_playlist (GstHLSDemux * demux, gboolean is_fast)
} else { } else {
GST_INFO_OBJECT (demux, "Unable to update playlist. Switching back"); GST_INFO_OBJECT (demux, "Unable to update playlist. Switching back");
GST_M3U8_CLIENT_LOCK (demux->client); GST_M3U8_CLIENT_LOCK (demux->client);
demux->client->main->current_variant = previous_list; demux->client->main->current_variant = previous_variant;
GST_M3U8_CLIENT_UNLOCK (demux->client); GST_M3U8_CLIENT_UNLOCK (demux->client);
gst_m3u8_client_set_current (demux->client, previous_list->data); gst_m3u8_client_set_current (demux->client, previous_variant->data);
/* Try a lower bitrate (or stop if we just tried the lowest) */
if (new_bandwidth ==
GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth)
return FALSE; return FALSE;
else
return gst_hls_demux_change_playlist (demux, new_bandwidth - 1);
} }
/* Force typefinding since we might have changed media type */ /* Force typefinding since we might have changed media type */
@ -1071,9 +1081,12 @@ static gboolean
gst_hls_demux_switch_playlist (GstHLSDemux * demux) gst_hls_demux_switch_playlist (GstHLSDemux * demux)
{ {
GTimeVal now; GTimeVal now;
gint64 diff, limit; GstClockTime diff;
gsize size;
gint bitrate;
GstFragment *fragment = g_queue_peek_tail (demux->queue);
GstBuffer *buffer;
g_get_current_time (&now);
GST_M3U8_CLIENT_LOCK (demux->client); GST_M3U8_CLIENT_LOCK (demux->client);
if (!demux->client->main->lists) { if (!demux->client->main->lists) {
GST_M3U8_CLIENT_UNLOCK (demux->client); GST_M3U8_CLIENT_UNLOCK (demux->client);
@ -1083,35 +1096,17 @@ gst_hls_demux_switch_playlist (GstHLSDemux * demux)
/* compare the time when the fragment was downloaded with the time when it was /* compare the time when the fragment was downloaded with the time when it was
* scheduled */ * scheduled */
diff = (GST_TIMEVAL_TO_TIME (demux->next_update) - GST_TIMEVAL_TO_TIME (now)); g_get_current_time (&now);
limit = gst_m3u8_client_get_target_duration (demux->client) diff = (GST_TIMEVAL_TO_TIME (now) - GST_TIMEVAL_TO_TIME (demux->next_update));
* demux->bitrate_switch_tol; buffer = gst_fragment_get_buffer (fragment);
size = gst_buffer_get_size (buffer);
bitrate = (size * 8) / ((double) diff / GST_SECOND);
GST_DEBUG ("diff:%s%" GST_TIME_FORMAT ", limit:%" GST_TIME_FORMAT, GST_DEBUG ("Downloaded %d bytes in %" GST_TIME_FORMAT ". Bitrate is : %d",
diff < 0 ? "-" : " ", GST_TIME_ARGS (ABS (diff)), GST_TIME_ARGS (limit)); size, GST_TIME_ARGS (diff), bitrate);
/* if we are on time switch to a higher bitrate */ gst_buffer_unref (buffer);
if (diff > limit) { return gst_hls_demux_change_playlist (demux, bitrate * demux->bitrate_limit);
while (diff > limit) {
if (!gst_hls_demux_change_playlist (demux, TRUE))
break;
diff -= limit;
}
demux->accumulated_delay = 0;
} else if (diff < 0) {
/* if the client is too slow wait until it has accumulated a certain delay
* to switch to a lower bitrate */
demux->accumulated_delay -= diff;
if (demux->accumulated_delay >= limit) {
while (demux->accumulated_delay >= limit) {
if (!gst_hls_demux_change_playlist (demux, FALSE))
break;
demux->accumulated_delay -= limit;
}
demux->accumulated_delay = 0;
}
}
return TRUE;
} }
static gboolean static gboolean

View file

@ -69,7 +69,7 @@ struct _GstHLSDemux
/* Properties */ /* Properties */
guint fragments_cache; /* number of fragments needed to be cached to start playing */ guint fragments_cache; /* number of fragments needed to be cached to start playing */
gfloat bitrate_switch_tol; /* tolerance with respect to the fragment duration to switch the bitarate*/ gfloat bitrate_limit; /* limit of the available bitrate to use */
/* Streaming task */ /* Streaming task */
GstTask *stream_task; GstTask *stream_task;
@ -81,7 +81,6 @@ struct _GstHLSDemux
GRecMutex updates_lock; GRecMutex updates_lock;
GMutex updates_timed_lock; GMutex updates_timed_lock;
GTimeVal next_update; /* Time of the next update */ GTimeVal next_update; /* Time of the next update */
gint64 accumulated_delay; /* Delay accumulated fetching fragments, used to decide a playlist switch */
gboolean cancelled; gboolean cancelled;
/* Position in the stream */ /* Position in the stream */