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
This commit is contained in:
Alex Ashley 2018-10-16 16:57:30 +01:00 committed by Nicolas Dufresne
parent 51ea6ec6b7
commit a11f7ed924
2 changed files with 130 additions and 26 deletions

View 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);

View file

@ -838,6 +838,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
*
@ -5882,6 +5973,8 @@ dash_suite (void)
dash_mpdparser_period_segmentList_multipleSegmentBaseType_bitstreamSwitching);
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,