2 * test-vaapicontext.c - Testsuite for VAAPI app context
4 * Copyright (C) 2017 Intel Corporation
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; either version 2.1
9 * of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free
18 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
23 #include <gst/video/videooverlay.h>
29 #ifdef GDK_WINDOWING_X11
32 #include <va/va_x11.h>
34 #ifdef GDK_WINDOWING_WAYLAND
35 #include <gdk/gdkwayland.h>
36 #include <va/va_wayland.h>
39 static gboolean g_multisink;
40 static gchar *g_filepath;
41 static GstElement *g_vaapisink;
43 static GOptionEntry g_options[] = {
44 {"multi", 'm', 0, G_OPTION_ARG_NONE, &g_multisink, "test multiple vaapisink",
46 {"file", 'f', 0, G_OPTION_ARG_STRING, &g_filepath, "file path to play", NULL},
50 typedef struct _CustomData
52 GtkWidget *main_window;
55 guintptr videoarea_handle[2];
56 GstObject *gstvaapidisplay;
57 GtkWidget *video_widget[2];
58 GstVideoOverlay *overlay[2];
62 delete_event_cb (GtkWidget * widget, GdkEvent * event, gpointer data)
66 gst_element_set_state (app->pipeline, GST_STATE_NULL);
71 button_rotate_cb (GtkWidget * widget, GstElement * elem)
73 static gint counter = 0;
74 const static gint tags[] = { 90, 180, 270, 0 };
76 g_object_set (elem, "rotation", tags[counter++ % G_N_ELEMENTS (tags)], NULL);
80 get_native_display (AppData * app, gboolean * is_x11)
82 GdkDisplay *gdk_display;
84 gdk_display = gtk_widget_get_display (app->main_window);
86 #if defined(GDK_WINDOWING_X11)
87 if (GDK_IS_X11_DISPLAY (gdk_display)) {
89 return gdk_x11_display_get_xdisplay (gdk_display);
92 #ifdef GDK_WINDOWING_WAYLAND
93 if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
95 return gdk_wayland_display_get_wl_display (gdk_display);
98 g_error ("Running in a non supported environment");
102 ensure_va_display (AppData * app, gpointer native_display, gboolean is_x11)
105 return app->va_display;
106 app->va_display = is_x11 ?
107 vaGetDisplay (native_display) : vaGetDisplayWl (native_display);
108 /* There's no need to call vaInitialize() since element does it
110 return app->va_display;
114 create_vaapi_app_display_context (AppData * app, gboolean new_va_display)
118 VADisplay va_display;
119 gpointer native_display = NULL;
120 const gchar *name = NULL;
123 native_display = get_native_display (app, &is_x11);
125 if (new_va_display) {
126 va_display = is_x11 ?
127 vaGetDisplay (native_display) : vaGetDisplayWl (native_display);
129 va_display = ensure_va_display (app, native_display, is_x11);
131 name = is_x11 ? "x11-display" : "wl-display";
133 context = gst_context_new ("gst.vaapi.app.Display", FALSE);
134 s = gst_context_writable_structure (context);
135 gst_structure_set (s, "va-display", G_TYPE_POINTER, va_display, NULL);
136 gst_structure_set (s, name, G_TYPE_POINTER, native_display, NULL);
142 get_allocation (GtkWidget * widget, GtkAllocation * allocation)
144 GdkDisplay *display = gdk_display_get_default ();
146 gtk_widget_get_allocation (widget, allocation);
148 /* On Wayland the whole gtk window is one surface and the video is a
149 * subsurface of the top-level surface. So the position must be relative
150 * to the top-level window not relative to the parent widget */
151 if (GDK_IS_WAYLAND_DISPLAY (display))
152 gtk_widget_translate_coordinates (widget, gtk_widget_get_toplevel (widget),
153 0, 0, &allocation->x, &allocation->y);
156 static GstBusSyncReply
157 bus_sync_handler (GstBus * bus, GstMessage * msg, gpointer data)
161 switch (GST_MESSAGE_TYPE (msg)) {
162 case GST_MESSAGE_NEED_CONTEXT:{
163 const gchar *context_type;
164 gboolean new_va_disp;
167 gst_message_parse_context_type (msg, &context_type);
168 gst_println ("Got need context %s from %s", context_type,
169 GST_MESSAGE_SRC_NAME (msg));
171 if (g_strcmp0 (context_type, "gst.vaapi.Display") == 0) {
172 if (app->gstvaapidisplay) {
175 context = gst_context_new ("gst.vaapi.Display", FALSE);
176 s = gst_context_writable_structure (context);
177 gst_structure_set (s, "gst.vaapi.Display",
178 GST_TYPE_OBJECT, app->gstvaapidisplay, NULL);
183 if (g_strcmp0 (context_type, "gst.vaapi.app.Display") != 0)
186 /* create a new VA display *only* for the second video sink */
187 new_va_disp = (g_strcmp0 (GST_MESSAGE_SRC_NAME (msg), "sink2") == 0);
189 context = create_vaapi_app_display_context (app, new_va_disp);
190 gst_element_set_context (GST_ELEMENT (GST_MESSAGE_SRC (msg)), context);
191 gst_context_unref (context);
194 case GST_MESSAGE_ELEMENT:{
195 GstVideoOverlay *overlay;
196 GtkAllocation allocation;
199 if (!gst_is_video_overlay_prepare_window_handle_message (msg))
202 overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (msg));
204 i = (g_strcmp0 (GST_MESSAGE_SRC_NAME (msg), "sink2") == 0) ? 1 : 0;
206 app->overlay[i] = overlay;
207 get_allocation (app->video_widget[i], &allocation);
208 gst_video_overlay_set_window_handle (overlay, app->videoarea_handle[i]);
209 gtk_widget_queue_draw_area (app->video_widget[i], 0, 0, allocation.width,
214 case GST_MESSAGE_HAVE_CONTEXT:{
215 const gchar *context_type;
216 const GstStructure *s;
217 GstContext *context = NULL;
220 gst_message_parse_have_context (msg, &context);
224 context_type = gst_context_get_context_type (context);
225 gst_println ("Got have context %s from %s", context_type,
226 GST_MESSAGE_SRC_NAME (msg));
228 if (g_strcmp0 (context_type, "gst.vaapi.Display") != 0)
230 s = gst_context_get_structure (context);
233 value = gst_structure_get_value (s, "gst.vaapi.Display");
236 app->gstvaapidisplay = g_value_dup_object (value);
237 gst_println ("found display %s", GST_OBJECT_NAME (app->gstvaapidisplay));
240 case GST_MESSAGE_EOS:
251 play_cb (GtkButton * button, gpointer data)
255 gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
259 null_cb (GtkButton * button, gpointer data)
263 gst_element_set_state (app->pipeline, GST_STATE_NULL);
264 app->va_display = NULL;
269 realize_cb (GtkWidget * widget, gpointer data)
274 static guint counter = 0;
276 display = gdk_display_get_default ();
278 #ifdef GDK_WINDOWING_WAYLAND
279 /* On wayland gtk_widget_get_window() only works correctly for the
280 * toplevel widget. Otherwise a new wayland surface is created but
281 * never used and the video remains invisible. */
282 if (GDK_IS_WAYLAND_DISPLAY (display))
283 window = gtk_widget_get_window (app->main_window);
286 window = gtk_widget_get_window (widget);
288 if (!gdk_window_ensure_native (window))
289 g_error ("Couldn't create native window needed for GstOverlay!");
291 #ifdef GDK_WINDOWING_X11
292 if (GDK_IS_X11_DISPLAY (display)) {
293 app->videoarea_handle[counter++ % 2] = GDK_WINDOW_XID (window);
296 #ifdef GDK_WINDOWING_WAYLAND
297 if (GDK_IS_WAYLAND_DISPLAY (display)) {
298 app->videoarea_handle[counter++ % 2] =
299 (guintptr) gdk_wayland_window_get_wl_surface (window);
302 g_error ("Unsupported GDK backend");
306 draw_cb (GtkWidget * widget, cairo_t * cr, gpointer data)
309 GtkAllocation allocation;
312 i = (widget == app->video_widget[0]) ? 0 : 1;
314 get_allocation (widget, &allocation);
316 gst_println ("draw_cb[%d] x %d, y %d, w %d, h %d\n", i,
317 allocation.x, allocation.y, allocation.width, allocation.height);
319 if (app->overlay[i]) {
320 gst_video_overlay_set_render_rectangle (app->overlay[i], allocation.x,
321 allocation.y, allocation.width, allocation.height);
326 create_video_box (AppData * app)
328 GtkWidget *video_area;
330 video_area = gtk_drawing_area_new ();
331 gtk_widget_set_size_request (video_area, 640, 480);
332 g_signal_connect (video_area, "realize", G_CALLBACK (realize_cb), app);
333 g_signal_connect (video_area, "draw", G_CALLBACK (draw_cb), app);
338 create_rotate_button (AppData * app, const gchar * name)
343 sink = gst_bin_get_by_name (GST_BIN (app->pipeline), name);
344 if (!sink && !g_strcmp0 (name, "sink1"))
348 rotate = gtk_button_new_with_label ("Rotate");
349 g_signal_connect (rotate, "clicked", G_CALLBACK (button_rotate_cb), sink);
350 if (sink != g_vaapisink)
351 gst_object_unref (sink);
357 build_ui (AppData * app)
359 GtkWidget *mainwin, *vbox, *pane, *bbox;
361 mainwin = gtk_window_new (GTK_WINDOW_TOPLEVEL);
362 gtk_window_set_title (GTK_WINDOW (mainwin), "VAAPI display context test");
363 gtk_window_set_resizable (GTK_WINDOW (mainwin), FALSE);
364 g_signal_connect (mainwin, "delete-event", G_CALLBACK (delete_event_cb), app);
365 app->main_window = mainwin;
367 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
368 gtk_container_add (GTK_CONTAINER (mainwin), vbox);
370 pane = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
371 gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
373 /* first video box */
374 app->video_widget[0] = create_video_box (app);
375 gtk_paned_pack1 (GTK_PANED (pane), app->video_widget[0], TRUE, TRUE);
378 bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
379 gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
380 gtk_box_pack_end (GTK_BOX (vbox), bbox, TRUE, TRUE, 0);
382 gtk_box_pack_start (GTK_BOX (bbox), create_rotate_button (app, "sink1"), TRUE,
386 /* second video box */
387 app->video_widget[1] = create_video_box (app);
388 gtk_paned_pack2 (GTK_PANED (pane), app->video_widget[1], TRUE, TRUE);
390 gtk_box_pack_start (GTK_BOX (bbox), create_rotate_button (app, "sink2"),
395 button = gtk_button_new_with_label ("PLAYING");
396 gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, TRUE, 0);
397 g_signal_connect (button, "clicked", G_CALLBACK (play_cb), app);
399 button = gtk_button_new_with_label ("NULL");
400 gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, TRUE, 0);
401 g_signal_connect (button, "clicked", G_CALLBACK (null_cb), app);
404 gtk_widget_show_all (mainwin);
408 main (gint argc, gchar ** argv)
410 AppData app = { 0, };
413 GError *error = NULL;
417 ctx = g_option_context_new ("- test options");
421 g_option_context_add_group (ctx, gtk_get_option_group (TRUE));
422 g_option_context_add_group (ctx, gst_init_get_option_group ());
423 g_option_context_add_main_entries (ctx, g_options, NULL);
424 if (!g_option_context_parse (ctx, &argc, &argv, NULL))
426 g_option_context_free (ctx);
429 app.pipeline = gst_parse_launch ("videotestsrc ! tee name=t ! queue ! "
430 "vaapisink name=sink1 t. ! queue ! vaapisink name=sink2", &error);
431 } else if (!g_filepath) {
432 app.pipeline = gst_parse_launch ("videotestsrc ! vaapih264enc ! "
433 "vaapidecodebin ! vaapisink name=sink1", &error);
435 app.pipeline = gst_element_factory_make ("playbin", NULL);
436 g_assert (app.pipeline);
440 gst_printerrln ("failed to parse pipeline: %s", error->message);
441 g_error_free (error);
445 if (!g_multisink && g_filepath) {
446 g_vaapisink = gst_element_factory_make ("vaapisink", "sink1");
447 g_assert (g_vaapisink);
448 g_object_set (app.pipeline, "uri", g_filepath, "video-sink", g_vaapisink,
454 bus = gst_element_get_bus (app.pipeline);
455 gst_bus_set_sync_handler (bus, bus_sync_handler, (gpointer) & app, NULL);
456 gst_object_unref (bus);
458 gst_element_set_state (app.pipeline, GST_STATE_PLAYING);
459 gst_println ("Now playing…");
463 gst_object_unref (app.pipeline);
464 gst_object_unref (app.gstvaapidisplay);
465 /* there is no need to call vaTerminate() because it is done by the