From 2ce44d4e63907e66122c4ce9c66b856a44d26d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Fri, 29 Jan 2021 17:27:39 +0100 Subject: [PATCH] Combine GStreamer GTK4 plugin with API Ship custom gtk4glsink plugin as part of API insead of normal gstreamer plugin. This avoids gstreamer plugin registry conflicts with gtk3 plugin and allows more customization. --- lib/gst/clapper/clapper.h | 1 + lib/gst/clapper/gstclapper-gtk4-plugin.c | 145 ++++++ lib/gst/clapper/gstclapper-gtk4-plugin.h | 86 ++++ lib/gst/clapper/gtk4/gstgtkbasesink.c | 556 +++++++++++++++++++++ lib/gst/clapper/gtk4/gstgtkbasesink.h | 96 ++++ lib/gst/clapper/gtk4/gstgtkglsink.c | 387 +++++++++++++++ lib/gst/clapper/gtk4/gstgtkglsink.h | 65 +++ lib/gst/clapper/gtk4/gstgtkutils.c | 71 +++ lib/gst/clapper/gtk4/gstgtkutils.h | 29 ++ lib/gst/clapper/gtk4/gtkconfig.h | 31 ++ lib/gst/clapper/gtk4/gtkgstbasewidget.c | 605 +++++++++++++++++++++++ lib/gst/clapper/gtk4/gtkgstbasewidget.h | 101 ++++ lib/gst/clapper/gtk4/gtkgstglwidget.c | 592 ++++++++++++++++++++++ lib/gst/clapper/gtk4/gtkgstglwidget.h | 77 +++ lib/gst/clapper/meson.build | 55 ++- lib/meson.build | 79 ++- src/playerBase.js | 18 +- 17 files changed, 2964 insertions(+), 30 deletions(-) create mode 100644 lib/gst/clapper/gstclapper-gtk4-plugin.c create mode 100644 lib/gst/clapper/gstclapper-gtk4-plugin.h create mode 100644 lib/gst/clapper/gtk4/gstgtkbasesink.c create mode 100644 lib/gst/clapper/gtk4/gstgtkbasesink.h create mode 100644 lib/gst/clapper/gtk4/gstgtkglsink.c create mode 100644 lib/gst/clapper/gtk4/gstgtkglsink.h create mode 100644 lib/gst/clapper/gtk4/gstgtkutils.c create mode 100644 lib/gst/clapper/gtk4/gstgtkutils.h create mode 100644 lib/gst/clapper/gtk4/gtkconfig.h create mode 100644 lib/gst/clapper/gtk4/gtkgstbasewidget.c create mode 100644 lib/gst/clapper/gtk4/gtkgstbasewidget.h create mode 100644 lib/gst/clapper/gtk4/gtkgstglwidget.c create mode 100644 lib/gst/clapper/gtk4/gtkgstglwidget.h diff --git a/lib/gst/clapper/clapper.h b/lib/gst/clapper/clapper.h index d81739f4..eb8c483c 100644 --- a/lib/gst/clapper/clapper.h +++ b/lib/gst/clapper/clapper.h @@ -28,5 +28,6 @@ #include #include #include +#include #endif /* __CLAPPER_H__ */ diff --git a/lib/gst/clapper/gstclapper-gtk4-plugin.c b/lib/gst/clapper/gstclapper-gtk4-plugin.c new file mode 100644 index 00000000..5a38378f --- /dev/null +++ b/lib/gst/clapper/gstclapper-gtk4-plugin.c @@ -0,0 +1,145 @@ +/* GStreamer + * + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gstclapper-gtk4plugin + * @title: GstClapperGtk4Plugin + * @short_description: Clapper GTK4 plugin + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstclapper-gtk4-plugin.h" +#include "gtk4/gstgtkglsink.h" + +enum +{ + PROP_0, + PROP_VIDEO_SINK, + PROP_LAST +}; + +#define parent_class gst_clapper_gtk4_plugin_parent_class +G_DEFINE_TYPE_WITH_CODE (GstClapperGtk4Plugin, gst_clapper_gtk4_plugin, + G_TYPE_OBJECT, NULL); + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void gst_clapper_gtk4_plugin_constructed (GObject * object); +static void gst_clapper_gtk4_plugin_finalize (GObject * object); +static void gst_clapper_gtk4_plugin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void gst_clapper_gtk4_plugin_init + (G_GNUC_UNUSED GstClapperGtk4Plugin * self) +{ +} + +static void gst_clapper_gtk4_plugin_class_init + (G_GNUC_UNUSED GstClapperGtk4PluginClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->constructed = gst_clapper_gtk4_plugin_constructed; + gobject_class->get_property = gst_clapper_gtk4_plugin_get_property; + gobject_class->finalize = gst_clapper_gtk4_plugin_finalize; + + param_specs[PROP_VIDEO_SINK] = + g_param_spec_object ("video-sink", + "Video Sink", "Video sink to use with video renderer", + GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); +} + +static void +gst_clapper_gtk4_plugin_constructed (GObject * object) +{ + GstClapperGtk4Plugin *self = GST_CLAPPER_GTK4_PLUGIN (object); + + if (!self->video_sink) + self->video_sink = g_object_new (GST_TYPE_GTK_GL_SINK, NULL); + + gst_object_ref_sink (self->video_sink); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_clapper_gtk4_plugin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstClapperGtk4Plugin *self = GST_CLAPPER_GTK4_PLUGIN (object); + + switch (prop_id) { + case PROP_VIDEO_SINK: + g_value_set_object (value, self->video_sink); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_gtk4_plugin_finalize (GObject * object) +{ + GstClapperGtk4Plugin *self = GST_CLAPPER_GTK4_PLUGIN (object); + + gst_object_unref (self->video_sink); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +#define C_ENUM(v) ((gint) v) + +GType +gst_clapper_gtk4_plugin_type_get_type (void) +{ + static gsize id = 0; + static const GEnumValue values[] = { + {C_ENUM (GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA), "GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA", "glarea"}, + {0, NULL, NULL} + }; + + if (g_once_init_enter (&id)) { + GType tmp = g_enum_register_static ("GstClapperGtk4PluginType", values); + g_once_init_leave (&id, tmp); + } + + return (GType) id; +} + +/** + * gst_clapper_gtk4_plugin_new: + * @plugin_type: (allow-none): Requested GstClapperGtk4PluginType + * + * Creates a new GTK4 plugin. + * + * Returns: (transfer full): the new GstClapperGtk4Plugin + */ +GstClapperGtk4Plugin * +gst_clapper_gtk4_plugin_new (G_GNUC_UNUSED const GstClapperGtk4PluginType plugin_type) +{ + return g_object_new (GST_TYPE_CLAPPER_GTK4_PLUGIN, NULL); +} diff --git a/lib/gst/clapper/gstclapper-gtk4-plugin.h b/lib/gst/clapper/gstclapper-gtk4-plugin.h new file mode 100644 index 00000000..a729d9f6 --- /dev/null +++ b/lib/gst/clapper/gstclapper-gtk4-plugin.h @@ -0,0 +1,86 @@ +/* GStreamer + * + * Copyright (C) 2021 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CLAPPER_GTK4_PLUGIN_H__ +#define __GST_CLAPPER_GTK4_PLUGIN_H__ + +#include +#include + +G_BEGIN_DECLS + +/* PluginType */ +GST_CLAPPER_API +GType gst_clapper_gtk4_plugin_type_get_type (void); +#define GST_TYPE_CLAPPER_GTK4_PLUGIN_TYPE (gst_clapper_gtk4_plugin_type_get_type ()) + +/** + * GstClapperGtk4PluginType: + * @GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA: GTK4 GLArea sink. + */ +typedef enum +{ + GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA, +} GstClapperGtk4PluginType; + +#define GST_TYPE_CLAPPER_GTK4_PLUGIN (gst_clapper_gtk4_plugin_get_type ()) +#define GST_IS_CLAPPER_GTK4_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_GTK4_PLUGIN)) +#define GST_IS_CLAPPER_GTK4_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_GTK4_PLUGIN)) +#define GST_CLAPPER_GTK4_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_GTK4_PLUGIN, GstClapperGtk4PluginClass)) +#define GST_CLAPPER_GTK4_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_GTK4_PLUGIN, GstClapperGtk4Plugin)) +#define GST_CLAPPER_GTK4_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_GTK4_PLUGIN, GstClapperGtk4PluginClass)) +#define GST_CLAPPER_GTK4_PLUGIN_CAST(obj) ((GstClapperGtk4Plugin*)(obj)) + +typedef struct _GstClapperGtk4Plugin GstClapperGtk4Plugin; +typedef struct _GstClapperGtk4PluginClass GstClapperGtk4PluginClass; + +/** + * GstClapperGtk4Plugin: + * + * Opaque #GstClapperGtk4Plugin object + */ +struct _GstClapperGtk4Plugin +{ + /* */ + GObject parent; + + GstElement *video_sink; +}; + +/** + * GstClapperGtk4PluginClass: + * + * The #GstClapperGtk4PluginClass struct only contains private data + */ +struct _GstClapperGtk4PluginClass +{ + /* */ + GstElementClass parent_class; +}; + +GST_CLAPPER_API +GType gst_clapper_gtk4_plugin_get_type (void); + +GST_CLAPPER_API +GstClapperGtk4Plugin * gst_clapper_gtk4_plugin_new (const GstClapperGtk4PluginType plugin_type); + +G_END_DECLS + +#endif /* __GST_CLAPPER_GTK4_PLUGIN__ */ diff --git a/lib/gst/clapper/gtk4/gstgtkbasesink.c b/lib/gst/clapper/gtk4/gstgtkbasesink.c new file mode 100644 index 00000000..1ebdc81b --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkbasesink.c @@ -0,0 +1,556 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:gtkgstsink + * @title: GstGtkBaseSink + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstgtkbasesink.h" +#include "gstgtkutils.h" + +GST_DEBUG_CATEGORY (gst_debug_gtk_base_sink); +#define GST_CAT_DEFAULT gst_debug_gtk_base_sink + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 0 +#define DEFAULT_PAR_D 1 +#define DEFAULT_IGNORE_ALPHA TRUE + +static void gst_gtk_base_sink_finalize (GObject * object); +static void gst_gtk_base_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * param_spec); +static void gst_gtk_base_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * param_spec); + +static gboolean gst_gtk_base_sink_start (GstBaseSink * bsink); +static gboolean gst_gtk_base_sink_stop (GstBaseSink * bsink); + +static GstStateChangeReturn +gst_gtk_base_sink_change_state (GstElement * element, + GstStateChange transition); + +static void gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end); +static gboolean gst_gtk_base_sink_set_caps (GstBaseSink * bsink, + GstCaps * caps); +static GstFlowReturn gst_gtk_base_sink_show_frame (GstVideoSink * bsink, + GstBuffer * buf); + +static void +gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface); + +enum +{ + PROP_0, + PROP_WIDGET, + PROP_FORCE_ASPECT_RATIO, + PROP_PIXEL_ASPECT_RATIO, + PROP_IGNORE_ALPHA, +}; + +#define gst_gtk_base_sink_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstGtkBaseSink, gst_gtk_base_sink, + GST_TYPE_VIDEO_SINK, + G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, + gst_gtk_base_sink_navigation_interface_init); + GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_sink, + "gtkbasesink", 0, "GTK Video Sink base class")); + + +static void +gst_gtk_base_sink_class_init (GstGtkBaseSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + GstVideoSinkClass *gstvideosink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + gstvideosink_class = (GstVideoSinkClass *) klass; + + gobject_class->set_property = gst_gtk_base_sink_set_property; + gobject_class->get_property = gst_gtk_base_sink_get_property; + + g_object_class_install_property (gobject_class, PROP_WIDGET, + g_param_spec_object ("widget", "GTK Widget", + "The GtkWidget to place in the widget hierarchy " + "(must only be get from the GTK main thread)", + GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO, + g_param_spec_boolean ("force-aspect-ratio", + "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", + DEFAULT_FORCE_ASPECT_RATIO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO, + gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio", + "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D, + G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Disabling alpha was removed in GTK4 */ +#if !defined(BUILD_FOR_GTK4) + g_object_class_install_property (gobject_class, PROP_IGNORE_ALPHA, + g_param_spec_boolean ("ignore-alpha", "Ignore Alpha", + "When enabled, alpha will be ignored and converted to black", + DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif + + gobject_class->finalize = gst_gtk_base_sink_finalize; + + gstelement_class->change_state = gst_gtk_base_sink_change_state; + gstbasesink_class->set_caps = gst_gtk_base_sink_set_caps; + gstbasesink_class->get_times = gst_gtk_base_sink_get_times; + gstbasesink_class->start = gst_gtk_base_sink_start; + gstbasesink_class->stop = gst_gtk_base_sink_stop; + + gstvideosink_class->show_frame = gst_gtk_base_sink_show_frame; + + gst_type_mark_as_plugin_api (GST_TYPE_GTK_BASE_SINK, 0); +} + +static void +gst_gtk_base_sink_init (GstGtkBaseSink * gtk_sink) +{ + gtk_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + gtk_sink->par_n = DEFAULT_PAR_N; + gtk_sink->par_d = DEFAULT_PAR_D; + gtk_sink->ignore_alpha = DEFAULT_IGNORE_ALPHA; +} + +static void +gst_gtk_base_sink_finalize (GObject * object) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object); + + GST_DEBUG ("finalizing base sink"); + + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->window && gtk_sink->window_destroy_id) + g_signal_handler_disconnect (gtk_sink->window, gtk_sink->window_destroy_id); + if (gtk_sink->widget && gtk_sink->widget_destroy_id) + g_signal_handler_disconnect (gtk_sink->widget, gtk_sink->widget_destroy_id); + + g_clear_object (>k_sink->widget); + GST_OBJECT_UNLOCK (gtk_sink); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +widget_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink) +{ + GST_OBJECT_LOCK (gtk_sink); + g_clear_object (>k_sink->widget); + GST_OBJECT_UNLOCK (gtk_sink); +} + +static void +window_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink) +{ + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->widget) { + if (gtk_sink->widget_destroy_id) { + g_signal_handler_disconnect (gtk_sink->widget, + gtk_sink->widget_destroy_id); + gtk_sink->widget_destroy_id = 0; + } + g_clear_object (>k_sink->widget); + } + gtk_sink->window = NULL; + GST_OBJECT_UNLOCK (gtk_sink); +} + +static GtkGstBaseWidget * +gst_gtk_base_sink_get_widget (GstGtkBaseSink * gtk_sink) +{ + if (gtk_sink->widget != NULL) + return gtk_sink->widget; + + /* Ensure GTK is initialized, this has no side effect if it was already + * initialized. Also, we do that lazily, so the application can be first */ + if (!gtk_init_check ( +#if !defined(BUILD_FOR_GTK4) + NULL, NULL +#endif + )) { + GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization."); + return NULL; + } + + g_assert (GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget); + gtk_sink->widget = (GtkGstBaseWidget *) + GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget (); + + gtk_sink->bind_aspect_ratio = + g_object_bind_property (gtk_sink, "force-aspect-ratio", gtk_sink->widget, + "force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + gtk_sink->bind_pixel_aspect_ratio = + g_object_bind_property (gtk_sink, "pixel-aspect-ratio", gtk_sink->widget, + "pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); +#if !defined(BUILD_FOR_GTK4) + gtk_sink->bind_ignore_alpha = + g_object_bind_property (gtk_sink, "ignore-alpha", gtk_sink->widget, + "ignore-alpha", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); +#endif + + /* Take the floating ref, other wise the destruction of the container will + * make this widget disappear possibly before we are done. */ + gst_object_ref_sink (gtk_sink->widget); + + gtk_sink->widget_destroy_id = g_signal_connect (gtk_sink->widget, "destroy", + G_CALLBACK (widget_destroy_cb), gtk_sink); + + /* back pointer */ + gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (gtk_sink->widget), + GST_ELEMENT (gtk_sink)); + + return gtk_sink->widget; +} + +static void +gst_gtk_base_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object); + + switch (prop_id) { + case PROP_WIDGET: + { + GObject *widget = NULL; + + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->widget != NULL) + widget = G_OBJECT (gtk_sink->widget); + GST_OBJECT_UNLOCK (gtk_sink); + + if (!widget) + widget = + gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_base_sink_get_widget, + gtk_sink); + + g_value_set_object (value, widget); + break; + } + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, gtk_sink->force_aspect_ratio); + break; + case PROP_PIXEL_ASPECT_RATIO: + gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d); + break; + case PROP_IGNORE_ALPHA: + g_value_set_boolean (value, gtk_sink->ignore_alpha); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_gtk_base_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + gtk_sink->force_aspect_ratio = g_value_get_boolean (value); + break; + case PROP_PIXEL_ASPECT_RATIO: + gtk_sink->par_n = gst_value_get_fraction_numerator (value); + gtk_sink->par_d = gst_value_get_fraction_denominator (value); + break; + case PROP_IGNORE_ALPHA: + gtk_sink->ignore_alpha = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_gtk_base_sink_navigation_send_event (GstNavigation * navigation, + GstStructure * structure) +{ + GstGtkBaseSink *sink = GST_GTK_BASE_SINK (navigation); + GstEvent *event; + GstPad *pad; + + event = gst_event_new_navigation (structure); + pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink)); + + GST_TRACE_OBJECT (sink, "navigation event %" GST_PTR_FORMAT, structure); + + if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) { + if (!gst_pad_send_event (pad, gst_event_ref (event))) { + /* If upstream didn't handle the event we'll post a message with it + * for the application in case it wants to do something with it */ + gst_element_post_message (GST_ELEMENT_CAST (sink), + gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event)); + } + gst_event_unref (event); + gst_object_unref (pad); + } +} + +static void +gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface) +{ + iface->send_event = gst_gtk_base_sink_navigation_send_event; +} + +static gboolean +gst_gtk_base_sink_start_on_main (GstBaseSink * bsink) +{ + GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink); + GstGtkBaseSinkClass *klass = GST_GTK_BASE_SINK_GET_CLASS (bsink); + GtkWidget *toplevel; +#if defined(BUILD_FOR_GTK4) + GtkRoot *root; +#endif + + if (gst_gtk_base_sink_get_widget (gst_sink) == NULL) + return FALSE; + + /* After this point, gtk_sink->widget will always be set */ + +#if defined(BUILD_FOR_GTK4) + root = gtk_widget_get_root (GTK_WIDGET (gst_sink->widget)); + if (!GTK_IS_ROOT (root)) { + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (gst_sink->widget)); + if (parent) { + GtkWidget *temp_parent; + while ((temp_parent = gtk_widget_get_parent (parent))) + parent = temp_parent; + } + toplevel = (parent) ? parent : GTK_WIDGET (gst_sink->widget); +#else + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gst_sink->widget)); + if (!gtk_widget_is_toplevel (toplevel)) { +#endif + /* sanity check */ + g_assert (klass->window_title); + + /* User did not add widget its own UI, let's popup a new GtkWindow to + * make gst-launch-1.0 work. */ + gst_sink->window = gtk_window_new ( +#if !defined(BUILD_FOR_GTK4) + GTK_WINDOW_TOPLEVEL +#endif + ); + gtk_window_set_default_size (GTK_WINDOW (gst_sink->window), 640, 480); + gtk_window_set_title (GTK_WINDOW (gst_sink->window), klass->window_title); +#if defined(BUILD_FOR_GTK4) + gtk_window_set_child (GTK_WINDOW ( +#else + gtk_container_add (GTK_CONTAINER ( +#endif + gst_sink->window), toplevel); + + gst_sink->window_destroy_id = g_signal_connect ( +#if defined(BUILD_FOR_GTK4) + GTK_WINDOW (gst_sink->window), +#else + gst_sink->window, +#endif + "destroy", G_CALLBACK (window_destroy_cb), gst_sink); + } + + return TRUE; +} + +static gboolean +gst_gtk_base_sink_start (GstBaseSink * bsink) +{ + return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_gtk_base_sink_start_on_main, bsink); +} + +static gboolean +gst_gtk_base_sink_stop_on_main (GstBaseSink * bsink) +{ + GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink); + + if (gst_sink->window) { +#if defined(BUILD_FOR_GTK4) + gtk_window_destroy (GTK_WINDOW (gst_sink->window)); +#else + gtk_widget_destroy (gst_sink->window); +#endif + gst_sink->window = NULL; + gst_sink->widget = NULL; + } + + return TRUE; +} + +static gboolean +gst_gtk_base_sink_stop (GstBaseSink * bsink) +{ + GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink); + + if (gst_sink->window) + return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_gtk_base_sink_stop_on_main, bsink); + + return TRUE; +} + +static void +gst_gtk_window_show_all_and_unref (GtkWidget * window) +{ +#if defined(BUILD_FOR_GTK4) + gtk_window_present (GTK_WINDOW (window)); +#else + gtk_widget_show_all (window); +#endif + g_object_unref (window); +} + +static GstStateChangeReturn +gst_gtk_base_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (element, "changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + { + GtkWindow *window = NULL; + + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->window) + window = g_object_ref (GTK_WINDOW (gtk_sink->window)); + GST_OBJECT_UNLOCK (gtk_sink); + + if (window) { + gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_gtk_window_show_all_and_unref, window); + } + break; + } + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_OBJECT_LOCK (gtk_sink); + if (gtk_sink->widget) + gtk_gst_base_widget_set_buffer (gtk_sink->widget, NULL); + GST_OBJECT_UNLOCK (gtk_sink); + break; + default: + break; + } + + return ret; +} + +static void +gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end) +{ + GstGtkBaseSink *gtk_sink; + + gtk_sink = GST_GTK_BASE_SINK (bsink); + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + *start = GST_BUFFER_TIMESTAMP (buf); + if (GST_BUFFER_DURATION_IS_VALID (buf)) + *end = *start + GST_BUFFER_DURATION (buf); + else { + if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) { + *end = *start + + gst_util_uint64_scale_int (GST_SECOND, + GST_VIDEO_INFO_FPS_D (>k_sink->v_info), + GST_VIDEO_INFO_FPS_N (>k_sink->v_info)); + } + } + } +} + +gboolean +gst_gtk_base_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) +{ + GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (bsink); + + GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps); + + if (!gst_video_info_from_caps (>k_sink->v_info, caps)) + return FALSE; + + GST_OBJECT_LOCK (gtk_sink); + + if (gtk_sink->widget == NULL) { + GST_OBJECT_UNLOCK (gtk_sink); + GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND, + ("%s", "Output widget was destroyed"), (NULL)); + return FALSE; + } + + if (!gtk_gst_base_widget_set_format (gtk_sink->widget, >k_sink->v_info)) { + GST_OBJECT_UNLOCK (gtk_sink); + return FALSE; + } + GST_OBJECT_UNLOCK (gtk_sink); + + return TRUE; +} + +static GstFlowReturn +gst_gtk_base_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf) +{ + GstGtkBaseSink *gtk_sink; + + GST_TRACE ("rendering buffer:%p", buf); + + gtk_sink = GST_GTK_BASE_SINK (vsink); + + GST_OBJECT_LOCK (vsink); + + if (gtk_sink->widget == NULL) { + GST_OBJECT_UNLOCK (gtk_sink); + GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND, + ("%s", "Output widget was destroyed"), (NULL)); + return GST_FLOW_ERROR; + } + + gtk_gst_base_widget_set_buffer (gtk_sink->widget, buf); + + GST_OBJECT_UNLOCK (gtk_sink); + + return GST_FLOW_OK; +} diff --git a/lib/gst/clapper/gtk4/gstgtkbasesink.h b/lib/gst/clapper/gtk4/gstgtkbasesink.h new file mode 100644 index 00000000..db0acb2c --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkbasesink.h @@ -0,0 +1,96 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_GTK_BASE_SINK_H__ +#define __GST_GTK_BASE_SINK_H__ + +#include +#include +#include +#include + +#include "gtkgstbasewidget.h" + +#define GST_TYPE_GTK_BASE_SINK (gst_gtk_base_sink_get_type()) +#define GST_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSink)) +#define GST_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSinkClass)) +#define GST_GTK_BASE_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_GTK_BASE_SINK, GstGtkBaseSinkClass)) +#define GST_IS_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GTK_BASE_SINK)) +#define GST_IS_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GTK_BASE_SINK)) +#define GST_GTK_BASE_SINK_CAST(obj) ((GstGtkBaseSink*)(obj)) + +G_BEGIN_DECLS + +typedef struct _GstGtkBaseSink GstGtkBaseSink; +typedef struct _GstGtkBaseSinkClass GstGtkBaseSinkClass; + +GType gst_gtk_base_sink_get_type (void); + +/** + * GstGtkBaseSink: + * + * Opaque #GstGtkBaseSink object + */ +struct _GstGtkBaseSink +{ + /* */ + GstVideoSink parent; + + GstVideoInfo v_info; + + GtkGstBaseWidget *widget; + + /* properties */ + gboolean force_aspect_ratio; + GBinding *bind_aspect_ratio; + + gint par_n; + gint par_d; + GBinding *bind_pixel_aspect_ratio; + + gboolean ignore_alpha; + GBinding *bind_ignore_alpha; + + GtkWidget *window; + gulong widget_destroy_id; + gulong window_destroy_id; +}; + +/** + * GstGtkBaseSinkClass: + * + * The #GstGtkBaseSinkClass struct only contains private data + */ +struct _GstGtkBaseSinkClass +{ + GstVideoSinkClass object_class; + + /* metadata */ + const gchar *window_title; + + /* virtuals */ + GtkWidget* (*create_widget) (void); +}; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstGtkBaseSink, gst_object_unref) + +G_END_DECLS + +#endif /* __GST_GTK_BASE_SINK_H__ */ diff --git a/lib/gst/clapper/gtk4/gstgtkglsink.c b/lib/gst/clapper/gtk4/gstgtkglsink.c new file mode 100644 index 00000000..e680c5a0 --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkglsink.c @@ -0,0 +1,387 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-gtkglsink + * @title: gtkglsink + */ + +/** + * SECTION:element-gtk4glsink + * @title: gtk4glsink + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gtkconfig.h" +#include "gstgtkglsink.h" +#include "gtkgstglwidget.h" + +GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink); +#define GST_CAT_DEFAULT gst_debug_gtk_gl_sink + +static gboolean gst_gtk_gl_sink_start (GstBaseSink * bsink); +static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink); +static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query); +static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, + GstQuery * query); +static GstCaps *gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, + GstCaps * filter); + +static void gst_gtk_gl_sink_finalize (GObject * object); + +static GstStaticPadTemplate gst_gtk_gl_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA") "; " + GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_GL_MEMORY ", " + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, "RGBA"))); + +#define gst_gtk_gl_sink_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstGtkGLSink, gst_gtk_gl_sink, + GST_TYPE_GTK_BASE_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink, + GTKCONFIG_GLSINK, 0, GTKCONFIG_NAME " GL Video Sink")); + +static void +gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + GstGtkBaseSinkClass *gstgtkbasesink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + gstgtkbasesink_class = (GstGtkBaseSinkClass *) klass; + + gobject_class->finalize = gst_gtk_gl_sink_finalize; + + gstbasesink_class->query = gst_gtk_gl_sink_query; + gstbasesink_class->propose_allocation = gst_gtk_gl_sink_propose_allocation; + gstbasesink_class->start = gst_gtk_gl_sink_start; + gstbasesink_class->stop = gst_gtk_gl_sink_stop; + gstbasesink_class->get_caps = gst_gtk_gl_sink_get_caps; + + gstgtkbasesink_class->create_widget = gtk_gst_gl_widget_new; + gstgtkbasesink_class->window_title = GTKCONFIG_NAME " GL Renderer"; + + gst_element_class_set_metadata (gstelement_class, + GTKCONFIG_NAME " GL Video Sink", + "Sink/Video", "A video sink that renders to a GtkWidget using OpenGL", + "Matthew Waters , " + "Rafał Dzięgiel "); + + gst_element_class_add_static_pad_template (gstelement_class, + &gst_gtk_gl_sink_template); +} + +static void +gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink) +{ +} + +static gboolean +gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + gboolean res = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + { + if (gst_gl_handle_context_query ((GstElement *) gtk_sink, query, + gtk_sink->display, gtk_sink->context, gtk_sink->gtk_context)) + return TRUE; + break; + } + default: + res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query); + break; + } + + return res; +} + +static void +_size_changed_cb (GtkWidget * widget, gint width, + gint height, GstGtkGLSink * gtk_sink) +{ + gboolean reconfigure; + + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + + /* Ignore size changes before widget is negotiated + * we are going to queue a resize after negotiation */ + if (!base_widget->negotiated) + return; + + GST_OBJECT_LOCK (gtk_sink); + reconfigure = + (width != gtk_sink->display_width || height != gtk_sink->display_height); + gtk_sink->display_width = width; + gtk_sink->display_height = height; + GST_OBJECT_UNLOCK (gtk_sink); + + if (reconfigure) { + GST_DEBUG_OBJECT (gtk_sink, "Sending reconfigure event on sinkpad"); + gst_pad_push_event (GST_BASE_SINK (gtk_sink)->sinkpad, + gst_event_new_reconfigure ()); + } +} + +static void +destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink) +{ + if (gtk_sink->widget_resize_sig_handler) { + g_signal_handler_disconnect (widget, gtk_sink->widget_resize_sig_handler); + gtk_sink->widget_resize_sig_handler = 0; + } + + if (gtk_sink->widget_destroy_sig_handler) { + g_signal_handler_disconnect (widget, gtk_sink->widget_destroy_sig_handler); + gtk_sink->widget_destroy_sig_handler = 0; + } +} + +static gboolean +gst_gtk_gl_sink_start (GstBaseSink * bsink) +{ + GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink); + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + GtkGstGLWidget *gst_widget; + + if (!GST_BASE_SINK_CLASS (parent_class)->start (bsink)) + return FALSE; + + /* After this point, gtk_sink->widget will always be set */ + gst_widget = GTK_GST_GL_WIDGET (base_sink->widget); + + /* Track the allocation size */ + if (!gtk_sink->widget_resize_sig_handler) { + gtk_sink->widget_resize_sig_handler = + g_signal_connect (gst_widget, "resize", + G_CALLBACK (_size_changed_cb), gtk_sink); + } + + if (!gtk_sink->widget_destroy_sig_handler) { + gtk_sink->widget_destroy_sig_handler = + g_signal_connect (gst_widget, "destroy", G_CALLBACK (destroy_cb), + gtk_sink); + } + + if (!gtk_gst_gl_widget_init_winsys (gst_widget)) { + GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s", + "Failed to initialize OpenGL with GTK"), (NULL)); + return FALSE; + } + + if (!gtk_sink->display) + gtk_sink->display = gtk_gst_gl_widget_get_display (gst_widget); + if (!gtk_sink->context) + gtk_sink->context = gtk_gst_gl_widget_get_context (gst_widget); + if (!gtk_sink->gtk_context) + gtk_sink->gtk_context = gtk_gst_gl_widget_get_gtk_context (gst_widget); + + if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context) { + GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s", + "Failed to retrieve OpenGL context from GTK"), (NULL)); + return FALSE; + } + + gst_gl_element_propagate_display_context (GST_ELEMENT (bsink), + gtk_sink->display); + + return TRUE; +} + +static gboolean +gst_gtk_gl_sink_stop (GstBaseSink * bsink) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink); + + if (gtk_sink->widget_resize_sig_handler) { + g_signal_handler_disconnect (base_sink->widget, + gtk_sink->widget_resize_sig_handler); + gtk_sink->widget_resize_sig_handler = 0; + } + + if (gtk_sink->display) { + gst_object_unref (gtk_sink->display); + gtk_sink->display = NULL; + } + + if (gtk_sink->context) { + gst_object_unref (gtk_sink->context); + gtk_sink->context = NULL; + } + + if (gtk_sink->gtk_context) { + gst_object_unref (gtk_sink->gtk_context); + gtk_sink->gtk_context = NULL; + } + + return GST_BASE_SINK_CLASS (parent_class)->stop (bsink); +} + +static gboolean +gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink); + GstBufferPool *pool = NULL; + GstStructure *config; + GstCaps *caps; + GstVideoInfo info; + guint size; + gboolean need_pool; + GstStructure *allocation_meta = NULL; + gint display_width, display_height; + + if (!gtk_sink->display || !gtk_sink->context) + return FALSE; + + gst_query_parse_allocation (query, &caps, &need_pool); + + if (caps == NULL) + goto no_caps; + + if (!gst_video_info_from_caps (&info, caps)) + goto invalid_caps; + + /* the normal size of a frame */ + size = info.size; + + if (need_pool) { + GST_DEBUG_OBJECT (gtk_sink, "create new pool"); + pool = gst_gl_buffer_pool_new (gtk_sink->context); + + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_GL_SYNC_META); + + if (!gst_buffer_pool_set_config (pool, config)) + goto config_failed; + } + + /* we need at least 2 buffer because we hold on to the last one */ + gst_query_add_allocation_pool (query, pool, size, 2, 0); + if (pool) + gst_object_unref (pool); + + GST_OBJECT_LOCK (gtk_sink); + display_width = gtk_sink->display_width; + display_height = gtk_sink->display_height; + GST_OBJECT_UNLOCK (gtk_sink); + + if (display_width != 0 && display_height != 0) { + GST_DEBUG_OBJECT (gtk_sink, "sending alloc query with size %dx%d", + display_width, display_height); + allocation_meta = gst_structure_new ("GstVideoOverlayCompositionMeta", + "width", G_TYPE_UINT, display_width, + "height", G_TYPE_UINT, display_height, NULL); + } + + gst_query_add_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta); + + if (allocation_meta) + gst_structure_free (allocation_meta); + + /* we also support various metadata */ + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0); + + if (gtk_sink->context->gl_vtable->FenceSync) + gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0); + + return TRUE; + + /* ERRORS */ +no_caps: + { + GST_DEBUG_OBJECT (bsink, "no caps specified"); + return FALSE; + } +invalid_caps: + { + GST_DEBUG_OBJECT (bsink, "invalid caps specified"); + return FALSE; + } +config_failed: + { + GST_DEBUG_OBJECT (bsink, "failed setting config"); + return FALSE; + } +} + +static GstCaps * +gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) +{ + GstCaps *tmp = NULL; + GstCaps *result = NULL; + + tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); + + if (filter) { + GST_DEBUG_OBJECT (bsink, "intersecting with filter caps %" GST_PTR_FORMAT, + filter); + + result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (tmp); + } else { + result = tmp; + } + + result = gst_gl_overlay_compositor_add_caps (result); + + GST_DEBUG_OBJECT (bsink, "returning caps: %" GST_PTR_FORMAT, result); + + return result; +} + +static void +gst_gtk_gl_sink_finalize (GObject * object) +{ + GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object); + GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (object); + + if (gtk_sink->widget_resize_sig_handler) { + g_signal_handler_disconnect (base_sink->widget, + gtk_sink->widget_resize_sig_handler); + gtk_sink->widget_resize_sig_handler = 0; + } + + if (gtk_sink->widget_destroy_sig_handler) { + g_signal_handler_disconnect (base_sink->widget, + gtk_sink->widget_destroy_sig_handler); + gtk_sink->widget_destroy_sig_handler = 0; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} diff --git a/lib/gst/clapper/gtk4/gstgtkglsink.h b/lib/gst/clapper/gtk4/gstgtkglsink.h new file mode 100644 index 00000000..56595f23 --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkglsink.h @@ -0,0 +1,65 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_GTK_GL_SINK_H__ +#define __GST_GTK_GL_SINK_H__ + +#include +#include +#include +#include +#include + +#include "gstgtkbasesink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_GTK_GL_SINK (gst_gtk_gl_sink_get_type ()) +G_DECLARE_FINAL_TYPE (GstGtkGLSink, gst_gtk_gl_sink, GST, GTK_GL_SINK, + GstGtkBaseSink); + +/** + * GstGtkGLSink: + * + * Opaque #GstGtkGLSink object + */ +struct _GstGtkGLSink +{ + /* */ + GstGtkBaseSink parent; + + GstGLDisplay *display; + GstGLContext *context; + GstGLContext *gtk_context; + + GstGLUpload *upload; + GstBuffer *uploaded_buffer; + + /* read/write with object lock */ + gint display_width; + gint display_height; + + gulong widget_resize_sig_handler; + gulong widget_destroy_sig_handler; +}; + +G_END_DECLS + +#endif /* __GST_GTK_GL_SINK_H__ */ diff --git a/lib/gst/clapper/gtk4/gstgtkutils.c b/lib/gst/clapper/gtk4/gstgtkutils.c new file mode 100644 index 00000000..c730f018 --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkutils.c @@ -0,0 +1,71 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "gstgtkutils.h" + +struct invoke_context +{ + GThreadFunc func; + gpointer data; + GMutex lock; + GCond cond; + gboolean fired; + + gpointer res; +}; + +static gboolean +gst_gtk_invoke_func (struct invoke_context *info) +{ + g_mutex_lock (&info->lock); + info->res = info->func (info->data); + info->fired = TRUE; + g_cond_signal (&info->cond); + g_mutex_unlock (&info->lock); + + return G_SOURCE_REMOVE; +} + +gpointer +gst_gtk_invoke_on_main (GThreadFunc func, gpointer data) +{ + GMainContext *main_context = g_main_context_default (); + struct invoke_context info; + + g_mutex_init (&info.lock); + g_cond_init (&info.cond); + info.fired = FALSE; + info.func = func; + info.data = data; + + g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func, + &info); + + g_mutex_lock (&info.lock); + while (!info.fired) + g_cond_wait (&info.cond, &info.lock); + g_mutex_unlock (&info.lock); + + g_mutex_clear (&info.lock); + g_cond_clear (&info.cond); + + return info.res; +} diff --git a/lib/gst/clapper/gtk4/gstgtkutils.h b/lib/gst/clapper/gtk4/gstgtkutils.h new file mode 100644 index 00000000..7584ae2c --- /dev/null +++ b/lib/gst/clapper/gtk4/gstgtkutils.h @@ -0,0 +1,29 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_GTK_UTILS_H__ +#define __GST_GTK_UTILS_H__ + +#include + +gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data); + +#endif /* __GST_GTK_UTILS_H__ */ diff --git a/lib/gst/clapper/gtk4/gtkconfig.h b/lib/gst/clapper/gtk4/gtkconfig.h new file mode 100644 index 00000000..ecbf9558 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkconfig.h @@ -0,0 +1,31 @@ +/* + * GStreamer + * Copyright (C) 2020 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#if defined(BUILD_FOR_GTK4) +#define GTKCONFIG_PLUGIN gtk4 +#define GTKCONFIG_NAME "GTK4" +#define GTKCONFIG_SINK "gtk4sink" +#define GTKCONFIG_GLSINK "gtk4glsink" +#else +#define GTKCONFIG_PLUGIN gtk +#define GTKCONFIG_NAME "GTK" +#define GTKCONFIG_SINK "gtksink" +#define GTKCONFIG_GLSINK "gtkglsink" +#endif diff --git a/lib/gst/clapper/gtk4/gtkgstbasewidget.c b/lib/gst/clapper/gtk4/gtkgstbasewidget.c new file mode 100644 index 00000000..374eb7f9 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstbasewidget.c @@ -0,0 +1,605 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gtkgstbasewidget.h" + +GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget); +#define GST_CAT_DEFAULT gst_debug_gtk_base_widget + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 0 +#define DEFAULT_PAR_D 1 +#define DEFAULT_IGNORE_ALPHA TRUE + +enum +{ + PROP_0, + PROP_FORCE_ASPECT_RATIO, + PROP_PIXEL_ASPECT_RATIO, + PROP_IGNORE_ALPHA, +}; + +static void +gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min, + gint * natural) +{ + GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget; + gint video_width = gst_widget->display_width; + + if (!gst_widget->negotiated) + video_width = 10; + + if (min) + *min = 1; + if (natural) + *natural = video_width; +} + +static void +gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min, + gint * natural) +{ + GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget; + gint video_height = gst_widget->display_height; + + if (!gst_widget->negotiated) + video_height = 10; + + if (min) + *min = 1; + if (natural) + *natural = video_height; +} + +#if defined(BUILD_FOR_GTK4) +static void +gtk_gst_base_widget_measure (GtkWidget * widget, GtkOrientation orientation, + gint for_size, gint * min, gint * natural, + gint * minimum_baseline, gint * natural_baseline) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_gst_base_widget_get_preferred_width (widget, min, natural); + else + gtk_gst_base_widget_get_preferred_height (widget, min, natural); + + *minimum_baseline = -1; + *natural_baseline = -1; +} +#endif + +static void +gtk_gst_base_widget_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + gtk_widget->force_aspect_ratio = g_value_get_boolean (value); + break; + case PROP_PIXEL_ASPECT_RATIO: + gtk_widget->par_n = gst_value_get_fraction_numerator (value); + gtk_widget->par_d = gst_value_get_fraction_denominator (value); + break; + case PROP_IGNORE_ALPHA: + gtk_widget->ignore_alpha = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_gst_base_widget_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, gtk_widget->force_aspect_ratio); + break; + case PROP_PIXEL_ASPECT_RATIO: + gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d); + break; + case PROP_IGNORE_ALPHA: + g_value_set_boolean (value, gtk_widget->ignore_alpha); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +_calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info) +{ + gboolean ok; + gint width, height; + gint par_n, par_d; + gint display_par_n, display_par_d; + + width = GST_VIDEO_INFO_WIDTH (info); + height = GST_VIDEO_INFO_HEIGHT (info); + + par_n = GST_VIDEO_INFO_PAR_N (info); + par_d = GST_VIDEO_INFO_PAR_D (info); + + if (!par_n) + par_n = 1; + + /* get display's PAR */ + if (widget->par_n != 0 && widget->par_d != 0) { + display_par_n = widget->par_n; + display_par_d = widget->par_d; + } else { + display_par_n = 1; + display_par_d = 1; + } + + + ok = gst_video_calculate_display_ratio (&widget->display_ratio_num, + &widget->display_ratio_den, width, height, par_n, par_d, display_par_n, + display_par_d); + + if (ok) { + GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n, + display_par_d); + return TRUE; + } + + return FALSE; +} + +static void +_apply_par (GtkGstBaseWidget * widget) +{ + guint display_ratio_num, display_ratio_den; + gint width, height; + + width = GST_VIDEO_INFO_WIDTH (&widget->v_info); + height = GST_VIDEO_INFO_HEIGHT (&widget->v_info); + + display_ratio_num = widget->display_ratio_num; + display_ratio_den = widget->display_ratio_den; + + if (height % display_ratio_den == 0) { + GST_DEBUG ("keeping video height"); + widget->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, + display_ratio_den); + widget->display_height = height; + } else if (width % display_ratio_num == 0) { + GST_DEBUG ("keeping video width"); + widget->display_width = width; + widget->display_height = (guint) + gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num); + } else { + GST_DEBUG ("approximating while keeping video height"); + widget->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, + display_ratio_den); + widget->display_height = height; + } + + GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height); +} + +static gboolean +_queue_draw (GtkGstBaseWidget * widget) +{ + GTK_GST_BASE_WIDGET_LOCK (widget); + widget->draw_id = 0; + + if (widget->pending_resize) { + widget->pending_resize = FALSE; + + widget->v_info = widget->pending_v_info; + widget->negotiated = TRUE; + + _apply_par (widget); + + gtk_widget_queue_resize (GTK_WIDGET (widget)); + } else { + gtk_widget_queue_draw (GTK_WIDGET (widget)); + } + + GTK_GST_BASE_WIDGET_UNLOCK (widget); + + return G_SOURCE_REMOVE; +} + +static const gchar * +_gdk_key_to_navigation_string (guint keyval) +{ + /* TODO: expand */ + switch (keyval) { +#define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key) + KEY (Up); + KEY (Down); + KEY (Left); + KEY (Right); + KEY (Home); + KEY (End); +#undef KEY + default: + return NULL; + } +} + +static GdkEvent * +_get_current_event (GtkEventController * controller) +{ +#if defined(BUILD_FOR_GTK4) + return gtk_event_controller_get_current_event (controller); +#else + return gtk_get_current_event (); +#endif +} + +static void +_gdk_event_free (GdkEvent * event) +{ +#if !defined(BUILD_FOR_GTK4) + if (event) + gdk_event_free (event); +#endif +} + +static gboolean +gtk_gst_base_widget_key_event (GtkEventControllerKey * key_controller, + guint keyval, guint keycode, GdkModifierType state) +{ + GtkEventController *controller = GTK_EVENT_CONTROLLER (key_controller); + GtkWidget *widget = gtk_event_controller_get_widget (controller); + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + GstElement *element; + + if ((element = g_weak_ref_get (&base_widget->element))) { + if (GST_IS_NAVIGATION (element)) { + GdkEvent *event = _get_current_event (controller); + const gchar *str = _gdk_key_to_navigation_string (keyval); + + if (str) { + const gchar *key_type = + gdk_event_get_event_type (event) == + GDK_KEY_PRESS ? "key-press" : "key-release"; + gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str); + } + _gdk_event_free (event); + } + g_object_unref (element); + } + + return FALSE; +} + +static void +_fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget, + GtkAllocation * allocation, GstVideoRectangle * result) +{ + if (base_widget->force_aspect_ratio) { + GstVideoRectangle src, dst; + + src.x = 0; + src.y = 0; + src.w = base_widget->display_width; + src.h = base_widget->display_height; + + dst.x = 0; + dst.y = 0; + dst.w = allocation->width; + dst.h = allocation->height; + + gst_video_sink_center_rect (src, dst, result, TRUE); + } else { + result->x = 0; + result->y = 0; + result->w = allocation->width; + result->h = allocation->height; + } +} + +static void +_display_size_to_stream_size (GtkGstBaseWidget * base_widget, gdouble x, + gdouble y, gdouble * stream_x, gdouble * stream_y) +{ + gdouble stream_width, stream_height; + GtkAllocation allocation; + GstVideoRectangle result; + + gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation); + _fit_stream_to_allocated_size (base_widget, &allocation, &result); + + stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info); + stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info); + + /* from display coordinates to stream coordinates */ + if (result.w > 0) + *stream_x = (x - result.x) / result.w * stream_width; + else + *stream_x = 0.; + + /* clip to stream size */ + if (*stream_x < 0.) + *stream_x = 0.; + if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info)) + *stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info); + + /* same for y-axis */ + if (result.h > 0) + *stream_y = (y - result.y) / result.h * stream_height; + else + *stream_y = 0.; + + if (*stream_y < 0.) + *stream_y = 0.; + if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info)) + *stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info); + + GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y); +} + +static gboolean +gtk_gst_base_widget_button_event ( +#if defined(BUILD_FOR_GTK4) + GtkGestureClick * gesture, +#else + GtkGestureMultiPress * gesture, +#endif + gint n_press, gdouble x, gdouble y) +{ + GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture); + GtkWidget *widget = gtk_event_controller_get_widget (controller); + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + GstElement *element; + + if ((element = g_weak_ref_get (&base_widget->element))) { + if (GST_IS_NAVIGATION (element)) { + GdkEvent *event = _get_current_event (controller); + const gchar *key_type = + gdk_event_get_event_type (event) == GDK_BUTTON_PRESS + ? "mouse-button-press" : "mouse-button-release"; + gdouble stream_x, stream_y; +#if !defined(BUILD_FOR_GTK4) + guint button; + gdk_event_get_button (event, &button); +#endif + + _display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y); + + gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type, +#if defined(BUILD_FOR_GTK4) + /* Gesture is set to ignore other buttons so we do not have to check */ + GDK_BUTTON_PRIMARY, +#else + button, +#endif + stream_x, stream_y); + + _gdk_event_free (event); + } + g_object_unref (element); + } + + return FALSE; +} + +static gboolean +gtk_gst_base_widget_motion_event (GtkEventControllerMotion * motion_controller, + gdouble x, gdouble y) +{ + GtkEventController *controller = GTK_EVENT_CONTROLLER (motion_controller); + GtkWidget *widget = gtk_event_controller_get_widget (controller); + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + GstElement *element; + + if ((element = g_weak_ref_get (&base_widget->element))) { + if (GST_IS_NAVIGATION (element)) { + gdouble stream_x, stream_y; + + _display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y); + + gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move", + 0, stream_x, stream_y); + } + g_object_unref (element); + } + + return FALSE; +} + +void +gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass) +{ + GObjectClass *gobject_klass = (GObjectClass *) klass; + GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass; + + gobject_klass->set_property = gtk_gst_base_widget_set_property; + gobject_klass->get_property = gtk_gst_base_widget_get_property; + + g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO, + g_param_spec_boolean ("force-aspect-ratio", + "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", + DEFAULT_FORCE_ASPECT_RATIO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO, + gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio", + "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D, + G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA, + g_param_spec_boolean ("ignore-alpha", "Ignore Alpha", + "When enabled, alpha will be ignored and converted to black", + DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + +#if defined(BUILD_FOR_GTK4) + widget_klass->measure = gtk_gst_base_widget_measure; +#else + widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width; + widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height; +#endif + + GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0, + "GTK Video Base Widget"); +} + +void +gtk_gst_base_widget_init (GtkGstBaseWidget * widget) +{ + widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + widget->par_n = DEFAULT_PAR_N; + widget->par_d = DEFAULT_PAR_D; + widget->ignore_alpha = DEFAULT_IGNORE_ALPHA; + + gst_video_info_init (&widget->v_info); + gst_video_info_init (&widget->pending_v_info); + + g_weak_ref_init (&widget->element, NULL); + g_mutex_init (&widget->lock); + + widget->key_controller = gtk_event_controller_key_new ( +#if !defined(BUILD_FOR_GTK4) + GTK_WIDGET (widget) +#endif + ); + g_signal_connect (widget->key_controller, "key-pressed", + G_CALLBACK (gtk_gst_base_widget_key_event), NULL); + g_signal_connect (widget->key_controller, "key-released", + G_CALLBACK (gtk_gst_base_widget_key_event), NULL); + + widget->motion_controller = gtk_event_controller_motion_new ( +#if !defined(BUILD_FOR_GTK4) + GTK_WIDGET (widget) +#endif + ); + g_signal_connect (widget->motion_controller, "motion", + G_CALLBACK (gtk_gst_base_widget_motion_event), NULL); + + widget->click_gesture = +#if defined(BUILD_FOR_GTK4) + gtk_gesture_click_new (); +#else + gtk_gesture_multi_press_new (GTK_WIDGET (widget)); +#endif + g_signal_connect (widget->click_gesture, "pressed", + G_CALLBACK (gtk_gst_base_widget_button_event), NULL); + g_signal_connect (widget->click_gesture, "released", + G_CALLBACK (gtk_gst_base_widget_button_event), NULL); + +#if defined(BUILD_FOR_GTK4) + /* Otherwise widget in grid will appear as a 1x1px + * video which might be misleading for users */ + gtk_widget_set_hexpand (GTK_WIDGET (widget), TRUE); + gtk_widget_set_vexpand (GTK_WIDGET (widget), TRUE); + + gtk_widget_set_focusable (GTK_WIDGET (widget), TRUE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (widget->click_gesture), + GDK_BUTTON_PRIMARY); + + gtk_widget_add_controller (GTK_WIDGET (widget), widget->key_controller); + gtk_widget_add_controller (GTK_WIDGET (widget), widget->motion_controller); + gtk_widget_add_controller (GTK_WIDGET (widget), + GTK_EVENT_CONTROLLER (widget->click_gesture)); +#endif + + gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE); +} + +void +gtk_gst_base_widget_finalize (GObject * object) +{ + GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object); + + /* GTK4 takes ownership of EventControllers + * while GTK3 still needs manual unref */ +#if !defined(BUILD_FOR_GTK4) + g_object_unref (widget->key_controller); + g_object_unref (widget->motion_controller); + g_object_unref (widget->click_gesture); +#endif + + gst_buffer_replace (&widget->pending_buffer, NULL); + gst_buffer_replace (&widget->buffer, NULL); + g_mutex_clear (&widget->lock); + g_weak_ref_clear (&widget->element); + + if (widget->draw_id) + g_source_remove (widget->draw_id); +} + +void +gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget, + GstElement * element) +{ + g_weak_ref_set (&widget->element, element); +} + +gboolean +gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget, + GstVideoInfo * v_info) +{ + GTK_GST_BASE_WIDGET_LOCK (widget); + + if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) { + GTK_GST_BASE_WIDGET_UNLOCK (widget); + return TRUE; + } + + if (!_calculate_par (widget, v_info)) { + GTK_GST_BASE_WIDGET_UNLOCK (widget); + return FALSE; + } + + widget->pending_resize = TRUE; + widget->pending_v_info = *v_info; + + GTK_GST_BASE_WIDGET_UNLOCK (widget); + + return TRUE; +} + +void +gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer) +{ + /* As we have no type, this is better then no check */ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + GTK_GST_BASE_WIDGET_LOCK (widget); + + gst_buffer_replace (&widget->pending_buffer, buffer); + + if (!widget->draw_id) { + widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT, + (GSourceFunc) _queue_draw, widget, NULL); + } + + GTK_GST_BASE_WIDGET_UNLOCK (widget); +} diff --git a/lib/gst/clapper/gtk4/gtkgstbasewidget.h b/lib/gst/clapper/gtk4/gtkgstbasewidget.h new file mode 100644 index 00000000..e96d2480 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstbasewidget.h @@ -0,0 +1,101 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GTK_GST_BASE_WIDGET_H__ +#define __GTK_GST_BASE_WIDGET_H__ + +#include +#include +#include + +#if !defined(BUILD_FOR_GTK4) +#include +#endif + +#define GTK_GST_BASE_WIDGET(w) ((GtkGstBaseWidget *)(w)) +#define GTK_GST_BASE_WIDGET_CLASS(k) ((GtkGstBaseWidgetClass *)(k)) +#define GTK_GST_BASE_WIDGET_LOCK(w) g_mutex_lock(&((GtkGstBaseWidget*)(w))->lock) +#define GTK_GST_BASE_WIDGET_UNLOCK(w) g_mutex_unlock(&((GtkGstBaseWidget*)(w))->lock) + +G_BEGIN_DECLS + +typedef struct _GtkGstBaseWidget GtkGstBaseWidget; +typedef struct _GtkGstBaseWidgetClass GtkGstBaseWidgetClass; + +struct _GtkGstBaseWidget +{ + union { + GtkGLArea gl_area; + } parent; + + /* properties */ + gboolean force_aspect_ratio; + gint par_n, par_d; + gboolean ignore_alpha; + + gint display_width; + gint display_height; + + gboolean negotiated; + GstBuffer *pending_buffer; + GstBuffer *buffer; + GstVideoInfo v_info; + + /* resize */ + gboolean pending_resize; + GstVideoInfo pending_v_info; + guint display_ratio_num; + guint display_ratio_den; + + /*< private >*/ + GMutex lock; + GWeakRef element; + + /* event controllers */ + GtkEventController *key_controller; + GtkEventController *motion_controller; + GtkGesture *click_gesture; + + /* Pending draw idles callback */ + guint draw_id; +}; + +struct _GtkGstBaseWidgetClass +{ + union { + GtkGLAreaClass gl_area_class; + } parent_class; +}; + +/* For implementer */ +void gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass); +void gtk_gst_base_widget_init (GtkGstBaseWidget * widget); + +void gtk_gst_base_widget_finalize (GObject * object); + +/* API */ +gboolean gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget, GstVideoInfo * v_info); +void gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer); +void gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget, GstElement * element); + +G_END_DECLS + +#endif /* __GTK_GST_BASE_WIDGET_H__ */ diff --git a/lib/gst/clapper/gtk4/gtkgstglwidget.c b/lib/gst/clapper/gtk4/gtkgstglwidget.c new file mode 100644 index 00000000..a9250676 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstglwidget.c @@ -0,0 +1,592 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gtkgstglwidget.h" +#include "gstgtkutils.h" +#include +#include + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) +#if defined(BUILD_FOR_GTK4) +#include +#else +#include +#endif +#include +#endif + +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND) +#if defined(BUILD_FOR_GTK4) +#include +#else +#include +#endif +#include +#endif + +/** + * SECTION:gtkgstglwidget + * @title: GtkGstGlWidget + * @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers + * @see_also: #GtkGLArea, #GstBuffer + * + * #GtkGstGLWidget is an #GtkWidget that renders GStreamer video buffers. + */ + +#define GST_CAT_DEFAULT gtk_gst_gl_widget_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _GtkGstGLWidgetPrivate +{ + gboolean initted; + GstGLDisplay *display; + GdkGLContext *gdk_context; + GstGLContext *other_context; + GstGLContext *context; + GstGLUpload *upload; + GstGLShader *shader; + GLuint vao; + GLuint vertex_buffer; + GLint attr_position; + GLint attr_texture; + GLuint current_tex; + GstGLOverlayCompositor *overlay_compositor; +}; + +static const GLfloat vertices[] = { + 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 1.0f +}; + +G_DEFINE_TYPE_WITH_CODE (GtkGstGLWidget, gtk_gst_gl_widget, GTK_TYPE_GL_AREA, + G_ADD_PRIVATE (GtkGstGLWidget) + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkgstglwidget", 0, + "GTK Gst GL Widget")); + +static void +gtk_gst_gl_widget_bind_buffer (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + + gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer); + + /* Load the vertex position */ + gl->VertexAttribPointer (priv->attr_position, 3, GL_FLOAT, GL_FALSE, + 5 * sizeof (GLfloat), (void *) 0); + + /* Load the texture coordinate */ + gl->VertexAttribPointer (priv->attr_texture, 2, GL_FLOAT, GL_FALSE, + 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat))); + + gl->EnableVertexAttribArray (priv->attr_position); + gl->EnableVertexAttribArray (priv->attr_texture); +} + +static void +gtk_gst_gl_widget_unbind_buffer (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + + gl->BindBuffer (GL_ARRAY_BUFFER, 0); + + gl->DisableVertexAttribArray (priv->attr_position); + gl->DisableVertexAttribArray (priv->attr_texture); +} + +static void +gtk_gst_gl_widget_init_redisplay (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + GError *error = NULL; + + gst_gl_insert_debug_marker (priv->other_context, "initializing redisplay"); + if (!(priv->shader = gst_gl_shader_new_default (priv->context, &error))) { + GST_ERROR ("Failed to initialize shader: %s", error->message); + return; + } + + priv->attr_position = + gst_gl_shader_get_attribute_location (priv->shader, "a_position"); + priv->attr_texture = + gst_gl_shader_get_attribute_location (priv->shader, "a_texcoord"); + + if (gl->GenVertexArrays) { + gl->GenVertexArrays (1, &priv->vao); + gl->BindVertexArray (priv->vao); + } + + gl->GenBuffers (1, &priv->vertex_buffer); + gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer); + gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices, + GL_STATIC_DRAW); + + if (gl->GenVertexArrays) { + gtk_gst_gl_widget_bind_buffer (gst_widget); + gl->BindVertexArray (0); + } + + gl->BindBuffer (GL_ARRAY_BUFFER, 0); + + priv->overlay_compositor = + gst_gl_overlay_compositor_new (priv->other_context); + + priv->initted = TRUE; +} + +static void +_redraw_texture (GtkGstGLWidget * gst_widget, guint tex) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->context->gl_vtable; + const GLushort indices[] = { 0, 1, 2, 0, 2, 3 }; + + if (gst_widget->base.force_aspect_ratio) { + GstVideoRectangle src, dst, result; + gint widget_width, widget_height, widget_scale; + + gl->ClearColor (0.0, 0.0, 0.0, 0.0); + gl->Clear (GL_COLOR_BUFFER_BIT); + + widget_scale = gtk_widget_get_scale_factor ((GtkWidget *) gst_widget); + widget_width = gtk_widget_get_allocated_width ((GtkWidget *) gst_widget); + widget_height = gtk_widget_get_allocated_height ((GtkWidget *) gst_widget); + + src.x = 0; + src.y = 0; + src.w = gst_widget->base.display_width; + src.h = gst_widget->base.display_height; + + dst.x = 0; + dst.y = 0; + dst.w = widget_width * widget_scale; + dst.h = widget_height * widget_scale; + + gst_video_sink_center_rect (src, dst, &result, TRUE); + + gl->Viewport (result.x, result.y, result.w, result.h); + } + + gst_gl_shader_use (priv->shader); + + if (gl->BindVertexArray) + gl->BindVertexArray (priv->vao); + gtk_gst_gl_widget_bind_buffer (gst_widget); + + gl->ActiveTexture (GL_TEXTURE0); + gl->BindTexture (GL_TEXTURE_2D, tex); + gst_gl_shader_set_uniform_1i (priv->shader, "tex", 0); + + gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices); + + if (gl->BindVertexArray) + gl->BindVertexArray (0); + else + gtk_gst_gl_widget_unbind_buffer (gst_widget); + + gl->BindTexture (GL_TEXTURE_2D, 0); +} + +static inline void +_draw_black (GstGLContext * context) +{ + const GstGLFuncs *gl = context->gl_vtable; + + gst_gl_insert_debug_marker (context, "no buffer. rendering black"); + gl->ClearColor (0.0, 0.0, 0.0, 0.0); + gl->Clear (GL_COLOR_BUFFER_BIT); +} + +static gboolean +gtk_gst_gl_widget_render (GtkGLArea * widget, GdkGLContext * context) +{ + GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (widget)->priv; + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget); + + GTK_GST_BASE_WIDGET_LOCK (widget); + + if (!priv->context || !priv->other_context) + goto done; + + gst_gl_context_activate (priv->other_context, TRUE); + + if (!priv->initted) + gtk_gst_gl_widget_init_redisplay (GTK_GST_GL_WIDGET (widget)); + + if (!priv->initted || !base_widget->negotiated) { + _draw_black (priv->other_context); + goto done; + } + + /* Upload latest buffer */ + if (base_widget->pending_buffer) { + GstBuffer *buffer = base_widget->pending_buffer; + GstVideoFrame gl_frame; + GstGLSyncMeta *sync_meta; + + if (!gst_video_frame_map (&gl_frame, &base_widget->v_info, buffer, + GST_MAP_READ | GST_MAP_GL)) { + _draw_black (priv->other_context); + goto done; + } + + priv->current_tex = *(guint *) gl_frame.data[0]; + gst_gl_insert_debug_marker (priv->other_context, "redrawing texture %u", + priv->current_tex); + + gst_gl_overlay_compositor_upload_overlays (priv->overlay_compositor, + buffer); + + sync_meta = gst_buffer_get_gl_sync_meta (buffer); + if (sync_meta) { + /* XXX: the set_sync() seems to be needed for resizing */ + gst_gl_sync_meta_set_sync_point (sync_meta, priv->context); + gst_gl_sync_meta_wait (sync_meta, priv->other_context); + } + + gst_video_frame_unmap (&gl_frame); + + if (base_widget->buffer) + gst_buffer_unref (base_widget->buffer); + + /* Keep the buffer to ensure current_tex stay valid */ + base_widget->buffer = buffer; + base_widget->pending_buffer = NULL; + } + + GST_DEBUG ("rendering buffer %p with gdk context %p", + base_widget->buffer, context); + + _redraw_texture (GTK_GST_GL_WIDGET (widget), priv->current_tex); + gst_gl_overlay_compositor_draw_overlays (priv->overlay_compositor); + + gst_gl_insert_debug_marker (priv->other_context, "texture %u redrawn", + priv->current_tex); + +done: + if (priv->other_context) + gst_gl_context_activate (priv->other_context, FALSE); + + GTK_GST_BASE_WIDGET_UNLOCK (widget); + return FALSE; +} + +static void +_reset_gl (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + const GstGLFuncs *gl = priv->other_context->gl_vtable; + + if (!priv->gdk_context) + priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget)); + + if (priv->gdk_context == NULL) + return; + + gdk_gl_context_make_current (priv->gdk_context); + gst_gl_context_activate (priv->other_context, TRUE); + + if (priv->vao) { + gl->DeleteVertexArrays (1, &priv->vao); + priv->vao = 0; + } + + if (priv->vertex_buffer) { + gl->DeleteBuffers (1, &priv->vertex_buffer); + priv->vertex_buffer = 0; + } + + if (priv->upload) { + gst_object_unref (priv->upload); + priv->upload = NULL; + } + + if (priv->shader) { + gst_object_unref (priv->shader); + priv->shader = NULL; + } + + if (priv->overlay_compositor) + gst_object_unref (priv->overlay_compositor); + + gst_gl_context_activate (priv->other_context, FALSE); + + gst_object_unref (priv->other_context); + priv->other_context = NULL; + + gdk_gl_context_clear_current (); + + g_object_unref (priv->gdk_context); + priv->gdk_context = NULL; +} + +static void +gtk_gst_gl_widget_finalize (GObject * object) +{ + GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (object)->priv; + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (object); + + if (priv->other_context) + gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) _reset_gl, base_widget); + + if (priv->context) + gst_object_unref (priv->context); + + if (priv->display) + gst_object_unref (priv->display); + + gtk_gst_base_widget_finalize (object); + G_OBJECT_CLASS (gtk_gst_gl_widget_parent_class)->finalize (object); +} + +static void +gtk_gst_gl_widget_class_init (GtkGstGLWidgetClass * klass) +{ + GObjectClass *gobject_klass = (GObjectClass *) klass; + GtkGLAreaClass *gl_widget_klass = (GtkGLAreaClass *) klass; + + gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass)); + + gobject_klass->finalize = gtk_gst_gl_widget_finalize; + gl_widget_klass->render = gtk_gst_gl_widget_render; +} + +static void +gtk_gst_gl_widget_init (GtkGstGLWidget * gst_widget) +{ + GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (gst_widget); + GdkDisplay *display; + GtkGstGLWidgetPrivate *priv; + + gtk_gst_base_widget_init (base_widget); + + gst_widget->priv = priv = gtk_gst_gl_widget_get_instance_private (gst_widget); + + display = gdk_display_get_default (); + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) + if (GDK_IS_X11_DISPLAY (display)) { + priv->display = (GstGLDisplay *) + gst_gl_display_x11_new_with_display (gdk_x11_display_get_xdisplay + (display)); + } +#endif +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND) + if (GDK_IS_WAYLAND_DISPLAY (display)) { + struct wl_display *wayland_display = + gdk_wayland_display_get_wl_display (display); + priv->display = (GstGLDisplay *) + gst_gl_display_wayland_new_with_display (wayland_display); + } +#endif + + (void) display; + + if (!priv->display) + priv->display = gst_gl_display_new (); + + GST_INFO ("Created %" GST_PTR_FORMAT, priv->display); + + /* GTK4 always has alpha */ +#if !defined(BUILD_FOR_GTK4) + gtk_gl_area_set_has_alpha (GTK_GL_AREA (gst_widget), + !base_widget->ignore_alpha); +#endif +} + +static void +_get_gl_context (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + GstGLPlatform platform = GST_GL_PLATFORM_NONE; + GstGLAPI gl_api = GST_GL_API_NONE; + guintptr gl_handle = 0; + + gtk_widget_realize (GTK_WIDGET (gst_widget)); + + if (priv->other_context) + gst_object_unref (priv->other_context); + priv->other_context = NULL; + + if (priv->gdk_context) + g_object_unref (priv->gdk_context); + + priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget)); + if (priv->gdk_context == NULL) { + GError *error = gtk_gl_area_get_error (GTK_GL_AREA (gst_widget)); + + GST_ERROR_OBJECT (gst_widget, "Error creating GdkGLContext : %s", + error ? error->message : "No error set by Gdk"); + g_clear_error (&error); + return; + } + + g_object_ref (priv->gdk_context); + + gdk_gl_context_make_current (priv->gdk_context); + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) + if (GST_IS_GL_DISPLAY_X11 (priv->display)) { +#if GST_GL_HAVE_PLATFORM_GLX + if (!gl_handle) { + platform = GST_GL_PLATFORM_GLX; + gl_handle = gst_gl_context_get_current_gl_context (platform); + } +#endif + +#if GST_GL_HAVE_PLATFORM_EGL + if (!gl_handle) { + platform = GST_GL_PLATFORM_EGL; + gl_handle = gst_gl_context_get_current_gl_context (platform); + } +#endif + + if (gl_handle) { + gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL); + priv->other_context = + gst_gl_context_new_wrapped (priv->display, gl_handle, + platform, gl_api); + } + } +#endif +#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND) + if (GST_IS_GL_DISPLAY_WAYLAND (priv->display)) { + platform = GST_GL_PLATFORM_EGL; + gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL); + gl_handle = gst_gl_context_get_current_gl_context (platform); + if (gl_handle) + priv->other_context = + gst_gl_context_new_wrapped (priv->display, gl_handle, + platform, gl_api); + } +#endif + + (void) platform; + (void) gl_api; + (void) gl_handle; + + if (priv->other_context) { + GError *error = NULL; + + GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, + priv->other_context); + gst_gl_context_activate (priv->other_context, TRUE); + if (!gst_gl_context_fill_info (priv->other_context, &error)) { + GST_ERROR ("failed to retrieve gdk context info: %s", error->message); + g_clear_error (&error); + g_object_unref (priv->other_context); + priv->other_context = NULL; + } else { + gst_gl_context_activate (priv->other_context, FALSE); + } + } else { + GST_WARNING ("Could not retrieve Gdk OpenGL context"); + } +} + +GtkWidget * +gtk_gst_gl_widget_new (void) +{ + return (GtkWidget *) g_object_new (GTK_TYPE_GST_GL_WIDGET, NULL); +} + +gboolean +gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * gst_widget) +{ + GtkGstGLWidgetPrivate *priv = gst_widget->priv; + GError *error = NULL; + + g_return_val_if_fail (GTK_IS_GST_GL_WIDGET (gst_widget), FALSE); + g_return_val_if_fail (priv->display != NULL, FALSE); + + GTK_GST_BASE_WIDGET_LOCK (gst_widget); + + if (priv->display && priv->gdk_context && priv->other_context) { + GST_TRACE ("have already initialized contexts"); + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return TRUE; + } + + if (!priv->other_context) { + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) _get_gl_context, gst_widget); + GTK_GST_BASE_WIDGET_LOCK (gst_widget); + } + + if (!GST_IS_GL_CONTEXT (priv->other_context)) { + GST_FIXME ("Could not retrieve Gdk OpenGL context"); + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return FALSE; + } + + GST_OBJECT_LOCK (priv->display); + if (!gst_gl_display_create_context (priv->display, priv->other_context, + &priv->context, &error)) { + GST_WARNING ("Could not create OpenGL context: %s", + error ? error->message : "Unknown"); + g_clear_error (&error); + GST_OBJECT_UNLOCK (priv->display); + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return FALSE; + } + gst_gl_display_add_context (priv->display, priv->context); + GST_OBJECT_UNLOCK (priv->display); + + GTK_GST_BASE_WIDGET_UNLOCK (gst_widget); + return TRUE; +} + +GstGLContext * +gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * gst_widget) +{ + if (!gst_widget->priv->other_context) + return NULL; + + return gst_object_ref (gst_widget->priv->other_context); +} + +GstGLContext * +gtk_gst_gl_widget_get_context (GtkGstGLWidget * gst_widget) +{ + if (!gst_widget->priv->context) + return NULL; + + return gst_object_ref (gst_widget->priv->context); +} + +GstGLDisplay * +gtk_gst_gl_widget_get_display (GtkGstGLWidget * gst_widget) +{ + if (!gst_widget->priv->display) + return NULL; + + return gst_object_ref (gst_widget->priv->display); +} diff --git a/lib/gst/clapper/gtk4/gtkgstglwidget.h b/lib/gst/clapper/gtk4/gtkgstglwidget.h new file mode 100644 index 00000000..7f055c48 --- /dev/null +++ b/lib/gst/clapper/gtk4/gtkgstglwidget.h @@ -0,0 +1,77 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GTK_GST_GL_WIDGET_H__ +#define __GTK_GST_GL_WIDGET_H__ + +#include +#include +#include + +#include "gtkgstbasewidget.h" + +G_BEGIN_DECLS + +GType gtk_gst_gl_widget_get_type (void); +#define GTK_TYPE_GST_GL_WIDGET (gtk_gst_gl_widget_get_type()) +#define GTK_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidget)) +#define GTK_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidgetClass)) +#define GTK_IS_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_GL_WIDGET)) +#define GTK_IS_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_GL_WIDGET)) +#define GTK_GST_GL_WIDGET_CAST(obj) ((GtkGstGLWidget*)(obj)) + +typedef struct _GtkGstGLWidget GtkGstGLWidget; +typedef struct _GtkGstGLWidgetClass GtkGstGLWidgetClass; +typedef struct _GtkGstGLWidgetPrivate GtkGstGLWidgetPrivate; + +/** + * GtkGstGLWidget: + * + * Opaque #GtkGstGLWidget object + */ +struct _GtkGstGLWidget +{ + /* */ + GtkGstBaseWidget base; + + GtkGstGLWidgetPrivate *priv; +}; + +/** + * GtkGstGLWidgetClass: + * + * The #GtkGstGLWidgetClass struct only contains private data + */ +struct _GtkGstGLWidgetClass +{ + /* */ + GtkGstBaseWidgetClass base_class; +}; + +GtkWidget * gtk_gst_gl_widget_new (void); + +gboolean gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * widget); +GstGLDisplay * gtk_gst_gl_widget_get_display (GtkGstGLWidget * widget); +GstGLContext * gtk_gst_gl_widget_get_context (GtkGstGLWidget * widget); +GstGLContext * gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * widget); + +G_END_DECLS + +#endif /* __GTK_GST_GL_WIDGET_H__ */ diff --git a/lib/gst/clapper/meson.build b/lib/gst/clapper/meson.build index fc93b596..1c6d9384 100644 --- a/lib/gst/clapper/meson.build +++ b/lib/gst/clapper/meson.build @@ -6,8 +6,14 @@ gstclapper_sources = [ 'gstclapper-g-main-context-signal-dispatcher.c', 'gstclapper-video-overlay-video-renderer.c', 'gstclapper-visualization.c', -] + 'gstclapper-gtk4-plugin.c', + 'gtk4/gstgtkbasesink.c', + 'gtk4/gstgtkutils.c', + 'gtk4/gtkgstbasewidget.c', + 'gtk4/gstgtkglsink.c', + 'gtk4/gtkgstglwidget.c', +] gstclapper_headers = [ 'clapper.h', 'clapper-prelude.h', @@ -19,20 +25,57 @@ gstclapper_headers = [ 'gstclapper-g-main-context-signal-dispatcher.h', 'gstclapper-video-overlay-video-renderer.h', 'gstclapper-visualization.h', + 'gstclapper-gtk4-plugin.h', ] +gstclapper_defines = [ + '-DHAVE_CONFIG_H', + '-DBUILDING_GST_CLAPPER', + '-DGST_USE_UNSTABLE_API', + '-DHAVE_GTK_GL', + '-DBUILD_FOR_GTK4', +] +gtk_deps = [gstgl_dep, gstglproto_dep] +have_gtk_gl_windowing = false -if not build_gir - error('Clapper requires GI bindings to be compiled') +gtk4_dep = dependency('gtk4', required : true) + +if not gtk4_dep.found() or not gtk4_dep.version().version_compare('>=4.0.0') + error('GTK4 is missing or is too old') +endif + +if not have_gstgl + error('GstGL is missing') +endif + +if gst_gl_have_window_x11 and gst_gl_have_platform_glx + gtk_x11_dep = dependency('gtk4-x11', required : false) + if gtk_x11_dep.found() + gtk_deps += [gtk_x11_dep, gstglx11_dep] + have_gtk_gl_windowing = true + endif +endif + +if gst_gl_have_window_wayland and gst_gl_have_platform_egl + gtk_wayland_dep = dependency('gtk4-wayland', required : false) + if gtk_wayland_dep.found() + gtk_deps += [gtk_wayland_dep, gstglegl_dep, gstglwayland_dep] + have_gtk_gl_windowing = true + endif +endif + +if not have_gtk_gl_windowing + error('GTK4 widget requires GL windowing') endif gstclapper = library('gstclapper-' + api_version, gstclapper_sources, - c_args : gst_clapper_args + ['-DBUILDING_GST_CLAPPER'], + c_args : gstclapper_defines, + link_args : noseh_link_args, include_directories : [configinc, libsinc], version : libversion, install : true, - dependencies : [gstbase_dep, gstvideo_dep, gstaudio_dep, - gsttag_dep, gstpbutils_dep], + dependencies : [gtk4_dep, gstbase_dep, gstvideo_dep, gstaudio_dep, + gsttag_dep, gstpbutils_dep, libm] + gtk_deps, ) clapper_gir = gnome.generate_gir(gstclapper, diff --git a/lib/meson.build b/lib/meson.build index a14f258d..bcbc3fbe 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -28,10 +28,12 @@ if cc.get_id() == 'msvc' '/w14189', # 'identifier' : local variable is initialized but not referenced ] add_project_arguments(msvc_args, language: ['c', 'cpp']) + noseh_link_args = ['/SAFESEH:NO'] else if cxx.has_argument('-Wno-non-virtual-dtor') add_project_arguments('-Wno-non-virtual-dtor', language: 'cpp') endif + noseh_link_args = [] endif if cc.has_link_argument('-Wl,-Bsymbolic-functions') @@ -153,13 +155,11 @@ warning_flags = [ '-Wvla', '-Wpointer-arith', ] - warning_c_flags = [ '-Wmissing-prototypes', '-Wdeclaration-after-statement', '-Wold-style-definition', ] - warning_cxx_flags = [ '-Wformat-nonliteral', ] @@ -194,13 +194,71 @@ gst_dep = dependency('gstreamer-1.0', version : gst_req, gstbase_dep = dependency('gstreamer-base-1.0', version : gst_req, fallback : ['gstreamer', 'gst_base_dep']) gstpbutils_dep = dependency('gstreamer-pbutils-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'pbutils_dep']) + fallback : ['gst-plugins-base', 'pbutils_dep']) gstaudio_dep = dependency('gstreamer-audio-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'audio_dep']) + fallback : ['gst-plugins-base', 'audio_dep']) gsttag_dep = dependency('gstreamer-tag-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'tag_dep']) + fallback : ['gst-plugins-base', 'tag_dep']) gstvideo_dep = dependency('gstreamer-video-1.0', version : gst_req, - fallback : ['gst-plugins-base', 'video_dep']) + fallback : ['gst-plugins-base', 'video_dep']) + +# GStreamer OpenGL +gstgl_dep = dependency('gstreamer-gl-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstgl_dep'], required: false) +gstglproto_dep = dependency('', required : false) +gstglx11_dep = dependency('', required : false) +gstglwayland_dep = dependency('', required : false) +gstglegl_dep = dependency('', required : false) + +have_gstgl = gstgl_dep.found() + +if have_gstgl + if gstgl_dep.type_name() == 'pkgconfig' + gst_gl_apis = gstgl_dep.get_pkgconfig_variable('gl_apis').split() + gst_gl_winsys = gstgl_dep.get_pkgconfig_variable('gl_winsys').split() + gst_gl_platforms = gstgl_dep.get_pkgconfig_variable('gl_platforms').split() + else + gstbase = subproject('gst-plugins-base') + gst_gl_apis = gstbase.get_variable('enabled_gl_apis') + gst_gl_winsys = gstbase.get_variable('enabled_gl_winsys') + gst_gl_platforms = gstbase.get_variable('enabled_gl_platforms') + endif + + message('GStreamer OpenGL window systems: @0@'.format(' '.join(gst_gl_winsys))) + message('GStreamer OpenGL platforms: @0@'.format(' '.join(gst_gl_platforms))) + message('GStreamer OpenGL apis: @0@'.format(' '.join(gst_gl_apis))) + + foreach ws : ['x11', 'wayland', 'android', 'cocoa', 'eagl', 'win32', 'dispmanx', 'viv_fb'] + set_variable('gst_gl_have_window_@0@'.format(ws), gst_gl_winsys.contains(ws)) + endforeach + + foreach p : ['glx', 'egl', 'cgl', 'eagl', 'wgl'] + set_variable('gst_gl_have_platform_@0@'.format(p), gst_gl_platforms.contains(p)) + endforeach + + foreach api : ['gl', 'gles2'] + set_variable('gst_gl_have_api_@0@'.format(api), gst_gl_apis.contains(api)) + endforeach + + gstglproto_dep = dependency('gstreamer-gl-prototypes-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglproto_dep'], required: true) + # Behind specific checks because meson fails at optional dependencies with a + # fallback to the same subproject. On the first failure, meson will never + # check the system again even if the fallback never existed. + # Last checked with meson 0.54.3 + if gst_gl_have_window_x11 + gstglx11_dep = dependency('gstreamer-gl-x11-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglx11_dep'], required: true) + endif + if gst_gl_have_window_wayland + gstglwayland_dep = dependency('gstreamer-gl-wayland-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglwayland_dep'], required: true) + endif + if gst_gl_have_platform_egl + gstglegl_dep = dependency('gstreamer-gl-egl-1.0', version : gst_req, + fallback : ['gst-plugins-base', 'gstglegl_dep'], required: true) + endif +endif libm = cc.find_library('m', required : false) glib_dep = dependency('glib-2.0', version : glib_req, fallback: ['glib', 'libglib_dep']) @@ -219,15 +277,18 @@ if cxx.has_argument ('-Wno-unused') add_project_arguments('-Wno-unused', language: 'cpp') endif -gst_clapper_args = ['-DHAVE_CONFIG_H'] configinc = include_directories('.') libsinc = include_directories('gst') python3 = import('python').find_installation() gnome = import('gnome') gir = find_program('g-ir-scanner', required : true) -build_gir = gir.found() -gir_init_section = [ '--add-init-section=extern void gst_init(gint*,gchar**);' + \ + +if not gir.found() + error('Clapper requires GI bindings to be compiled') +endif + +gir_init_section = ['--add-init-section=extern void gst_init(gint*,gchar**);' + \ 'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \ 'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \ 'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \ diff --git a/src/playerBase.js b/src/playerBase.js index d3db2478..5939101e 100644 --- a/src/playerBase.js +++ b/src/playerBase.js @@ -14,21 +14,9 @@ class ClapperPlayerBase extends GstClapper.Clapper { _init() { - if(!Gst.is_initialized()) - Gst.init(null); - - const plugin = 'gtk4glsink'; - const gtk4glsink = Gst.ElementFactory.make(plugin, null); - - if(!gtk4glsink) { - debug(new Error( - `Could not load "${plugin}".` - + ' Do you have gstreamer-plugins-good-gtk4 installed?' - )); - } - + const gtk4plugin = new GstClapper.ClapperGtk4Plugin(); const glsinkbin = Gst.ElementFactory.make('glsinkbin', null); - glsinkbin.sink = gtk4glsink; + glsinkbin.sink = gtk4plugin.video_sink; const dispatcher = new GstClapper.ClapperGMainContextSignalDispatcher(); const renderer = new GstClapper.ClapperVideoOverlayVideoRenderer({ @@ -40,7 +28,7 @@ class ClapperPlayerBase extends GstClapper.Clapper video_renderer: renderer }); - this.widget = gtk4glsink.widget; + this.widget = gtk4plugin.video_sink.widget; this.widget.vexpand = true; this.widget.hexpand = true;