tests: examples: add va-x11-render example
authorVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Mon, 3 Aug 2020 11:50:23 +0000 (13:50 +0200)
committerVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Mon, 3 Aug 2020 17:33:01 +0000 (19:33 +0200)
This a GTK+ example will share, through GstContext, a custom X11
VADisplay to a pipeline using vah264dec and appsink.

When the frames are processed for rendering, the VASurfaceID is
fetched from the buffer and it is rendered using vaPutSurface in a X11
widget.

tests/examples/meson.build
tests/examples/va/main.c [new file with mode: 0644]
tests/examples/va/meson.build [new file with mode: 0644]

index bc7fdb7..2eba1d4 100644 (file)
@@ -11,6 +11,7 @@ subdir('mxf')
 subdir('nvcodec')
 subdir('opencv', if_found: opencv_dep)
 subdir('uvch264')
+subdir('va')
 subdir('waylandsink')
 subdir('webrtc')
 
diff --git a/tests/examples/va/main.c b/tests/examples/va/main.c
new file mode 100644 (file)
index 0000000..2779139
--- /dev/null
@@ -0,0 +1,321 @@
+#include <stdlib.h>
+
+#include <X11/Xlib.h>
+
+#include <gdk/gdk.h>
+#if defined (GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#else
+#error "Only X11 is supported so far"
+#endif
+
+#include <gtk/gtk.h>
+
+#include <gst/gst.h>
+#include <gst/app/gstappsink.h>
+#include <gst/video/video.h>
+
+#include <va/va_x11.h>
+
+#define GST_MAP_VA (GST_MAP_FLAG_LAST << 1)
+
+static gchar **INPUT_FILES = NULL;
+
+struct _app
+{
+  GtkWidget *window;
+  GtkWidget *video;
+  GstElement *pipeline;
+  GstSample *sample;
+  GMutex mutex;
+  VADisplay va_dpy;
+};
+
+static GstBusSyncReply
+context_handler (GstBus * bus, GstMessage * msg, gpointer data)
+{
+  struct _app *app = data;
+  const gchar *context_type;
+
+  if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_NEED_CONTEXT)
+    return GST_BUS_DROP;
+
+  gst_message_parse_context_type (msg, &context_type);
+  gst_println ("got need context %s", context_type);
+
+  if (g_strcmp0 (context_type, "gst.va.display.handle") == 0) {
+    GstContext *context;
+    GstStructure *s;
+
+    context = gst_context_new ("gst.va.display.handle", TRUE);
+    s = gst_context_writable_structure (context);
+    gst_structure_set (s, "va-display", G_TYPE_POINTER, app->va_dpy, NULL);
+    gst_element_set_context (GST_ELEMENT (msg->src), context);
+  }
+
+  return GST_BUS_DROP;
+}
+
+static void
+delete_event_cb (GtkWidget * widget, GdkEvent * event, gpointer data)
+{
+  struct _app *app = data;
+
+  gst_element_set_state (app->pipeline, GST_STATE_NULL);
+
+  gtk_main_quit ();
+}
+
+static gboolean
+draw_unlocked (GtkWidget * widget, struct _app *app)
+{
+  GstBuffer *buffer;
+  GstCaps *caps;
+  GstMapInfo map_info;
+  GstVideoInfo vinfo;
+  VASurfaceID surface;
+  VAStatus va_status;
+  GstVideoRectangle src, dst, res;
+  gboolean ret = FALSE;
+
+  buffer = gst_sample_get_buffer (app->sample);
+  caps = gst_sample_get_caps (app->sample);
+
+  if (!gst_video_info_from_caps (&vinfo, caps))
+    return FALSE;
+
+  src.x = 0;
+  src.y = 0;
+  src.w = GST_VIDEO_INFO_WIDTH (&vinfo);
+  src.h = GST_VIDEO_INFO_HEIGHT (&vinfo);
+
+  if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ | GST_MAP_VA))
+    return FALSE;
+
+  surface = (*(VASurfaceID *) map_info.data);
+  if (surface == VA_INVALID_ID)
+    goto bail;
+
+  dst.x = 0;
+  dst.y = 0;
+  dst.w = gtk_widget_get_allocated_width (widget);
+  dst.h = gtk_widget_get_allocated_height (widget);
+
+  gst_video_sink_center_rect (src, dst, &res, TRUE);
+
+  va_status = vaPutSurface (app->va_dpy, surface,
+      GDK_WINDOW_XID (gtk_widget_get_window (widget)),
+      src.x, src.y, src.w, src.h, res.x, res.y, res.w, res.h, NULL, 0, 0);
+  if (va_status != VA_STATUS_SUCCESS)
+    gst_printerrln ("failed vaPutSurface: %s", vaErrorStr (va_status));
+  else
+    ret = TRUE;
+
+bail:
+  gst_buffer_unmap (buffer, &map_info);
+
+  return ret;
+}
+
+static gboolean
+redraw_cb (gpointer data)
+{
+  GtkWidget *video = data;
+
+  gtk_widget_queue_draw (video);
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+draw_cb (GtkWidget * widget, cairo_t * cr, gpointer data)
+{
+  struct _app *app = data;
+  gboolean ret = TRUE;
+
+  g_mutex_lock (&app->mutex);
+  if (app->sample)
+    ret = draw_unlocked (widget, app);
+  g_mutex_unlock (&app->mutex);
+
+  if (!ret)
+    gst_printerrln ("failed to paint frame");
+
+  return FALSE;
+}
+
+static void
+build_ui (struct _app *app)
+{
+  app->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title (GTK_WINDOW (app->window), "VA X11 render");
+  g_signal_connect (app->window, "delete-event", G_CALLBACK (delete_event_cb),
+      app);
+
+  app->video = gtk_drawing_area_new ();
+  g_signal_connect (app->video, "draw", G_CALLBACK (draw_cb), app);
+
+  gtk_container_add (GTK_CONTAINER (app->window), app->video);
+
+  gtk_widget_show_all (app->window);
+}
+
+static GstFlowReturn
+new_sample_cb (GstAppSink * sink, gpointer data)
+{
+  struct _app *app = data;
+
+  g_mutex_lock (&app->mutex);
+  if (app->sample)
+    gst_sample_unref (app->sample);
+  app->sample = gst_app_sink_pull_sample (sink);
+  g_mutex_unlock (&app->mutex);
+
+  g_idle_add (redraw_cb, app->video);
+
+  return GST_FLOW_OK;
+}
+
+static void
+end_stream_cb (GstBus * bus, GstMessage * msg, gpointer data)
+{
+  switch (GST_MESSAGE_TYPE (msg)) {
+    case GST_MESSAGE_ERROR:{
+      gchar *debug = NULL;
+      GError *err = NULL;
+
+      gst_message_parse_error (msg, &err, &debug);
+      gst_printerrln ("GStreamer error: %s\n%s", err->message,
+          debug ? debug : "");
+      if (debug)
+        g_free (debug);
+      if (err)
+        g_error_free (err);
+      break;
+    }
+    default:
+      break;
+  }
+
+  gtk_main_quit ();
+}
+
+static gboolean
+build_pipeline (struct _app *app)
+{
+  GstElement *src, *sink;
+  GstCaps *caps;
+  GstBus *bus;
+  GstAppSinkCallbacks callbacks = {
+    .new_sample = new_sample_cb,
+  };
+  GError *err = NULL;
+
+  app->pipeline = gst_parse_launch ("filesrc name=src ! "
+      "parsebin ! vah264dec ! appsink name=sink", &err);
+  if (err) {
+    gst_printerrln ("Couldn't create pipeline: %s", err->message);
+    g_error_free (err);
+    return FALSE;
+  }
+
+  src = gst_bin_get_by_name (GST_BIN (app->pipeline), "src");
+  g_object_set (src, "location", INPUT_FILES[0], NULL);
+  gst_object_unref (src);
+
+  sink = gst_bin_get_by_name (GST_BIN (app->pipeline), "sink");
+  caps = gst_caps_from_string ("video/x-raw (memory:VAMemory)");
+  g_object_set (sink, "caps", caps, NULL);
+  gst_caps_unref (caps);
+  gst_app_sink_set_callbacks (GST_APP_SINK (sink), &callbacks, app, NULL);
+  gst_object_unref (src);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
+  gst_bus_add_signal_watch (bus);
+  g_signal_connect (bus, "message::error", G_CALLBACK (end_stream_cb), app);
+  gst_bus_set_sync_handler (bus, context_handler, app, NULL);
+  gst_object_unref (bus);
+
+  return TRUE;
+}
+
+static gboolean
+parse_arguments (int *argc, char ***argv)
+{
+  GOptionContext *ctxt;
+  GError *err = NULL;
+  const GOptionEntry options[] = {
+    {G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY,
+        &INPUT_FILES, "H.264 video files to play", NULL},
+    {NULL,},
+  };
+
+  ctxt = g_option_context_new ("— VA X11 render");
+  g_option_context_add_main_entries (ctxt, options, NULL);
+  g_option_context_add_group (ctxt, gtk_get_option_group (TRUE));
+  g_option_context_add_group (ctxt, gst_init_get_option_group ());
+
+  if (!g_option_context_parse (ctxt, argc, argv, &err)) {
+    gst_printerrln ("option parsing failed: %s", err->message);
+    g_error_free (err);
+    return FALSE;
+  }
+
+  g_option_context_free (ctxt);
+  return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+  GdkDisplay *gdk_dpy;
+  VAStatus va_status;
+  struct _app app = { NULL, };
+  int maj, min, ret = EXIT_FAILURE;
+
+#if defined (GDK_WINDOWING_X11)
+  XInitThreads ();
+#endif
+
+  if (!parse_arguments (&argc, &argv))
+    return EXIT_FAILURE;
+
+  if (!(INPUT_FILES && INPUT_FILES[0]))
+    goto gtk_failed;
+
+  gdk_dpy = gdk_display_get_default ();
+  if (!GDK_IS_X11_DISPLAY (gdk_dpy)) {
+    gst_printerrln ("This example is only for native X11");
+    goto gtk_failed;
+  }
+
+  g_mutex_init (&app.mutex);
+
+  if (!build_pipeline (&app))
+    goto gst_failed;
+
+  app.va_dpy = vaGetDisplay (GDK_DISPLAY_XDISPLAY (gdk_dpy));
+  va_status = vaInitialize (app.va_dpy, &maj, &min);
+  if (va_status != VA_STATUS_SUCCESS) {
+    gst_printerrln ("failed to initialize VA: %s", vaErrorStr (va_status));
+    goto va_failed;
+  }
+
+  build_ui (&app);
+
+  gst_element_set_state (app.pipeline, GST_STATE_PLAYING);
+
+  gtk_main ();
+
+  ret = EXIT_SUCCESS;
+
+va_failed:
+  vaTerminate (app.va_dpy);
+  gst_object_unref (app.pipeline);
+gst_failed:
+  g_mutex_clear (&app.mutex);
+gtk_failed:
+  g_strfreev (INPUT_FILES);
+  gst_deinit ();
+
+  return ret;
+}
diff --git a/tests/examples/va/meson.build b/tests/examples/va/meson.build
new file mode 100644 (file)
index 0000000..c20d184
--- /dev/null
@@ -0,0 +1,14 @@
+gtk_dep = dependency('gtk+-3.0', required : get_option('examples'))
+gtk_x11_dep = dependency('gtk+-x11-3.0', required : get_option('examples'))
+x11_dep = dependency('x11', required : get_option('examples'))
+libva_x11_dep = dependency('libva-x11', version: libva_req, required: get_option('examples'))
+
+if have_va and gtk_dep.found() and gtk_x11_dep.found() and x11_dep.found() and libva_x11_dep.found()
+  executable('va-x11-render',
+    'main.c',
+    install: false,
+    include_directories : [configinc],
+    dependencies : [gtk_dep, gtk_x11_dep, x11_dep, gst_dep, gstapp_dep, gstvideo_dep, libva_dep, libva_x11_dep],
+    c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
+  )
+endif