tests: elements: add testsuite of vaapi context
authorHyunjun Ko <zzoon@igalia.com>
Wed, 5 Jul 2017 06:59:43 +0000 (15:59 +0900)
committerVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Wed, 26 Jul 2017 12:22:02 +0000 (14:22 +0200)
Signed-off-by: Víctor Manuel Jáquez Leal <vjaquez@igalia.com>
https://bugzilla.gnome.org/show_bug.cgi?id=766704

configure.ac
tests/elements/Makefile.am
tests/elements/test-vaapicontext.c [new file with mode: 0644]

index 340b4649d024c6089ec680a3f084281577f95847..2e8c703ca040cb3ee732f4ee94aa6e803dfe42b3 100644 (file)
@@ -155,6 +155,11 @@ AC_ARG_WITH([glapi],
     [build with the specified OpenGL APIs @<:@default=default_glapi@:>@]),
   [GLAPI="$with_glapi"], [GLAPI=default_glapi])
 
+AC_ARG_WITH([gtk],
+  [AS_HELP_STRING([--with-gtk],
+    [compile GTK3 based test apps @<:@default=check@:>@])],
+  [], [with_gtk="check"])
+
 dnl *** checks for platform ***
 
 dnl * hardware/architecture *
@@ -479,6 +484,22 @@ if test "x$enable_wayland" = "xyes"; then
     ], [:])
 fi
 
+dnl Check for GTK for tests
+USE_GTK=0
+AS_IF([test "x$BUILD_EXAMPLES" = "xyes" -a $USE_X11 -eq 1],
+  [AS_CASE([$with_gtk],
+    [yes], [PKG_CHECK_MODULES([GTK3], [gtk+-3.0], [USE_GTK=1])],
+    [no], [],
+    [PKG_CHECK_MODULES([GTK3], [gtk+-3.0], [USE_GTK=1])])])
+AS_IF([test $USE_GTK -eq 1],
+  [
+    saved_CPPFLAGS="$CPPFLAGS"
+    CPPFLAGS="$CPPFLAGS $GTK3_CFLAGS"
+    AC_CHECK_HEADERS([gtk/gtk.h], [], [USE_GTK=0])
+    CPPFLAGS="$saved_CPPFLAGS"
+  ])
+AM_CONDITIONAL([USE_GTK], [test $USE_GTK -eq 1])
+
 dnl ---------------------------------------------------------------------------
 dnl -- VA-API                                                                --
 dnl ---------------------------------------------------------------------------
index 86c49f5f9596c40c0055fa7d28a057b136e3865f..742d923a672bb50a00b8196abbba21272ec70e48 100644 (file)
@@ -27,4 +27,24 @@ test_roi_SOURCES = test-roi.c
 test_roi_CFLAGS  = $(TEST_CFLAGS)
 test_roi_LDADD   = $(TEST_LIBS)
 
+if USE_GTK
+noinst_PROGRAMS += test-vaapicontext
+
+test_vaapicontext_SOURCES = test-vaapicontext.c
+test_vaapicontext_CFLAGS = \
+       $(TEST_CFLAGS)          \
+       $(GTK3_CFLAGS)          \
+       $(X11_CFLAGS)           \
+       $(LIBVA_CFLAGS)         \
+       $(LIBVA_X11_CFLAGS)     \
+       $(NULL)
+test_vaapicontext_LDADD = \
+       $(TEST_LIBS)            \
+       $(GTK3_LIBS)            \
+       $(X11_LIBS)             \
+       $(LIBVA_LIBS)           \
+       $(LIBVA_X11_LIBS)       \
+       $(NULL)
+endif
+
 -include $(top_srcdir)/git.mk
