dashdemux: include both Period start and presentationTimeOffset in segment start
authorAlex Ashley <bugzilla@ashley-family.net>
Tue, 16 Oct 2018 15:57:30 +0000 (16:57 +0100)
committerNicolas Dufresne <nicolas@ndufresne.ca>
Sat, 1 Jun 2019 21:25:33 +0000 (21:25 +0000)
The start of each segment is relative to the Period start, minus
the presentation time offset.

As specified in section 5.3.9.6 of the MPEG DASH specification:
    The value of the @t attribute minus the value of the
    @presentationTimeOffset specifies the MPD start time of
    the first Segment in the series.

dashdemux was not taking account of presentationTimeOffset and in
some methods was not taking into account the Period start time.
This commit modifies the segment->start value to always be
relative to the MPD start time (zero for VOD,
availabilityStartTime for live streams). This makes all uses of
the segment list consistent.

Fixes #841

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

index 6535cae..b75626c 100644 (file)
@@ -4021,7 +4021,7 @@ gst_mpd_client_setup_representation (GstMpdClient * client,
       GST_DEBUG ("No useful SegmentList node for the current Representation");
       /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */
       if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0,
-              PeriodEnd - PeriodStart, 0, PeriodEnd - PeriodStart)) {
+              PeriodEnd - PeriodStart, PeriodStart, PeriodEnd - PeriodStart)) {
         return FALSE;
       }
     } else {
@@ -4036,14 +4036,22 @@ gst_mpd_client_setup_representation (GstMpdClient * client,
       /* build segment list */
       i = stream->cur_segment_list->MultSegBaseType->startNumber;
       start = 0;
-      start_time = 0;
+      start_time = PeriodStart;
 
       GST_LOG ("Building media segment list using a SegmentList node");
       if (stream->cur_segment_list->MultSegBaseType->SegmentTimeline) {
         GstSegmentTimelineNode *timeline;
         GstSNode *S;
         GList *list;
-
+        GstClockTime presentationTimeOffset;
+        GstSegmentBaseType *segbase;
+
+        segbase = stream->cur_segment_list->MultSegBaseType->SegBaseType;
+        presentationTimeOffset =
+            gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND,
+            segbase->timescale);
+        GST_LOG ("presentationTimeOffset = %" G_GUINT64_FORMAT,
+            presentationTimeOffset);
         timeline = stream->cur_segment_list->MultSegBaseType->SegmentTimeline;
         for (list = g_queue_peek_head_link (&timeline->S); list;
             list = g_list_next (list)) {
@@ -4058,7 +4066,8 @@ gst_mpd_client_setup_representation (GstMpdClient * client,
 
           if (S->t > 0) {
             start = S->t;
-            start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale);
+            start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale)
+                + PeriodStart - presentationTimeOffset;
           }
 
           if (!SegmentURL) {
@@ -4115,8 +4124,15 @@ gst_mpd_client_setup_representation (GstMpdClient * client,
         return FALSE;
       }
     } else {
+      GstClockTime presentationTimeOffset;
       GstMultSegmentBaseType *mult_seg =
           stream->cur_seg_template->MultSegBaseType;
+
+      presentationTimeOffset =
+          gst_util_uint64_scale (mult_seg->SegBaseType->presentationTimeOffset,
+          GST_SECOND, mult_seg->SegBaseType->timescale);
+      GST_LOG ("presentationTimeOffset = %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (presentationTimeOffset));
       /* build segment list */
       i = mult_seg->startNumber;
       start = 0;
@@ -4143,7 +4159,8 @@ gst_mpd_client_setup_representation (GstMpdClient * client,
           duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale);
           if (S->t > 0) {
             start = S->t;
-            start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale);
+            start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale)
+                + PeriodStart - presentationTimeOffset;
           }
 
           if (!gst_mpd_client_add_media_segment (stream, NULL, i, S->r, start,
@@ -4170,13 +4187,12 @@ gst_mpd_client_setup_representation (GstMpdClient * client,
         GstMediaSegment *media_segment =
             g_ptr_array_index (stream->segments, n);
         if (media_segment) {
-          if (media_segment->start + media_segment->duration >
-              PeriodEnd - PeriodStart) {
-            GstClockTime stop = PeriodEnd - PeriodStart;
+          if (media_segment->start + media_segment->duration > PeriodEnd) {
+            GstClockTime stop = PeriodEnd;
             if (n < stream->segments->len - 1) {
               GstMediaSegment *next_segment =
                   g_ptr_array_index (stream->segments, n + 1);
-              if (next_segment && next_segment->start < PeriodEnd - PeriodStart)
+              if (next_segment && next_segment->start < PeriodEnd)
                 stop = next_segment->start;
             }
             media_segment->duration =
@@ -5015,6 +5031,11 @@ gst_mpd_client_get_last_fragment_timestamp_end (GstMpdClient * client,
     *ts = stream_period->start + stream_period->duration;
   } else {
     segment_idx = gst_mpd_client_get_segments_counts (client, stream) - 1;
+    if (segment_idx >= stream->segments->len) {
+      GST_WARNING ("Segment index %d is outside of segment list of length %d",
+          segment_idx, stream->segments->len);
+      return FALSE;
+    }
     currentChunk = g_ptr_array_index (stream->segments, segment_idx);
 
     if (currentChunk->repeat >= 0) {
@@ -5979,14 +6000,18 @@ gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client,
 {
   GstDateTime *availability_start_time, *rv;
   gint seg_idx;
-  GstStreamPeriod *stream_period;
   GstMediaSegment *segment;
   GstClockTime segmentEndTime;
+  const GstStreamPeriod *stream_period;
+  GstClockTime period_start = 0;
 
   g_return_val_if_fail (client != NULL, NULL);
   g_return_val_if_fail (stream != NULL, NULL);
 
   stream_period = gst_mpdparser_get_stream_period (client);
+  if (stream_period && stream_period->period) {
+    period_start = stream_period->start;
+  }
 
   seg_idx = stream->segment_index;
 
@@ -6001,16 +6026,15 @@ gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client,
           g_ptr_array_index (stream->segments, seg_idx + 1);
       segmentEndTime = next_segment->start;
     } else {
-      const GstStreamPeriod *stream_period;
-      stream_period = gst_mpdparser_get_stream_period (client);
-      segmentEndTime = stream_period->start + stream_period->duration;
+      g_return_val_if_fail (stream_period != NULL, NULL);
+      segmentEndTime = period_start + stream_period->duration;
     }
   } else {
     GstClockTime seg_duration;
     seg_duration = gst_mpd_client_get_segment_duration (client, stream, NULL);
     if (seg_duration == 0)
       return NULL;
-    segmentEndTime = (1 + seg_idx) * seg_duration;
+    segmentEndTime = period_start + (1 + seg_idx) * seg_duration;
   }
 
   availability_start_time = gst_mpd_client_get_availability_start_time (client);
@@ -6019,19 +6043,6 @@ gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client,
     return NULL;
   }
 
-  if (stream_period && stream_period->period) {
-    GstDateTime *t =
-        gst_mpd_client_add_time_difference (availability_start_time,
-        stream_period->start / GST_USECOND);
-    gst_date_time_unref (availability_start_time);
-    availability_start_time = t;
-
-    if (availability_start_time == NULL) {
-      GST_WARNING_OBJECT (client, "Failed to offset availability_start_time");
-      return NULL;
-    }
-  }
-
   rv = gst_mpd_client_add_time_difference (availability_start_time,
       segmentEndTime / GST_USECOND);
   gst_date_time_unref (availability_start_time);
index d0cf1a4..d9455fa 100644 (file)
@@ -839,6 +839,97 @@ GST_START_TEST (dash_mpdparser_period_segmentTemplate)
 GST_END_TEST;
 
 /*
+ * Test parsing Period SegmentTemplate attributes where a
+ * presentationTimeOffset attribute has been specified
+ *
+ */
+GST_START_TEST (dash_mpdparser_period_segmentTemplateWithPresentationTimeOffset)
+{
+  const gchar *xml =
+      "<?xml version=\"1.0\"?>"
+      "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
+      "     profiles=\"urn:mpeg:dash:profile:isoff-main:2011\">"
+      "  <Period start=\"PT1M\" duration=\"PT40S\">"
+      "    <AdaptationSet"
+      "      bitstreamSwitching=\"false\""
+      "      mimeType=\"video/mp4\""
+      "      contentType=\"video\">"
+      "      <SegmentTemplate media=\"$RepresentationID$/TestMedia-$Time$.mp4\""
+      "                     index=\"$RepresentationID$/TestIndex.mp4\""
+      "                     timescale=\"100\""
+      "                     presentationTimeOffset=\"6000\""
+      "                     initialization=\"$RepresentationID$/TestInitialization\""
+      "                     bitstreamSwitching=\"true\">"
+      "        <SegmentTimeline>"
+      "          <S d=\"400\" r=\"9\" t=\"100\"/>"
+      "        </SegmentTimeline></SegmentTemplate>"
+      "      <Representation bandwidth=\"95866\" frameRate=\"90000/3600\""
+      "        id=\"vrep\" /></AdaptationSet></Period></MPD>";
+
+  gboolean ret;
+  GList *adaptationSets;
+  GstAdaptationSetNode *adapt_set;
+  GstActiveStream *activeStream;
+  GstMediaFragmentInfo fragment;
+  GstClockTime expectedDuration;
+  GstClockTime expectedTimestamp;
+  GstMpdClient *mpdclient;
+  GstPeriodNode *periodNode;
+  GstSegmentTemplateNode *segmentTemplate;
+
+  mpdclient = gst_mpd_client_new ();
+  ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml));
+  assert_equals_int (ret, TRUE);
+
+  ret =
+      gst_mpd_client_setup_media_presentation (mpdclient, GST_CLOCK_TIME_NONE,
+      -1, NULL);
+  assert_equals_int (ret, TRUE);
+
+  periodNode =
+      (GstPeriodNode *) g_list_nth_data (mpdclient->mpd_node->Periods, 0);
+  fail_if (periodNode == NULL);
+
+  /* get the list of adaptation sets of the first period */
+  adaptationSets = gst_mpd_client_get_adaptation_sets (mpdclient);
+  fail_if (adaptationSets == NULL);
+
+  /* setup streaming from the first adaptation set */
+  adapt_set = (GstAdaptationSetNode *) g_list_nth_data (adaptationSets, 0);
+  fail_if (adapt_set == NULL);
+  ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set);
+  assert_equals_int (ret, TRUE);
+  activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0);
+  fail_if (activeStream == NULL);
+
+  segmentTemplate = adapt_set->SegmentTemplate;
+  fail_if (segmentTemplate == NULL);
+  assert_equals_string (segmentTemplate->media,
+      "$RepresentationID$/TestMedia-$Time$.mp4");
+  assert_equals_string (segmentTemplate->index,
+      "$RepresentationID$/TestIndex.mp4");
+  assert_equals_string (segmentTemplate->initialization,
+      "$RepresentationID$/TestInitialization");
+  assert_equals_string (segmentTemplate->bitstreamSwitching, "true");
+
+  ret = gst_mpd_client_get_next_fragment (mpdclient, 0, &fragment);
+  assert_equals_int (ret, TRUE);
+  expectedDuration = duration_to_ms (0, 0, 0, 0, 0, 4, 0);
+  /* start = Period@start + S@t - presentationTimeOffset */
+  expectedTimestamp = duration_to_ms (0, 0, 0, 0, 0, 1, 0);
+  assert_equals_uint64 (fragment.duration, expectedDuration * GST_MSECOND);
+  assert_equals_uint64 (fragment.timestamp, expectedTimestamp * GST_MSECOND);
+  /* the $Time$ expansion uses the @t value, without including
+     Period@start or presentationTimeOffset */
+  assert_equals_string (fragment.uri, "/vrep/TestMedia-100.mp4");
+  gst_media_fragment_info_clear (&fragment);
+
+  gst_mpd_client_free (mpdclient);
+}
+
+GST_END_TEST;
+
+/*
  * Test parsing Period SegmentTemplate MultipleSegmentBaseType attributes
  *
  */
@@ -5883,6 +5974,8 @@ dash_suite (void)
   tcase_add_test (tc_simpleMPD, dash_mpdparser_period_segmentList_segmentURL);
   tcase_add_test (tc_simpleMPD, dash_mpdparser_period_segmentTemplate);
   tcase_add_test (tc_simpleMPD,
+      dash_mpdparser_period_segmentTemplateWithPresentationTimeOffset);
+  tcase_add_test (tc_simpleMPD,
       dash_mpdparser_period_segmentTemplate_multipleSegmentBaseType);
   tcase_add_test (tc_simpleMPD,
       dash_mpdparser_period_segmentTemplate_multipleSegmentBaseType_segmentBaseType);