tests/examples/seek/instant-rate-change: Add example app
authorJan Schmidt <jan@centricular.com>
Fri, 17 Aug 2018 17:37:09 +0000 (03:37 +1000)
committerSebastian Dröge <slomo@coaxion.net>
Wed, 1 Apr 2020 21:01:38 +0000 (21:01 +0000)
Add an example app to exercise instant rate changes in a few
scenarios. Currently it deadlocks a lot sending rate changes to
paused pipelines.

tests/examples/seek/instant-rate-change.c [new file with mode: 0644]
tests/examples/seek/meson.build

diff --git a/tests/examples/seek/instant-rate-change.c b/tests/examples/seek/instant-rate-change.c
new file mode 100644 (file)
index 0000000..6e4dec7
--- /dev/null
@@ -0,0 +1,316 @@
+/* Test example for instant rate changes.
+ * The example takes an input URI and runs a set of actions,
+ * seeking and pausing etc.
+ *
+ * Copyright (C) 2015-2018 Centricular Ltd
+ *  @author:  Edward Hervey <edward@centricular.com>
+ *  @author:  Jan Schmidt <jan@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+
+/* There are several supported scenarios 
+0) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (repeat as fast as possible for 2 sec) -> let play for 2s
+1) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> play
+2) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> Apply 'instant-rate-change' to 1x -> play
+3) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> seeking (flush+key-unit) to 30s -> wait 10s -> play
+4) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (toggle every 500ms)
+*/
+
+#define PLAY_PAUSE_DELAY 10
+#define IDLE_CYCLE_DELAY 2
+
+#define TARGET_RATE_1 0.25
+#define TARGET_RATE_2 2.0
+
+/* Set DISABLE_AUDIO to run with video only */
+// #define DISABLE_AUDIO
+
+/* Set to force the use of the system clock on the pipeline */
+// #define FORCE_SYSTEM_CLOCK
+
+typedef struct _MyDataStruct
+{
+  GMainLoop *mainloop;
+  GstElement *pipeline;
+  GstBus *bus;
+
+  gdouble rate;
+  gboolean paused;
+
+  guint scenario;
+  GstClockTime start;
+  GstClock *clock;
+
+  guint timeout_id;
+  guint idle_id;
+} MyDataStruct;
+
+static gboolean toggle_rate (MyDataStruct * data);
+
+static gboolean
+do_enable_disable_idle (MyDataStruct * data)
+{
+  if (data->idle_id) {
+    g_print ("Disabling idle handler\n");
+    g_source_remove (data->idle_id);
+    data->idle_id = 0;
+  } else {
+    g_print ("Enabling idle handler\n");
+    data->idle_id = g_idle_add ((GSourceFunc) toggle_rate, data);
+  }
+
+  return TRUE;
+}
+
+static gboolean
+do_play_pause (MyDataStruct * data)
+{
+  data->paused = !data->paused;
+
+  switch (data->scenario) {
+    case 1:
+      g_print ("%s\n", data->paused ? "Pausing" : "Unpausing");
+      gst_element_set_state (data->pipeline,
+          data->paused ? GST_STATE_PAUSED : GST_STATE_PLAYING);
+      data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
+          (GSourceFunc) do_play_pause, data);
+      break;
+    case 2:
+      if (!data->paused) {
+        gint64 pos = GST_CLOCK_TIME_NONE;
+        gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &pos);
+
+        /* Change rate between 2x and 1x before unpausing */
+        data->rate = (data->rate == 2.0) ? 1.0 : 2.0;
+        g_print ("Switching rate to %f (position %" GST_TIME_FORMAT ")\n",
+            data->rate, GST_TIME_ARGS (pos));
+        gst_element_send_event (data->pipeline, gst_event_new_seek (data->rate,
+                GST_FORMAT_TIME, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
+                GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE,
+                GST_CLOCK_TIME_NONE));
+      }
+
+      g_print ("%s\n", data->paused ? "Pausing" : "Unpausing");
+      gst_element_set_state (data->pipeline,
+          data->paused ? GST_STATE_PAUSED : GST_STATE_PLAYING);
+      data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
+          (GSourceFunc) do_play_pause, data);
+      break;
+    case 3:
+      g_print ("%s\n", data->paused ? "Pausing" : "Unpausing");
+      gst_element_set_state (data->pipeline,
+          data->paused ? GST_STATE_PAUSED : GST_STATE_PLAYING);
+      if (data->paused) {
+        /* On pause, seek to 30 seconds */
+        g_print ("Seeking to 30s\n");
+        gst_element_send_event (data->pipeline,
+            gst_event_new_seek (data->rate, GST_FORMAT_TIME,
+                GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
+                GST_SEEK_TYPE_SET, 30 * GST_SECOND,
+                GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE));
+      }
+      data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
+          (GSourceFunc) do_play_pause, data);
+      break;
+    default:
+      break;
+  }
+  return FALSE;
+}
+
+static gboolean
+toggle_rate (MyDataStruct * data)
+{
+  gint64 pos = GST_CLOCK_TIME_NONE;
+  gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &pos);
+
+  /* Toggle rate between the 2 target rates */
+  if (data->rate != TARGET_RATE_2)
+    data->rate = TARGET_RATE_2;
+  else
+    data->rate = TARGET_RATE_1;
+  g_print ("Switching rate to %f (position %" GST_TIME_FORMAT ")\n", data->rate,
+      GST_TIME_ARGS (pos));
+  gst_element_send_event (data->pipeline, gst_event_new_seek (data->rate,
+          GST_FORMAT_TIME, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
+          GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE,
+          GST_CLOCK_TIME_NONE));
+  return TRUE;
+}
+
+static void
+on_preroll (MyDataStruct * data)
+{
+  if (data->timeout_id != 0)
+    return;                     /* Already scheduled out scenario timer */
+
+  switch (data->scenario) {
+    case 0:
+      data->idle_id = g_idle_add ((GSourceFunc) toggle_rate, data);
+      data->timeout_id = g_timeout_add_seconds (IDLE_CYCLE_DELAY,
+          (GSourceFunc) do_enable_disable_idle, data);
+      break;
+    case 1:
+    case 2:
+    case 3:{
+      gint64 pos = GST_CLOCK_TIME_NONE;
+      gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &pos);
+
+      /* Change rate to 2x and play for 10 sec, pause for 10 sec */
+      data->rate = 2.0;
+      g_print ("Switching rate to %f (position %" GST_TIME_FORMAT ")\n",
+          data->rate, GST_TIME_ARGS (pos));
+      gst_element_send_event (data->pipeline, gst_event_new_seek (data->rate,
+              GST_FORMAT_TIME, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
+              GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE,
+              GST_CLOCK_TIME_NONE));
+      /* Instant rate change completed, schedule play/pause */
+      data->timeout_id = g_timeout_add_seconds (PLAY_PAUSE_DELAY,
+          (GSourceFunc) do_play_pause, data);
+      break;
+    }
+    case 4:
+      g_timeout_add (250, (GSourceFunc) toggle_rate, data);
+      break;
+    default:
+      break;
+  }
+}
+
+static gboolean
+_on_bus_message (GstBus * bus, GstMessage * message, gpointer userdata)
+{
+  MyDataStruct *data = (MyDataStruct *) (userdata);
+
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:{
+      GError *err = NULL;
+      gchar *name = gst_object_get_path_string (GST_MESSAGE_SRC (message));
+      gst_message_parse_error (message, &err, NULL);
+
+      g_printerr ("ERROR: from element %s: %s\n", name, err->message);
+      g_error_free (err);
+      g_free (name);
+
+      g_printf ("Stopping\n");
+      g_main_loop_quit (data->mainloop);
+      break;
+    }
+    case GST_MESSAGE_EOS:
+      g_printf ("EOS ! Stopping \n");
+      g_main_loop_quit (data->mainloop);
+      break;
+    case GST_MESSAGE_ASYNC_DONE:
+      on_preroll (data);
+      break;
+    default:
+      break;
+  }
+
+  return TRUE;
+}
+
+static gchar *
+cmdline_to_uri (const gchar * arg)
+{
+  if (gst_uri_is_valid (arg))
+    return g_strdup (arg);
+
+  return gst_filename_to_uri (arg, NULL);
+}
+
+static void
+print_usage (const char *arg0)
+{
+  g_print
+      ("Usage: %s <0-4> URI\nSelect test scenario 0 to 4, and supply a URI to test\n",
+      arg0);
+  g_print ("Scenarios:\n"
+      " 0) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (repeat as fast as possible for 2 sec) -> let play for 2s\n"
+      " 1) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> play\n"
+      " 2) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> wait 10s -> Apply 'instant-rate-change' to 1x -> play\n"
+      " 3) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> run for 10s, then pause -> seeking (flush+key-unit) to 30s -> wait 10s -> play\n"
+      " 4) Play rate to 1x -> Apply 'instant-rate-change' to 2x -> Apply 'instant-rate-change' to 0.25x (toggle every 500ms)\n");
+}
+
+int
+main (int argc, gchar ** argv)
+{
+  GstBus *bus;
+  MyDataStruct *data;
+  gchar *uri;
+#ifdef FORCE_SYSTEM_CLOCK
+  GstClock *clock;
+#endif
+
+  gst_init (&argc, &argv);
+
+  data = g_new0 (MyDataStruct, 1);
+
+  if (argc < 3) {
+    print_usage (argv[0]);
+    return 1;
+  }
+
+  data->scenario = atoi (argv[1]);
+  uri = cmdline_to_uri (argv[2]);
+  if (data->scenario > 4 || uri == NULL) {
+    print_usage (argv[0]);
+    return 1;
+  }
+
+  data->pipeline = gst_element_factory_make ("playbin", NULL);
+  if (data->pipeline == NULL) {
+    g_printerr ("Failed to create playbin element. Aborting");
+    return 1;
+  }
+#ifdef FORCE_SYSTEM_CLOCK
+  clock = gst_system_clock_obtain ();
+  gst_pipeline_use_clock (GST_PIPELINE (data->pipeline), clock);
+#endif
+
+#ifdef DISABLE_AUDIO
+  g_object_set (data->pipeline, "flags", 0x00000615, NULL);
+#endif
+  g_object_set (data->pipeline, "uri", uri, NULL);
+  g_free (uri);
+
+  /* Put a bus handler */
+  bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
+  gst_bus_add_watch (bus, _on_bus_message, data);
+
+  /* Start pipeline */
+  data->mainloop = g_main_loop_new (NULL, TRUE);
+  gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
+  g_main_loop_run (data->mainloop);
+
+  gst_element_set_state (data->pipeline, GST_STATE_NULL);
+
+  gst_object_unref (data->pipeline);
+  gst_object_unref (bus);
+
+  return 0;
+}
index fc6ce23..eb721b0 100644 (file)
@@ -30,3 +30,10 @@ executable('stepping2', 'stepping2.c',
   include_directories: [configinc, libsinc],
   dependencies : [libm, glib_deps, gst_dep, video_dep],
   install: false)
+
+executable('instant-rate-change', 'instant-rate-change.c',
+  c_args : gst_plugins_base_args,
+  include_directories: [configinc, libsinc],
+  dependencies : [libm, glib_deps, gst_dep, video_dep],
+  install: false)
+