diff --git a/tests/elements/test-vaapicontext.c b/tests/elements/test-vaapicontext.c
new file mode 100644 (file)
index 0000000..ac974cc
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ *  test-vaapicontext.c - Testsuite for VAAPI app context
+ *
+ *  Copyright (C) 2017 Intel Corporation
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1
+ *  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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ *  Boston, MA 02110-1301 USA
+ */
+
+#include <gst/gst.h>
+#include <gst/video/videooverlay.h>
+
+#include <va/va.h>
+#include <gtk/gtk.h>
+
+#include <X11/Xlib.h>
+#include <va/va_x11.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#else
+#error "X11 is not supported in GTK+"
+#endif
+
+static gboolean g_multisink;
+static gchar *g_filepath;
+
+static GOptionEntry g_options[] = {
+  {"multi", 'm', 0, G_OPTION_ARG_NONE, &g_multisink, "test multiple vaapisink",
+      NULL},
+  {"file", 'f', 0, G_OPTION_ARG_STRING, &g_filepath,
+      "file path to play (only mp4/h264)", NULL},
+  {NULL,}
+};
+
+typedef struct _CustomData
+{
+  GtkWidget *main_window;
+  VADisplay va_display;
+  GstElement *pipeline;
+  guintptr videoarea_handle[2];
+} AppData;
+
+static void
+delete_event_cb (GtkWidget * widget, GdkEvent * event, gpointer data)
+{
+  AppData *app = data;
+
+  gst_element_set_state (app->pipeline, GST_STATE_NULL);
+  gtk_main_quit ();
+}
+
+static void
+button_rotate_cb (GtkWidget * widget, GstElement * elem)
+{
+  static gint counter = 0;
+  const static gint tags[] = { 90, 180, 270, 0 };
+
+  g_object_set (elem, "rotation", tags[counter++ % G_N_ELEMENTS (tags)], NULL);
+}
+
+static Display *
+get_x11_window_display (AppData * app)
+{
+#if defined(GDK_WINDOWING_X11)
+  GdkDisplay *gdk_display;
+  Display *x11_display;
+
+  gdk_display = gtk_widget_get_display (app->main_window);
+  x11_display = gdk_x11_display_get_xdisplay (gdk_display);
+  return x11_display;
+#endif
+  g_error ("Running in a non-X11 environment");
+}
+
+static VADisplay
+ensure_va_display (AppData * app)
+{
+  if (app->va_display)
+    return app->va_display;
+  app->va_display = vaGetDisplay (get_x11_window_display (app));
+  /* There's no need to call vaInitialize() since element does it
+   * internally */
+  return app->va_display;
+}
+
+static GstContext *
+create_vaapi_app_display_context (AppData * app, gboolean new_va_display)
+{
+  GstContext *context;
+  GstStructure *s;
+  VADisplay va_display;
+  Display *x11_display;
+
+  x11_display = get_x11_window_display (app);
+
+  if (new_va_display)
+    va_display = vaGetDisplay (x11_display);
+  else
+    va_display = ensure_va_display (app);
+
+  context = gst_context_new ("gst.vaapi.app.Display", TRUE);
+  s = gst_context_writable_structure (context);
+  gst_structure_set (s, "va-display", G_TYPE_POINTER, va_display, NULL);
+  gst_structure_set (s, "x11-display", G_TYPE_POINTER, x11_display, NULL);
+
+  return context;
+}
+
+static GstBusSyncReply
+bus_sync_handler (GstBus * bus, GstMessage * msg, gpointer data)
+{
+  AppData *app = data;
+
+  switch (GST_MESSAGE_TYPE (msg)) {
+    case GST_MESSAGE_NEED_CONTEXT:{
+      const gchar *context_type;
+      gboolean new_va_disp;
+      GstContext *context;
+
+      gst_message_parse_context_type (msg, &context_type);
+      gst_println ("Got need context %s from %s", context_type,
+          GST_MESSAGE_SRC_NAME (msg));
+
+      if (g_strcmp0 (context_type, "gst.vaapi.app.Display") != 0)
+        break;
+
+      /* create a new VA display *only* for the second video sink */
+      new_va_disp = (g_strcmp0 (GST_MESSAGE_SRC_NAME (msg), "sink2") == 0);
+
+      context = create_vaapi_app_display_context (app, new_va_disp);
+      gst_element_set_context (GST_ELEMENT (GST_MESSAGE_SRC (msg)), context);
+      gst_context_unref (context);
+      break;
+    }
+    case GST_MESSAGE_ELEMENT:{
+      if (!gst_is_video_overlay_prepare_window_handle_message (msg))
+        break;
+
+      if (g_strcmp0 (GST_MESSAGE_SRC_NAME (msg), "sink2") == 0)
+        gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY
+            (GST_MESSAGE_SRC (msg)), app->videoarea_handle[1]);
+      else
+        gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY
+            (GST_MESSAGE_SRC (msg)), app->videoarea_handle[0]);
+      break;
+    }
+    case GST_MESSAGE_EOS:
+      gtk_main_quit ();
+      break;
+    default:
+      break;
+  }
+
+  return GST_BUS_PASS;
+}
+
+static void
+realize_cb (GtkWidget * widget, gpointer data)
+{
+  AppData *app = data;
+  GdkWindow *window;
+  static guint counter = 0;
+
+#if defined(GDK_WINDOWING_X11)
+  window = gtk_widget_get_window (widget);
+
+  if (!gdk_window_ensure_native (window))
+    g_error ("Couldn't create native window needed for GstXOverlay!");
+
+  app->videoarea_handle[counter++ % 2] = GDK_WINDOW_XID (window);
+#endif
+}
+
+static GtkWidget *
+create_video_box (AppData * app)
+{
+  GtkWidget *video_area;
+
+  video_area = gtk_drawing_area_new ();
+  gtk_widget_set_size_request (video_area, 640, 480);
+  g_signal_connect (video_area, "realize", G_CALLBACK (realize_cb), app);
+  return video_area;
+}
+
+static GtkWidget *
+create_rotate_button (AppData * app, const gchar * name)
+{
+  GtkWidget *rotate;
+  GstElement *sink;
+
+  sink = gst_bin_get_by_name (GST_BIN (app->pipeline), name);
+  g_assert (sink);
+
+  rotate = gtk_button_new_with_label ("Rotate");
+  g_signal_connect (rotate, "clicked", G_CALLBACK (button_rotate_cb), sink);
+  gst_object_unref (sink);
+
+  return rotate;
+}
+
+static void
+build_ui (AppData * app)
+{
+  GtkWidget *mainwin, *vbox, *pane, *bbox;
+
+  mainwin = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title (GTK_WINDOW (mainwin), "VAAPI display context test");
+  gtk_window_set_resizable (GTK_WINDOW (mainwin), FALSE);
+  g_signal_connect (mainwin, "delete-event", G_CALLBACK (delete_event_cb), app);
+  app->main_window = mainwin;
+
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_container_add (GTK_CONTAINER (mainwin), vbox);
+
+  pane = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
+
+  /* first video box */
+  gtk_paned_pack1 (GTK_PANED (pane), create_video_box (app), TRUE, TRUE);
+
+  /* rotate buttons */
+  bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+  gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+  gtk_box_pack_end (GTK_BOX (vbox), bbox, TRUE, TRUE, 0);
+
+  gtk_box_pack_start (GTK_BOX (bbox), create_rotate_button (app, "sink1"), TRUE,
+      TRUE, 0);
+
+  if (g_multisink) {
+    /* second video box */
+    gtk_paned_pack2 (GTK_PANED (pane), create_video_box (app), TRUE, TRUE);
+
+    gtk_box_pack_start (GTK_BOX (bbox), create_rotate_button (app, "sink2"),
+        TRUE, TRUE, 0);
+  }
+
+  gtk_widget_show_all (mainwin);
+}
+
+int
+main (gint argc, gchar ** argv)
+{
+  AppData app = { 0, };
+  GstBus *bus;
+  GOptionContext *ctx;
+  GError *error = NULL;
+
+  XInitThreads ();
+
+  ctx = g_option_context_new ("- test options");
+  if (!ctx)
+    return -1;
+
+  g_option_context_add_group (ctx, gtk_get_option_group (TRUE));
+  g_option_context_add_group (ctx, gst_init_get_option_group ());
+  g_option_context_add_main_entries (ctx, g_options, NULL);
+  if (!g_option_context_parse (ctx, &argc, &argv, NULL))
+    return -1;
+  g_option_context_free (ctx);
+
+  if (g_multisink) {
+    app.pipeline = gst_parse_launch ("videotestsrc ! tee name=t ! queue ! "
+        "vaapisink name=sink1 t. ! queue ! vaapisink name=sink2", &error);
+  } else if (!g_filepath) {
+    app.pipeline = gst_parse_launch ("videotestsrc ! vaapih264enc ! "
+        "vaapidecodebin ! vaapisink name=sink1", &error);
+  } else {
+    app.pipeline = gst_parse_launch ("filesrc name=src ! qtdemux ! h264parse ! "
+        "vaapidecodebin ! vaapisink name=sink1", &error);
+  }
+
+  if (error) {
+    gst_printerrln ("failed to parse pipeline: %s", error->message);
+    g_error_free (error);
+    return -1;
+  }
+
+  if (!g_multisink && g_filepath) {
+    GstElement *src;
+
+    src = gst_bin_get_by_name (GST_BIN (app.pipeline), "src");
+    g_assert (src);
+    g_object_set (src, "location", g_filepath, NULL);
+    gst_object_unref (src);
+  }
+
+  build_ui (&app);
+
+  bus = gst_element_get_bus (app.pipeline);
+  gst_bus_set_sync_handler (bus, bus_sync_handler, (gpointer) & app, NULL);
+  gst_object_unref (bus);
+
+  gst_element_set_state (app.pipeline, GST_STATE_PLAYING);
+  gst_println ("Now playing…");
+
+  gtk_main ();
+
+  gst_object_unref (app.pipeline);
+  /* there is no need to call vaTerminate() because it is done by the
+   * vaapi elements */
+  return 0;
+}