test: vaapicontext: fix draw callback with multiple videos
[platform/upstream/gstreamer.git] / tests / examples / test-vaapicontext.c
1 /*
2  *  test-vaapicontext.c - Testsuite for VAAPI app context
3  *
4  *  Copyright (C) 2017 Intel Corporation
5  *
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.
10  *
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.
15  *
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
20  */
21
22 #include <gst/gst.h>
23 #include <gst/video/videooverlay.h>
24
25 #include <va/va.h>
26
27 #include <gtk/gtk.h>
28
29 #ifdef GDK_WINDOWING_X11
30 #include <X11/Xlib.h>
31 #include <gdk/gdkx.h>
32 #include <va/va_x11.h>
33 #endif
34 #ifdef GDK_WINDOWING_WAYLAND
35 #include <gdk/gdkwayland.h>
36 #include <va/va_wayland.h>
37 #endif
38
39 static gboolean g_multisink;
40 static gchar *g_filepath;
41 static GstElement *g_vaapisink;
42
43 static GOptionEntry g_options[] = {
44   {"multi", 'm', 0, G_OPTION_ARG_NONE, &g_multisink, "test multiple vaapisink",
45       NULL},
46   {"file", 'f', 0, G_OPTION_ARG_STRING, &g_filepath, "file path to play", NULL},
47   {NULL,}
48 };
49
50 typedef struct _CustomData
51 {
52   GtkWidget *main_window;
53   VADisplay va_display;
54   GstElement *pipeline;
55   guintptr videoarea_handle[2];
56   GstObject *gstvaapidisplay;
57   GtkWidget *video_widget[2];
58   GstVideoOverlay *overlay[2];
59 } AppData;
60
61 static void
62 delete_event_cb (GtkWidget * widget, GdkEvent * event, gpointer data)
63 {
64   AppData *app = data;
65
66   gst_element_set_state (app->pipeline, GST_STATE_NULL);
67   gtk_main_quit ();
68 }
69
70 static void
71 button_rotate_cb (GtkWidget * widget, GstElement * elem)
72 {
73   static gint counter = 0;
74   const static gint tags[] = { 90, 180, 270, 0 };
75
76   g_object_set (elem, "rotation", tags[counter++ % G_N_ELEMENTS (tags)], NULL);
77 }
78
79 static gpointer
80 get_native_display (AppData * app, gboolean * is_x11)
81 {
82   GdkDisplay *gdk_display;
83
84   gdk_display = gtk_widget_get_display (app->main_window);
85
86 #if defined(GDK_WINDOWING_X11)
87   if (GDK_IS_X11_DISPLAY (gdk_display)) {
88     *is_x11 = TRUE;
89     return gdk_x11_display_get_xdisplay (gdk_display);
90   } else
91 #endif
92 #ifdef GDK_WINDOWING_WAYLAND
93   if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
94     *is_x11 = FALSE;
95     return gdk_wayland_display_get_wl_display (gdk_display);
96   } else
97 #endif
98     g_error ("Running in a non supported environment");
99 }
100
101 static VADisplay
102 ensure_va_display (AppData * app, gpointer native_display, gboolean is_x11)
103 {
104   if (app->va_display)
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
109    * internally */
110   return app->va_display;
111 }
112
113 static GstContext *
114 create_vaapi_app_display_context (AppData * app, gboolean new_va_display)
115 {
116   GstContext *context;
117   GstStructure *s;
118   VADisplay va_display;
119   gpointer native_display = NULL;
120   const gchar *name = NULL;
121   gboolean is_x11;
122
123   native_display = get_native_display (app, &is_x11);
124
125   if (new_va_display) {
126     va_display = is_x11 ?
127         vaGetDisplay (native_display) : vaGetDisplayWl (native_display);
128   } else
129     va_display = ensure_va_display (app, native_display, is_x11);
130
131   name = is_x11 ? "x11-display" : "wl-display";
132
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);
137
138   return context;
139 }
140
141 static void
142 get_allocation (GtkWidget * widget, GtkAllocation * allocation)
143 {
144   GdkDisplay *display = gdk_display_get_default ();
145
146   gtk_widget_get_allocation (widget, allocation);
147
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);
154 }
155
156 static GstBusSyncReply
157 bus_sync_handler (GstBus * bus, GstMessage * msg, gpointer data)
158 {
159   AppData *app = data;
160
161   switch (GST_MESSAGE_TYPE (msg)) {
162     case GST_MESSAGE_NEED_CONTEXT:{
163       const gchar *context_type;
164       gboolean new_va_disp;
165       GstContext *context;
166
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));
170
171       if (g_strcmp0 (context_type, "gst.vaapi.Display") == 0) {
172         if (app->gstvaapidisplay) {
173           GstStructure *s;
174
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);
179         }
180         break;
181       }
182
183       if (g_strcmp0 (context_type, "gst.vaapi.app.Display") != 0)
184         break;
185
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);
188
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);
192       break;
193     }
194     case GST_MESSAGE_ELEMENT:{
195       GstVideoOverlay *overlay;
196       GtkAllocation allocation;
197       guint i;
198
199       if (!gst_is_video_overlay_prepare_window_handle_message (msg))
200         break;
201
202       overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (msg));
203
204       i = (g_strcmp0 (GST_MESSAGE_SRC_NAME (msg), "sink2") == 0) ? 1 : 0;
205
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,
210           allocation.height);
211
212       break;
213     }
214     case GST_MESSAGE_HAVE_CONTEXT:{
215       const gchar *context_type;
216       const GstStructure *s;
217       GstContext *context = NULL;
218       const GValue *value;
219
220       gst_message_parse_have_context (msg, &context);
221       if (!context)
222         break;
223
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));
227
228       if (g_strcmp0 (context_type, "gst.vaapi.Display") != 0)
229         break;
230       s = gst_context_get_structure (context);
231       if (!s)
232         break;
233       value = gst_structure_get_value (s, "gst.vaapi.Display");
234       if (!value)
235         break;
236       app->gstvaapidisplay = g_value_dup_object (value);
237       gst_println ("found display %s", GST_OBJECT_NAME (app->gstvaapidisplay));
238       break;
239     }
240     case GST_MESSAGE_EOS:
241       gtk_main_quit ();
242       break;
243     default:
244       break;
245   }
246
247   return GST_BUS_PASS;
248 }
249
250 static void
251 play_cb (GtkButton * button, gpointer data)
252 {
253   AppData *app = data;
254
255   gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
256 }
257
258 static void
259 null_cb (GtkButton * button, gpointer data)
260 {
261   AppData *app = data;
262
263   gst_element_set_state (app->pipeline, GST_STATE_NULL);
264   app->va_display = NULL;
265 }
266
267
268 static void
269 realize_cb (GtkWidget * widget, gpointer data)
270 {
271   AppData *app = data;
272   GdkWindow *window;
273   GdkDisplay *display;
274   static guint counter = 0;
275
276   display = gdk_display_get_default ();
277
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);
284   else
285 #endif
286     window = gtk_widget_get_window (widget);
287
288   if (!gdk_window_ensure_native (window))
289     g_error ("Couldn't create native window needed for GstOverlay!");
290
291 #ifdef GDK_WINDOWING_X11
292   if (GDK_IS_X11_DISPLAY (display)) {
293     app->videoarea_handle[counter++ % 2] = GDK_WINDOW_XID (window);
294   } else
295 #endif
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);
300   } else
301 #endif
302     g_error ("Unsupported GDK backend");
303 }
304
305 static void
306 draw_cb (GtkWidget * widget, cairo_t * cr, gpointer data)
307 {
308   AppData *app = data;
309   GtkAllocation allocation;
310   int i;
311
312   i = (widget == app->video_widget[0]) ? 0 : 1;
313
314   get_allocation (widget, &allocation);
315
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);
318
319   if (app->overlay[i]) {
320     gst_video_overlay_set_render_rectangle (app->overlay[i], allocation.x,
321         allocation.y, allocation.width, allocation.height);
322   }
323 }
324
325 static GtkWidget *
326 create_video_box (AppData * app)
327 {
328   GtkWidget *video_area;
329
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);
334   return video_area;
335 }
336
337 static GtkWidget *
338 create_rotate_button (AppData * app, const gchar * name)
339 {
340   GtkWidget *rotate;
341   GstElement *sink;
342
343   sink = gst_bin_get_by_name (GST_BIN (app->pipeline), name);
344   if (!sink && !g_strcmp0 (name, "sink1"))
345     sink = g_vaapisink;
346   g_assert (sink);
347
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);
352
353   return rotate;
354 }
355
356 static void
357 build_ui (AppData * app)
358 {
359   GtkWidget *mainwin, *vbox, *pane, *bbox;
360
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;
366
367   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
368   gtk_container_add (GTK_CONTAINER (mainwin), vbox);
369
370   pane = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
371   gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
372
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);
376
377   /* rotate buttons */
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);
381
382   gtk_box_pack_start (GTK_BOX (bbox), create_rotate_button (app, "sink1"), TRUE,
383       TRUE, 0);
384
385   if (g_multisink) {
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);
389
390     gtk_box_pack_start (GTK_BOX (bbox), create_rotate_button (app, "sink2"),
391         TRUE, TRUE, 0);
392   } else {
393     GtkWidget *button;
394
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);
398
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);
402   }
403
404   gtk_widget_show_all (mainwin);
405 }
406
407 int
408 main (gint argc, gchar ** argv)
409 {
410   AppData app = { 0, };
411   GstBus *bus;
412   GOptionContext *ctx;
413   GError *error = NULL;
414
415   XInitThreads ();
416
417   ctx = g_option_context_new ("- test options");
418   if (!ctx)
419     return -1;
420
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))
425     return -1;
426   g_option_context_free (ctx);
427
428   if (g_multisink) {
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);
434   } else {
435     app.pipeline = gst_element_factory_make ("playbin", NULL);
436     g_assert (app.pipeline);
437   }
438
439   if (error) {
440     gst_printerrln ("failed to parse pipeline: %s", error->message);
441     g_error_free (error);
442     return -1;
443   }
444
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,
449         NULL);
450   }
451
452   build_ui (&app);
453
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);
457
458   gst_element_set_state (app.pipeline, GST_STATE_PLAYING);
459   gst_println ("Now playing…");
460
461   gtk_main ();
462
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
466    * vaapi elements */
467   return 0;
468 }