dashdemux: provide a default suggestedPresentationDelay

If MPD@suggestedPresentationDelay is not present in the manifest,
dashdemux selects the fragment closest to the most recently generated
fragment. This causes a playback issue because this choice does not allow
the DASH client to build up any buffer of downloaded fragments without
pausing playback. This is because by definition new fragments appear on
the server in real-time (e.g. if segment duration is 4 seconds, a new
fragment will appear on the server every 4 seconds). If the starting
playback position was n*segmentDuration seconds behind "now", the DASH
client could download up to 'n' fragments faster than realtime before it
reached the point where it needed to wait for fragments to appear on the
server.

The MPD@suggestedPresentationDelay attribute allows a content publisher
to provide a suggested starting position that is behind the current
"live" position.

If the MPD@suggestedPresentationDelay attribute is not present, provide
a suitable default value as a property of the dashdemux element. To
allow the default presentation delay to be specified either using
fragments or seconds, the property is a string that contains a number
and a unit (e.g. "10 seconds", "4 fragments", "2500ms").
This commit is contained in:
Alex Ashley 2015-07-07 15:38:08 +01:00 committed by Vincent Penquerc'h
parent d7a167c5b4
commit c8ef39cac7
5 changed files with 114 additions and 0 deletions

View file

@ -146,6 +146,7 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <gio/gio.h>
#include <gst/base/gsttypefindhelper.h>
@ -188,6 +189,7 @@ enum
PROP_MAX_BUFFERING_TIME,
PROP_BANDWIDTH_USAGE,
PROP_MAX_BITRATE,
PROP_PRESENTATION_DELAY,
PROP_LAST
};
@ -195,6 +197,7 @@ enum
#define DEFAULT_MAX_BUFFERING_TIME 30 /* in seconds */
#define DEFAULT_BANDWIDTH_USAGE 0.8 /* 0 to 1 */
#define DEFAULT_MAX_BITRATE 24000000 /* in bit/s */
#define DEFAULT_PRESENTATION_DELAY NULL /* zero */
/* Clock drift compensation for live streams */
#define SLOW_CLOCK_UPDATE_INTERVAL (1000000 * 30 * 60) /* 30 minutes */
@ -299,6 +302,7 @@ gst_dash_demux_dispose (GObject * obj)
gst_dash_demux_clock_drift_free (demux->clock_drift);
demux->clock_drift = NULL;
g_free (demux->default_presentation_delay);
G_OBJECT_CLASS (parent_class)->dispose (obj);
}
@ -379,6 +383,12 @@ gst_dash_demux_class_init (GstDashDemuxClass * klass)
1000, G_MAXUINT, DEFAULT_MAX_BITRATE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PRESENTATION_DELAY,
g_param_spec_string ("presentation-delay", "Presentation delay",
"Default presentation delay (in seconds, milliseconds or fragments) (e.g. 12s, 2500ms, 3f)",
DEFAULT_PRESENTATION_DELAY,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&gst_dash_demux_audiosrc_template));
gst_element_class_add_pad_template (gstelement_class,
@ -441,6 +451,7 @@ gst_dash_demux_init (GstDashDemux * demux)
/* Properties */
demux->max_buffering_time = DEFAULT_MAX_BUFFERING_TIME * GST_SECOND;
demux->max_bitrate = DEFAULT_MAX_BITRATE;
demux->default_presentation_delay = DEFAULT_PRESENTATION_DELAY;
g_mutex_init (&demux->client_lock);
@ -465,6 +476,10 @@ gst_dash_demux_set_property (GObject * object, guint prop_id,
case PROP_MAX_BITRATE:
demux->max_bitrate = g_value_get_uint (value);
break;
case PROP_PRESENTATION_DELAY:
g_free (demux->default_presentation_delay);
demux->default_presentation_delay = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -488,6 +503,12 @@ gst_dash_demux_get_property (GObject * object, guint prop_id, GValue * value,
case PROP_MAX_BITRATE:
g_value_set_uint (value, demux->max_bitrate);
break;
case PROP_PRESENTATION_DELAY:
if (demux->default_presentation_delay == NULL)
g_value_set_static_string (value, "");
else
g_value_set_string (value, demux->default_presentation_delay);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -684,6 +705,13 @@ gst_dash_demux_setup_streams (GstAdaptiveDemux * demux)
dashdemux->client->mpd_node->suggestedPresentationDelay * -1000);
gst_date_time_unref (now);
now = target;
} else if (dashdemux->default_presentation_delay) {
gint64 dfp =
gst_mpd_client_parse_default_presentation_delay (dashdemux->client,
dashdemux->default_presentation_delay);
GstDateTime *target = gst_mpd_client_add_time_difference (now, dfp);
gst_date_time_unref (now);
now = target;
}
period_idx =
gst_mpd_client_get_period_index_at_time (dashdemux->client, now);

View file

@ -96,6 +96,7 @@ struct _GstDashDemux
/* Properties */
GstClockTime max_buffering_time; /* Maximum buffering time accumulated during playback */
guint64 max_bitrate; /* max of bitrate supported by target decoder */
gchar* default_presentation_delay; /* presentation time delay if MPD@suggestedPresentationDelay is not present */
gint n_audio_streams;
gint n_video_streams;

View file

@ -5807,3 +5807,43 @@ gst_mpd_client_has_isoff_ondemand_profile (GstMpdClient * client)
{
return client->profile_isoff_ondemand;
}
/**
* gst_mpd_client_parse_default_presentation_delay:
* @client: #GstMpdClient that has a parsed manifest
* @default_presentation_delay: A string that specifies a time period
* in fragments (e.g. "5 f"), seconds ("12 s") or milliseconds
* ("12000 ms")
* Returns: the parsed string in milliseconds
*
* Since: 1.6
*/
gint64
gst_mpd_client_parse_default_presentation_delay (GstMpdClient * client,
const gchar * default_presentation_delay)
{
gint64 value;
char *endptr = NULL;
g_return_val_if_fail (client != NULL, 0);
g_return_val_if_fail (default_presentation_delay != NULL, 0);
value = strtol (default_presentation_delay, &endptr, 10);
if (endptr == default_presentation_delay || value == 0) {
return 0;
}
while (*endptr == ' ')
endptr++;
if (*endptr == 's' || *endptr == 'S') {
value *= 1000; /* convert to ms */
} else if (*endptr == 'f' || *endptr == 'F') {
gint64 segment_duration;
g_assert (client->mpd_node != NULL);
segment_duration = client->mpd_node->maxSegmentDuration;
value *= segment_duration;
} else if (*endptr != 'm' && *endptr != 'M') {
GST_ERROR ("Unable to parse default presentation delay: %s",
default_presentation_delay);
value = 0;
}
return value;
}

View file

@ -594,6 +594,7 @@ guint gst_mpdparser_get_list_and_nb_of_audio_language (GstMpdClient *client, GLi
gint64 gst_mpd_client_calculate_time_difference (const GstDateTime * t1, const GstDateTime * t2);
GstDateTime *gst_mpd_client_add_time_difference (GstDateTime * t1, gint64 usecs);
gint64 gst_mpd_client_parse_default_presentation_delay(GstMpdClient * client, const gchar * default_presentation_delay);
/* profiles */
gboolean gst_mpd_client_has_isoff_ondemand_profile (GstMpdClient *client);

View file

@ -4750,6 +4750,49 @@ GST_START_TEST (dash_mpdparser_unmatched_segmentTimeline_segmentURL)
GST_END_TEST;
/*
* Test parsing of the default presentation delay property
*/
GST_START_TEST (dash_mpdparser_default_presentation_delay)
{
const gchar *xml =
"<?xml version=\"1.0\"?>"
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
" profiles=\"urn:mpeg:dash:profile:isoff-main:2011\""
" maxSegmentDuration=\"PT2S\">"
" <Period id=\"Period0\" start=\"P0S\"></Period></MPD>";
gboolean ret;
GstMpdClient *mpdclient = gst_mpd_client_new ();
gint64 value;
ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml));
assert_equals_int (ret, TRUE);
value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "5s");
assert_equals_int64 (value, 5000);
value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "5S");
assert_equals_int64 (value, 5000);
value =
gst_mpd_client_parse_default_presentation_delay (mpdclient, "5 seconds");
assert_equals_int64 (value, 5000);
value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "2500ms");
assert_equals_int64 (value, 2500);
value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "3f");
assert_equals_int64 (value, 6000);
value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "3F");
assert_equals_int64 (value, 6000);
value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "");
assert_equals_int64 (value, 0);
value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "10");
assert_equals_int64 (value, 0);
value =
gst_mpd_client_parse_default_presentation_delay (mpdclient,
"not a number");
assert_equals_int64 (value, 0);
}
GST_END_TEST;
/*
* create a test suite containing all dash testcases
*/
@ -4871,6 +4914,7 @@ dash_suite (void)
tcase_add_test (tc_simpleMPD, dash_mpdparser_isoff_ondemand_profile);
tcase_add_test (tc_simpleMPD, dash_mpdparser_GstDateTime);
tcase_add_test (tc_simpleMPD, dash_mpdparser_various_duration_formats);
tcase_add_test (tc_simpleMPD, dash_mpdparser_default_presentation_delay);
/* tests checking the MPD management
* (eg. setting active streams, obtaining attributes values)