From a11f7ed9244a6af9f1e77154e0a19e1462ec77d2 Mon Sep 17 00:00:00 2001 From: Alex Ashley Date: Tue, 16 Oct 2018 16:57:30 +0100 Subject: [PATCH] dashdemux: include both Period start and presentationTimeOffset in segment start 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 | 65 ++++++++++++++++------------ tests/check/elements/dash_mpd.c | 93 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 27 deletions(-) diff --git a/ext/dash/gstmpdparser.c b/ext/dash/gstmpdparser.c index 6535cae..b75626c 100644 --- a/ext/dash/gstmpdparser.c +++ b/ext/dash/gstmpdparser.c @@ -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); diff --git a/tests/check/elements/dash_mpd.c b/tests/check/elements/dash_mpd.c index d0cf1a4..d9455fa 100644 --- a/tests/check/elements/dash_mpd.c +++ b/tests/check/elements/dash_mpd.c @@ -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 = + "" + "" + " " + " " + " " + " " + " " + " " + " "; + + 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); -- 2.7.4