469 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			469 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *  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>
 | |
| 
 | |
| #ifdef GDK_WINDOWING_X11
 | |
| #include <X11/Xlib.h>
 | |
| #include <gdk/gdkx.h>
 | |
| #include <va/va_x11.h>
 | |
| #endif
 | |
| #ifdef GDK_WINDOWING_WAYLAND
 | |
| #include <gdk/gdkwayland.h>
 | |
| #include <va/va_wayland.h>
 | |
| #endif
 | |
| 
 | |
| static gboolean g_multisink;
 | |
| static gchar *g_filepath;
 | |
| static GstElement *g_vaapisink;
 | |
| 
 | |
| 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", NULL},
 | |
|   {NULL,}
 | |
| };
 | |
| 
 | |
| typedef struct _CustomData
 | |
| {
 | |
|   GtkWidget *main_window;
 | |
|   VADisplay va_display;
 | |
|   GstElement *pipeline;
 | |
|   guintptr videoarea_handle[2];
 | |
|   GstObject *gstvaapidisplay;
 | |
|   GtkWidget *video_widget[2];
 | |
|   GstVideoOverlay *overlay[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 gpointer
 | |
| get_native_display (AppData * app, gboolean * is_x11)
 | |
| {
 | |
|   GdkDisplay *gdk_display;
 | |
| 
 | |
|   gdk_display = gtk_widget_get_display (app->main_window);
 | |
| 
 | |
| #if defined(GDK_WINDOWING_X11)
 | |
|   if (GDK_IS_X11_DISPLAY (gdk_display)) {
 | |
|     *is_x11 = TRUE;
 | |
|     return gdk_x11_display_get_xdisplay (gdk_display);
 | |
|   } else
 | |
| #endif
 | |
| #ifdef GDK_WINDOWING_WAYLAND
 | |
|   if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
 | |
|     *is_x11 = FALSE;
 | |
|     return gdk_wayland_display_get_wl_display (gdk_display);
 | |
|   } else
 | |
| #endif
 | |
|     g_error ("Running in a non supported environment");
 | |
| }
 | |
| 
 | |
| static VADisplay
 | |
| ensure_va_display (AppData * app, gpointer native_display, gboolean is_x11)
 | |
| {
 | |
|   if (app->va_display)
 | |
|     return app->va_display;
 | |
|   app->va_display = is_x11 ?
 | |
|       vaGetDisplay (native_display) : vaGetDisplayWl (native_display);
 | |
|   /* 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;
 | |
|   gpointer native_display = NULL;
 | |
|   const gchar *name = NULL;
 | |
|   gboolean is_x11;
 | |
| 
 | |
|   native_display = get_native_display (app, &is_x11);
 | |
| 
 | |
|   if (new_va_display) {
 | |
|     va_display = is_x11 ?
 | |
|         vaGetDisplay (native_display) : vaGetDisplayWl (native_display);
 | |
|   } else
 | |
|     va_display = ensure_va_display (app, native_display, is_x11);
 | |
| 
 | |
|   name = is_x11 ? "x11-display" : "wl-display";
 | |
| 
 | |
|   context = gst_context_new ("gst.vaapi.app.Display", FALSE);
 | |
|   s = gst_context_writable_structure (context);
 | |
|   gst_structure_set (s, "va-display", G_TYPE_POINTER, va_display, NULL);
 | |
|   gst_structure_set (s, name, G_TYPE_POINTER, native_display, NULL);
 | |
| 
 | |
|   return context;
 | |
| }
 | |
| 
 | |
| static void
 | |
| get_allocation (GtkWidget * widget, GtkAllocation * allocation)
 | |
| {
 | |
|   GdkDisplay *display = gdk_display_get_default ();
 | |
| 
 | |
|   gtk_widget_get_allocation (widget, allocation);
 | |
| 
 | |
|   /* On Wayland the whole gtk window is one surface and the video is a
 | |
|    * subsurface of the top-level surface. So the position must be relative
 | |
|    * to the top-level window not relative to the parent widget */
 | |
|   if (GDK_IS_WAYLAND_DISPLAY (display))
 | |
|     gtk_widget_translate_coordinates (widget, gtk_widget_get_toplevel (widget),
 | |
|         0, 0, &allocation->x, &allocation->y);
 | |
| }
 | |
| 
 | |
| 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.Display") == 0) {
 | |
|         if (app->gstvaapidisplay) {
 | |
|           GstStructure *s;
 | |
| 
 | |
|           context = gst_context_new ("gst.vaapi.Display", FALSE);
 | |
|           s = gst_context_writable_structure (context);
 | |
|           gst_structure_set (s, "gst.vaapi.Display",
 | |
|               GST_TYPE_OBJECT, app->gstvaapidisplay, NULL);
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       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:{
 | |
|       GstVideoOverlay *overlay;
 | |
|       GtkAllocation allocation;
 | |
|       guint i;
 | |
| 
 | |
|       if (!gst_is_video_overlay_prepare_window_handle_message (msg))
 | |
|         break;
 | |
| 
 | |
|       overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (msg));
 | |
| 
 | |
|       i = (g_strcmp0 (GST_MESSAGE_SRC_NAME (msg), "sink2") == 0) ? 1 : 0;
 | |
| 
 | |
|       app->overlay[i] = overlay;
 | |
|       get_allocation (app->video_widget[i], &allocation);
 | |
|       gst_video_overlay_set_window_handle (overlay, app->videoarea_handle[i]);
 | |
|       gtk_widget_queue_draw_area (app->video_widget[i], 0, 0, allocation.width,
 | |
|           allocation.height);
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     case GST_MESSAGE_HAVE_CONTEXT:{
 | |
|       const gchar *context_type;
 | |
|       const GstStructure *s;
 | |
|       GstContext *context = NULL;
 | |
|       const GValue *value;
 | |
| 
 | |
|       gst_message_parse_have_context (msg, &context);
 | |
|       if (!context)
 | |
|         break;
 | |
| 
 | |
|       context_type = gst_context_get_context_type (context);
 | |
|       gst_println ("Got have context %s from %s", context_type,
 | |
|           GST_MESSAGE_SRC_NAME (msg));
 | |
| 
 | |
|       if (g_strcmp0 (context_type, "gst.vaapi.Display") != 0)
 | |
|         break;
 | |
|       s = gst_context_get_structure (context);
 | |
|       if (!s)
 | |
|         break;
 | |
|       value = gst_structure_get_value (s, "gst.vaapi.Display");
 | |
|       if (!value)
 | |
|         break;
 | |
|       app->gstvaapidisplay = g_value_dup_object (value);
 | |
|       gst_println ("found display %s", GST_OBJECT_NAME (app->gstvaapidisplay));
 | |
|       break;
 | |
|     }
 | |
|     case GST_MESSAGE_EOS:
 | |
|       gtk_main_quit ();
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return GST_BUS_PASS;
 | |
| }
 | |
| 
 | |
| static void
 | |
| play_cb (GtkButton * button, gpointer data)
 | |
| {
 | |
|   AppData *app = data;
 | |
| 
 | |
|   gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
 | |
| }
 | |
| 
 | |
| static void
 | |
| null_cb (GtkButton * button, gpointer data)
 | |
| {
 | |
|   AppData *app = data;
 | |
| 
 | |
|   gst_element_set_state (app->pipeline, GST_STATE_NULL);
 | |
|   app->va_display = NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| realize_cb (GtkWidget * widget, gpointer data)
 | |
| {
 | |
|   AppData *app = data;
 | |
|   GdkWindow *window;
 | |
|   GdkDisplay *display;
 | |
|   static guint counter = 0;
 | |
| 
 | |
|   display = gdk_display_get_default ();
 | |
| 
 | |
| #ifdef GDK_WINDOWING_WAYLAND
 | |
|   /* On wayland gtk_widget_get_window() only works correctly for the
 | |
|    * toplevel widget. Otherwise a new wayland surface is created but
 | |
|    * never used and the video remains invisible. */
 | |
|   if (GDK_IS_WAYLAND_DISPLAY (display))
 | |
|     window = gtk_widget_get_window (app->main_window);
 | |
|   else
 | |
| #endif
 | |
|     window = gtk_widget_get_window (widget);
 | |
| 
 | |
|   if (!gdk_window_ensure_native (window))
 | |
|     g_error ("Couldn't create native window needed for GstOverlay!");
 | |
| 
 | |
| #ifdef GDK_WINDOWING_X11
 | |
|   if (GDK_IS_X11_DISPLAY (display)) {
 | |
|     app->videoarea_handle[counter++ % 2] = GDK_WINDOW_XID (window);
 | |
|   } else
 | |
| #endif
 | |
| #ifdef GDK_WINDOWING_WAYLAND
 | |
|   if (GDK_IS_WAYLAND_DISPLAY (display)) {
 | |
|     app->videoarea_handle[counter++ % 2] =
 | |
|         (guintptr) gdk_wayland_window_get_wl_surface (window);
 | |
|   } else
 | |
| #endif
 | |
|     g_error ("Unsupported GDK backend");
 | |
| }
 | |
| 
 | |
| static void
 | |
| draw_cb (GtkWidget * widget, cairo_t * cr, gpointer data)
 | |
| {
 | |
|   AppData *app = data;
 | |
|   GtkAllocation allocation;
 | |
|   int i;
 | |
| 
 | |
|   i = (widget == app->video_widget[0]) ? 0 : 1;
 | |
| 
 | |
|   get_allocation (widget, &allocation);
 | |
| 
 | |
|   gst_println ("draw_cb[%d] x %d, y %d, w %d, h %d\n", i,
 | |
|       allocation.x, allocation.y, allocation.width, allocation.height);
 | |
| 
 | |
|   if (app->overlay[i]) {
 | |
|     gst_video_overlay_set_render_rectangle (app->overlay[i], allocation.x,
 | |
|         allocation.y, allocation.width, allocation.height);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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);
 | |
|   g_signal_connect (video_area, "draw", G_CALLBACK (draw_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);
 | |
|   if (!sink && !g_strcmp0 (name, "sink1"))
 | |
|     sink = g_vaapisink;
 | |
|   g_assert (sink);
 | |
| 
 | |
|   rotate = gtk_button_new_with_label ("Rotate");
 | |
|   g_signal_connect (rotate, "clicked", G_CALLBACK (button_rotate_cb), sink);
 | |
|   if (sink != g_vaapisink)
 | |
|     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 */
 | |
|   app->video_widget[0] = create_video_box (app);
 | |
|   gtk_paned_pack1 (GTK_PANED (pane), app->video_widget[0], 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 */
 | |
|     app->video_widget[1] = create_video_box (app);
 | |
|     gtk_paned_pack2 (GTK_PANED (pane), app->video_widget[1], TRUE, TRUE);
 | |
| 
 | |
|     gtk_box_pack_start (GTK_BOX (bbox), create_rotate_button (app, "sink2"),
 | |
|         TRUE, TRUE, 0);
 | |
|   } else {
 | |
|     GtkWidget *button;
 | |
| 
 | |
|     button = gtk_button_new_with_label ("PLAYING");
 | |
|     gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, TRUE, 0);
 | |
|     g_signal_connect (button, "clicked", G_CALLBACK (play_cb), app);
 | |
| 
 | |
|     button = gtk_button_new_with_label ("NULL");
 | |
|     gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, TRUE, 0);
 | |
|     g_signal_connect (button, "clicked", G_CALLBACK (null_cb), app);
 | |
|   }
 | |
| 
 | |
|   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_element_factory_make ("playbin", NULL);
 | |
|     g_assert (app.pipeline);
 | |
|   }
 | |
| 
 | |
|   if (error) {
 | |
|     gst_printerrln ("failed to parse pipeline: %s", error->message);
 | |
|     g_error_free (error);
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   if (!g_multisink && g_filepath) {
 | |
|     g_vaapisink = gst_element_factory_make ("vaapisink", "sink1");
 | |
|     g_assert (g_vaapisink);
 | |
|     g_object_set (app.pipeline, "uri", g_filepath, "video-sink", g_vaapisink,
 | |
|         NULL);
 | |
|   }
 | |
| 
 | |
|   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);
 | |
|   gst_object_unref (app.gstvaapidisplay);
 | |
|   /* there is no need to call vaTerminate() because it is done by the
 | |
|    * vaapi elements */
 | |
|   return 0;
 | |
| }
 |