diff --git a/lib/gst/clapper/meson.build b/lib/gst/clapper/meson.build index 168946b8..38135e7c 100644 --- a/lib/gst/clapper/meson.build +++ b/lib/gst/clapper/meson.build @@ -34,36 +34,13 @@ gstclapper_defines = [ '-DGST_USE_UNSTABLE_API', '-DHAVE_GTK_GL', ] -gtk_deps = [gstgl_dep, gstglproto_dep] -have_gtk_gl_windowing = false -gtk4_dep = dependency('gtk4', required: true) - -if not gtk4_dep.version().version_compare('>=4.0.0') - error('GTK4 version on this system is too old') +if not get_option('lib') + subdir_done() endif -if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx) - gtk_x11_dep = dependency('gtk4-x11', required: false) - if gtk_x11_dep.found() - gtk_deps += gtk_x11_dep - if gst_gl_have_platform_glx - gtk_deps += gstglx11_dep - endif - 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, gstglwayland_dep] - have_gtk_gl_windowing = true - endif -endif - -if gst_gl_have_platform_egl - gtk_deps += gstglegl_dep +if not gir.found() + error('Clapper lib requires GI bindings to be compiled') endif if not have_gtk_gl_windowing diff --git a/lib/gst/meson.build b/lib/gst/meson.build index 90cf6581..55a0d756 100644 --- a/lib/gst/meson.build +++ b/lib/gst/meson.build @@ -1 +1,2 @@ subdir('clapper') +subdir('plugin') diff --git a/lib/gst/plugin/gstclappersink.c b/lib/gst/plugin/gstclappersink.c new file mode 100644 index 00000000..55b66653 --- /dev/null +++ b/lib/gst/plugin/gstclappersink.c @@ -0,0 +1,681 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020-2022 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 "gstclappersink.h" +#include "gstgtkutils.h" + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 0 +#define DEFAULT_PAR_D 1 + +#define SINK_FORMATS \ + "{ BGR, RGB, BGRA, RGBA, ABGR, ARGB, RGBx, BGRx, RGBA64_LE, RGBA64_BE }" + +#define GST_CLAPPER_GL_SINK_CAPS \ + "video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), " \ + "format = (string) RGBA, " \ + "width = " GST_VIDEO_SIZE_RANGE ", " \ + "height = " GST_VIDEO_SIZE_RANGE ", " \ + "framerate = " GST_VIDEO_FPS_RANGE ", " \ + "texture-target = (string) { 2D, external-oes } " \ + " ; " \ + "video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "," \ + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION "), " \ + "format = (string) RGBA, " \ + "width = " GST_VIDEO_SIZE_RANGE ", " \ + "height = " GST_VIDEO_SIZE_RANGE ", " \ + "framerate = " GST_VIDEO_FPS_RANGE ", " \ + "texture-target = (string) { 2D, external-oes } " + +enum +{ + PROP_0, + PROP_WIDGET, + PROP_FORCE_ASPECT_RATIO, + PROP_PIXEL_ASPECT_RATIO, + PROP_LAST +}; + +GST_DEBUG_CATEGORY (gst_debug_clapper_sink); +#define GST_CAT_DEFAULT gst_debug_clapper_sink + +static GstStaticPadTemplate gst_clapper_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("memory:DMABuf", SINK_FORMATS) ";" + GST_CLAPPER_GL_SINK_CAPS ";" + GST_VIDEO_CAPS_MAKE (SINK_FORMATS))); + +static void gst_clapper_sink_navigation_interface_init ( + GstNavigationInterface *iface); + +#define gst_clapper_sink_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstClapperSink, gst_clapper_sink, + GST_TYPE_VIDEO_SINK, + G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, + gst_clapper_sink_navigation_interface_init); + GST_DEBUG_CATEGORY_INIT (gst_debug_clapper_sink, + "clappersink", 0, "Clapper Sink")); +GST_ELEMENT_REGISTER_DEFINE (clappersink, "clappersink", GST_RANK_NONE, + GST_TYPE_CLAPPER_SINK); + +static void gst_clapper_sink_finalize (GObject *object); +static void gst_clapper_sink_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *param_spec); +static void gst_clapper_sink_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *param_spec); + +static gboolean gst_clapper_sink_propose_allocation (GstBaseSink *bsink, + GstQuery *query); +static gboolean gst_clapper_sink_start (GstBaseSink *bsink); +static gboolean gst_clapper_sink_stop (GstBaseSink *bsink); + +static GstStateChangeReturn +gst_clapper_sink_change_state (GstElement *element, GstStateChange transition); + +static void gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer, + GstClockTime *start, GstClockTime *end); +static GstCaps * gst_clapper_sink_get_caps (GstBaseSink *bsink, + GstCaps *filter); +static gboolean gst_clapper_sink_set_caps (GstBaseSink *bsink, + GstCaps *caps); +static GstFlowReturn gst_clapper_sink_show_frame (GstVideoSink *bsink, + GstBuffer *buffer); + +static void +gst_clapper_sink_class_init (GstClapperSinkClass *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_clapper_sink_set_property; + gobject_class->get_property = gst_clapper_sink_get_property; + gobject_class->finalize = gst_clapper_sink_finalize; + + //gst_gtk_install_shared_properties (gobject_class); + + gstelement_class->change_state = gst_clapper_sink_change_state; + + gstbasesink_class->get_caps = gst_clapper_sink_get_caps; + gstbasesink_class->set_caps = gst_clapper_sink_set_caps; + gstbasesink_class->get_times = gst_clapper_sink_get_times; + gstbasesink_class->propose_allocation = gst_clapper_sink_propose_allocation; + gstbasesink_class->start = gst_clapper_sink_start; + gstbasesink_class->stop = gst_clapper_sink_stop; + + gstvideosink_class->show_frame = gst_clapper_sink_show_frame; + + gst_element_class_set_metadata (gstelement_class, + "Clapper Video Sink", + "Sink/Video", "A GTK4 video sink used by Clapper media player", + "Rafał Dzięgiel "); + + gst_element_class_add_static_pad_template (gstelement_class, + &gst_clapper_sink_template); +} + +static void +gst_clapper_sink_init (GstClapperSink *self) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) GST_CLAPPER_SINK_GET_CLASS (self); + + /* HACK: install here instead of class init to avoid GStreamer + * plugin scanner GObject type conflicts with older GTK versions */ + if (!g_object_class_find_property (gobject_class, "widget")) { + 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)); + } + + self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + self->par_n = DEFAULT_PAR_N; + self->par_d = DEFAULT_PAR_D; +} + +static void +gst_clapper_sink_finalize (GObject *object) +{ + GstClapperSink *self = GST_CLAPPER_SINK (object); + + GST_TRACE ("Finalize"); + GST_OBJECT_LOCK (self); + + if (self->window && self->window_destroy_id) + g_signal_handler_disconnect (self->window, self->window_destroy_id); + //if (self->widget && self->widget_destroy_id) + // g_signal_handler_disconnect (self->widget, self->widget_destroy_id); + + g_clear_object (&self->obj); + GST_OBJECT_UNLOCK (self); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +widget_destroy_cb (GtkWidget *widget, GstClapperSink *self) +{ + GST_OBJECT_LOCK (self); + g_clear_object (&self->obj); + GST_OBJECT_UNLOCK (self); +} + +static void +window_destroy_cb (GtkWidget *window, GstClapperSink *self) +{ + GST_OBJECT_LOCK (self); + + if (self->obj) { + if (self->widget_destroy_id) { + GtkWidget *widget; + + widget = gtk_clapper_object_get_widget (self->obj); + + g_signal_handler_disconnect (widget, self->widget_destroy_id); + self->widget_destroy_id = 0; + } + g_clear_object (&self->obj); + } + self->window = NULL; + + GST_OBJECT_UNLOCK (self); +} + +static GtkWidget * +gst_clapper_sink_get_widget (GstClapperSink *self) +{ + if (G_UNLIKELY (self->obj == NULL)) { + /* Ensure GTK is initialized */ + if (!gtk_init_check ()) { + GST_ERROR_OBJECT (self, "Could not ensure GTK initialization"); + return NULL; + } + + self->obj = gtk_clapper_object_new (); + + /* Take the floating ref, otherwise the destruction of the container will + * make this widget disappear possibly before we are done. */ + //g_object_ref_sink (self->obj); + + //self->widget_destroy_id = g_signal_connect (widget, + // "destroy", G_CALLBACK (widget_destroy_cb), self); + + /* Back pointer */ + gtk_clapper_object_set_element ( + GTK_CLAPPER_OBJECT (self->obj), GST_ELEMENT (self)); + } + + return gtk_clapper_object_get_widget (self->obj); +} + +static void +gst_clapper_sink_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstClapperSink *self = GST_CLAPPER_SINK (object); + + switch (prop_id) { + case PROP_WIDGET:{ + GObject *widget = NULL; + + GST_OBJECT_LOCK (self); + if (G_LIKELY (self->obj != NULL)) + widget = G_OBJECT (gtk_clapper_object_get_widget (self->obj)); + GST_OBJECT_UNLOCK (self); + + if (G_UNLIKELY (widget == NULL)) { + widget = gst_gtk_invoke_on_main ( + (GThreadFunc) gst_clapper_sink_get_widget, self); + } + + g_value_set_object (value, widget); + break; + } + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, self->force_aspect_ratio); + break; + case PROP_PIXEL_ASPECT_RATIO: + gst_value_set_fraction (value, self->par_n, self->par_d); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_sink_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstClapperSink *self = GST_CLAPPER_SINK (object); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + self->force_aspect_ratio = g_value_get_boolean (value); + break; + case PROP_PIXEL_ASPECT_RATIO: + self->par_n = gst_value_get_fraction_numerator (value); + self->par_d = gst_value_get_fraction_denominator (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_clapper_sink_navigation_send_event (GstNavigation *navigation, + GstStructure *structure) +{ + GstClapperSink *sink = GST_CLAPPER_SINK_CAST (navigation); + GstEvent *event; + + GST_TRACE_OBJECT (sink, "Navigation event: %" GST_PTR_FORMAT, structure); + event = gst_event_new_navigation (structure); + + if (G_LIKELY (GST_IS_EVENT (event))) { + GstPad *pad; + + pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink)); + + if (G_LIKELY (GST_IS_PAD (pad))) { + 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_object_unref (pad); + } + gst_event_unref (event); + } +} + +static void +gst_clapper_sink_navigation_interface_init (GstNavigationInterface *iface) +{ + iface->send_event = gst_clapper_sink_navigation_send_event; +} + +static gboolean +gst_clapper_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query) +{ + GstClapperSink *self = GST_CLAPPER_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 (!self->display || !self->context) + // return FALSE; + + gst_query_parse_allocation (query, &caps, &need_pool); + + if (!caps) + goto no_caps; + + if (!gst_video_info_from_caps (&info, caps)) + goto invalid_caps; + + /* Normal size of a frame */ + size = GST_VIDEO_INFO_SIZE (&info); + + if (need_pool) { + GST_DEBUG_OBJECT (self, "Creating new pool"); + + pool = gst_buffer_pool_new (); + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + gst_object_unref (pool); + goto config_failed; + } + } + + /* We need at least 3 buffers because we keep around the current one + * for memory to stay valid during resizing and hold on to the pending one */ + gst_query_add_allocation_pool (query, pool, size, 3, 0); + if (pool) + gst_object_unref (pool); + + /* FIXME: Read calculated display sizes from widget */ + display_width = GST_VIDEO_INFO_WIDTH (&info); + display_height = GST_VIDEO_INFO_HEIGHT (&info); + + if (display_width != 0 && display_height != 0) { + GST_DEBUG_OBJECT (self, "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, NULL); + + 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 to set config"); + return FALSE; +} + +static gboolean +gst_clapper_sink_start_on_main (GstClapperSink *self) +{ + GtkWidget *widget; + + /* Make sure widget is created */ + if (!(widget = gst_clapper_sink_get_widget (self))) + return FALSE; + + /* After this point, self->obj will always be set */ + + if (!GTK_IS_ROOT (gtk_widget_get_root (widget))) { + GtkWidget *toplevel, *parent; + gchar *win_title; + + if ((parent = gtk_widget_get_parent (widget))) { + GtkWidget *temp_parent; + + while ((temp_parent = gtk_widget_get_parent (parent))) + parent = temp_parent; + } + toplevel = (parent) ? parent : widget; + + /* User did not add widget its own UI, let's popup a new GtkWindow to + * make "gst-launch-1.0" work. */ + self->window = (GtkWindow *) gtk_window_new (); + + win_title = g_strdup_printf ("Clapper Sink - GTK %u.%u.%u Window", + gtk_get_major_version (), + gtk_get_minor_version (), + gtk_get_micro_version ()); + + gtk_window_set_default_size (self->window, 640, 480); + gtk_window_set_title (self->window, win_title); + gtk_window_set_child (self->window, toplevel); + + g_free (win_title); + + self->window_destroy_id = g_signal_connect (self->window, + "destroy", G_CALLBACK (window_destroy_cb), self); + } + + return TRUE; +} + +static gboolean +gst_clapper_sink_start (GstBaseSink *bsink) +{ + GstClapperSink *self = GST_CLAPPER_SINK (bsink); + GtkClapperObject *obj = NULL; + + if (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_clapper_sink_start_on_main, self))) + return FALSE; + + //widget = GTK_CLAPPER_WIDGET (self->widget); + + GST_OBJECT_LOCK (self); + if (self->obj) + obj = g_object_ref (self->obj); + GST_OBJECT_UNLOCK (self); + + if (G_UNLIKELY (obj == NULL)) { + GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s", + "Clapper widget does not exist"), (NULL)); + return FALSE; + } + + if (!gtk_clapper_object_init_winsys (obj)) { + GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s", + "Failed to initialize OpenGL with GTK"), (NULL)); + return FALSE; + } +/* + if (!clapper_sink->display) + clapper_sink->display = gtk_clapper_gl_widget_get_display (clapper_widget); + if (!clapper_sink->context) + clapper_sink->context = gtk_clapper_gl_widget_get_context (clapper_widget); + if (!clapper_sink->gtk_context) + clapper_sink->gtk_context = gtk_clapper_gl_widget_get_gtk_context (clapper_widget); + + if (!clapper_sink->display || !clapper_sink->context || !clapper_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), + clapper_sink->display); +*/ + + return TRUE; +} + +static gboolean +gst_clapper_sink_stop_on_main (GstClapperSink *self) +{ + if (self->window) { + gtk_window_destroy (self->window); + self->window = NULL; + //self->widget = NULL; + } + + return TRUE; +} + +static gboolean +gst_clapper_sink_stop (GstBaseSink *bsink) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); + + if (G_UNLIKELY (self->window != NULL)) { + return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_clapper_sink_stop_on_main, self); + } + + return TRUE; +} + +static void +gst_gtk_window_show_all_and_unref (GtkWindow *window) +{ + gtk_window_present (window); + g_object_unref (window); +} + +static GstStateChangeReturn +gst_clapper_sink_change_state (GstElement *element, GstStateChange transition) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (self, "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 (G_UNLIKELY (ret == GST_STATE_CHANGE_FAILURE)) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED:{ + GtkWindow *window = NULL; + + GST_OBJECT_LOCK (self); + if (self->window) + window = g_object_ref (self->window); + GST_OBJECT_UNLOCK (self); + + 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 (self); + if (G_LIKELY (self->obj != NULL)) + gtk_clapper_object_set_buffer (self->obj, NULL); + GST_OBJECT_UNLOCK (self); + break; + default: + break; + } + + return ret; +} + +static void +gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer, + GstClockTime *start, GstClockTime *end) +{ + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { + *start = GST_BUFFER_TIMESTAMP (buffer); + + if (GST_BUFFER_DURATION_IS_VALID (buffer)) { + *end = *start + GST_BUFFER_DURATION (buffer); + } else { + GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); + + if (GST_VIDEO_INFO_FPS_N (&self->v_info) > 0) { + *end = *start + gst_util_uint64_scale_int (GST_SECOND, + GST_VIDEO_INFO_FPS_D (&self->v_info), + GST_VIDEO_INFO_FPS_N (&self->v_info)); + } + } + } +} + +static GstCaps * +gst_clapper_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 gboolean +gst_clapper_sink_set_caps (GstBaseSink *bsink, GstCaps *caps) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); + + GST_DEBUG ("Set caps: %" GST_PTR_FORMAT, caps); + GST_OBJECT_LOCK (self); + + if (!gst_video_info_from_caps (&self->v_info, caps)) { + GST_OBJECT_UNLOCK (self); + return FALSE; + } + + if (G_UNLIKELY (self->obj == NULL)) { + GST_OBJECT_UNLOCK (self); + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("%s", "Output widget was destroyed"), (NULL)); + return FALSE; + } + + if (!gtk_clapper_object_set_format (self->obj, &self->v_info)) { + GST_OBJECT_UNLOCK (self); + return FALSE; + } + + GST_OBJECT_UNLOCK (self); + + return TRUE; +} + +static GstFlowReturn +gst_clapper_sink_show_frame (GstVideoSink *vsink, GstBuffer *buffer) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink); + + GST_TRACE ("Rendering buffer: %p", buffer); + GST_OBJECT_LOCK (self); + + if (G_UNLIKELY (self->obj == NULL)) { + GST_OBJECT_UNLOCK (self); + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("%s", "Output widget was destroyed"), (NULL)); + return GST_FLOW_ERROR; + } + + gtk_clapper_object_set_buffer (self->obj, buffer); + GST_OBJECT_UNLOCK (self); + + return GST_FLOW_OK; +} diff --git a/lib/gst/plugin/gstclappersink.h b/lib/gst/plugin/gstclappersink.h new file mode 100644 index 00000000..881cb37c --- /dev/null +++ b/lib/gst/plugin/gstclappersink.h @@ -0,0 +1,74 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020-2022 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "gtkclapperobject.h" + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_SINK (gst_clapper_sink_get_type ()) +#define GST_IS_CLAPPER_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_SINK)) +#define GST_IS_CLAPPER_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_SINK)) +#define GST_CLAPPER_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass)) +#define GST_CLAPPER_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass)) +#define GST_CLAPPER_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSink)) +#define GST_CLAPPER_SINK_CAST(obj) ((GstClapperSink*)(obj)) + +typedef struct _GstClapperSink GstClapperSink; +typedef struct _GstClapperSinkClass GstClapperSinkClass; + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperSink, gst_object_unref) +#endif + +struct _GstClapperSink +{ + GstVideoSink parent; + + GstVideoInfo v_info; + + GtkClapperObject *obj; + GtkWindow *window; + + /* properties */ + gboolean force_aspect_ratio; + gint par_n, par_d; + + gint display_width, display_height; + gulong widget_destroy_id, window_destroy_id; +}; + +struct _GstClapperSinkClass +{ + GstVideoSinkClass parent_class; +}; + +GST_ELEMENT_REGISTER_DECLARE (clappersink); + +GType gst_clapper_sink_get_type (void); + +G_END_DECLS diff --git a/lib/gst/plugin/gstgtkutils.c b/lib/gst/plugin/gstgtkutils.c new file mode 100644 index 00000000..c730f018 --- /dev/null +++ b/lib/gst/plugin/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/plugin/gstgtkutils.h b/lib/gst/plugin/gstgtkutils.h new file mode 100644 index 00000000..8c33a90b --- /dev/null +++ b/lib/gst/plugin/gstgtkutils.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#pragma once + +#include + +gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data); diff --git a/lib/gst/plugin/gstplugin.c b/lib/gst/plugin/gstplugin.c new file mode 100644 index 00000000..64f38681 --- /dev/null +++ b/lib/gst/plugin/gstplugin.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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 "gstclappersink.h" + +static gboolean +plugin_init (GstPlugin *plugin) +{ + gboolean res = FALSE; + + res |= GST_ELEMENT_REGISTER (clappersink, plugin); + + return res; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, + clapper, "Clapper elements", plugin_init, VERSION, "LGPL", + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/lib/gst/plugin/gtkclapperobject.c b/lib/gst/plugin/gtkclapperobject.c new file mode 100644 index 00000000..7792ff70 --- /dev/null +++ b/lib/gst/plugin/gtkclapperobject.c @@ -0,0 +1,737 @@ +/* + * GStreamer + * Copyright (C) 2022 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 + +#include "gtkclapperobject.h" +#include "gstgtkutils.h" + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) +#include +#if GST_GL_HAVE_PLATFORM_EGL +#include +#endif +#endif + +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND) +#include +#include +#endif + +#if GST_GL_HAVE_PLATFORM_EGL +#include +#endif + +GST_DEBUG_CATEGORY (gst_debug_clapper_object); +#define GST_CAT_DEFAULT gst_debug_clapper_object + +static void gtk_clapper_object_paintable_iface_init (GdkPaintableInterface *iface); +static void gtk_clapper_object_finalize (GObject *object); + +#define gtk_clapper_object_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GtkClapperObject, gtk_clapper_object, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + gtk_clapper_object_paintable_iface_init) + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkclapperobject", 0, + "GTK Clapper Object")); + +static void +gtk_clapper_object_class_init (GtkClapperObjectClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gtk_clapper_object_finalize; +} + +static void +gtk_clapper_object_init (GtkClapperObject *self) +{ + self->last_pos_x = 0; + self->last_pos_y = 0; + + self->picture = (GtkPicture *) gtk_picture_new (); + gtk_picture_set_paintable (self->picture, GDK_PAINTABLE (self)); + + gst_video_info_init (&self->v_info); + gst_video_info_init (&self->pending_v_info); + + g_weak_ref_init (&self->element, NULL); + g_mutex_init (&self->lock); +} + +static void +gtk_clapper_object_finalize (GObject *object) +{ + GtkClapperObject *self = GTK_CLAPPER_OBJECT (object); + + if (self->draw_id) + g_source_remove (self->draw_id); + + gst_buffer_replace (&self->pending_buffer, NULL); + gst_buffer_replace (&self->buffer, NULL); + + g_mutex_clear (&self->lock); + g_weak_ref_clear (&self->element); + + GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static void +_gdk_gl_context_set_active (GtkClapperObject *self, gboolean activate) +{ + /* We wrap around a GDK context, so we need to make + * both GTK and GStreamer aware of its active state */ + if (activate) { + gdk_gl_context_make_current (self->gdk_context); + gst_gl_context_activate (self->wrapped_context, TRUE); + } else { + gst_gl_context_activate (self->wrapped_context, FALSE); + gdk_gl_context_clear_current (); + } +} + +static GdkMemoryFormat +video_format_to_gdk_memory_format (GstVideoFormat format) +{ + switch (format) { + case GST_VIDEO_FORMAT_BGR: + return GDK_MEMORY_B8G8R8; + case GST_VIDEO_FORMAT_RGB: + return GDK_MEMORY_R8G8B8; + case GST_VIDEO_FORMAT_BGRA: + return GDK_MEMORY_B8G8R8A8; + case GST_VIDEO_FORMAT_RGBA: + return GDK_MEMORY_R8G8B8A8; + case GST_VIDEO_FORMAT_ABGR: + return GDK_MEMORY_A8B8G8R8; + case GST_VIDEO_FORMAT_ARGB: + return GDK_MEMORY_A8R8G8B8; + case GST_VIDEO_FORMAT_BGRx: + return GDK_MEMORY_B8G8R8A8_PREMULTIPLIED; + case GST_VIDEO_FORMAT_RGBx: + return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; + case GST_VIDEO_FORMAT_RGBA64_LE: + case GST_VIDEO_FORMAT_RGBA64_BE: + return GDK_MEMORY_R16G16B16A16_PREMULTIPLIED; + default: + g_assert_not_reached (); + } + + /* Number not belonging to any format */ + return GDK_MEMORY_N_FORMATS; +} + +static GdkTexture * +import_dmabuf (GtkClapperObject *self, guint n_planes, + gint *fds, gsize *offsets) +{ + GdkTexture *texture; + GstEGLImage *image; + const GstGLFuncs *gl; + + _gdk_gl_context_set_active (self, TRUE); + + image = gst_egl_image_from_dmabuf_direct_target (self->wrapped_context, + fds, offsets, &self->v_info, GST_GL_TEXTURE_TARGET_2D); + + /* FIXME: Can we handle `GST_GL_TEXTURE_TARGET_EXTERNAL_OES` + * without reinventing GLArea sink all over again? */ + + /* If HW colorspace conversion failed and there is only one + * plane, we can just make it into single EGLImage as is */ + if (!image && n_planes == 1) + image = gst_egl_image_from_dmabuf (self->wrapped_context, + fds[0], &self->v_info, 0, offsets[0]); + + /* Still no image? Give up then */ + if (!image) { + _gdk_gl_context_set_active (self, FALSE); + return NULL; + } + + gl = self->wrapped_context->gl_vtable; + + if (!self->texture_id) + gl->GenTextures (1, &self->texture_id); + + gl->BindTexture (GL_TEXTURE_2D, self->texture_id); + + gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + gl->EGLImageTargetTexture2D (GL_TEXTURE_2D, gst_egl_image_get_image (image)); + + texture = gdk_gl_texture_new (self->gdk_context, self->texture_id, + GST_VIDEO_INFO_WIDTH (&self->v_info), + GST_VIDEO_INFO_HEIGHT (&self->v_info), + NULL, NULL); + + gst_egl_image_unref (image); + _gdk_gl_context_set_active (self, FALSE); + + return texture; +} + +typedef gboolean (*MemTypeCheckFunc) (gpointer data); + +static gboolean +buffer_memory_type_check (GstBuffer *buffer, MemTypeCheckFunc func) +{ + guint i, n_mems; + + n_mems = gst_buffer_n_memory (buffer); + + for (i = 0; i < n_mems; i++) { + if (!func (gst_buffer_peek_memory (buffer, i))) + return FALSE; + } + + return n_mems > 0; +} + +static gboolean +verify_dmabuf_memory (GtkClapperObject *self, guint n_planes, + gint *fds, gsize *offsets) +{ + guint i; + + for (i = 0; i < n_planes; i++) { + GstMemory *memory; + gsize plane_size, mem_skip; + guint mem_idx, length; + + plane_size = gst_gl_get_plane_data_size (&self->v_info, NULL, i); + + if (!gst_buffer_find_memory (self->buffer, + GST_VIDEO_INFO_PLANE_OFFSET (&self->v_info, i), + plane_size, &mem_idx, &length, &mem_skip)) { + GST_DEBUG_OBJECT (self, "Could not find memory %u", i); + return FALSE; + } + + /* We can't have more then one DMABuf per plane */ + if (length != 1) { + GST_DEBUG_OBJECT (self, "Data for plane %u spans %u memories", + i, length); + return FALSE; + } + + memory = gst_buffer_peek_memory (self->buffer, mem_idx); + + offsets[i] = memory->offset + mem_skip; + fds[i] = gst_dmabuf_memory_get_fd (memory); + } + + return TRUE; +} + +static GdkTexture * +obtain_texture_from_current_buffer (GtkClapperObject *self) +{ + GdkTexture *texture = NULL; + GstVideoFrame frame; + + /* DMABuf */ + if (buffer_memory_type_check (self->buffer, (MemTypeCheckFunc) gst_is_dmabuf_memory)) { + gsize offsets[GST_VIDEO_MAX_PLANES]; + gint fds[GST_VIDEO_MAX_PLANES]; + guint n_planes; + + n_planes = GST_VIDEO_INFO_N_PLANES (&self->v_info); + + if (!verify_dmabuf_memory (self, n_planes, fds, offsets)) { + GST_ERROR ("DMABuf memory is invalid"); + return NULL; + } + + if (!((texture = import_dmabuf (self, n_planes, fds, offsets)))) + GST_ERROR ("Could not create texture from DMABuf"); + + return texture; + } + + /* GL Memory */ + if (buffer_memory_type_check (self->buffer, (MemTypeCheckFunc) gst_is_gl_memory)) { + if (gst_video_frame_map (&frame, &self->v_info, self->buffer, GST_MAP_READ | GST_MAP_GL)) { + + GST_FIXME_OBJECT (self, "Handle GstGLMemory"); + + texture = gdk_gl_texture_new ( + self->gdk_context, + *(guint *) GST_VIDEO_FRAME_PLANE_DATA (&frame, 0), + GST_VIDEO_FRAME_WIDTH (&frame), + GST_VIDEO_FRAME_HEIGHT (&frame), + NULL, NULL); + + gst_video_frame_unmap (&frame); + } + + return texture; + } + + /* RAW */ + if (gst_video_frame_map (&frame, &self->v_info, self->buffer, GST_MAP_READ)) { + GBytes *bytes; + + /* Our ref on a buffer together with 2 buffers pool ensures that + * current buffer will not be freed while another one is prepared */ + bytes = g_bytes_new_with_free_func ( + GST_VIDEO_FRAME_PLANE_DATA (&frame, 0), + GST_VIDEO_FRAME_HEIGHT (&frame) * GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0), + NULL, NULL); + + texture = gdk_memory_texture_new ( + GST_VIDEO_FRAME_WIDTH (&frame), + GST_VIDEO_FRAME_HEIGHT (&frame), + video_format_to_gdk_memory_format (GST_VIDEO_FRAME_FORMAT (&frame)), + bytes, + GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0)); + + g_bytes_unref (bytes); + gst_video_frame_unmap (&frame); + } + + return texture; +} + +static gboolean +calculate_display_par (GtkClapperObject *self, GstVideoInfo *info) +{ + gboolean success; + 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; + + /* User set props */ + if (self->par_n != 0 && self->par_d != 0) { + display_par_n = self->par_n; + display_par_d = self->par_d; + } else { + display_par_n = 1; + display_par_d = 1; + } + + if ((success = gst_video_calculate_display_ratio (&self->display_ratio_num, + &self->display_ratio_den, width, height, par_n, par_d, display_par_n, + display_par_d))) { + GST_LOG ("PAR: %u/%u, DAR: %u/%u", par_n, par_d, display_par_n, display_par_d); + } + + return success; +} + +static void +update_display_size (GtkClapperObject *self) +{ + guint display_ratio_num, display_ratio_den; + gint width, height; + + display_ratio_num = self->display_ratio_num; + display_ratio_den = self->display_ratio_den; + + width = GST_VIDEO_INFO_WIDTH (&self->v_info); + height = GST_VIDEO_INFO_HEIGHT (&self->v_info); + + if (height % display_ratio_den == 0) { + GST_DEBUG ("Keeping video height"); + + self->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, display_ratio_den); + self->display_height = height; + } else if (width % display_ratio_num == 0) { + GST_DEBUG ("Keeping video width"); + + self->display_width = width; + self->display_height = (guint) + gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num); + } else { + GST_DEBUG ("Approximating while keeping video height"); + + self->display_width = (guint) + gst_util_uint64_scale_int (height, display_ratio_num, display_ratio_den); + self->display_height = height; + } + + self->display_aspect_ratio = ((gdouble) self->display_width + / (gdouble) self->display_height); + GST_DEBUG ("Scaling to %dx%d", self->display_width, self->display_height); +} + +static void +update_paintable (GtkClapperObject *self, GdkPaintable *paintable) +{ + /* No change, so discard the new one */ + if (self->paintable == paintable) { + if (paintable) + g_object_unref (paintable); + + return; + } + + if (self->paintable) + g_object_unref (self->paintable); + + self->paintable = paintable; + + if (self->pending_resize) { + update_display_size (self); + gdk_paintable_invalidate_size ((GdkPaintable *) self); + + self->pending_resize = FALSE; + } + + gdk_paintable_invalidate_contents ((GdkPaintable *) self); +} + +static gboolean +draw_on_main_cb (GtkClapperObject *self) +{ + GdkTexture *texture; + + GTK_CLAPPER_OBJECT_LOCK (self); + + /* Replace used buffer and set matching v_info */ + gst_buffer_replace (&self->buffer, self->pending_buffer); + self->v_info = self->pending_v_info; + + texture = obtain_texture_from_current_buffer (self); + if (texture) + update_paintable (self, (GdkPaintable *) texture); + + self->draw_id = 0; + GTK_CLAPPER_OBJECT_UNLOCK (self); + + return G_SOURCE_REMOVE; +} + +void +gtk_clapper_object_set_element (GtkClapperObject *self, GstElement *element) +{ + g_weak_ref_set (&self->element, element); +} + +gboolean +gtk_clapper_object_set_format (GtkClapperObject *self, GstVideoInfo *v_info) +{ + GTK_CLAPPER_OBJECT_LOCK (self); + + if (gst_video_info_is_equal (&self->pending_v_info, v_info)) { + GTK_CLAPPER_OBJECT_UNLOCK (self); + return TRUE; + } + + if (!calculate_display_par (self, v_info)) { + GTK_CLAPPER_OBJECT_UNLOCK (self); + return FALSE; + } + + self->pending_resize = TRUE; + self->pending_v_info = *v_info; + + GTK_CLAPPER_OBJECT_UNLOCK (self); + + return TRUE; +} + +void +gtk_clapper_object_set_buffer (GtkClapperObject *self, GstBuffer *buffer) +{ + GstVideoMeta *meta = NULL; + + GTK_CLAPPER_OBJECT_LOCK (self); + + gst_buffer_replace (&self->pending_buffer, buffer); + + if (self->draw_id) { + GTK_CLAPPER_OBJECT_UNLOCK (self); + return; + } + + if (self->pending_buffer) + meta = gst_buffer_get_video_meta (self->pending_buffer); + + /* Update pending info from video meta */ + if (meta) { + guint i; + + GST_VIDEO_INFO_WIDTH (&self->pending_v_info) = meta->width; + GST_VIDEO_INFO_HEIGHT (&self->pending_v_info) = meta->height; + + for (i = 0; i < meta->n_planes; i++) { + GST_VIDEO_INFO_PLANE_OFFSET (&self->pending_v_info, i) = meta->offset[i]; + GST_VIDEO_INFO_PLANE_STRIDE (&self->pending_v_info, i) = meta->stride[i]; + } + } + + self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT, + (GSourceFunc) draw_on_main_cb, self, NULL); + + GTK_CLAPPER_OBJECT_UNLOCK (self); +} + +GtkClapperObject * +gtk_clapper_object_new (void) +{ + return g_object_new (GTK_TYPE_CLAPPER_OBJECT, NULL); +} + +GtkWidget * +gtk_clapper_object_get_widget (GtkClapperObject *self) +{ + return (GtkWidget *) self->picture; +} + + + + + + + + + + + + + + + + + + +static gboolean +wrap_current_gl (GstGLDisplay *display, GstGLPlatform platform, GstGLContext **context) +{ + GstGLAPI gl_api = GST_GL_API_NONE; + guint gl_major = 0, gl_minor = 0; + + gl_api = gst_gl_context_get_current_gl_api (platform, &gl_major, &gl_minor); + + if (gl_api) { + const gboolean is_es = gl_api & (GST_GL_API_GLES1 | GST_GL_API_GLES2); + gchar *gl_api_str = gst_gl_api_to_string (gl_api); + guintptr gl_handle = 0; + + GST_INFO ("Using GL API: %s, ver: %d.%d", gl_api_str, gl_major, gl_minor); + g_free (gl_api_str); + + if (is_es && platform == GST_GL_PLATFORM_EGL && !g_getenv ("GST_GL_API")) { + GST_DEBUG ("No GST_GL_API env and GTK is using EGL GLES2, enforcing it"); + gst_gl_display_filter_gl_api (display, GST_GL_API_GLES2); + } + + gl_handle = gst_gl_context_get_current_gl_context (platform); + if (gl_handle) { + if ((*context = gst_gl_context_new_wrapped (display, + gl_handle, platform, gl_api))) + return TRUE; + } + } + + return FALSE; +} + +static void +retrieve_gl_context_on_main (GtkClapperObject *self) +{ + GdkDisplay *gdk_display; + GstGLPlatform platform = GST_GL_PLATFORM_NONE; + GError *error = NULL; + + gst_clear_object (&self->wrapped_context); + g_clear_object (&self->gdk_context); + + gtk_widget_realize (GTK_WIDGET (self->picture)); + if (!((self->gdk_context = gdk_surface_create_gl_context (gtk_native_get_surface ( + gtk_widget_get_native (GTK_WIDGET (self->picture))), &error)))) { + GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s", + error ? error->message : "No error set by Gdk"); + g_clear_error (&error); + return; + } + + gdk_display = gdk_gl_context_get_display (self->gdk_context); + +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) + if (GDK_IS_X11_DISPLAY (gdk_display)) { + gpointer display_ptr; +#if GST_GL_HAVE_PLATFORM_EGL && GTK_CHECK_VERSION(4,3,1) + display_ptr = gdk_x11_display_get_egl_display (gdk_display); + if (display_ptr) + self->display = (GstGLDisplay *) + gst_gl_display_egl_new_with_egl_display (display_ptr); +#endif + } +#endif +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND) + if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) { + struct wl_display *wayland_display = + gdk_wayland_display_get_wl_display (gdk_display); + self->display = (GstGLDisplay *) + gst_gl_display_wayland_new_with_display (wayland_display); + } +#endif + if (G_UNLIKELY (!self->display)) { + GST_WARNING_OBJECT (self, "Unknown Gdk display!"); + self->display = gst_gl_display_new (); + } + +#if GST_GL_HAVE_PLATFORM_EGL +#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND) + if (GST_IS_GL_DISPLAY_WAYLAND (self->display)) { + platform = GST_GL_PLATFORM_EGL; + GST_DEBUG ("Using EGL on Wayland"); + goto have_platform; + } +#endif +#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11) + if (GST_IS_GL_DISPLAY_EGL (self->display)) { + platform = GST_GL_PLATFORM_EGL; + GST_DEBUG ("Using EGL on x11"); + goto have_platform; + } +#endif +#endif /* GST_GL_HAVE_PLATFORM_EGL */ + + GST_ERROR ("Unsupported GL platform"); + return; + +have_platform: + g_object_ref (self->gdk_context); + gdk_gl_context_make_current (self->gdk_context); + + if (!wrap_current_gl (self->display, platform, &self->wrapped_context)) { + GST_WARNING ("Could not retrieve Gdk OpenGL context"); + return; + } + + GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, self->wrapped_context); + gst_gl_context_activate (self->wrapped_context, TRUE); + + if (!gst_gl_context_fill_info (self->wrapped_context, &error)) { + GST_ERROR ("Failed to retrieve Gdk context info: %s", error->message); + g_clear_error (&error); + g_clear_object (&self->wrapped_context); + } + + /* Deactivate in both places */ + _gdk_gl_context_set_active (self, FALSE); +} + +gboolean +gtk_clapper_object_init_winsys (GtkClapperObject *self) +{ + GTK_CLAPPER_OBJECT_LOCK (self); + + if (self->display && self->gdk_context && self->wrapped_context) { + GST_TRACE ("Have already initialized contexts"); + GTK_CLAPPER_OBJECT_UNLOCK (self); + return TRUE; + } + + if (!self->wrapped_context) { + GTK_CLAPPER_OBJECT_UNLOCK (self); + gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) retrieve_gl_context_on_main, self); + GTK_CLAPPER_OBJECT_LOCK (self); + } + + if (!GST_IS_GL_CONTEXT (self->wrapped_context)) { + GST_FIXME ("Could not retrieve Gdk GL context"); + GTK_CLAPPER_OBJECT_UNLOCK (self); + return FALSE; + } + + GTK_CLAPPER_OBJECT_UNLOCK (self); + + return TRUE; +} + +/* + * GdkPaintableInterface + */ +static void +gtk_clapper_object_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, gdouble width, gdouble height) +{ + GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable); + + if (self->paintable) + gdk_paintable_snapshot (self->paintable, snapshot, width, height); +} + +static GdkPaintable * +gtk_clapper_object_paintable_get_current_image (GdkPaintable *paintable) +{ + GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable); + + return (self->paintable) + ? g_object_ref (self->paintable) + : gdk_paintable_new_empty (0, 0); +} + +static gint +gtk_clapper_object_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable); + + return self->display_width; +} + +static gint +gtk_clapper_object_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable); + + return self->display_height; +} + +static gdouble +gtk_clapper_object_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) +{ + GtkClapperObject *self = GTK_CLAPPER_OBJECT_CAST (paintable); + + return self->display_aspect_ratio; +} + +static void +gtk_clapper_object_paintable_iface_init (GdkPaintableInterface *iface) +{ + iface->snapshot = gtk_clapper_object_paintable_snapshot; + iface->get_current_image = gtk_clapper_object_paintable_get_current_image; + iface->get_intrinsic_width = gtk_clapper_object_paintable_get_intrinsic_width; + iface->get_intrinsic_height = gtk_clapper_object_paintable_get_intrinsic_height; + iface->get_intrinsic_aspect_ratio = gtk_clapper_object_paintable_get_intrinsic_aspect_ratio; +} diff --git a/lib/gst/plugin/gtkclapperobject.h b/lib/gst/plugin/gtkclapperobject.h new file mode 100644 index 00000000..76d9128b --- /dev/null +++ b/lib/gst/plugin/gtkclapperobject.h @@ -0,0 +1,117 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2020-2022 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. + */ + +#pragma once + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CLAPPER_OBJECT (gtk_clapper_object_get_type ()) +#define GTK_IS_CLAPPER_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CLAPPER_OBJECT)) +#define GTK_IS_CLAPPER_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CLAPPER_OBJECT)) +#define GTK_CLAPPER_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CLAPPER_OBJECT, GtkClapperObjectClass)) +#define GTK_CLAPPER_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CLAPPER_OBJECT, GtkClapperObjectClass)) +#define GTK_CLAPPER_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CLAPPER_OBJECT, GtkClapperObject)) +#define GTK_CLAPPER_OBJECT_CAST(obj) ((GtkClapperObject*)(obj)) + +#define GTK_CLAPPER_OBJECT_LOCK(w) g_mutex_lock(&((GtkClapperObject*)(w))->lock) +#define GTK_CLAPPER_OBJECT_UNLOCK(w) g_mutex_unlock(&((GtkClapperObject*)(w))->lock) + +typedef struct _GtkClapperObject GtkClapperObject; +typedef struct _GtkClapperObjectClass GtkClapperObjectClass; + +struct _GtkClapperObject +{ + GObject parent; + + GtkPicture *picture; + GdkPaintable *paintable; + + GstGLDisplay *display; + GdkGLContext *gdk_context; + GstGLContext *wrapped_context; + + /* properties */ + gboolean force_aspect_ratio; + gint par_n, par_d; + gboolean keep_last_frame; + + gint display_width; + gint display_height; + gdouble display_aspect_ratio; + + /* Object dimensions */ + gint scaled_width; + gint scaled_height; + + /* Position coords */ + gdouble last_pos_x; + gdouble last_pos_y; + + gboolean negotiated; + gboolean ignore_buffers; + + GstBuffer *pending_buffer; + GstBuffer *buffer; + + GstVideoInfo pending_v_info; + GstVideoInfo v_info; + + guint texture_id; + + /* resize */ + gboolean pending_resize; + 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 _GtkClapperObjectClass +{ + GObjectClass parent_class; +}; + +GType gtk_clapper_object_get_type (void); +GtkClapperObject * gtk_clapper_object_new (void); +GtkWidget * gtk_clapper_object_get_widget (GtkClapperObject *object); + +gboolean gtk_clapper_object_init_winsys (GtkClapperObject *object); + +gboolean gtk_clapper_object_set_format (GtkClapperObject *object, GstVideoInfo *v_info); +void gtk_clapper_object_set_buffer (GtkClapperObject *object, GstBuffer *buffer); +void gtk_clapper_object_set_element (GtkClapperObject *object, GstElement *element); + +G_END_DECLS diff --git a/lib/gst/plugin/meson.build b/lib/gst/plugin/meson.build new file mode 100644 index 00000000..4fa0b566 --- /dev/null +++ b/lib/gst/plugin/meson.build @@ -0,0 +1,55 @@ +gst_plugins_libdir = join_paths(prefix, libdir, 'gstreamer-1.0') + +gst_clapper_plugin_args = [ + '-DHAVE_CONFIG_H', + '-DGST_USE_UNSTABLE_API', +] +gst_clapper_plugin_deps = [ + gtk4_dep, + gst_dep, + gstbase_dep, + gstvideo_dep, + gstallocators_dep, +] + +if get_option('default_library') == 'static' + gst_clapper_plugin_args += ['-DGST_STATIC_COMPILATION'] +endif + +gst_clapper_plugin_option = get_option('gst-plugin') +if gst_clapper_plugin_option.disabled() + subdir_done() +endif + +foreach dep : gst_clapper_plugin_deps + if not dep.found() + if gst_clapper_plugin_option.enabled() + error('GStreamer plugin was enabled, but required dependencies were not found') + endif + subdir_done() + endif +endforeach + +if not have_gtk_gl_windowing + if gst_clapper_plugin_option.enabled() + error('GTK4 widget requires GL windowing') + else + subdir_done() + endif +endif + +gst_clapper_plugin_sources = [ + 'gstclappersink.c', + 'gtkclapperobject.c', + 'gstgtkutils.c', + 'gstplugin.c', +] + +library('gstclapper', + gst_clapper_plugin_sources, + c_args: gst_clapper_plugin_args, + include_directories: configinc, + dependencies: gst_clapper_plugin_deps + gtk_deps, + install: true, + install_dir: gst_plugins_libdir, +) diff --git a/lib/meson.build b/lib/meson.build index 588ddb94..f8a4a848 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,5 +1,5 @@ glib_req = '>= 2.56.0' -gst_req = '>= 1.18.0' +gst_req = '>= 1.19.1' api_version = '1.0' libversion = meson.project_version() @@ -200,6 +200,8 @@ gsttag_dep = dependency('gstreamer-tag-1.0', version: gst_req, fallback: ['gst-plugins-base', 'tag_dep']) gstvideo_dep = dependency('gstreamer-video-1.0', version: gst_req, fallback: ['gst-plugins-base', 'video_dep']) +gstallocators_dep = dependency('gstreamer-allocators-1.0', version: gst_req, + fallback : ['gst-plugins-base', 'allocators_dep']) # GStreamer OpenGL gstgl_dep = dependency('gstreamer-gl-1.0', version: gst_req, @@ -251,21 +253,49 @@ giounix_dep = dependency('gio-unix-2.0', version: glib_req, fallback: ['glib', ' cdata.set('DISABLE_ORC', 1) cdata.set('GST_ENABLE_EXTRA_CHECKS', get_option('devel-checks')) -cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', 'Unknown') configinc = include_directories('.') libsinc = include_directories('gst') -gir = find_program('g-ir-scanner', required: true) -if not gir.found() - error('Clapper requires GI bindings to be compiled') -endif - +gir = find_program('g-ir-scanner') 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);' + \ 'gst_init(NULL,NULL);', '--quiet' ] + +gtk_deps = [gstgl_dep, gstglproto_dep] +have_gtk_gl_windowing = false + +gtk4_dep = dependency('gtk4', required: true) + +if not gtk4_dep.version().version_compare('>=4.0.0') + error('GTK4 version on this system is too old') +endif + +if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx) + gtk_x11_dep = dependency('gtk4-x11', required: false) + if gtk_x11_dep.found() + gtk_deps += gtk_x11_dep + if gst_gl_have_platform_glx + gtk_deps += gstglx11_dep + endif + 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, gstglwayland_dep] + have_gtk_gl_windowing = true + endif +endif + +if gst_gl_have_platform_egl + gtk_deps += gstglegl_dep +endif + subdir('gst') configure_file(output: 'config.h', configuration: cdata) diff --git a/meson.build b/meson.build index b962661d..e154c3a1 100644 --- a/meson.build +++ b/meson.build @@ -19,7 +19,7 @@ datadir = join_paths(get_option('prefix'), get_option('datadir')) pkglibdir = join_paths(libdir, meson.project_name()) pkgdatadir = join_paths(datadir, meson.project_name()) -if get_option('lib') +if get_option('lib') or not get_option('gst-plugin').disabled() subdir('lib') endif diff --git a/meson_options.txt b/meson_options.txt index 1e0d763a..5487292e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -8,6 +8,11 @@ option('lib', value: true, description: 'Build GstClapper lib' ) +option('gst-plugin', + type: 'feature', + value: 'enabled', + description: 'Build GStreamer plugin (includes GTK video sink element)' +) option('devel-checks', type: 'boolean', value: false,