dashdemux: provide a default suggestedPresentationDelay
authorAlex Ashley <bugzilla@ashley-family.net>
Tue, 7 Jul 2015 14:38:08 +0000 (15:38 +0100)
committerVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Thu, 29 Oct 2015 13:26:46 +0000 (13:26 +0000)
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").

ext/dash/gstdashdemux.c
ext/dash/gstdashdemux.h
ext/dash/gstmpdparser.c
ext/dash/gstmpdparser.h
tests/check/elements/dash_mpd.c

index 3d909da..4156bd5 100644 (file)
 
 #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);
index 3d01a0b..5ed9620 100644 (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;
index 953e207..3a1cb82 100644 (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;
+}
index 6e29d22..f76b379 100644 (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);
index 633beb2..00799fa 100644 (file)
@@ -4751,6 +4751,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
  */
 static Suite *
@@ -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)