splitmux: Avoid negative DTS

In order to concatenate fragments, splitmuxsrc offsets
the start of each fragment PTS to 0 to align it with the
previous file. This means that DTS can go negative for
the first fragment, with really bad results.

Add a fixed offset to outgoing timestamp ranges to
avoid that.
This commit is contained in:
Jan Schmidt 2020-02-21 02:14:11 +11:00 committed by GStreamer Merge Bot
parent 54f68ff36b
commit f490c38416
4 changed files with 72 additions and 32 deletions

View file

@ -149,6 +149,9 @@ handle_buffer_measuring (GstSplitMuxPartReader * reader,
/* Adjust buffer timestamps */
offset = reader->start_offset + part_pad->segment.base;
offset -= part_pad->initial_ts_offset;
/* We don't add the ts_offset here, because we
* want to measure the logical length of the stream,
* not to generate output timestamps */
/* Update the stored max duration on the pad,
* always preferring making DTS contiguous
@ -159,8 +162,8 @@ handle_buffer_measuring (GstSplitMuxPartReader * reader,
ts = GST_BUFFER_PTS (buf) + offset;
GST_DEBUG_OBJECT (reader, "Pad %" GST_PTR_FORMAT
" incoming PTS %" GST_TIME_FORMAT
" DTS %" GST_TIME_FORMAT " offset by %" GST_STIME_FORMAT
" incoming DTS %" GST_TIME_FORMAT
" PTS %" GST_TIME_FORMAT " offset by %" GST_STIME_FORMAT
" to %" GST_STIME_FORMAT, part_pad,
GST_TIME_ARGS (GST_BUFFER_DTS (buf)),
GST_TIME_ARGS (GST_BUFFER_PTS (buf)),
@ -228,6 +231,7 @@ splitmux_part_pad_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
/* Adjust buffer timestamps */
offset = reader->start_offset + part_pad->segment.base;
offset -= part_pad->initial_ts_offset;
offset += reader->ts_offset;
if (GST_BUFFER_PTS_IS_VALID (buf))
GST_BUFFER_PTS (buf) += offset;
@ -356,21 +360,22 @@ splitmux_part_pad_event (GstPad * pad, GstObject * parent, GstEvent * event)
goto wrong_segment;
/* Adjust segment */
/* Adjust start/stop so the overall file is 0 + start_offset based */
/* Adjust start/stop so the overall file is 0 + start_offset based,
* adding a fixed offset so that DTS is never negative */
if (seg->stop != -1) {
seg->stop -= seg->start;
seg->stop += seg->time + reader->start_offset;
seg->stop += seg->time + reader->start_offset + reader->ts_offset;
}
seg->start = seg->time + reader->start_offset;
seg->start = seg->time + reader->start_offset + reader->ts_offset;
seg->time += reader->start_offset;
seg->position += reader->start_offset;
GST_LOG_OBJECT (pad, "Adjusted segment now %" GST_PTR_FORMAT, event);
/* Replace event */
gst_event_unref (event);
event = gst_event_new_segment (seg);
GST_LOG_OBJECT (pad, "Adjusted segment now %" GST_PTR_FORMAT, event);
if (reader->prep_state != PART_STATE_PREPARING_COLLECT_STREAMS
&& reader->prep_state != PART_STATE_PREPARING_MEASURE_STREAMS)
break; /* Only do further stuff with segments during initial measuring */
@ -1246,12 +1251,13 @@ gst_splitmux_part_reader_get_end_offset (GstSplitMuxPartReader * reader)
void
gst_splitmux_part_reader_set_start_offset (GstSplitMuxPartReader * reader,
GstClockTime offset)
GstClockTime time_offset, GstClockTime ts_offset)
{
SPLITMUX_PART_LOCK (reader);
reader->start_offset = offset;
GST_INFO_OBJECT (reader, "TS offset now %" GST_TIME_FORMAT,
GST_TIME_ARGS (offset));
reader->start_offset = time_offset;
reader->ts_offset = ts_offset;
GST_INFO_OBJECT (reader, "Time offset now %" GST_TIME_FORMAT,
GST_TIME_ARGS (time_offset));
SPLITMUX_PART_UNLOCK (reader);
}

