videorate: Add fixed rate property
authorJoris Valette <joris.valette@gmail.com>
Tue, 17 Sep 2013 15:42:05 +0000 (17:42 +0200)
committerThibault Saunier <thibault.saunier@osg.samsung.com>
Fri, 4 Nov 2016 17:01:54 +0000 (14:01 -0300)
https://bugzilla.gnome.org/show_bug.cgi?id=699077

gst/videorate/gstvideorate.c
gst/videorate/gstvideorate.h
tests/check/elements/videorate.c

index eb3cc8c..86b4d89 100644 (file)
  * Note that property notification will happen from the streaming thread, so
  * applications should be prepared for this.
  *
+ * The property #GstVideoRate:rate allows the modification of video speed by a
+ * certain factor. It must not be confused with framerate. Think of rate as
+ * speed and framerate as flow.
+ *
  * <refsect2>
  * <title>Example pipelines</title>
  * |[
@@ -91,6 +95,7 @@ enum
 #define DEFAULT_DROP_ONLY       FALSE
 #define DEFAULT_AVERAGE_PERIOD  0
 #define DEFAULT_MAX_RATE        G_MAXINT
+#define DEFAULT_RATE            1.0
 
 enum
 {
@@ -104,7 +109,8 @@ enum
   PROP_SKIP_TO_FIRST,
   PROP_DROP_ONLY,
   PROP_AVERAGE_PERIOD,
-  PROP_MAX_RATE
+  PROP_MAX_RATE,
+  PROP_RATE
 };
 
 static GstStaticPadTemplate gst_video_rate_src_template =
@@ -127,6 +133,8 @@ static void gst_video_rate_swap_prev (GstVideoRate * videorate,
     GstBuffer * buffer, gint64 time);
 static gboolean gst_video_rate_sink_event (GstBaseTransform * trans,
     GstEvent * event);
+static gboolean gst_video_rate_src_event (GstBaseTransform * trans,
+    GstEvent * event);
 static gboolean gst_video_rate_query (GstBaseTransform * trans,
     GstPadDirection direction, GstQuery * query);
 
@@ -175,6 +183,7 @@ gst_video_rate_class_init (GstVideoRateClass * klass)
       GST_DEBUG_FUNCPTR (gst_video_rate_transform_caps);
   base_class->transform_ip = GST_DEBUG_FUNCPTR (gst_video_rate_transform_ip);
   base_class->sink_event = GST_DEBUG_FUNCPTR (gst_video_rate_sink_event);
+  base_class->src_event = GST_DEBUG_FUNCPTR (gst_video_rate_src_event);
   base_class->start = GST_DEBUG_FUNCPTR (gst_video_rate_start);
   base_class->stop = GST_DEBUG_FUNCPTR (gst_video_rate_stop);
   base_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_video_rate_fixate_caps);
