clocksync: Add a new property "sync-to-first" for automatic ts-offset setup
authorSeungha Yang <seungha@centricular.com>
Wed, 18 Nov 2020 13:32:30 +0000 (22:32 +0900)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Thu, 18 Mar 2021 14:22:44 +0000 (14:22 +0000)
Add a new property so that clocksync can setup "ts-offset" value
based on the first buffer and pipeline's running time when the
first arrived. Newly update "ts-offset" in this case would be
a value that allows outputting the first buffer without clock waiting.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/702>

docs/plugins/gst_plugins_cache.json
plugins/elements/gstclocksync.c
plugins/elements/gstclocksync.h
tests/check/elements/clocksync.c

index 3c64ac228835e4926bd9c53d36ec6619379f1f55..66f5bd1f6c7610813c97994a9b30e20f84b9aff4 100644 (file)
                         "type": "gboolean",
                         "writable": true
                     },
+                    "sync-to-first": {
+                        "blurb": "Automatically set ts-offset based on running time of the first buffer and pipeline's running time (i.e., ts-offset = \"pipeline running time\" - \"buffer running time\"). When enabled, clocksync element will update ts-offset on the first buffer per flush event or READY to PAUSED state change. This property can be useful in case that buffer timestamp does not necessarily have to be synchronized with pipeline's running time, but duration of the buffer through clocksync element needs to be synchronized with the amount of clock time go. Note that mixed use of ts-offset and this property would be racy if clocksync element is running already.",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "false",
+                        "mutable": "null",
+                        "readable": true,
+                        "type": "gboolean",
+                        "writable": true
+                    },
                     "ts-offset": {
                         "blurb": "Timestamp offset in nanoseconds for synchronisation, negative for earlier sync",
                         "conditionally-available": false,
index 4d5d3e2544fcc3d5f0bcff5c7bea57698901167d..5b841db5f88feffe69bea8cfc6bc5e60e4075a0a 100644 (file)
@@ -56,14 +56,19 @@ GST_DEBUG_CATEGORY_STATIC (gst_clock_sync_debug);
 /* ClockSync args */
 #define DEFAULT_SYNC                    TRUE
 #define DEFAULT_TS_OFFSET               0
+#define DEFAULT_SYNC_TO_FIRST           FALSE
 
 enum
 {
   PROP_0,
   PROP_SYNC,
-  PROP_TS_OFFSET
+  PROP_TS_OFFSET,
+  PROP_SYNC_TO_FIRST,
+  PROP_LAST
 };
 
+static GParamSpec *properties[PROP_LAST];
+
 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
@@ -116,15 +121,42 @@ gst_clock_sync_class_init (GstClockSyncClass * klass)
   gobject_class->get_property = gst_clock_sync_get_property;
   gobject_class->finalize = gst_clock_sync_finalize;
 
-  g_object_class_install_property (gobject_class, PROP_SYNC,
+  properties[PROP_SYNC] =
       g_param_spec_boolean ("sync", "Synchronize",
-          "Synchronize to pipeline clock", DEFAULT_SYNC,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-  g_object_class_install_property (gobject_class, PROP_TS_OFFSET,
+      "Synchronize to pipeline clock", DEFAULT_SYNC,
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  properties[PROP_TS_OFFSET] =
       g_param_spec_int64 ("ts-offset", "Timestamp offset for synchronisation",
-          "Timestamp offset in nanoseconds for synchronisation, negative for earlier sync",
-          G_MININT64, G_MAXINT64, DEFAULT_TS_OFFSET,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+      "Timestamp offset in nanoseconds for synchronisation, negative for earlier sync",
+      G_MININT64, G_MAXINT64, DEFAULT_TS_OFFSET,
+      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GstClockSync:sync-to-first:
+   *
+   * When enabled, clocksync elemenet will adjust "ts-offset" value
+   * automatically by using given timestamp of the first buffer and running
+   * time of pipeline, so that clocksync element can output the first buffer
+   * immediately without clock waiting.
+   *
+   * Since: 1.20
+   */
+  properties[PROP_SYNC_TO_FIRST] =
+      g_param_spec_boolean ("sync-to-first",
+      "Sync to first",
+      "Automatically set ts-offset based on running time of the first "
+      "buffer and pipeline's running time "
+      "(i.e., ts-offset = \"pipeline running time\" - \"buffer running time\"). "
+      "When enabled, clocksync element will update ts-offset on the first "
+      "buffer per flush event or READY to PAUSED state change. "
+      "This property can be useful in case that buffer timestamp does not "
+      "necessarily have to be synchronized with pipeline's running time, "
+      "but duration of the buffer through clocksync element needs to be "
+      "synchronized with the amount of clock time go. "
+      "Note that mixed use of ts-offset and this property would be racy "
+      "if clocksync element is running already.",
+      DEFAULT_SYNC_TO_FIRST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_properties (gobject_class, PROP_LAST, properties);
 
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_clocksync_change_state);
@@ -172,6 +204,7 @@ gst_clock_sync_init (GstClockSync * clocksync)
 
   clocksync->ts_offset = DEFAULT_TS_OFFSET;
   clocksync->sync = DEFAULT_SYNC;
+  clocksync->sync_to_first = DEFAULT_SYNC_TO_FIRST;
   g_cond_init (&clocksync->blocked_cond);
 
   GST_OBJECT_FLAG_SET (clocksync, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
@@ -211,6 +244,9 @@ gst_clock_sync_set_property (GObject * object, guint prop_id,
     case PROP_TS_OFFSET:
       clocksync->ts_offset = g_value_get_int64 (value);
       break;
+    case PROP_SYNC_TO_FIRST:
+      clocksync->sync_to_first = g_value_get_boolean (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -230,6 +266,9 @@ gst_clock_sync_get_property (GObject * object, guint prop_id,
     case PROP_TS_OFFSET:
       g_value_set_int64 (value, clocksync->ts_offset);
       break;
+    case PROP_SYNC_TO_FIRST:
+      g_value_set_boolean (value, clocksync->sync_to_first);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -346,6 +385,8 @@ gst_clock_sync_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
       clocksync->flushing = FALSE;
       gst_segment_init (&clocksync->segment, GST_FORMAT_UNDEFINED);
       GST_OBJECT_UNLOCK (clocksync);
+      clocksync->is_first = TRUE;
+      break;
     default:
       break;
   }
@@ -355,6 +396,42 @@ gst_clock_sync_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
   return ret;
 }
 
+static void
+gst_clock_sync_update_ts_offset (GstClockSync * clocksync,
+    GstClockTime runtimestamp)
+{
+  GstClock *clock;
+  GstClockTimeDiff ts_offset = 0;
+  GstClockTime running_time;
+
+  if (!clocksync->sync_to_first || !clocksync->is_first || !clocksync->sync)
+    return;
+
+  GST_OBJECT_LOCK (clocksync);
+  clock = GST_ELEMENT_CLOCK (clocksync);
+  if (!clock) {
+    GST_DEBUG_OBJECT (clocksync, "We have no clock");
+    GST_OBJECT_UNLOCK (clocksync);
+    return;
+  }
+
+  running_time = gst_clock_get_time (clock) -
+      GST_ELEMENT_CAST (clocksync)->base_time;
+  ts_offset = GST_CLOCK_DIFF (runtimestamp, running_time);
+  GST_OBJECT_UNLOCK (clocksync);
+
+  GST_DEBUG_OBJECT (clocksync, "Running time %" GST_TIME_FORMAT
+      ", running time stamp %" GST_TIME_FORMAT ", calculated ts-offset %"
+      GST_STIME_FORMAT, GST_TIME_ARGS (running_time),
+      GST_TIME_ARGS (runtimestamp), GST_STIME_ARGS (ts_offset));
+
+  clocksync->is_first = FALSE;
+  if (ts_offset != clocksync->ts_offset) {
+    clocksync->ts_offset = ts_offset;
+    g_object_notify_by_pspec (G_OBJECT (clocksync), properties[PROP_TS_OFFSET]);
+  }
+}
+
 static GstFlowReturn
 gst_clock_sync_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
 {
@@ -388,6 +465,8 @@ gst_clock_sync_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
     else if (GST_CLOCK_TIME_IS_VALID (runpts))
       runtimestamp = runpts;
 
+    gst_clock_sync_update_ts_offset (clocksync, runtimestamp);
+
     ret = gst_clocksync_do_sync (clocksync, runtimestamp);
     if (ret != GST_FLOW_OK) {
       GST_LOG_OBJECT (clocksync,
@@ -431,6 +510,8 @@ gst_clock_sync_chain_list (GstPad * pad, GstObject * parent,
     else if (GST_CLOCK_TIME_IS_VALID (runpts))
       runtimestamp = runpts;
 
+    gst_clock_sync_update_ts_offset (clocksync, runtimestamp);
+
     ret = gst_clocksync_do_sync (clocksync, runtimestamp);
     if (ret != GST_FLOW_OK) {
       gst_buffer_list_unref (buffer_list);
@@ -512,6 +593,7 @@ gst_clocksync_change_state (GstElement * element, GstStateChange transition)
       GST_OBJECT_UNLOCK (clocksync);
       if (clocksync->sync)
         no_preroll = TRUE;
+      clocksync->is_first = TRUE;
       break;
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
       GST_OBJECT_LOCK (clocksync);
index 82d929e3e3227cfe8df5fde759d5b72bc9fa4595..647d73e158936937f9669aa5c61f44bb64cca7f2 100644 (file)
@@ -59,6 +59,8 @@ struct _GstClockSync
   GCond          blocked_cond;
   gboolean       blocked;
   GstClockTimeDiff  ts_offset;
+  gboolean sync_to_first;
+  gboolean is_first;
 
   GstClockTime   upstream_latency;
 };
index 3069e72e6332b36f7bd76d9457b90e4640a91b6e..df04b6b9f73d94e9e2dd505a0b57f0ca9a51f1cf 100644 (file)
@@ -168,6 +168,76 @@ GST_START_TEST (test_stopping_element_unschedules_sync)
 
 GST_END_TEST;
 
+typedef struct
+{
+  guint notify_count;
+  GstClockTimeDiff ts_offset;
+} ClockSyncTestData;
+
+static void
+clock_sync_ts_offset_changed_cb (GstElement * clocksync, GParamSpec * pspec,
+    ClockSyncTestData * data)
+{
+  data->notify_count++;
+  g_object_get (clocksync, "ts-offset", &data->ts_offset, NULL);
+}
+
+GST_START_TEST (test_sync_to_first)
+{
+  /* the reason to use the queue in front of the clocksync element
+     is to effectively make gst_harness_push asynchronous, not locking
+     up the test, waiting for gst_clock_id_wait */
+  GstHarness *h =
+      gst_harness_new_parse ("queue ! clocksync sync-to-first=true");
+  GstBuffer *buf;
+  GstClock *clock;
+  GstClockTime timestamp = 123456789;
+  GstElement *clocksync;
+  ClockSyncTestData data;
+  data.notify_count = 0;
+  data.ts_offset = 0;
+
+  clocksync = gst_harness_find_element (h, "clocksync");
+  g_signal_connect (clocksync, "notify::ts-offset",
+      G_CALLBACK (clock_sync_ts_offset_changed_cb), &data);
+  gst_object_unref (clocksync);
+
+  /* use testclock */
+  gst_harness_use_testclock (h);
+  gst_harness_set_src_caps_str (h, "mycaps");
+
+  /* make a buffer and set the timestamp */
+  buf = gst_buffer_new ();
+  GST_BUFFER_PTS (buf) = timestamp;
+
+  /* push the buffer, and verify it does *not* make it through */
+  gst_harness_push (h, buf);
+  fail_unless_equals_int (0, gst_harness_buffers_in_queue (h));
+
+  /* verify the clocksync element has registered exactly one GstClockID */
+  fail_unless (gst_harness_wait_for_clock_id_waits (h, 1, 42));
+
+  /* crank the clock and pull the buffer */
+  gst_harness_crank_single_clock_wait (h);
+  buf = gst_harness_pull (h);
+
+  /* verify that the buffer has the right timestamp, and that the time on
+     the clock is equal to the timestamp */
+  fail_unless_equals_int64 (timestamp, GST_BUFFER_PTS (buf));
+  clock = gst_element_get_clock (h->element);
+  /* this buffer must be pushed without clock waiting */
+  fail_unless_equals_int64 (gst_clock_get_time (clock), 0);
+  fail_unless_equals_int (data.notify_count, 1);
+  fail_unless_equals_int64 (data.ts_offset, -timestamp);
+
+  /* cleanup */
+  gst_object_unref (clock);
+  gst_buffer_unref (buf);
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
 static Suite *
 clocksync_suite (void)
 {
@@ -179,6 +249,7 @@ clocksync_suite (void)
   tcase_add_test (tc_chain, test_sync_on_timestamp);
   tcase_add_test (tc_chain, test_stopping_element_unschedules_sync);
   tcase_add_test (tc_chain, test_no_sync_on_timestamp);
+  tcase_add_test (tc_chain, test_sync_to_first);
 
 
   return s;