View file

@ -73,6 +73,7 @@ struct _GstSplitMuxPartReader
GstClockTime duration;
GstClockTime start_offset;
GstClockTime ts_offset;
GList *pads;
@ -107,7 +108,7 @@ void gst_splitmux_part_reader_deactivate (GstSplitMuxPartReader *part);
gboolean gst_splitmux_part_reader_is_active (GstSplitMuxPartReader *part);
gboolean gst_splitmux_part_reader_src_query (GstSplitMuxPartReader *part, GstPad *src_pad, GstQuery * query);
void gst_splitmux_part_reader_set_start_offset (GstSplitMuxPartReader *part, GstClockTime offset);
void gst_splitmux_part_reader_set_start_offset (GstSplitMuxPartReader *part, GstClockTime time_offset, GstClockTime ts_offset);
GstClockTime gst_splitmux_part_reader_get_start_offset (GstSplitMuxPartReader *part);
GstClockTime gst_splitmux_part_reader_get_end_offset (GstSplitMuxPartReader *part);
GstClockTime gst_splitmux_part_reader_get_duration (GstSplitMuxPartReader * reader);

View file

@ -55,6 +55,8 @@
GST_DEBUG_CATEGORY (splitmux_debug);
#define GST_CAT_DEFAULT splitmux_debug
#define FIXED_TS_OFFSET (1000*GST_SECOND)
enum
{
PROP_0,
@ -642,14 +644,14 @@ gst_splitmux_handle_event (GstSplitMuxSrc * splitmux,
* seg or play_segment */
if (splitmux->play_segment.rate > 0.0) {
if (splitmux->play_segment.stop != -1)
seg.stop = splitmux->play_segment.stop;
seg.stop = splitmux->play_segment.stop + FIXED_TS_OFFSET;
else
seg.stop = splitpad->segment.stop;
} else {
/* Reverse playback from stop time to start time */
/* See if an end point was requested in the seek */
if (splitmux->play_segment.start != -1) {
seg.start = splitmux->play_segment.start;
seg.start = splitmux->play_segment.start + FIXED_TS_OFFSET;
seg.time = splitmux->play_segment.time;
} else {
seg.start = splitpad->segment.start;
@ -858,7 +860,7 @@ gst_splitmux_src_prepare_next_part (GstSplitMuxSrc * splitmux)
splitmux->parts[idx]->path, idx);
gst_splitmux_part_reader_set_start_offset (splitmux->parts[idx],
splitmux->end_offset);
splitmux->end_offset, FIXED_TS_OFFSET);
if (!gst_splitmux_part_reader_prepare (splitmux->parts[idx])) {
GST_WARNING_OBJECT (splitmux,
"Failed to prepare file part %s. Cannot play past there.",

View file

@ -146,17 +146,40 @@ seek_pipeline (GstElement * pipeline, gdouble rate, GstClockTime start,
current_rate = rate;
};
static void
receive_handoff (GstElement * object G_GNUC_UNUSED, GstBuffer * buf,
GstPad * arg1 G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED)
static GstFlowReturn
receive_sample (GstAppSink * appsink, gpointer user_data G_GNUC_UNUSED)
{
GstClockTime start = GST_BUFFER_TIMESTAMP (buf);
GstClockTime end = start;
GstSample *sample;
GstSegment *seg;
GstBuffer *buf;
GstClockTime start;
GstClockTime end;
if (GST_BUFFER_DURATION_IS_VALID (buf))
end += GST_BUFFER_DURATION (buf);
g_signal_emit_by_name (appsink, "pull-sample", &sample);
fail_unless (sample != NULL);
GST_LOG ("Got buffer %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
seg = gst_sample_get_segment (sample);
fail_unless (seg != NULL);
buf = gst_sample_get_buffer (sample);
fail_unless (buf != NULL);
GST_LOG ("Got buffer %" GST_PTR_FORMAT, buf);
start = GST_BUFFER_PTS (buf);
end = start;
if (GST_CLOCK_TIME_IS_VALID (start))
start = gst_segment_to_stream_time (seg, GST_FORMAT_TIME, start);
if (GST_CLOCK_TIME_IS_VALID (end)) {
if (GST_BUFFER_DURATION_IS_VALID (buf))
end += GST_BUFFER_DURATION (buf);
end = gst_segment_to_stream_time (seg, GST_FORMAT_TIME, end);
}
GST_DEBUG ("Got buffer stream time %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
GST_TIME_ARGS (start), GST_TIME_ARGS (end));
/* Check time is moving in the right direction */
@ -184,6 +207,10 @@ receive_handoff (GstElement * object G_GNUC_UNUSED, GstBuffer * buf,
first_ts = start;
if (!GST_CLOCK_TIME_IS_VALID (last_ts) || end > last_ts)
last_ts = end;
gst_sample_unref (sample);
return GST_FLOW_OK;
}
static void
@ -192,16 +219,19 @@ test_playback (const gchar * in_pattern, GstClockTime exp_first_time,
{
GstMessage *msg;
GstElement *pipeline;
GstElement *fakesink;
GstElement *appsink;
GstElement *fakesink2;
GstAppSinkCallbacks callbacks = { NULL };
gchar *uri;
GST_DEBUG ("Playing back files matching %s", in_pattern);
pipeline = gst_element_factory_make ("playbin", NULL);
fail_if (pipeline == NULL);
fakesink = gst_element_factory_make ("fakesink", NULL);
fail_if (fakesink == NULL);
g_object_set (G_OBJECT (pipeline), "video-sink", fakesink, NULL);
appsink = gst_element_factory_make ("appsink", NULL);
fail_if (appsink == NULL);
g_object_set (G_OBJECT (pipeline), "video-sink", appsink, NULL);
fakesink2 = gst_element_factory_make ("fakesink", NULL);
fail_if (fakesink2 == NULL);
g_object_set (G_OBJECT (pipeline), "audio-sink", fakesink2, NULL);
@ -211,8 +241,8 @@ test_playback (const gchar * in_pattern, GstClockTime exp_first_time,
g_object_set (G_OBJECT (pipeline), "uri", uri, NULL);
g_free (uri);
g_signal_connect (fakesink, "handoff", (GCallback) receive_handoff, NULL);
g_object_set (G_OBJECT (fakesink), "signal-handoffs", TRUE, NULL);
callbacks.new_sample = receive_sample;
gst_app_sink_set_callbacks (GST_APP_SINK (appsink), &callbacks, NULL, NULL);
/* test forwards */
seek_pipeline (pipeline, 1.0, 0, -1);
@ -223,11 +253,12 @@ test_playback (const gchar * in_pattern, GstClockTime exp_first_time,
/* Check we saw the entire range of values */
fail_unless (first_ts == exp_first_time,
"Expected start of playback range 0, got %" GST_TIME_FORMAT,
"Expected start of playback range %" GST_TIME_FORMAT ", got %"
GST_TIME_FORMAT, GST_TIME_ARGS (exp_first_time),
GST_TIME_ARGS (first_ts));
fail_unless (last_ts == exp_last_time,
"Expected end of playback range 3s, got %" GST_TIME_FORMAT,
GST_TIME_ARGS (last_ts));
"Expected end of playback range %" GST_TIME_FORMAT ", got %"
GST_TIME_FORMAT, GST_TIME_ARGS (exp_last_time), GST_TIME_ARGS (last_ts));
if (test_reverse) {
/* Test backwards */