splitmux: Avoid negative DTS
authorJan Schmidt <jan@centricular.com>
Thu, 20 Feb 2020 15:14:11 +0000 (02:14 +1100)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Wed, 4 Mar 2020 05:42:21 +0000 (05:42 +0000)
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.

gst/multifile/gstsplitmuxpartreader.c
gst/multifile/gstsplitmuxpartreader.h
gst/multifile/gstsplitmuxsrc.c
tests/check/elements/splitmux.c

index 4859dd8..51a8635 100644 (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);
 }
 
index b1d2456..1659b6e 100644 (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);
index a9cfa56..757f3bd 100644 (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.",
index 2919d4d..1bafdde 100644 (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;
+
+  g_signal_emit_by_name (appsink, "pull-sample", &sample);
+  fail_unless (sample != NULL);
+
+  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_BUFFER_DURATION_IS_VALID (buf))
-    end += GST_BUFFER_DURATION (buf);
+  if (GST_CLOCK_TIME_IS_VALID (start))
+    start = gst_segment_to_stream_time (seg, GST_FORMAT_TIME, start);
 
-  GST_LOG ("Got buffer %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+  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 */