@@ -207,7 +216,7 @@ gst_video_rate_class_init (GstVideoRateClass * klass)
 
   /**
    * GstVideoRate:skip-to-first:
-   * 
+   *
    * Don't produce buffers before the first one we receive.
    */
   g_object_class_install_property (object_class, PROP_SKIP_TO_FIRST,
@@ -250,6 +259,19 @@ gst_video_rate_class_init (GstVideoRateClass * klass)
           1, G_MAXINT, DEFAULT_MAX_RATE,
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * GstVideoRate:rate:
+   *
+   * Factor of speed for frame displaying
+   *
+   * Since: 1.12
+   */
+  g_object_class_install_property (object_class, PROP_RATE,
+      g_param_spec_double ("rate", "Rate",
+          "Factor of speed for frame displaying", 0.0, G_MAXDOUBLE,
+          DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_MUTABLE_READY));
+
   gst_element_class_set_static_metadata (element_class,
       "Video rate adjuster", "Filter/Effect/Video",
       "Drops/duplicates/adjusts timestamps on video frames to make a perfect stream",
@@ -588,6 +610,7 @@ gst_video_rate_init (GstVideoRate * videorate)
   videorate->average_period = DEFAULT_AVERAGE_PERIOD;
   videorate->average_period_set = DEFAULT_AVERAGE_PERIOD;
   videorate->max_rate = DEFAULT_MAX_RATE;
+  videorate->rate = DEFAULT_RATE;
 
   videorate->from_rate_numerator = 0;
   videorate->from_rate_denominator = 0;
@@ -727,11 +750,11 @@ gst_video_rate_sink_event (GstBaseTransform * trans, GstEvent * event)
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_SEGMENT:
     {
-      const GstSegment *segment;
+      GstSegment segment;
+      gint seqnum;
 
-      gst_event_parse_segment (event, &segment);
-
-      if (segment->format != GST_FORMAT_TIME)
+      gst_event_copy_segment (event, &segment);
+      if (segment.format != GST_FORMAT_TIME)
         goto format_error;
 
       GST_DEBUG_OBJECT (videorate, "handle NEWSEGMENT");
@@ -776,10 +799,23 @@ gst_video_rate_sink_event (GstBaseTransform * trans, GstEvent * event)
       videorate->next_ts = GST_CLOCK_TIME_NONE;
 
       /* We just want to update the accumulated stream_time  */
-      gst_segment_copy_into (segment, &videorate->segment);
 
+      segment.start = (gint64) (segment.start / videorate->rate);
+      segment.position = (gint64) (segment.position / videorate->rate);
+      if (GST_CLOCK_TIME_IS_VALID (segment.stop))
+        segment.stop = (gint64) (segment.stop / videorate->rate);
+      segment.time = (gint64) (segment.time / videorate->rate);
+
+      gst_segment_copy_into (&segment, &videorate->segment);
       GST_DEBUG_OBJECT (videorate, "updated segment: %" GST_SEGMENT_FORMAT,
           &videorate->segment);
+
+
+      seqnum = gst_event_get_seqnum (event);
+      gst_event_unref (event);
+      event = gst_event_new_segment (&segment);
+      gst_event_set_seqnum (event, seqnum);
+
       break;
     }
     case GST_EVENT_EOS:{
@@ -872,6 +908,47 @@ format_error:
 }
 
 static gboolean
+gst_video_rate_src_event (GstBaseTransform * trans, GstEvent * event)
+{
+  GstVideoRate *videorate;
+  GstPad *sinkpad;
+  gboolean res = FALSE;
+
+  videorate = GST_VIDEO_RATE (trans);
+  sinkpad = GST_BASE_TRANSFORM_SINK_PAD (trans);
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:
+    {
+      gdouble srate;
+      GstSeekFlags flags;
+      GstSeekType start_type, stop_type;
+      gint64 start, stop;
+      gint seqnum = gst_event_get_seqnum (event);
+
+      gst_event_parse_seek (event, &srate, NULL, &flags, &start_type, &start,
+          &stop_type, &stop);
+
+      start = (gint64) (start * videorate->rate);
+      if (GST_CLOCK_TIME_IS_VALID (stop)) {
+        stop = (gint64) (stop * videorate->rate);
+      }
+
+      gst_event_unref (event);
+      event = gst_event_new_seek (srate, GST_FORMAT_TIME,
+          flags, start_type, start, stop_type, stop);
+      gst_event_set_seqnum (event, seqnum);
+
+      res = gst_pad_push_event (sinkpad, event);
+      break;
+    }
+    default:
+      res = gst_pad_push_event (sinkpad, event);
+      break;
+  }
+  return res;
+}
+
+static gboolean
 gst_video_rate_query (GstBaseTransform * trans, GstPadDirection direction,
     GstQuery * query)
 {
@@ -937,6 +1014,49 @@ gst_video_rate_query (GstBaseTransform * trans, GstPadDirection direction,
       /* Simple fallthrough if we don't have a latency or not a peer that we
        * can't ask about its latency yet.. */
     }
+    case GST_QUERY_DURATION:
+    {
+      GstFormat format;
+      gint64 duration;
+
+
+
+      gst_query_parse_duration (query, &format, &duration);
+
+      if (format != GST_FORMAT_TIME) {
+        GST_DEBUG_OBJECT (videorate, "not TIME format");
+        break;
+      }
+      GST_LOG_OBJECT (videorate, "upstream duration: %" G_GINT64_FORMAT,
+          duration);
+      if (GST_CLOCK_TIME_IS_VALID (duration)) {
+        duration = (gint64) (duration / videorate->rate);
+      }
+      GST_LOG_OBJECT (videorate, "our duration: %" G_GINT64_FORMAT, duration);
+      gst_query_set_duration (query, format, duration);
+      res = TRUE;
+      break;
+    }
+    case GST_QUERY_POSITION:
+    {
+      GstFormat dst_format;
+      gint64 dst_value;
+
+      gst_query_parse_position (query, &dst_format, &dst_value);
+
+      if (dst_format != GST_FORMAT_TIME) {
+        GST_DEBUG_OBJECT (videorate, "not TIME format");
+        break;
+      }
+      dst_value =
+          (gint64) (gst_segment_to_stream_time (&videorate->segment,
+              GST_FORMAT_TIME, videorate->last_ts / videorate->rate));
+      GST_LOG_OBJECT (videorate, "our position: %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (dst_value));
+      gst_query_set_position (query, dst_format, dst_value);
+      res = TRUE;
+      break;
+    }
     default:
       res =
           GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
@@ -1272,23 +1392,24 @@ gst_video_rate_transform_ip (GstBaseTransform * trans, GstBuffer * buffer)
 
     /* got 2 buffers, see which one is the best */
     do {
+      GstClockTime next_ts = videorate->next_ts * videorate->rate;
 
       /* take absolute values, beware: abs and ABS don't work for gint64 */
-      if (prevtime > videorate->next_ts)
-        diff1 = prevtime - videorate->next_ts;
+      if (prevtime > next_ts)
+        diff1 = prevtime - next_ts;
       else
-        diff1 = videorate->next_ts - prevtime;
+        diff1 = next_ts - prevtime;
 
-      if (intime > videorate->next_ts)
-        diff2 = intime - videorate->next_ts;
+      if (intime > next_ts)
+        diff2 = intime - next_ts;
       else
-        diff2 = videorate->next_ts - intime;
+        diff2 = next_ts - intime;
 
       GST_LOG_OBJECT (videorate,
           "diff with prev %" GST_TIME_FORMAT " diff with new %"
           GST_TIME_FORMAT " outgoing ts %" GST_TIME_FORMAT,
           GST_TIME_ARGS (diff1), GST_TIME_ARGS (diff2),
-          GST_TIME_ARGS (videorate->next_ts));
+          GST_TIME_ARGS (next_ts));
 
       if (videorate->segment.rate < 0.0) {
         /* Make sure that we have a duration for this buffer. The previous
@@ -1385,6 +1506,15 @@ gst_video_rate_stop (GstBaseTransform * trans)
 }
 
 static void
+gst_videorate_update_duration (GstVideoRate * videorate)
+{
+  GstMessage *m;
+
+  m = gst_message_new_duration_changed (GST_OBJECT (videorate));
+  gst_element_post_message (GST_ELEMENT (videorate), m);
+}
+
+static void
 gst_video_rate_set_property (GObject * object,
     guint prop_id, const GValue * value, GParamSpec * pspec)
 {
@@ -1416,11 +1546,18 @@ gst_video_rate_set_property (GObject * object,
     case PROP_MAX_RATE:
       g_atomic_int_set (&videorate->max_rate, g_value_get_int (value));
       goto reconfigure;
+    case PROP_RATE:
+      videorate->rate = g_value_get_double (value);
+      GST_OBJECT_UNLOCK (videorate);
+
+      gst_videorate_update_duration (videorate);
+      return;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
   }
   GST_OBJECT_UNLOCK (videorate);
+
   return;
 
 reconfigure:
@@ -1471,6 +1608,9 @@ gst_video_rate_get_property (GObject * object,
     case PROP_MAX_RATE:
       g_value_set_int (value, g_atomic_int_get (&videorate->max_rate));
       break;
+    case PROP_RATE:
+      g_value_set_double (value, videorate->rate);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
index d5e7c19..9a64413 100644 (file)
@@ -24,7 +24,6 @@
 #include <gst/base/gstbasetransform.h>
 
 G_BEGIN_DECLS
-
 #define GST_TYPE_VIDEO_RATE \
   (gst_video_rate_get_type())
 #define GST_VIDEO_RATE(obj) \
@@ -35,7 +34,6 @@ G_BEGIN_DECLS
   (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_RATE))
 #define GST_IS_VIDEO_RATE_CLASS(klass) \
   (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_RATE))
-
 typedef struct _GstVideoRate GstVideoRate;
 typedef struct _GstVideoRateClass GstVideoRateClass;
 
@@ -80,6 +78,7 @@ struct _GstVideoRate
   guint64 average_period_set;
 
   volatile int max_rate;
+  gdouble rate;
 };
 
 struct _GstVideoRateClass
@@ -90,5 +89,4 @@ struct _GstVideoRateClass
 GType gst_video_rate_get_type (void);
 
 G_END_DECLS
-
 #endif /* __GST_VIDEO_RATE_H__ */
index f0ce428..a788196 100644 (file)
@@ -536,9 +536,9 @@ GST_START_TEST (test_no_framerate)
 
 GST_END_TEST;
 
-/* This test outputs 2 buffers of same dimensions (320x240), then 1 buffer of 
- * differing dimensions (240x120), and then another buffer of previous 
- * dimensions (320x240) and checks that the 3 buffers output as a result have 
+/* This test outputs 2 buffers of same dimensions (320x240), then 1 buffer of
+ * differing dimensions (240x120), and then another buffer of previous
+ * dimensions (320x240) and checks that the 3 buffers output as a result have
  * correct caps (first 2 with 320x240 and 3rd with 240x120).
  */
 GST_START_TEST (test_changing_size)
@@ -999,8 +999,7 @@ check_caps_identical (GstCaps * a, GstCaps * b, const char *name)
 fail:
   caps_str_a = gst_caps_to_string (a);
   caps_str_b = gst_caps_to_string (b);
-  fail ("%s caps (%s) is not equal to caps (%s)",
-      name, caps_str_a, caps_str_b);
+  fail ("%s caps (%s) is not equal to caps (%s)", name, caps_str_a, caps_str_b);
   g_free (caps_str_a);
   g_free (caps_str_b);
 }
@@ -1168,6 +1167,231 @@ GST_START_TEST (test_variable_framerate_renegotiation)
 
 GST_END_TEST;
 
+/* Rate tests info */
+typedef struct
+{
+  gdouble rate;
+  guint64 expected_in, expected_out, expected_drop, expected_dup;
+  gint current_buf;
+  GstClockTime expected_ts;
+} RateInfo;
+
+static RateInfo rate_tests[] = {
+  {
+        .rate = 1.0,
+        .expected_in = 34,
+        .expected_out = 25,
+        .expected_drop = 8,
+        .expected_dup = 0,
+        .current_buf = 0,
+      .expected_ts = 0},
+  {
+        .rate = 0.5,
+        .expected_in = 34,
+        .expected_out = 50,
+        .expected_drop = 0,
+        .expected_dup = 17,
+        .current_buf = 0,
+      .expected_ts = 0},
+  {
+        .rate = 2.0,
+        .expected_in = 34,
+        .expected_out = 13,
+        .expected_drop = 20,
+        .expected_dup = 0,
+        .current_buf = 0,
+      .expected_ts = 0},
+};
+
+static GstPadProbeReturn
+listen_outbuffer_ts (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+  GstBuffer *buffer;
+  guint64 buf_ts;
+  RateInfo *test = (RateInfo *) user_data;
+
+  buffer = GST_PAD_PROBE_INFO_BUFFER (info);
+  buf_ts = GST_BUFFER_TIMESTAMP (buffer);
+
+  GST_DEBUG ("Probed %d outbuf. ts : %" GST_TIME_FORMAT
+      ", expected : %" GST_TIME_FORMAT, test->current_buf,
+      GST_TIME_ARGS (buf_ts), GST_TIME_ARGS (test->expected_ts));
+  fail_unless_equals_uint64 (buf_ts, test->expected_ts);
+
+  /* Next expected timestamp with fps 25/1 */
+  test->expected_ts += 40000000;
+  test->current_buf += 1;
+
+  fail_if (test->current_buf > test->expected_out);
+
+  return GST_PAD_PROBE_OK;
+}
+
+GST_START_TEST (test_rate)
+{
+  GstElement *videorate;
+  RateInfo *test = &rate_tests[__i__];
+  GstClockTime ts;
+  GstBuffer *buf;
+  GstCaps *caps;
+  gulong probe;
+
+  videorate = setup_videorate ();
+  fail_unless (gst_element_set_state (videorate,
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
+      "could not set to playing");
+  probe = gst_pad_add_probe (mysinkpad, GST_PAD_PROBE_TYPE_BUFFER,
+      (GstPadProbeCallback) listen_outbuffer_ts, test, NULL);
+
+  buf = gst_buffer_new_and_alloc (4);
+  gst_buffer_memset (buf, 0, 0, 4);
+  caps = gst_caps_from_string (VIDEO_CAPS_STRING);
+  gst_check_setup_events (mysrcpad, videorate, caps, GST_FORMAT_TIME);
+  gst_caps_unref (caps);
+  ASSERT_BUFFER_REFCOUNT (buf, "inbuffer", 1);
+
+  /* Setting rate */
+  g_object_set (videorate, "rate", test->rate, NULL);
+
+  /* Push 1 second of buffers */
+  for (ts = 0; ts < 1 * GST_SECOND; ts += GST_SECOND / 33) {
+    GstBuffer *inbuf;
+
+    inbuf = gst_buffer_copy (buf);
+    GST_BUFFER_TIMESTAMP (inbuf) = ts;
+
+    fail_unless_equals_int (gst_pad_push (mysrcpad, inbuf), GST_FLOW_OK);
+  }
+
+  fail_unless_equals_int (g_list_length (buffers), test->expected_out);
+  assert_videorate_stats (videorate, "last buffer", test->expected_in,
+      test->expected_out, test->expected_drop, test->expected_dup);
+
+  /* cleanup */
+  gst_pad_remove_probe (mysinkpad, probe);
+  cleanup_videorate (videorate);
+}
+
+GST_END_TEST;
+
+/* Probing the pad to force a fake upstream duration */
+static GstPadProbeReturn
+listen_sink_query_duration (GstPad * pad, GstPadProbeInfo * info,
+    gpointer user_data)
+{
+  GstQuery *query;
+  gint64 *duration = (gint64 *) user_data;
+
+  query = gst_pad_probe_info_get_query (info);
+
+  if (GST_QUERY_TYPE (query) == GST_QUERY_DURATION) {
+    gst_query_set_duration (query, GST_FORMAT_TIME, *duration);
+  }
+  return GST_PAD_PROBE_OK;
+}
+
+GST_START_TEST (test_query_duration)
+{
+  GstElement *videorate;
+  gulong probe_sink;
+  gint64 duration;
+  GstQuery *query;
+
+  videorate = setup_videorate ();
+  fail_unless (gst_element_set_state (videorate,
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
+      "could not set to playing");
+  probe_sink =
+      gst_pad_add_probe (mysrcpad,
+      GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM | GST_PAD_PROBE_TYPE_PUSH,
+      (GstPadProbeCallback) listen_sink_query_duration, &duration, NULL);
+
+  query = gst_query_new_duration (GST_FORMAT_TIME);
+  duration = GST_CLOCK_TIME_NONE;
+  gst_pad_peer_query (mysrcpad, query);
+  gst_query_parse_duration (query, NULL, &duration);
+  fail_unless_equals_uint64 (duration, GST_CLOCK_TIME_NONE);
+
+  /* Setting fake upstream duration to 1 second */
+  duration = GST_SECOND;
+
+  /* Setting rate to 2.0 */
+  g_object_set (videorate, "rate", 2.0, NULL);
+
+  gst_pad_peer_query (mysrcpad, query);
+  gst_query_parse_duration (query, NULL, &duration);
+  fail_unless_equals_uint64 (duration, 0.5 * GST_SECOND);
+
+  /* cleanup */
+  gst_query_unref (query);
+  gst_pad_remove_probe (mysrcpad, probe_sink);
+  cleanup_videorate (videorate);
+}
+
+GST_END_TEST;
+
+/* Position tests info */
+typedef struct
+{
+  gdouble rate;
+} PositionInfo;
+
+static PositionInfo position_tests[] = {
+  {
+      .rate = 1.0},
+  {
+      .rate = 0.5},
+  {
+      .rate = 2.0},
+  {
+      .rate = 1.7},
+};
+
+GST_START_TEST (test_query_position)
+{
+  GstElement *videorate;
+  PositionInfo *test = &position_tests[__i__];
+  GstClockTime ts;
+  GstBuffer *buf;
+  GstCaps *caps;
+  gint64 position, expected_position = 0;
+
+  videorate = setup_videorate ();
+  fail_unless (gst_element_set_state (videorate,
+          GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
+      "could not set to playing");
+
+  buf = gst_buffer_new_and_alloc (4);
+  gst_buffer_memset (buf, 0, 0, 4);
+  caps = gst_caps_from_string (VIDEO_CAPS_STRING);
+  gst_check_setup_events (mysrcpad, videorate, caps, GST_FORMAT_TIME);
+  gst_caps_unref (caps);
+  ASSERT_BUFFER_REFCOUNT (buf, "inbuffer", 1);
+
+  /* Push a few buffers */
+  g_object_set (videorate, "rate", test->rate, NULL);
+  for (ts = 0; ts < GST_SECOND; ts += GST_SECOND / 20) {
+    GstBuffer *inbuf;
+
+    inbuf = gst_buffer_copy (buf);
+    GST_BUFFER_TIMESTAMP (inbuf) = ts;
+
+    fail_unless_equals_int (gst_pad_push (mysrcpad, inbuf), GST_FLOW_OK);
+
+    expected_position = ts / test->rate;
+    gst_element_query_position (videorate, GST_FORMAT_TIME, &position);
+    GST_DEBUG_OBJECT (NULL,
+        "pushed buffer %" GST_TIME_FORMAT ", queried pos: %" GST_TIME_FORMAT
+        ", expected pos: %" GST_TIME_FORMAT, GST_TIME_ARGS (ts),
+        GST_TIME_ARGS (position), GST_TIME_ARGS (expected_position));
+    fail_unless_equals_uint64 (position, expected_position);
+  }
+
+  /* cleanup */
+  cleanup_videorate (videorate);
+}
+
+GST_END_TEST;
 
 static Suite *
 videorate_suite (void)
@@ -1189,6 +1413,10 @@ videorate_suite (void)
       0, G_N_ELEMENTS (caps_negotiation_tests));
   tcase_add_test (tc_chain, test_fixed_framerate);
   tcase_add_test (tc_chain, test_variable_framerate_renegotiation);
+  tcase_add_loop_test (tc_chain, test_rate, 0, G_N_ELEMENTS (rate_tests));
+  tcase_add_test (tc_chain, test_query_duration);
+  tcase_add_loop_test (tc_chain, test_query_position, 0,
+      G_N_ELEMENTS (position_tests));
 
   return s;
 }