3 * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
4 * Copyright (C) 2021 Collabora Ltd.
5 * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
27 #include "gstgtkwaylandsink.h"
30 #include <gst/wayland/wayland.h>
32 #include "gstgtkutils.h"
33 #include "gtkgstwaylandwidget.h"
35 #ifdef GDK_WINDOWING_WAYLAND
36 #include <gdk/gdkwayland.h>
38 #error "Wayland is not supported in GTK+"
41 #define GST_CAT_DEFAULT gst_debug_gtk_wayland_sink
42 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
44 #ifndef GST_CAPS_FEATURE_MEMORY_DMABUF
45 #define GST_CAPS_FEATURE_MEMORY_DMABUF "memory:DMABuf"
48 #define WL_VIDEO_FORMATS \
49 "{ BGRx, BGRA, RGBx, xBGR, xRGB, RGBA, ABGR, ARGB, RGB, BGR, " \
50 "RGB16, BGR16, YUY2, YVYU, UYVY, AYUV, NV12, NV21, NV16, NV61, " \
51 "YUV9, YVU9, Y41B, I420, YV12, Y42B, v308 }"
53 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
56 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (WL_VIDEO_FORMATS) ";"
57 GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_DMABUF,
61 static void gst_gtk_wayland_sink_get_property (GObject * object,
62 guint prop_id, GValue * value, GParamSpec * pspec);
63 static void gst_gtk_wayland_sink_set_property (GObject * object,
64 guint prop_id, const GValue * value, GParamSpec * pspec);
65 static void gst_gtk_wayland_sink_finalize (GObject * object);
67 static GstStateChangeReturn gst_gtk_wayland_sink_change_state (GstElement *
68 element, GstStateChange transition);
70 static gboolean gst_gtk_wayland_sink_event (GstBaseSink * sink,
72 static GstCaps *gst_gtk_wayland_sink_get_caps (GstBaseSink * bsink,
74 static gboolean gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink,
76 static gboolean gst_gtk_wayland_sink_propose_allocation (GstBaseSink * bsink,
78 static GstFlowReturn gst_gtk_wayland_sink_show_frame (GstVideoSink * bsink,
80 static void gst_gtk_wayland_sink_set_rotate_method (GstGtkWaylandSink * self,
81 GstVideoOrientationMethod method, gboolean from_tag);
84 gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface);
87 calculate_adjustment (GtkWidget * start_widget, GtkAllocation * allocation);
97 typedef struct _GstGtkWaylandSinkPrivate
99 GtkWidget *gtk_widget;
100 GtkWidget *gtk_window;
101 gulong gtk_window_destroy_id;
103 /* from GstWaylandSink */
105 GstWlDisplay *display;
107 GstWlWindow *wl_window;
108 gboolean is_wl_window_sync;
111 GstBuffer *last_buffer;
114 gboolean video_info_changed;
115 GstVideoInfo video_info;
117 gboolean redraw_pending;
120 GstVideoOrientationMethod sink_rotate_method;
121 GstVideoOrientationMethod tag_rotate_method;
122 GstVideoOrientationMethod current_rotate_method;
124 struct wl_callback *callback;
125 } GstGtkWaylandSinkPrivate;
127 #define gst_gtk_wayland_sink_parent_class parent_class
128 G_DEFINE_TYPE_WITH_CODE (GstGtkWaylandSink, gst_gtk_wayland_sink,
129 GST_TYPE_VIDEO_SINK, G_ADD_PRIVATE (GstGtkWaylandSink)
130 G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
131 gst_gtk_wayland_sink_navigation_interface_init)
132 GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_wayland_sink,
133 "gtkwaylandsink", 0, "Gtk Wayland Video sink");
135 GST_ELEMENT_REGISTER_DEFINE (gtkwaylandsink, "gtkwaylandsink",
136 GST_RANK_MARGINAL, GST_TYPE_GTK_WAYLAND_SINK);
139 gst_gtk_wayland_sink_class_init (GstGtkWaylandSinkClass * klass)
141 GObjectClass *gobject_class = (GObjectClass *) klass;
142 GstElementClass *gstelement_class = (GstElementClass *) klass;
143 GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
144 GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass;
146 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_finalize);
147 gobject_class->get_property =
148 GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_get_property);
149 gobject_class->set_property =
150 GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_set_property);
152 g_object_class_install_property (gobject_class, PROP_WIDGET,
153 g_param_spec_object ("widget", "Gtk Widget",
154 "The GtkWidget to place in the widget hierarchy "
155 "(must only be get from the GTK main thread)",
157 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS |
158 GST_PARAM_DOC_SHOW_DEFAULT));
160 g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD,
161 g_param_spec_enum ("rotate-method",
164 GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,
165 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
167 gstelement_class->change_state =
168 GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_change_state);
170 gst_element_class_set_metadata (gstelement_class, "Gtk Wayland Video Sink",
172 "A video sink that renders to a GtkWidget using Wayland API",
173 "George Kiagiadakis <george.kiagiadakis@collabora.com>");
175 gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
177 gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_event);
178 gstbasesink_class->get_caps =
179 GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_get_caps);
180 gstbasesink_class->set_caps =
181 GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_set_caps);
182 gstbasesink_class->propose_allocation =
183 GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_propose_allocation);
185 gstvideosink_class->show_frame =
186 GST_DEBUG_FUNCPTR (gst_gtk_wayland_sink_show_frame);
190 gst_gtk_wayland_sink_init (GstGtkWaylandSink * self)
192 GstGtkWaylandSinkPrivate *priv =
193 gst_gtk_wayland_sink_get_instance_private (self);
195 g_mutex_init (&priv->display_lock);
196 g_mutex_init (&priv->render_lock);
200 gst_gtk_wayland_sink_finalize (GObject * object)
202 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
203 GstGtkWaylandSinkPrivate *priv =
204 gst_gtk_wayland_sink_get_instance_private (self);
206 g_clear_object (&priv->gtk_widget);
208 G_OBJECT_CLASS (parent_class)->finalize (object);
212 widget_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
214 GstGtkWaylandSinkPrivate *priv =
215 gst_gtk_wayland_sink_get_instance_private (self);
217 GST_OBJECT_LOCK (self);
218 g_clear_object (&priv->gtk_widget);
219 GST_OBJECT_UNLOCK (self);
223 window_destroy_cb (GtkWidget * widget, GstGtkWaylandSink * self)
225 GstGtkWaylandSinkPrivate *priv =
226 gst_gtk_wayland_sink_get_instance_private (self);
228 GST_OBJECT_LOCK (self);
229 priv->gtk_window = NULL;
230 GST_OBJECT_UNLOCK (self);
232 GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Window was closed"), (NULL));
236 widget_size_allocate_cb (GtkWidget * widget, GtkAllocation * allocation,
239 GstGtkWaylandSink *self = user_data;
240 GstGtkWaylandSinkPrivate *priv =
241 gst_gtk_wayland_sink_get_instance_private (self);
242 struct wl_subsurface *window_subsurface;
244 g_mutex_lock (&priv->render_lock);
246 priv->is_wl_window_sync = TRUE;
248 window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
249 if (window_subsurface)
250 wl_subsurface_set_sync (window_subsurface);
252 calculate_adjustment (priv->gtk_widget, allocation);
254 GST_DEBUG_OBJECT (self, "window geometry changed to (%d, %d) %d x %d",
255 allocation->x, allocation->y, allocation->width, allocation->height);
256 gst_wl_window_set_render_rectangle (priv->wl_window, allocation->x,
257 allocation->y, allocation->width, allocation->height);
259 g_mutex_unlock (&priv->render_lock);
265 window_after_after_paint_cb (GtkWidget * widget, gpointer user_data)
267 GstGtkWaylandSink *self = user_data;
268 GstGtkWaylandSinkPrivate *priv =
269 gst_gtk_wayland_sink_get_instance_private (self);
271 g_mutex_lock (&priv->render_lock);
273 if (priv->is_wl_window_sync) {
274 struct wl_subsurface *window_subsurface;
276 priv->is_wl_window_sync = FALSE;
278 window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
279 if (window_subsurface)
280 wl_subsurface_set_desync (window_subsurface);
283 g_mutex_unlock (&priv->render_lock);
289 gst_gtk_wayland_sink_get_widget (GstGtkWaylandSink * self)
291 GstGtkWaylandSinkPrivate *priv =
292 gst_gtk_wayland_sink_get_instance_private (self);
294 if (priv->gtk_widget != NULL)
295 return g_object_ref (priv->gtk_widget);
297 /* Ensure GTK is initialized, this has no side effect if it was already
298 * initialized. Also, we do that lazily, so the application can be first */
299 if (!gtk_init_check (NULL, NULL)) {
300 GST_INFO_OBJECT (self, "Could not ensure GTK initialization.");
304 priv->gtk_widget = gtk_gst_wayland_widget_new ();
305 gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (priv->gtk_widget),
308 /* Take the floating ref, other wise the destruction of the container will
309 * make this widget disappear possibly before we are done. */
310 g_object_ref_sink (priv->gtk_widget);
311 g_signal_connect_object (priv->gtk_widget, "destroy",
312 G_CALLBACK (widget_destroy_cb), self, 0);
314 return g_object_ref (priv->gtk_widget);
318 gst_gtk_wayland_sink_acquire_widget (GstGtkWaylandSink * self)
320 GstGtkWaylandSinkPrivate *priv =
321 gst_gtk_wayland_sink_get_instance_private (self);
322 gpointer widget = NULL;
324 GST_OBJECT_LOCK (self);
325 if (priv->gtk_widget != NULL)
326 widget = g_object_ref (priv->gtk_widget);
327 GST_OBJECT_UNLOCK (self);
331 gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_wayland_sink_get_widget,
338 gst_gtk_wayland_sink_event (GstBaseSink * sink, GstEvent * event)
340 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (sink);
342 GstVideoOrientationMethod method;
345 GST_DEBUG_OBJECT (self, "handling %s event", GST_EVENT_TYPE_NAME (event));
347 switch (GST_EVENT_TYPE (event)) {
349 gst_event_parse_tag (event, &taglist);
351 if (gst_video_orientation_from_tag (taglist, &method)) {
352 gst_gtk_wayland_sink_set_rotate_method (self, method, TRUE);
360 ret = GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
366 gst_gtk_wayland_sink_get_property (GObject * object, guint prop_id,
367 GValue * value, GParamSpec * pspec)
369 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
370 GstGtkWaylandSinkPrivate *priv =
371 gst_gtk_wayland_sink_get_instance_private (self);
375 g_value_take_object (value, gst_gtk_wayland_sink_acquire_widget (self));
377 case PROP_ROTATE_METHOD:
378 g_value_set_enum (value, priv->current_rotate_method);
381 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
387 gst_gtk_wayland_sink_set_property (GObject * object, guint prop_id,
388 const GValue * value, GParamSpec * pspec)
390 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
393 case PROP_ROTATE_METHOD:
394 gst_gtk_wayland_sink_set_rotate_method (self, g_value_get_enum (value),
398 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
404 calculate_adjustment (GtkWidget * widget, GtkAllocation * allocation)
409 window = gtk_widget_get_window (widget);
410 gdk_window_get_origin (window, &wx, &wy);
417 scrollable_window_adjustment_changed_cb (GtkAdjustment * adjustment,
420 GstGtkWaylandSink *self = user_data;
421 GstGtkWaylandSinkPrivate *priv =
422 gst_gtk_wayland_sink_get_instance_private (self);
423 GtkAllocation allocation;
425 gtk_widget_get_allocation (priv->gtk_widget, &allocation);
426 calculate_adjustment (priv->gtk_widget, &allocation);
427 gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
428 allocation.y, allocation.width, allocation.height);
434 setup_wl_window (GstGtkWaylandSink * self)
436 GstGtkWaylandSinkPrivate *priv =
437 gst_gtk_wayland_sink_get_instance_private (self);
438 GdkWindow *gdk_window;
439 GdkFrameClock *gdk_frame_clock;
440 GtkAllocation allocation;
443 g_mutex_lock (&priv->render_lock);
445 gdk_window = gtk_widget_get_window (priv->gtk_widget);
446 g_assert (gdk_window);
448 if (!priv->wl_window) {
449 struct wl_surface *wl_surface;
451 wl_surface = gdk_wayland_window_get_wl_surface (gdk_window);
453 GST_INFO_OBJECT (self, "setting window handle");
455 priv->wl_window = gst_wl_window_new_in_surface (priv->display,
456 wl_surface, &priv->render_lock);
457 gst_wl_window_set_rotate_method (priv->wl_window,
458 priv->current_rotate_method);
461 /* In order to position the subsurface correctly within a scrollable widget,
462 * we can not rely on the allocation alone but need to take the window
463 * origin into account
465 widget = priv->gtk_widget;
467 if (GTK_IS_SCROLLABLE (widget)) {
468 GtkAdjustment *hadjustment;
469 GtkAdjustment *vadjustment;
471 hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
472 vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
474 g_signal_connect (hadjustment, "value-changed",
475 G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
476 g_signal_connect (vadjustment, "value-changed",
477 G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
479 } while ((widget = gtk_widget_get_parent (widget)));
481 gtk_widget_get_allocation (priv->gtk_widget, &allocation);
482 calculate_adjustment (priv->gtk_widget, &allocation);
483 gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
484 allocation.y, allocation.width, allocation.height);
486 /* Make subsurfaces syncronous during resizes.
487 * Unfortunately GTK/GDK does not provide easier to use signals.
489 g_signal_connect (priv->gtk_widget, "size-allocate",
490 G_CALLBACK (widget_size_allocate_cb), self);
491 gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
492 g_signal_connect_after (gdk_frame_clock, "after-paint",
493 G_CALLBACK (window_after_after_paint_cb), self);
495 /* Ensure the base widget is initialized */
496 gtk_gst_base_widget_set_buffer (GTK_GST_BASE_WIDGET (priv->gtk_widget), NULL);
498 g_mutex_unlock (&priv->render_lock);
502 window_initial_map_cb (GtkWidget * widget, gpointer user_data)
504 GstGtkWaylandSink *self = user_data;
505 GstGtkWaylandSinkPrivate *priv =
506 gst_gtk_wayland_sink_get_instance_private (self);
508 setup_wl_window (self);
509 g_signal_handlers_disconnect_by_func (priv->gtk_widget,
510 window_initial_map_cb, self);
514 gst_gtk_wayland_sink_navigation_send_event (GstNavigation * navigation,
517 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (navigation);
521 event = gst_event_make_writable (event);
523 if (gst_navigation_event_get_coordinates (event, &x, &y)) {
524 GtkGstBaseWidget *widget =
525 GTK_GST_BASE_WIDGET (gst_gtk_wayland_sink_get_widget (self));
526 gdouble stream_x, stream_y;
528 if (widget == NULL) {
529 GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
533 gtk_gst_base_widget_display_size_to_stream_size (widget,
534 x, y, &stream_x, &stream_y);
535 gst_navigation_event_set_coordinates (event, stream_x, stream_y);
538 pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (self));
540 GST_TRACE_OBJECT (self, "navigation event %" GST_PTR_FORMAT,
541 gst_event_get_structure (event));
543 if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
544 if (!gst_pad_send_event (pad, gst_event_ref (event))) {
545 /* If upstream didn't handle the event we'll post a message with it
546 * for the application in case it wants to do something with it */
547 gst_element_post_message (GST_ELEMENT_CAST (self),
548 gst_navigation_message_new_event (GST_OBJECT_CAST (self), event));
550 gst_event_unref (event);
551 gst_object_unref (pad);
556 gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface)
558 iface->send_event_simple = gst_gtk_wayland_sink_navigation_send_event;
563 gst_gtk_wayland_sink_start_on_main (GstGtkWaylandSink * self)
565 GstGtkWaylandSinkPrivate *priv =
566 gst_gtk_wayland_sink_get_instance_private (self);
568 GdkDisplay *gdk_display;
569 struct wl_display *wl_display;
571 if ((toplevel = gst_gtk_wayland_sink_get_widget (self)) == NULL) {
572 GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
575 g_object_unref (toplevel);
577 /* After this point, priv->gtk_widget will always be set */
579 gdk_display = gtk_widget_get_display (priv->gtk_widget);
580 if (!GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
581 GST_ERROR_OBJECT (self, "GDK is not using its wayland backend.");
584 wl_display = gdk_wayland_display_get_wl_display (gdk_display);
585 priv->display = gst_wl_display_new_existing (wl_display, FALSE, NULL);
587 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (priv->gtk_widget));
588 if (!gtk_widget_is_toplevel (toplevel)) {
589 /* User did not add widget its own UI, let's popup a new GtkWindow to
590 * make gst-launch-1.0 work. */
591 priv->gtk_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
592 gtk_window_set_default_size (GTK_WINDOW (priv->gtk_window), 640, 480);
593 gtk_window_set_title (GTK_WINDOW (priv->gtk_window),
594 "Gst GTK Wayland Sink");
595 gtk_container_add (GTK_CONTAINER (priv->gtk_window), toplevel);
596 priv->gtk_window_destroy_id = g_signal_connect (priv->gtk_window, "destroy",
597 G_CALLBACK (window_destroy_cb), self);
599 g_signal_connect (priv->gtk_widget, "map",
600 G_CALLBACK (window_initial_map_cb), self);
602 if (gtk_widget_get_mapped (priv->gtk_widget)) {
603 setup_wl_window (self);
605 g_signal_connect (priv->gtk_widget, "map",
606 G_CALLBACK (window_initial_map_cb), self);
614 gst_gtk_wayland_sink_stop_on_main (GstGtkWaylandSink * self)
616 GstGtkWaylandSinkPrivate *priv =
617 gst_gtk_wayland_sink_get_instance_private (self);
619 if (priv->gtk_window) {
620 if (priv->gtk_window_destroy_id)
621 g_signal_handler_disconnect (priv->gtk_window,
622 priv->gtk_window_destroy_id);
623 priv->gtk_window_destroy_id = 0;
624 gtk_widget_destroy (priv->gtk_window);
625 priv->gtk_window = NULL;
628 if (priv->gtk_widget) {
630 GdkWindow *gdk_window;
632 g_signal_handlers_disconnect_by_func (priv->gtk_widget,
633 widget_size_allocate_cb, self);
635 widget = priv->gtk_widget;
637 if (GTK_IS_SCROLLABLE (widget)) {
638 GtkAdjustment *hadjustment;
639 GtkAdjustment *vadjustment;
641 hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
642 vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
644 g_signal_handlers_disconnect_by_func (hadjustment,
645 scrollable_window_adjustment_changed_cb, self);
646 g_signal_handlers_disconnect_by_func (vadjustment,
647 scrollable_window_adjustment_changed_cb, self);
649 } while ((widget = gtk_widget_get_parent (widget)));
651 gdk_window = gtk_widget_get_window (priv->gtk_widget);
653 GdkFrameClock *gdk_frame_clock;
655 gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
656 g_signal_handlers_disconnect_by_func (gdk_frame_clock,
657 window_after_after_paint_cb, self);
665 gst_gtk_widget_show_all_and_unref (GtkWidget * widget)
667 gtk_widget_show_all (widget);
668 g_object_unref (widget);
671 static GstStateChangeReturn
672 gst_gtk_wayland_sink_change_state (GstElement * element,
673 GstStateChange transition)
675 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (element);
676 GstGtkWaylandSinkPrivate *priv =
677 gst_gtk_wayland_sink_get_instance_private (self);
678 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
680 switch (transition) {
681 case GST_STATE_CHANGE_NULL_TO_READY:
682 if (!gst_gtk_invoke_on_main ((GThreadFunc)
683 gst_gtk_wayland_sink_start_on_main, element))
684 return GST_STATE_CHANGE_FAILURE;
686 case GST_STATE_CHANGE_READY_TO_PAUSED:
688 GtkWindow *window = NULL;
690 GST_OBJECT_LOCK (self);
691 if (priv->gtk_window)
692 window = g_object_ref (GTK_WINDOW (priv->gtk_window));
693 GST_OBJECT_UNLOCK (self);
696 gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_widget_show_all_and_unref,
705 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
706 if (ret != GST_STATE_CHANGE_SUCCESS)
709 switch (transition) {
710 case GST_STATE_CHANGE_READY_TO_NULL:
711 case GST_STATE_CHANGE_NULL_TO_NULL:
712 gst_gtk_invoke_on_main ((GThreadFunc)
713 gst_gtk_wayland_sink_stop_on_main, element);
715 case GST_STATE_CHANGE_PAUSED_TO_READY:
716 gst_buffer_replace (&priv->last_buffer, NULL);
717 if (priv->wl_window) {
718 /* remove buffer from surface, show nothing */
719 gst_wl_window_render (priv->wl_window, NULL, NULL);
722 g_mutex_lock (&priv->render_lock);
723 if (priv->callback) {
724 wl_callback_destroy (priv->callback);
725 priv->callback = NULL;
727 priv->redraw_pending = FALSE;
728 g_mutex_unlock (&priv->render_lock);
738 gst_gtk_wayland_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
740 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
741 GstGtkWaylandSinkPrivate *priv =
742 gst_gtk_wayland_sink_get_instance_private (self);
745 caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (self));
746 caps = gst_caps_make_writable (caps);
748 g_mutex_lock (&priv->display_lock);
751 GValue shm_list = G_VALUE_INIT, dmabuf_list = G_VALUE_INIT;
752 GValue value = G_VALUE_INIT;
758 g_value_init (&shm_list, GST_TYPE_LIST);
759 g_value_init (&dmabuf_list, GST_TYPE_LIST);
761 /* Add corresponding shm formats */
762 formats = gst_wl_display_get_shm_formats (priv->display);
763 for (i = 0; i < formats->len; i++) {
764 fmt = g_array_index (formats, uint32_t, i);
765 gfmt = gst_wl_shm_format_to_video_format (fmt);
766 if (gfmt != GST_VIDEO_FORMAT_UNKNOWN) {
767 g_value_init (&value, G_TYPE_STRING);
768 g_value_set_static_string (&value, gst_video_format_to_string (gfmt));
769 gst_value_list_append_and_take_value (&shm_list, &value);
773 gst_structure_take_value (gst_caps_get_structure (caps, 0), "format",
776 /* Add corresponding dmabuf formats */
777 formats = gst_wl_display_get_dmabuf_formats (priv->display);
778 for (i = 0; i < formats->len; i++) {
779 fmt = g_array_index (formats, uint32_t, i);
780 gfmt = gst_wl_dmabuf_format_to_video_format (fmt);
781 if (gfmt != GST_VIDEO_FORMAT_UNKNOWN) {
782 g_value_init (&value, G_TYPE_STRING);
783 g_value_set_static_string (&value, gst_video_format_to_string (gfmt));
784 gst_value_list_append_and_take_value (&dmabuf_list, &value);
788 gst_structure_take_value (gst_caps_get_structure (caps, 1), "format",
791 GST_DEBUG_OBJECT (self, "display caps: %" GST_PTR_FORMAT, caps);
794 g_mutex_unlock (&priv->display_lock);
797 GstCaps *intersection;
800 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
801 gst_caps_unref (caps);
808 static GstBufferPool *
809 gst_gtk_wayland_create_pool (GstGtkWaylandSink * self, GstCaps * caps)
811 GstGtkWaylandSinkPrivate *priv =
812 gst_gtk_wayland_sink_get_instance_private (self);
813 GstBufferPool *pool = NULL;
814 GstStructure *structure;
815 gsize size = priv->video_info.size;
818 pool = gst_wl_video_buffer_pool_new ();
820 structure = gst_buffer_pool_get_config (pool);
821 gst_buffer_pool_config_set_params (structure, caps, size, 2, 0);
823 alloc = gst_wl_shm_allocator_get ();
824 gst_buffer_pool_config_set_allocator (structure, alloc, NULL);
825 if (!gst_buffer_pool_set_config (pool, structure)) {
826 g_object_unref (pool);
829 g_object_unref (alloc);
835 gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
837 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
838 GstGtkWaylandSinkPrivate *priv =
839 gst_gtk_wayland_sink_get_instance_private (self);
841 GstVideoFormat format;
843 GST_DEBUG_OBJECT (self, "set caps %" GST_PTR_FORMAT, caps);
845 /* extract info from caps */
846 if (!gst_video_info_from_caps (&priv->video_info, caps))
849 format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
850 priv->video_info_changed = TRUE;
852 /* create a new pool for the new caps */
854 gst_object_unref (priv->pool);
855 priv->pool = gst_gtk_wayland_create_pool (self, caps);
857 use_dmabuf = gst_caps_features_contains (gst_caps_get_features (caps, 0),
858 GST_CAPS_FEATURE_MEMORY_DMABUF);
860 /* validate the format base on the memory type. */
862 if (!gst_wl_display_check_format_for_dmabuf (priv->display, format))
863 goto unsupported_format;
864 } else if (!gst_wl_display_check_format_for_shm (priv->display, format)) {
865 goto unsupported_format;
868 GST_OBJECT_LOCK (self);
870 if (priv->gtk_widget == NULL) {
871 GST_OBJECT_UNLOCK (self);
872 GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
873 ("Output widget was destroyed"), (NULL));
877 if (!gtk_gst_base_widget_set_format (GTK_GST_BASE_WIDGET (priv->gtk_widget),
878 &priv->video_info)) {
879 GST_OBJECT_UNLOCK (self);
882 GST_OBJECT_UNLOCK (self);
884 priv->use_dmabuf = use_dmabuf;
890 GST_ERROR_OBJECT (self,
891 "Could not locate image format from caps %" GST_PTR_FORMAT, caps);
896 GST_ERROR_OBJECT (self, "Format %s is not available on the display",
897 gst_video_format_to_string (format));
903 gst_gtk_wayland_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
905 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
906 GstGtkWaylandSinkPrivate *priv =
907 gst_gtk_wayland_sink_get_instance_private (self);
909 GstBufferPool *pool = NULL;
913 gst_query_parse_allocation (query, &caps, &need_pool);
916 pool = gst_gtk_wayland_create_pool (self, caps);
918 gst_query_add_allocation_pool (query, pool, priv->video_info.size, 2, 0);
920 g_object_unref (pool);
922 alloc = gst_wl_shm_allocator_get ();
923 gst_query_add_allocation_param (query, alloc, NULL);
924 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
925 g_object_unref (alloc);
931 frame_redraw_callback (void *data, struct wl_callback *callback, uint32_t time)
933 GstGtkWaylandSink *self = data;
934 GstGtkWaylandSinkPrivate *priv =
935 gst_gtk_wayland_sink_get_instance_private (self);
937 GST_LOG_OBJECT (self, "frame_redraw_cb");
939 g_mutex_lock (&priv->render_lock);
940 priv->redraw_pending = FALSE;
942 if (priv->callback) {
943 wl_callback_destroy (callback);
944 priv->callback = NULL;
946 g_mutex_unlock (&priv->render_lock);
949 static const struct wl_callback_listener frame_callback_listener = {
950 frame_redraw_callback
953 /* must be called with the render lock */
955 render_last_buffer (GstGtkWaylandSink * self, gboolean redraw)
957 GstGtkWaylandSinkPrivate *priv =
958 gst_gtk_wayland_sink_get_instance_private (self);
959 GstWlBuffer *wlbuffer;
960 const GstVideoInfo *info = NULL;
961 struct wl_surface *surface;
962 struct wl_callback *callback;
964 if (!priv->wl_window)
967 wlbuffer = gst_buffer_get_wl_buffer (priv->display, priv->last_buffer);
968 surface = gst_wl_window_get_wl_surface (priv->wl_window);
970 priv->redraw_pending = TRUE;
971 callback = wl_surface_frame (surface);
972 priv->callback = callback;
973 wl_callback_add_listener (callback, &frame_callback_listener, self);
975 if (G_UNLIKELY (priv->video_info_changed && !redraw)) {
976 info = &priv->video_info;
977 priv->video_info_changed = FALSE;
979 gst_wl_window_render (priv->wl_window, wlbuffer, info);
983 gst_gtk_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
985 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (vsink);
986 GstGtkWaylandSinkPrivate *priv =
987 gst_gtk_wayland_sink_get_instance_private (self);
988 GstBuffer *to_render;
989 GstWlBuffer *wlbuffer;
991 GstVideoFormat format;
992 GstVideoInfo old_vinfo;
994 struct wl_buffer *wbuf = NULL;
996 GstFlowReturn ret = GST_FLOW_OK;
998 g_mutex_lock (&priv->render_lock);
1000 GST_LOG_OBJECT (self, "render buffer %" GST_PTR_FORMAT "", buffer);
1002 if (!priv->wl_window) {
1003 GST_LOG_OBJECT (self,
1004 "buffer %" GST_PTR_FORMAT " dropped (waiting for window)", buffer);
1008 /* drop buffers until we get a frame callback */
1009 if (priv->redraw_pending) {
1010 GST_LOG_OBJECT (self, "buffer %" GST_PTR_FORMAT " dropped (redraw pending)",
1015 /* make sure that the application has called set_render_rectangle() */
1016 if (G_UNLIKELY (gst_wl_window_get_render_rectangle (priv->wl_window)->w == 0))
1017 goto no_window_size;
1019 wlbuffer = gst_buffer_get_wl_buffer (priv->display, buffer);
1021 if (G_LIKELY (wlbuffer &&
1022 gst_wl_buffer_get_display (wlbuffer) == priv->display)) {
1023 GST_LOG_OBJECT (self,
1024 "buffer %" GST_PTR_FORMAT " has a wl_buffer from our display, "
1025 "writing directly", buffer);
1030 /* update video info from video meta */
1031 mem = gst_buffer_peek_memory (buffer, 0);
1033 old_vinfo = priv->video_info;
1034 vmeta = gst_buffer_get_video_meta (buffer);
1038 for (i = 0; i < vmeta->n_planes; i++) {
1039 priv->video_info.offset[i] = vmeta->offset[i];
1040 priv->video_info.stride[i] = vmeta->stride[i];
1042 priv->video_info.size = gst_buffer_get_size (buffer);
1045 GST_LOG_OBJECT (self,
1046 "buffer %" GST_PTR_FORMAT " does not have a wl_buffer from our "
1047 "display, creating it", buffer);
1049 format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
1050 if (gst_wl_display_check_format_for_dmabuf (priv->display, format)) {
1051 guint i, nb_dmabuf = 0;
1053 for (i = 0; i < gst_buffer_n_memory (buffer); i++)
1054 if (gst_is_dmabuf_memory (gst_buffer_peek_memory (buffer, i)))
1057 if (nb_dmabuf && (nb_dmabuf == gst_buffer_n_memory (buffer)))
1058 wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (buffer, priv->display,
1062 if (!wbuf && gst_wl_display_check_format_for_shm (priv->display, format)) {
1063 if (gst_buffer_n_memory (buffer) == 1 && gst_is_fd_memory (mem))
1064 wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
1067 /* If nothing worked, copy into our internal pool */
1069 GstVideoFrame src, dst;
1070 GstVideoInfo src_info = priv->video_info;
1072 /* rollback video info changes */
1073 priv->video_info = old_vinfo;
1075 /* we don't know how to create a wl_buffer directly from the provided
1076 * memory, so we have to copy the data to shm memory that we know how
1079 GST_LOG_OBJECT (self,
1080 "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer, "
1081 "copying to wl_shm memory", buffer);
1083 /* priv->pool always exists (created in set_caps), but it may not
1084 * be active if upstream is not using it */
1085 if (!gst_buffer_pool_is_active (priv->pool)) {
1086 GstStructure *config;
1089 config = gst_buffer_pool_get_config (priv->pool);
1090 gst_buffer_pool_config_get_params (config, &caps, NULL, NULL, NULL);
1092 /* revert back to default strides and offsets */
1093 gst_video_info_from_caps (&priv->video_info, caps);
1094 gst_buffer_pool_config_set_params (config, caps, priv->video_info.size,
1097 /* This is a video pool, it should not fail with basic settings */
1098 if (!gst_buffer_pool_set_config (priv->pool, config) ||
1099 !gst_buffer_pool_set_active (priv->pool, TRUE))
1100 goto activate_failed;
1103 ret = gst_buffer_pool_acquire_buffer (priv->pool, &to_render, NULL);
1104 if (ret != GST_FLOW_OK)
1107 wlbuffer = gst_buffer_get_wl_buffer (priv->display, to_render);
1109 /* attach a wl_buffer if there isn't one yet */
1110 if (G_UNLIKELY (!wlbuffer)) {
1111 mem = gst_buffer_peek_memory (to_render, 0);
1112 wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
1115 if (G_UNLIKELY (!wbuf))
1116 goto no_wl_buffer_shm;
1118 wlbuffer = gst_buffer_add_wl_buffer (to_render, wbuf, priv->display);
1121 if (!gst_video_frame_map (&dst, &priv->video_info, to_render,
1123 goto dst_map_failed;
1125 if (!gst_video_frame_map (&src, &src_info, buffer, GST_MAP_READ)) {
1126 gst_video_frame_unmap (&dst);
1127 goto src_map_failed;
1130 gst_video_frame_copy (&dst, &src);
1132 gst_video_frame_unmap (&src);
1133 gst_video_frame_unmap (&dst);
1142 wlbuffer = gst_buffer_add_wl_buffer (buffer, wbuf, priv->display);
1146 /* drop double rendering */
1147 if (G_UNLIKELY (wlbuffer ==
1148 gst_buffer_get_wl_buffer (priv->display, priv->last_buffer))) {
1149 GST_LOG_OBJECT (self, "Buffer already being rendered");
1153 gst_buffer_replace (&priv->last_buffer, to_render);
1154 render_last_buffer (self, FALSE);
1156 if (buffer != to_render)
1157 gst_buffer_unref (to_render);
1162 GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
1163 ("Window has no size set"),
1164 ("Make sure you set the size after calling set_window_handle"));
1165 ret = GST_FLOW_ERROR;
1170 GST_WARNING_OBJECT (self, "could not create buffer");
1175 GST_ERROR_OBJECT (self, "could not create wl_buffer out of wl_shm memory");
1176 ret = GST_FLOW_ERROR;
1181 GST_ERROR_OBJECT (self,
1182 "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer", buffer);
1183 ret = GST_FLOW_ERROR;
1188 GST_ERROR_OBJECT (self, "failed to activate bufferpool.");
1189 ret = GST_FLOW_ERROR;
1194 GST_ELEMENT_ERROR (self, RESOURCE, READ,
1195 ("Video memory can not be read from userspace."), (NULL));
1196 ret = GST_FLOW_ERROR;
1201 GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
1202 ("Video memory can not be written from userspace."), (NULL));
1203 ret = GST_FLOW_ERROR;
1208 g_mutex_unlock (&priv->render_lock);
1214 gst_gtk_wayland_sink_set_rotate_method (GstGtkWaylandSink * self,
1215 GstVideoOrientationMethod method, gboolean from_tag)
1217 GstGtkWaylandSinkPrivate *priv =
1218 gst_gtk_wayland_sink_get_instance_private (self);
1219 GstVideoOrientationMethod new_method;
1221 if (method == GST_VIDEO_ORIENTATION_CUSTOM) {
1222 GST_WARNING_OBJECT (self, "unsupported custom orientation");
1226 GST_OBJECT_LOCK (self);
1228 priv->tag_rotate_method = method;
1230 priv->sink_rotate_method = method;
1232 if (priv->sink_rotate_method == GST_VIDEO_ORIENTATION_AUTO)
1233 new_method = priv->tag_rotate_method;
1235 new_method = priv->sink_rotate_method;
1237 if (new_method != priv->current_rotate_method) {
1238 GST_DEBUG_OBJECT (priv, "Changing method from %d to %d",
1239 priv->current_rotate_method, new_method);
1241 if (priv->wl_window) {
1242 g_mutex_lock (&priv->render_lock);
1243 gst_wl_window_set_rotate_method (priv->wl_window, new_method);
1244 g_mutex_unlock (&priv->render_lock);
1247 priv->current_rotate_method = new_method;
1249 GST_OBJECT_UNLOCK (self);