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 g_clear_object (&priv->wl_window);
230 priv->gtk_window = NULL;
231 GST_OBJECT_UNLOCK (self);
233 GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Window was closed"), (NULL));
237 widget_size_allocate_cb (GtkWidget * widget, GtkAllocation * allocation,
240 GstGtkWaylandSink *self = user_data;
241 GstGtkWaylandSinkPrivate *priv =
242 gst_gtk_wayland_sink_get_instance_private (self);
243 struct wl_subsurface *window_subsurface;
245 g_mutex_lock (&priv->render_lock);
247 priv->is_wl_window_sync = TRUE;
249 window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
250 if (window_subsurface)
251 wl_subsurface_set_sync (window_subsurface);
253 calculate_adjustment (priv->gtk_widget, allocation);
255 GST_DEBUG_OBJECT (self, "window geometry changed to (%d, %d) %d x %d",
256 allocation->x, allocation->y, allocation->width, allocation->height);
257 gst_wl_window_set_render_rectangle (priv->wl_window, allocation->x,
258 allocation->y, allocation->width, allocation->height);
260 g_mutex_unlock (&priv->render_lock);
266 window_after_after_paint_cb (GtkWidget * widget, gpointer user_data)
268 GstGtkWaylandSink *self = user_data;
269 GstGtkWaylandSinkPrivate *priv =
270 gst_gtk_wayland_sink_get_instance_private (self);
272 g_mutex_lock (&priv->render_lock);
274 if (priv->is_wl_window_sync) {
275 struct wl_subsurface *window_subsurface;
277 priv->is_wl_window_sync = FALSE;
279 window_subsurface = gst_wl_window_get_subsurface (priv->wl_window);
280 if (window_subsurface)
281 wl_subsurface_set_desync (window_subsurface);
284 g_mutex_unlock (&priv->render_lock);
290 gst_gtk_wayland_sink_get_widget (GstGtkWaylandSink * self)
292 GstGtkWaylandSinkPrivate *priv =
293 gst_gtk_wayland_sink_get_instance_private (self);
295 if (priv->gtk_widget != NULL)
296 return g_object_ref (priv->gtk_widget);
298 /* Ensure GTK is initialized, this has no side effect if it was already
299 * initialized. Also, we do that lazily, so the application can be first */
300 if (!gtk_init_check (NULL, NULL)) {
301 GST_INFO_OBJECT (self, "Could not ensure GTK initialization.");
305 priv->gtk_widget = gtk_gst_wayland_widget_new ();
306 gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (priv->gtk_widget),
309 /* Take the floating ref, other wise the destruction of the container will
310 * make this widget disappear possibly before we are done. */
311 g_object_ref_sink (priv->gtk_widget);
312 g_signal_connect_object (priv->gtk_widget, "destroy",
313 G_CALLBACK (widget_destroy_cb), self, 0);
315 return g_object_ref (priv->gtk_widget);
319 gst_gtk_wayland_sink_acquire_widget (GstGtkWaylandSink * self)
321 GstGtkWaylandSinkPrivate *priv =
322 gst_gtk_wayland_sink_get_instance_private (self);
323 gpointer widget = NULL;
325 GST_OBJECT_LOCK (self);
326 if (priv->gtk_widget != NULL)
327 widget = g_object_ref (priv->gtk_widget);
328 GST_OBJECT_UNLOCK (self);
332 gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_wayland_sink_get_widget,
339 gst_gtk_wayland_sink_event (GstBaseSink * sink, GstEvent * event)
341 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (sink);
343 GstVideoOrientationMethod method;
346 GST_DEBUG_OBJECT (self, "handling %s event", GST_EVENT_TYPE_NAME (event));
348 switch (GST_EVENT_TYPE (event)) {
350 gst_event_parse_tag (event, &taglist);
352 if (gst_video_orientation_from_tag (taglist, &method)) {
353 gst_gtk_wayland_sink_set_rotate_method (self, method, TRUE);
361 ret = GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
367 gst_gtk_wayland_sink_get_property (GObject * object, guint prop_id,
368 GValue * value, GParamSpec * pspec)
370 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
371 GstGtkWaylandSinkPrivate *priv =
372 gst_gtk_wayland_sink_get_instance_private (self);
376 g_value_take_object (value, gst_gtk_wayland_sink_acquire_widget (self));
378 case PROP_ROTATE_METHOD:
379 g_value_set_enum (value, priv->current_rotate_method);
382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
388 gst_gtk_wayland_sink_set_property (GObject * object, guint prop_id,
389 const GValue * value, GParamSpec * pspec)
391 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (object);
394 case PROP_ROTATE_METHOD:
395 gst_gtk_wayland_sink_set_rotate_method (self, g_value_get_enum (value),
399 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
405 calculate_adjustment (GtkWidget * widget, GtkAllocation * allocation)
410 window = gtk_widget_get_window (widget);
411 gdk_window_get_origin (window, &wx, &wy);
418 scrollable_window_adjustment_changed_cb (GtkAdjustment * adjustment,
421 GstGtkWaylandSink *self = user_data;
422 GstGtkWaylandSinkPrivate *priv =
423 gst_gtk_wayland_sink_get_instance_private (self);
424 GtkAllocation allocation;
426 gtk_widget_get_allocation (priv->gtk_widget, &allocation);
427 calculate_adjustment (priv->gtk_widget, &allocation);
428 gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
429 allocation.y, allocation.width, allocation.height);
435 wl_window_map_cb (GstWlWindow * wl_window, GstGtkWaylandSink * self)
437 GstGtkWaylandSinkPrivate *priv =
438 gst_gtk_wayland_sink_get_instance_private (self);
440 GST_DEBUG_OBJECT (self, "waylandsink surface is ready");
442 gtk_gst_base_widget_queue_draw (GTK_GST_BASE_WIDGET (priv->gtk_widget));
446 setup_wl_window (GstGtkWaylandSink * self)
448 GstGtkWaylandSinkPrivate *priv =
449 gst_gtk_wayland_sink_get_instance_private (self);
450 GdkWindow *gdk_window;
451 GdkFrameClock *gdk_frame_clock;
452 GtkAllocation allocation;
455 g_mutex_lock (&priv->render_lock);
457 gdk_window = gtk_widget_get_window (priv->gtk_widget);
458 g_assert (gdk_window);
460 if (!priv->wl_window) {
461 struct wl_surface *wl_surface;
463 wl_surface = gdk_wayland_window_get_wl_surface (gdk_window);
465 GST_INFO_OBJECT (self, "setting window handle");
467 priv->wl_window = gst_wl_window_new_in_surface (priv->display,
468 wl_surface, &priv->render_lock);
469 gst_wl_window_set_rotate_method (priv->wl_window,
470 priv->current_rotate_method);
471 g_signal_connect_object (priv->wl_window, "map",
472 G_CALLBACK (wl_window_map_cb), self, 0);
475 /* In order to position the subsurface correctly within a scrollable widget,
476 * we can not rely on the allocation alone but need to take the window
477 * origin into account
479 widget = priv->gtk_widget;
481 if (GTK_IS_SCROLLABLE (widget)) {
482 GtkAdjustment *hadjustment;
483 GtkAdjustment *vadjustment;
485 hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
486 vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
488 g_signal_connect (hadjustment, "value-changed",
489 G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
490 g_signal_connect (vadjustment, "value-changed",
491 G_CALLBACK (scrollable_window_adjustment_changed_cb), self);
493 } while ((widget = gtk_widget_get_parent (widget)));
495 gtk_widget_get_allocation (priv->gtk_widget, &allocation);
496 calculate_adjustment (priv->gtk_widget, &allocation);
497 gst_wl_window_set_render_rectangle (priv->wl_window, allocation.x,
498 allocation.y, allocation.width, allocation.height);
500 /* Make subsurfaces syncronous during resizes.
501 * Unfortunately GTK/GDK does not provide easier to use signals.
503 g_signal_connect (priv->gtk_widget, "size-allocate",
504 G_CALLBACK (widget_size_allocate_cb), self);
505 gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
506 g_signal_connect_after (gdk_frame_clock, "after-paint",
507 G_CALLBACK (window_after_after_paint_cb), self);
509 /* Ensure the base widget is initialized */
510 gtk_gst_base_widget_set_buffer (GTK_GST_BASE_WIDGET (priv->gtk_widget), NULL);
512 g_mutex_unlock (&priv->render_lock);
516 window_initial_map_cb (GtkWidget * widget, gpointer user_data)
518 GstGtkWaylandSink *self = user_data;
519 GstGtkWaylandSinkPrivate *priv =
520 gst_gtk_wayland_sink_get_instance_private (self);
522 setup_wl_window (self);
523 g_signal_handlers_disconnect_by_func (priv->gtk_widget,
524 window_initial_map_cb, self);
528 gst_gtk_wayland_sink_navigation_send_event (GstNavigation * navigation,
531 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (navigation);
535 event = gst_event_make_writable (event);
537 if (gst_navigation_event_get_coordinates (event, &x, &y)) {
538 GtkGstBaseWidget *widget =
539 GTK_GST_BASE_WIDGET (gst_gtk_wayland_sink_get_widget (self));
540 gdouble stream_x, stream_y;
542 if (widget == NULL) {
543 GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
547 gtk_gst_base_widget_display_size_to_stream_size (widget,
548 x, y, &stream_x, &stream_y);
549 gst_navigation_event_set_coordinates (event, stream_x, stream_y);
552 pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (self));
554 GST_TRACE_OBJECT (self, "navigation event %" GST_PTR_FORMAT,
555 gst_event_get_structure (event));
557 if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
558 if (!gst_pad_send_event (pad, gst_event_ref (event))) {
559 /* If upstream didn't handle the event we'll post a message with it
560 * for the application in case it wants to do something with it */
561 gst_element_post_message (GST_ELEMENT_CAST (self),
562 gst_navigation_message_new_event (GST_OBJECT_CAST (self), event));
564 gst_event_unref (event);
565 gst_object_unref (pad);
570 gst_gtk_wayland_sink_navigation_interface_init (GstNavigationInterface * iface)
572 iface->send_event_simple = gst_gtk_wayland_sink_navigation_send_event;
577 gst_gtk_wayland_sink_start_on_main (GstGtkWaylandSink * self)
579 GstGtkWaylandSinkPrivate *priv =
580 gst_gtk_wayland_sink_get_instance_private (self);
582 GdkDisplay *gdk_display;
583 struct wl_display *wl_display;
585 if ((toplevel = gst_gtk_wayland_sink_get_widget (self)) == NULL) {
586 GST_ERROR_OBJECT (self, "Could not ensure GTK initialization.");
589 g_object_unref (toplevel);
591 /* After this point, priv->gtk_widget will always be set */
593 gdk_display = gtk_widget_get_display (priv->gtk_widget);
594 if (!GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
595 GST_ERROR_OBJECT (self, "GDK is not using its wayland backend.");
598 wl_display = gdk_wayland_display_get_wl_display (gdk_display);
599 priv->display = gst_wl_display_new_existing (wl_display, FALSE, NULL);
601 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (priv->gtk_widget));
602 if (!gtk_widget_is_toplevel (toplevel)) {
603 /* User did not add widget its own UI, let's popup a new GtkWindow to
604 * make gst-launch-1.0 work. */
605 priv->gtk_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
606 gtk_window_set_default_size (GTK_WINDOW (priv->gtk_window), 640, 480);
607 gtk_window_set_title (GTK_WINDOW (priv->gtk_window),
608 "Gst GTK Wayland Sink");
609 gtk_container_add (GTK_CONTAINER (priv->gtk_window), toplevel);
610 priv->gtk_window_destroy_id = g_signal_connect (priv->gtk_window, "destroy",
611 G_CALLBACK (window_destroy_cb), self);
613 g_signal_connect (priv->gtk_widget, "map",
614 G_CALLBACK (window_initial_map_cb), self);
616 if (gtk_widget_get_mapped (priv->gtk_widget)) {
617 setup_wl_window (self);
619 g_signal_connect (priv->gtk_widget, "map",
620 G_CALLBACK (window_initial_map_cb), self);
628 gst_gtk_wayland_sink_stop_on_main (GstGtkWaylandSink * self)
630 GstGtkWaylandSinkPrivate *priv =
631 gst_gtk_wayland_sink_get_instance_private (self);
633 if (priv->gtk_window) {
634 if (priv->gtk_window_destroy_id)
635 g_signal_handler_disconnect (priv->gtk_window,
636 priv->gtk_window_destroy_id);
637 priv->gtk_window_destroy_id = 0;
638 g_clear_object (&priv->wl_window);
639 gtk_widget_destroy (priv->gtk_window);
640 priv->gtk_window = NULL;
643 if (priv->gtk_widget) {
645 GdkWindow *gdk_window;
647 g_signal_handlers_disconnect_by_func (priv->gtk_widget,
648 widget_size_allocate_cb, self);
650 widget = priv->gtk_widget;
652 if (GTK_IS_SCROLLABLE (widget)) {
653 GtkAdjustment *hadjustment;
654 GtkAdjustment *vadjustment;
656 hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
657 vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
659 g_signal_handlers_disconnect_by_func (hadjustment,
660 scrollable_window_adjustment_changed_cb, self);
661 g_signal_handlers_disconnect_by_func (vadjustment,
662 scrollable_window_adjustment_changed_cb, self);
664 } while ((widget = gtk_widget_get_parent (widget)));
666 gdk_window = gtk_widget_get_window (priv->gtk_widget);
668 GdkFrameClock *gdk_frame_clock;
670 gdk_frame_clock = gdk_window_get_frame_clock (gdk_window);
671 g_signal_handlers_disconnect_by_func (gdk_frame_clock,
672 window_after_after_paint_cb, self);
680 gst_gtk_widget_show_all_and_unref (GtkWidget * widget)
682 gtk_widget_show_all (widget);
683 g_object_unref (widget);
686 static GstStateChangeReturn
687 gst_gtk_wayland_sink_change_state (GstElement * element,
688 GstStateChange transition)
690 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (element);
691 GstGtkWaylandSinkPrivate *priv =
692 gst_gtk_wayland_sink_get_instance_private (self);
693 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
695 switch (transition) {
696 case GST_STATE_CHANGE_NULL_TO_READY:
697 if (!gst_gtk_invoke_on_main ((GThreadFunc)
698 gst_gtk_wayland_sink_start_on_main, element))
699 return GST_STATE_CHANGE_FAILURE;
701 case GST_STATE_CHANGE_READY_TO_PAUSED:
703 GtkWindow *window = NULL;
705 GST_OBJECT_LOCK (self);
706 if (priv->gtk_window)
707 window = g_object_ref (GTK_WINDOW (priv->gtk_window));
708 GST_OBJECT_UNLOCK (self);
711 gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_widget_show_all_and_unref,
720 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
721 if (ret != GST_STATE_CHANGE_SUCCESS)
724 switch (transition) {
725 case GST_STATE_CHANGE_READY_TO_NULL:
726 case GST_STATE_CHANGE_NULL_TO_NULL:
727 gst_gtk_invoke_on_main ((GThreadFunc)
728 gst_gtk_wayland_sink_stop_on_main, element);
730 case GST_STATE_CHANGE_PAUSED_TO_READY:
731 gst_buffer_replace (&priv->last_buffer, NULL);
732 if (priv->wl_window) {
733 /* remove buffer from surface, show nothing */
734 gst_wl_window_render (priv->wl_window, NULL, NULL);
737 g_mutex_lock (&priv->render_lock);
738 if (priv->callback) {
739 wl_callback_destroy (priv->callback);
740 priv->callback = NULL;
742 priv->redraw_pending = FALSE;
743 g_mutex_unlock (&priv->render_lock);
753 gst_gtk_wayland_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
755 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
756 GstGtkWaylandSinkPrivate *priv =
757 gst_gtk_wayland_sink_get_instance_private (self);
760 caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (self));
761 caps = gst_caps_make_writable (caps);
763 g_mutex_lock (&priv->display_lock);
766 GValue shm_list = G_VALUE_INIT, dmabuf_list = G_VALUE_INIT;
767 GValue value = G_VALUE_INIT;
773 g_value_init (&shm_list, GST_TYPE_LIST);
774 g_value_init (&dmabuf_list, GST_TYPE_LIST);
776 /* Add corresponding shm formats */
777 formats = gst_wl_display_get_shm_formats (priv->display);
778 for (i = 0; i < formats->len; i++) {
779 fmt = g_array_index (formats, uint32_t, i);
780 gfmt = gst_wl_shm_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 (&shm_list, &value);
788 gst_structure_take_value (gst_caps_get_structure (caps, 0), "format",
791 /* Add corresponding dmabuf formats */
792 formats = gst_wl_display_get_dmabuf_formats (priv->display);
793 for (i = 0; i < formats->len; i++) {
794 fmt = g_array_index (formats, uint32_t, i);
795 gfmt = gst_wl_dmabuf_format_to_video_format (fmt);
796 if (gfmt != GST_VIDEO_FORMAT_UNKNOWN) {
797 g_value_init (&value, G_TYPE_STRING);
798 g_value_set_static_string (&value, gst_video_format_to_string (gfmt));
799 gst_value_list_append_and_take_value (&dmabuf_list, &value);
803 gst_structure_take_value (gst_caps_get_structure (caps, 1), "format",
806 GST_DEBUG_OBJECT (self, "display caps: %" GST_PTR_FORMAT, caps);
809 g_mutex_unlock (&priv->display_lock);
812 GstCaps *intersection;
815 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
816 gst_caps_unref (caps);
823 static GstBufferPool *
824 gst_gtk_wayland_create_pool (GstGtkWaylandSink * self, GstCaps * caps)
826 GstGtkWaylandSinkPrivate *priv =
827 gst_gtk_wayland_sink_get_instance_private (self);
828 GstBufferPool *pool = NULL;
829 GstStructure *structure;
830 gsize size = priv->video_info.size;
833 pool = gst_wl_video_buffer_pool_new ();
835 structure = gst_buffer_pool_get_config (pool);
836 gst_buffer_pool_config_set_params (structure, caps, size, 2, 0);
838 alloc = gst_wl_shm_allocator_get ();
839 gst_buffer_pool_config_set_allocator (structure, alloc, NULL);
840 if (!gst_buffer_pool_set_config (pool, structure)) {
841 g_object_unref (pool);
844 g_object_unref (alloc);
850 gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
852 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
853 GstGtkWaylandSinkPrivate *priv =
854 gst_gtk_wayland_sink_get_instance_private (self);
856 GstVideoFormat format;
858 GST_DEBUG_OBJECT (self, "set caps %" GST_PTR_FORMAT, caps);
860 /* extract info from caps */
861 if (!gst_video_info_from_caps (&priv->video_info, caps))
864 format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
865 priv->video_info_changed = TRUE;
867 /* create a new pool for the new caps */
869 gst_object_unref (priv->pool);
870 priv->pool = gst_gtk_wayland_create_pool (self, caps);
872 use_dmabuf = gst_caps_features_contains (gst_caps_get_features (caps, 0),
873 GST_CAPS_FEATURE_MEMORY_DMABUF);
875 /* validate the format base on the memory type. */
877 if (!gst_wl_display_check_format_for_dmabuf (priv->display, format))
878 goto unsupported_format;
879 } else if (!gst_wl_display_check_format_for_shm (priv->display, format)) {
880 goto unsupported_format;
883 GST_OBJECT_LOCK (self);
885 if (priv->gtk_widget == NULL) {
886 GST_OBJECT_UNLOCK (self);
887 GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
888 ("Output widget was destroyed"), (NULL));
892 if (!gtk_gst_base_widget_set_format (GTK_GST_BASE_WIDGET (priv->gtk_widget),
893 &priv->video_info)) {
894 GST_OBJECT_UNLOCK (self);
897 GST_OBJECT_UNLOCK (self);
899 priv->use_dmabuf = use_dmabuf;
905 GST_ERROR_OBJECT (self,
906 "Could not locate image format from caps %" GST_PTR_FORMAT, caps);
911 GST_ERROR_OBJECT (self, "Format %s is not available on the display",
912 gst_video_format_to_string (format));
918 gst_gtk_wayland_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
920 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (bsink);
921 GstGtkWaylandSinkPrivate *priv =
922 gst_gtk_wayland_sink_get_instance_private (self);
924 GstBufferPool *pool = NULL;
928 gst_query_parse_allocation (query, &caps, &need_pool);
931 pool = gst_gtk_wayland_create_pool (self, caps);
933 gst_query_add_allocation_pool (query, pool, priv->video_info.size, 2, 0);
935 g_object_unref (pool);
937 alloc = gst_wl_shm_allocator_get ();
938 gst_query_add_allocation_param (query, alloc, NULL);
939 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
940 g_object_unref (alloc);
946 frame_redraw_callback (void *data, struct wl_callback *callback, uint32_t time)
948 GstGtkWaylandSink *self = data;
949 GstGtkWaylandSinkPrivate *priv =
950 gst_gtk_wayland_sink_get_instance_private (self);
952 GST_LOG_OBJECT (self, "frame_redraw_cb");
954 g_mutex_lock (&priv->render_lock);
955 priv->redraw_pending = FALSE;
957 if (priv->callback) {
958 wl_callback_destroy (callback);
959 priv->callback = NULL;
961 g_mutex_unlock (&priv->render_lock);
964 static const struct wl_callback_listener frame_callback_listener = {
965 frame_redraw_callback
968 /* must be called with the render lock */
970 render_last_buffer (GstGtkWaylandSink * self, gboolean redraw)
972 GstGtkWaylandSinkPrivate *priv =
973 gst_gtk_wayland_sink_get_instance_private (self);
974 GstWlBuffer *wlbuffer;
975 const GstVideoInfo *info = NULL;
976 struct wl_surface *surface;
977 struct wl_callback *callback;
979 if (!priv->wl_window)
982 wlbuffer = gst_buffer_get_wl_buffer (priv->display, priv->last_buffer);
983 surface = gst_wl_window_get_wl_surface (priv->wl_window);
985 priv->redraw_pending = TRUE;
986 callback = wl_surface_frame (surface);
987 priv->callback = callback;
988 wl_callback_add_listener (callback, &frame_callback_listener, self);
990 if (G_UNLIKELY (priv->video_info_changed && !redraw)) {
991 info = &priv->video_info;
992 priv->video_info_changed = FALSE;
994 gst_wl_window_render (priv->wl_window, wlbuffer, info);
998 gst_gtk_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
1000 GstGtkWaylandSink *self = GST_GTK_WAYLAND_SINK (vsink);
1001 GstGtkWaylandSinkPrivate *priv =
1002 gst_gtk_wayland_sink_get_instance_private (self);
1003 GstBuffer *to_render;
1004 GstWlBuffer *wlbuffer;
1005 GstVideoMeta *vmeta;
1006 GstVideoFormat format;
1007 GstVideoInfo old_vinfo;
1009 struct wl_buffer *wbuf = NULL;
1011 GstFlowReturn ret = GST_FLOW_OK;
1013 g_mutex_lock (&priv->render_lock);
1015 GST_LOG_OBJECT (self, "render buffer %" GST_PTR_FORMAT "", buffer);
1017 if (!priv->wl_window) {
1018 GST_LOG_OBJECT (self,
1019 "buffer %" GST_PTR_FORMAT " dropped (waiting for window)", buffer);
1023 /* drop buffers until we get a frame callback */
1024 if (priv->redraw_pending) {
1025 GST_LOG_OBJECT (self, "buffer %" GST_PTR_FORMAT " dropped (redraw pending)",
1030 /* make sure that the application has called set_render_rectangle() */
1031 if (G_UNLIKELY (gst_wl_window_get_render_rectangle (priv->wl_window)->w == 0))
1032 goto no_window_size;
1034 wlbuffer = gst_buffer_get_wl_buffer (priv->display, buffer);
1036 if (G_LIKELY (wlbuffer &&
1037 gst_wl_buffer_get_display (wlbuffer) == priv->display)) {
1038 GST_LOG_OBJECT (self,
1039 "buffer %" GST_PTR_FORMAT " has a wl_buffer from our display, "
1040 "writing directly", buffer);
1045 /* update video info from video meta */
1046 mem = gst_buffer_peek_memory (buffer, 0);
1048 old_vinfo = priv->video_info;
1049 vmeta = gst_buffer_get_video_meta (buffer);
1053 for (i = 0; i < vmeta->n_planes; i++) {
1054 priv->video_info.offset[i] = vmeta->offset[i];
1055 priv->video_info.stride[i] = vmeta->stride[i];
1057 priv->video_info.size = gst_buffer_get_size (buffer);
1060 GST_LOG_OBJECT (self,
1061 "buffer %" GST_PTR_FORMAT " does not have a wl_buffer from our "
1062 "display, creating it", buffer);
1064 format = GST_VIDEO_INFO_FORMAT (&priv->video_info);
1065 if (gst_wl_display_check_format_for_dmabuf (priv->display, format)) {
1066 guint i, nb_dmabuf = 0;
1068 for (i = 0; i < gst_buffer_n_memory (buffer); i++)
1069 if (gst_is_dmabuf_memory (gst_buffer_peek_memory (buffer, i)))
1072 if (nb_dmabuf && (nb_dmabuf == gst_buffer_n_memory (buffer)))
1073 wbuf = gst_wl_linux_dmabuf_construct_wl_buffer (buffer, priv->display,
1077 if (!wbuf && gst_wl_display_check_format_for_shm (priv->display, format)) {
1078 if (gst_buffer_n_memory (buffer) == 1 && gst_is_fd_memory (mem))
1079 wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
1082 /* If nothing worked, copy into our internal pool */
1084 GstVideoFrame src, dst;
1085 GstVideoInfo src_info = priv->video_info;
1087 /* rollback video info changes */
1088 priv->video_info = old_vinfo;
1090 /* we don't know how to create a wl_buffer directly from the provided
1091 * memory, so we have to copy the data to shm memory that we know how
1094 GST_LOG_OBJECT (self,
1095 "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer, "
1096 "copying to wl_shm memory", buffer);
1098 /* priv->pool always exists (created in set_caps), but it may not
1099 * be active if upstream is not using it */
1100 if (!gst_buffer_pool_is_active (priv->pool)) {
1101 GstStructure *config;
1104 config = gst_buffer_pool_get_config (priv->pool);
1105 gst_buffer_pool_config_get_params (config, &caps, NULL, NULL, NULL);
1107 /* revert back to default strides and offsets */
1108 gst_video_info_from_caps (&priv->video_info, caps);
1109 gst_buffer_pool_config_set_params (config, caps, priv->video_info.size,
1112 /* This is a video pool, it should not fail with basic settings */
1113 if (!gst_buffer_pool_set_config (priv->pool, config) ||
1114 !gst_buffer_pool_set_active (priv->pool, TRUE))
1115 goto activate_failed;
1118 ret = gst_buffer_pool_acquire_buffer (priv->pool, &to_render, NULL);
1119 if (ret != GST_FLOW_OK)
1122 wlbuffer = gst_buffer_get_wl_buffer (priv->display, to_render);
1124 /* attach a wl_buffer if there isn't one yet */
1125 if (G_UNLIKELY (!wlbuffer)) {
1126 mem = gst_buffer_peek_memory (to_render, 0);
1127 wbuf = gst_wl_shm_memory_construct_wl_buffer (mem, priv->display,
1130 if (G_UNLIKELY (!wbuf))
1131 goto no_wl_buffer_shm;
1133 wlbuffer = gst_buffer_add_wl_buffer (to_render, wbuf, priv->display);
1136 if (!gst_video_frame_map (&dst, &priv->video_info, to_render,
1138 goto dst_map_failed;
1140 if (!gst_video_frame_map (&src, &src_info, buffer, GST_MAP_READ)) {
1141 gst_video_frame_unmap (&dst);
1142 goto src_map_failed;
1145 gst_video_frame_copy (&dst, &src);
1147 gst_video_frame_unmap (&src);
1148 gst_video_frame_unmap (&dst);
1157 wlbuffer = gst_buffer_add_wl_buffer (buffer, wbuf, priv->display);
1161 /* drop double rendering */
1162 if (G_UNLIKELY (wlbuffer ==
1163 gst_buffer_get_wl_buffer (priv->display, priv->last_buffer))) {
1164 GST_LOG_OBJECT (self, "Buffer already being rendered");
1168 gst_buffer_replace (&priv->last_buffer, to_render);
1169 render_last_buffer (self, FALSE);
1171 if (buffer != to_render)
1172 gst_buffer_unref (to_render);
1177 GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
1178 ("Window has no size set"),
1179 ("Make sure you set the size after calling set_window_handle"));
1180 ret = GST_FLOW_ERROR;
1185 GST_WARNING_OBJECT (self, "could not create buffer");
1190 GST_ERROR_OBJECT (self, "could not create wl_buffer out of wl_shm memory");
1191 ret = GST_FLOW_ERROR;
1196 GST_ERROR_OBJECT (self,
1197 "buffer %" GST_PTR_FORMAT " cannot have a wl_buffer", buffer);
1198 ret = GST_FLOW_ERROR;
1203 GST_ERROR_OBJECT (self, "failed to activate bufferpool.");
1204 ret = GST_FLOW_ERROR;
1209 GST_ELEMENT_ERROR (self, RESOURCE, READ,
1210 ("Video memory can not be read from userspace."), (NULL));
1211 ret = GST_FLOW_ERROR;
1216 GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
1217 ("Video memory can not be written from userspace."), (NULL));
1218 ret = GST_FLOW_ERROR;
1223 g_mutex_unlock (&priv->render_lock);
1229 gst_gtk_wayland_sink_set_rotate_method (GstGtkWaylandSink * self,
1230 GstVideoOrientationMethod method, gboolean from_tag)
1232 GstGtkWaylandSinkPrivate *priv =
1233 gst_gtk_wayland_sink_get_instance_private (self);
1234 GstVideoOrientationMethod new_method;
1236 if (method == GST_VIDEO_ORIENTATION_CUSTOM) {
1237 GST_WARNING_OBJECT (self, "unsupported custom orientation");
1241 GST_OBJECT_LOCK (self);
1243 priv->tag_rotate_method = method;
1245 priv->sink_rotate_method = method;
1247 if (priv->sink_rotate_method == GST_VIDEO_ORIENTATION_AUTO)
1248 new_method = priv->tag_rotate_method;
1250 new_method = priv->sink_rotate_method;
1252 if (new_method != priv->current_rotate_method) {
1253 GST_DEBUG_OBJECT (priv, "Changing method from %d to %d",
1254 priv->current_rotate_method, new_method);
1256 if (priv->wl_window) {
1257 g_mutex_lock (&priv->render_lock);
1258 gst_wl_window_set_rotate_method (priv->wl_window, new_method);
1259 g_mutex_unlock (&priv->render_lock);
1262 priv->current_rotate_method = new_method;
1264 GST_OBJECT_UNLOCK (self);