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/gstclapperbaseimport.c b/lib/gst/plugin/gstclapperbaseimport.c new file mode 100644 index 00000000..a7568a7d --- /dev/null +++ b/lib/gst/plugin/gstclapperbaseimport.c @@ -0,0 +1,374 @@ +/* + * 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 "gstclapperbaseimport.h" +#include "gstclappergdkmemory.h" +#include "gstclappergdkbufferpool.h" + +#define GST_CAT_DEFAULT gst_clapper_base_import_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define parent_class gst_clapper_base_import_parent_class +G_DEFINE_TYPE (GstClapperBaseImport, gst_clapper_base_import, GST_TYPE_BASE_TRANSFORM); + +static void +gst_clapper_base_import_init (GstClapperBaseImport *self) +{ + g_mutex_init (&self->lock); + + gst_video_info_init (&self->in_info); + gst_video_info_init (&self->out_info); +} + +static void +gst_clapper_base_import_finalize (GObject *object) +{ + GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (object); + + GST_TRACE ("Finalize"); + g_mutex_clear (&self->lock); + + GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static GstStateChangeReturn +gst_clapper_base_import_change_state (GstElement *element, GstStateChange transition) +{ + 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))); + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + +static gboolean +gst_clapper_base_import_start (GstBaseTransform *bt) +{ + GST_INFO_OBJECT (bt, "Start"); + + return TRUE; +} + +static gboolean +gst_clapper_base_import_stop (GstBaseTransform *bt) +{ + GST_INFO_OBJECT (bt, "Stop"); + + return TRUE; +} + +static GstCaps * +gst_clapper_base_import_transform_caps (GstBaseTransform *bt, + GstPadDirection direction, GstCaps *caps, GstCaps *filter) +{ + GstCaps *result, *tmp; + + tmp = (direction == GST_PAD_SINK) + ? gst_pad_get_pad_template_caps (GST_BASE_TRANSFORM_SRC_PAD (bt)) + : gst_pad_get_pad_template_caps (GST_BASE_TRANSFORM_SINK_PAD (bt)); + + if (filter) { + GST_DEBUG ("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; + } + GST_DEBUG ("Returning %s caps: %" GST_PTR_FORMAT, + (direction == GST_PAD_SINK) ? "src" : "sink", result); + + return result; +} + +static gboolean +_structure_filter_cb (GQuark field_id, GValue *value, + G_GNUC_UNUSED gpointer user_data) +{ + const gchar *str = g_quark_to_string (field_id); + + if (!strcmp (str, "format") + || !strcmp (str, "width") + || !strcmp (str, "height") + || !strcmp (str, "pixel-aspect-ratio") + || !strcmp (str, "framerate")) + return TRUE; + + return FALSE; +} + +static GstCaps * +gst_clapper_base_import_fixate_caps (GstBaseTransform *bt, + GstPadDirection direction, GstCaps *caps, GstCaps *othercaps) +{ + GstCaps *fixated; + + fixated = (!gst_caps_is_any (caps)) + ? gst_caps_fixate (gst_caps_ref (caps)) + : gst_caps_copy (caps); + + if (direction == GST_PAD_SINK) { + guint i, n = gst_caps_get_size (fixated); + + for (i = 0; i < n; i++) { + GstCapsFeatures *features; + GstStructure *structure; + gboolean had_overlay_comp; + + features = gst_caps_get_features (fixated, i); + had_overlay_comp = gst_caps_features_contains (features, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION); + + features = gst_caps_features_new (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY, NULL); + if (had_overlay_comp) + gst_caps_features_add (features, GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION); + + gst_caps_set_features (fixated, i, features); + + /* Remove fields that do not apply to our memory */ + if ((structure = gst_caps_get_structure (fixated, i))) { + gst_structure_filter_and_map_in_place (structure, + (GstStructureFilterMapFunc) _structure_filter_cb, NULL); + } + } + } + GST_DEBUG ("Fixated %s caps: %" GST_PTR_FORMAT, + (direction == GST_PAD_SRC) ? "sink" : "src", fixated); + + return fixated; +} + +static gboolean +gst_clapper_base_import_set_caps (GstBaseTransform *bt, + GstCaps *incaps, GstCaps *outcaps) +{ + GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (bt); + gboolean has_sink_info, has_src_info; + + if ((has_sink_info = gst_video_info_from_caps (&self->in_info, incaps))) + GST_INFO_OBJECT (self, "Set sink caps: %" GST_PTR_FORMAT, incaps); + if ((has_src_info = gst_video_info_from_caps (&self->out_info, outcaps))) + GST_INFO_OBJECT (self, "Set src caps: %" GST_PTR_FORMAT, outcaps); + + return (has_sink_info && has_src_info); +} + +static gboolean +gst_clapper_base_import_import_propose_allocation (GstBaseTransform *bt, + GstQuery *decide_query, GstQuery *query) +{ + GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (bt); + GstClapperBaseImportClass *bi_class = GST_CLAPPER_BASE_IMPORT_GET_CLASS (self); + GstBufferPool *pool = NULL; + GstCaps *caps; + GstVideoInfo info; + guint size; + gboolean need_pool; + + if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (bt, + decide_query, query)) + return FALSE; + + /* Passthrough, nothing to do */ + if (!decide_query) + return TRUE; + + gst_query_parse_allocation (query, &caps, &need_pool); + + if (!caps) { + GST_DEBUG_OBJECT (self, "No caps specified"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_DEBUG_OBJECT (self, "Invalid caps specified"); + return FALSE; + } + + /* Normal size of a frame */ + size = GST_VIDEO_INFO_SIZE (&info); + + if (need_pool) { + GstStructure *config = NULL; + + GST_DEBUG_OBJECT (self, "Need to create upstream pool"); + pool = bi_class->create_upstream_pool (self, &config); + + if (pool) { + /* If we did not get config, use default one */ + if (!config) + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_set_params (config, caps, size, 2, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + gst_object_unref (pool); + + GST_DEBUG_OBJECT (self, "Failed to set config"); + return FALSE; + } + } else if (config) { + GST_WARNING_OBJECT (self, "Got pool config without a pool to apply it!"); + gst_structure_free (config); + } + } + + gst_query_add_allocation_pool (query, pool, size, 2, 0); + if (pool) + gst_object_unref (pool); + + gst_query_add_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL); + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + + return TRUE; +} + +static gboolean +gst_clapper_base_import_decide_allocation (GstBaseTransform *bt, GstQuery *query) +{ + GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (bt); + GstBufferPool *pool = NULL; + GstCaps *caps; + GstVideoInfo info; + guint size = 0, min = 0, max = 0; + gboolean update_pool, need_pool = TRUE; + + gst_query_parse_allocation (query, &caps, NULL); + + if (!caps) { + GST_DEBUG_OBJECT (self, "No caps specified"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_DEBUG_OBJECT (self, "Invalid caps specified"); + return FALSE; + } + + if ((update_pool = gst_query_get_n_allocation_pools (query) > 0)) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + if (pool) { + if ((need_pool = !GST_IS_CLAPPER_GDK_BUFFER_POOL (pool))) + gst_clear_object (&pool); + } + } else { + size = GST_VIDEO_INFO_SIZE (&info); + } + + if (need_pool) { + GstStructure *config; + + GST_DEBUG_OBJECT (self, "Creating new downstream pool"); + + pool = gst_clapper_gdk_buffer_pool_new (); + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_set_params (config, caps, size, min, max); + + if (!gst_buffer_pool_set_config (pool, config)) { + gst_object_unref (pool); + + GST_DEBUG_OBJECT (self, "Failed to set config"); + return FALSE; + } + } + + if (update_pool) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + if (pool) + gst_object_unref (pool); + + return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (bt, query); +} + +static GstBufferPool * +gst_clapper_base_import_create_upstream_pool (GstClapperBaseImport *self, GstStructure **config) +{ + GST_FIXME_OBJECT (self, "Need to create upstream buffer pool"); + + return NULL; +} + +static void +gst_clapper_base_import_class_init (GstClapperBaseImportClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstBaseTransformClass *gstbasetransform_class = (GstBaseTransformClass *) klass; + GstClapperBaseImportClass *bi_class = (GstClapperBaseImportClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperbaseimport", 0, + "Clapper Base Import"); + + gobject_class->finalize = gst_clapper_base_import_finalize; + + gstelement_class->change_state = gst_clapper_base_import_change_state; + + gstbasetransform_class->passthrough_on_same_caps = TRUE; + gstbasetransform_class->transform_ip_on_passthrough = FALSE; + gstbasetransform_class->start = gst_clapper_base_import_start; + gstbasetransform_class->stop = gst_clapper_base_import_stop; + gstbasetransform_class->transform_caps = gst_clapper_base_import_transform_caps; + gstbasetransform_class->fixate_caps = gst_clapper_base_import_fixate_caps; + gstbasetransform_class->set_caps = gst_clapper_base_import_set_caps; + gstbasetransform_class->propose_allocation = gst_clapper_base_import_import_propose_allocation; + gstbasetransform_class->decide_allocation = gst_clapper_base_import_decide_allocation; + + bi_class->create_upstream_pool = gst_clapper_base_import_create_upstream_pool; +} + +/* + * Maps input video frame and output memory from in/out buffers + * using flags passed to this method. + * + * Remember to unmap both using `gst_video_frame_unmap` and + * `gst_memory_unmap` when done with the data. + */ +gboolean +gst_clapper_base_import_map_buffers (GstClapperBaseImport *self, + GstBuffer *in_buf, GstBuffer *out_buf, GstMapFlags in_flags, GstMapFlags out_flags, + GstVideoFrame *frame, GstMapInfo *info, GstMemory **mem) +{ + GST_LOG_OBJECT (self, "Transforming from %" GST_PTR_FORMAT + " into %" GST_PTR_FORMAT, in_buf, out_buf); + + if (G_UNLIKELY (!gst_video_frame_map (frame, &self->in_info, in_buf, in_flags))) { + GST_ERROR_OBJECT (self, "Could not map input buffer for reading"); + return FALSE; + } + + *mem = gst_buffer_peek_memory (out_buf, 0); + + if (G_UNLIKELY (!gst_memory_map (*mem, info, out_flags))) { + GST_ERROR_OBJECT (self, "Could not map output memory for writing"); + gst_video_frame_unmap (frame); + + return FALSE; + } + + return TRUE; +} diff --git a/lib/gst/plugin/gstclapperbaseimport.h b/lib/gst/plugin/gstclapperbaseimport.h new file mode 100644 index 00000000..b83bdc81 --- /dev/null +++ b/lib/gst/plugin/gstclapperbaseimport.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_BASE_IMPORT (gst_clapper_base_import_get_type()) +#define GST_IS_CLAPPER_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_BASE_IMPORT)) +#define GST_IS_CLAPPER_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_BASE_IMPORT)) +#define GST_CLAPPER_BASE_IMPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_BASE_IMPORT, GstClapperBaseImportClass)) +#define GST_CLAPPER_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_BASE_IMPORT, GstClapperBaseImport)) +#define GST_CLAPPER_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_BASE_IMPORT, GstClapperBaseImportClass)) +#define GST_CLAPPER_BASE_IMPORT_CAST(obj) ((GstClapperBaseImport *)(obj)) + +#define GST_CLAPPER_BASE_IMPORT_GET_LOCK(obj) (&GST_CLAPPER_BASE_IMPORT_CAST(obj)->lock) +#define GST_CLAPPER_BASE_IMPORT_LOCK(obj) g_mutex_lock (GST_CLAPPER_BASE_IMPORT_GET_LOCK(obj)) +#define GST_CLAPPER_BASE_IMPORT_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_BASE_IMPORT_GET_LOCK(obj)) + +typedef struct _GstClapperBaseImport GstClapperBaseImport; +typedef struct _GstClapperBaseImportClass GstClapperBaseImportClass; + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperBaseImport, gst_object_unref) +#endif + +struct _GstClapperBaseImport +{ + GstBaseTransform parent; + + GMutex lock; + + GstVideoInfo in_info, out_info; +}; + +struct _GstClapperBaseImportClass +{ + GstBaseTransformClass parent_class; + + GstBufferPool * (* create_upstream_pool) (GstClapperBaseImport *bi, + GstStructure **config); +}; + +GType gst_clapper_base_import_get_type (void); + +gboolean gst_clapper_base_import_map_buffers (GstClapperBaseImport *bi, + GstBuffer *in_buf, GstBuffer *out_buf, GstMapFlags in_flags, GstMapFlags out_flags, + GstVideoFrame *frame, GstMapInfo *info, GstMemory **mem); + +G_END_DECLS diff --git a/lib/gst/plugin/gstclappergdkbufferpool.c b/lib/gst/plugin/gstclappergdkbufferpool.c new file mode 100644 index 00000000..929a4cdd --- /dev/null +++ b/lib/gst/plugin/gstclappergdkbufferpool.c @@ -0,0 +1,173 @@ +/* + * 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 "gstclappergdkbufferpool.h" + +#define GST_CAT_DEFAULT gst_clapper_gdk_buffer_pool_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +#define parent_class gst_clapper_gdk_buffer_pool_parent_class +G_DEFINE_TYPE (GstClapperGdkBufferPool, gst_clapper_gdk_buffer_pool, GST_TYPE_BUFFER_POOL); + +static void +gst_clapper_gdk_buffer_pool_init (GstClapperGdkBufferPool *pool) +{ +} + +static const gchar ** +gst_clapper_gdk_buffer_pool_get_options (GstBufferPool *pool) +{ + static const gchar *options[] = { + GST_BUFFER_POOL_OPTION_VIDEO_META, + NULL + }; + + return options; +} + +static gboolean +gst_clapper_gdk_buffer_pool_set_config (GstBufferPool *pool, GstStructure *config) +{ + GstClapperGdkBufferPool *self = GST_CLAPPER_GDK_BUFFER_POOL_CAST (pool); + GstCaps *caps = NULL; + guint size, min_buffers, max_buffers; + GstVideoInfo info; + GstClapperGdkMemory *clapper_mem; + + if (!gst_buffer_pool_config_get_params (config, &caps, &size, + &min_buffers, &max_buffers)) { + GST_WARNING_OBJECT (self, "Invalid buffer pool config"); + return FALSE; + } + + if (!caps || !gst_video_info_from_caps (&info, caps)) { + GST_WARNING_OBJECT (pool, "Could not parse caps into video info"); + return FALSE; + } + + gst_clear_object (&self->allocator); + self->allocator = GST_CLAPPER_GDK_ALLOCATOR_CAST ( + gst_allocator_find (GST_CLAPPER_GDK_MEMORY_TYPE_NAME)); + + if (G_UNLIKELY (!self->allocator)) { + GST_ERROR_OBJECT (self, "ClapperGdkAllocator is unavailable"); + return FALSE; + } + + clapper_mem = GST_CLAPPER_GDK_MEMORY_CAST ( + gst_clapper_gdk_allocator_alloc (self->allocator, &info)); + if (G_UNLIKELY (!clapper_mem)) { + GST_ERROR_OBJECT (self, "Cannot create ClapperGdkMemory"); + return FALSE; + } + + gst_buffer_pool_config_set_params (config, caps, + GST_VIDEO_INFO_SIZE (&clapper_mem->info), min_buffers, max_buffers); + gst_memory_unref (GST_MEMORY_CAST (clapper_mem)); + + self->info = info; + + GST_DEBUG_OBJECT (self, "Set buffer pool config: %" GST_PTR_FORMAT, config); + + return GST_BUFFER_POOL_CLASS (parent_class)->set_config (pool, config); +} + +static GstFlowReturn +gst_clapper_gdk_buffer_pool_alloc (GstBufferPool *pool, GstBuffer **buffer, + GstBufferPoolAcquireParams *params) +{ + GstClapperGdkBufferPool *self = GST_CLAPPER_GDK_BUFFER_POOL_CAST (pool); + GstMemory *mem; + GstClapperGdkMemory *clapper_mem; + + mem = gst_clapper_gdk_allocator_alloc (self->allocator, &self->info); + if (G_UNLIKELY (!mem)) { + GST_ERROR_OBJECT (self, "Cannot create ClapperGdkMemory"); + return GST_FLOW_ERROR; + } + + clapper_mem = GST_CLAPPER_GDK_MEMORY_CAST (mem); + + *buffer = gst_buffer_new (); + gst_buffer_append_memory (*buffer, mem); + + gst_buffer_add_video_meta_full (*buffer, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (&self->info), GST_VIDEO_INFO_WIDTH (&self->info), + GST_VIDEO_INFO_HEIGHT (&self->info), GST_VIDEO_INFO_N_PLANES (&self->info), + clapper_mem->info.offset, clapper_mem->info.stride); + + GST_TRACE_OBJECT (self, "Allocated %" GST_PTR_FORMAT, *buffer); + + return GST_FLOW_OK; +} + +static void +gst_clapper_gdk_buffer_reset_buffer (GstBufferPool *pool, GstBuffer *buffer) +{ + GstClapperGdkMemory *clapper_mem; + + GST_TRACE ("Reset %" GST_PTR_FORMAT, buffer); + + clapper_mem = GST_CLAPPER_GDK_MEMORY_CAST (gst_buffer_peek_memory (buffer, 0)); + g_clear_object (&clapper_mem->texture); + + return GST_BUFFER_POOL_CLASS (parent_class)->reset_buffer (pool, buffer); +} + +static void +gst_clapper_gdk_buffer_pool_dispose (GObject *object) +{ + GstClapperGdkBufferPool *self = GST_CLAPPER_GDK_BUFFER_POOL_CAST (object); + + gst_clear_object (&self->allocator); + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); +} + +static void +gst_clapper_gdk_buffer_pool_class_init (GstClapperGdkBufferPoolClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstBufferPoolClass *bufferpool_class = (GstBufferPoolClass *) klass; + + gobject_class->dispose = gst_clapper_gdk_buffer_pool_dispose; + + bufferpool_class->get_options = gst_clapper_gdk_buffer_pool_get_options; + bufferpool_class->set_config = gst_clapper_gdk_buffer_pool_set_config; + bufferpool_class->alloc_buffer = gst_clapper_gdk_buffer_pool_alloc; + bufferpool_class->reset_buffer = gst_clapper_gdk_buffer_reset_buffer; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergdkbufferpool", 0, + "Clapper Gdk Buffer Pool"); +} + +GstBufferPool * +gst_clapper_gdk_buffer_pool_new (void) +{ + GstClapperGdkBufferPool *self; + + self = g_object_new (GST_TYPE_CLAPPER_GDK_BUFFER_POOL, NULL); + gst_object_ref_sink (self); + + return GST_BUFFER_POOL_CAST (self); +} diff --git a/lib/gst/plugin/gstclappergdkbufferpool.h b/lib/gst/plugin/gstclappergdkbufferpool.h new file mode 100644 index 00000000..93b6f770 --- /dev/null +++ b/lib/gst/plugin/gstclappergdkbufferpool.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include "gstclappergdkmemory.h" + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_GDK_BUFFER_POOL (gst_clapper_gdk_buffer_pool_get_type()) +G_DECLARE_FINAL_TYPE (GstClapperGdkBufferPool, gst_clapper_gdk_buffer_pool, GST, CLAPPER_GDK_BUFFER_POOL, GstBufferPool) + +#define GST_CLAPPER_GDK_BUFFER_POOL_CAST(obj) ((GstClapperGdkBufferPool *)(obj)) + +struct _GstClapperGdkBufferPool +{ + GstBufferPool parent; + + GstClapperGdkAllocator *allocator; + GstVideoInfo info; +}; + +GstBufferPool * gst_clapper_gdk_buffer_pool_new (void); + +G_END_DECLS diff --git a/lib/gst/plugin/gstclappergdkmemory.c b/lib/gst/plugin/gstclappergdkmemory.c new file mode 100644 index 00000000..24fe4a0e --- /dev/null +++ b/lib/gst/plugin/gstclappergdkmemory.c @@ -0,0 +1,141 @@ +/* + * 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 "gstclappergdkmemory.h" +#include "gstgtkutils.h" + +#define GST_CAT_DEFAULT gst_clapper_gdk_allocator_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +static GstAllocator *_gst_clapper_gdk_allocator = NULL; + +#define parent_class gst_clapper_gdk_allocator_parent_class +G_DEFINE_TYPE (GstClapperGdkAllocator, gst_clapper_gdk_allocator, GST_TYPE_ALLOCATOR); + +static void +gst_clapper_gdk_allocator_free (GstAllocator *self, GstMemory *memory) +{ + GstClapperGdkMemory *mem = GST_CLAPPER_GDK_MEMORY_CAST (memory); + + GST_TRACE_OBJECT (self, "Freeing ClapperGdkMemory: %" GST_PTR_FORMAT, mem); + + g_clear_object (&mem->texture); + g_free (mem); +} + +static gpointer +gst_clapper_gdk_mem_map_full (GstMemory *memory, GstMapInfo *info, gsize maxsize) +{ + GstClapperGdkMemory *mem = GST_CLAPPER_GDK_MEMORY_CAST (memory); + + return &mem->texture; +} + +static void +gst_clapper_gdk_mem_unmap_full (GstMemory *memory, GstMapInfo *info) +{ + /* NOOP */ +} + +static GstMemory * +gst_clapper_gdk_mem_copy (GstMemory *memory, gssize offset, gssize size) +{ + return NULL; +} + +static GstMemory * +gst_clapper_gdk_mem_share (GstMemory *memory, gssize offset, gssize size) +{ + return NULL; +} + +static gboolean +gst_clapper_gdk_mem_is_span (GstMemory *mem1, GstMemory *mem2, gsize *offset) +{ + return FALSE; +} + +static void +gst_clapper_gdk_allocator_init (GstClapperGdkAllocator *self) +{ + GstAllocator *alloc = GST_ALLOCATOR_CAST (self); + + alloc->mem_type = GST_CLAPPER_GDK_MEMORY_TYPE_NAME; + alloc->mem_map_full = gst_clapper_gdk_mem_map_full; + alloc->mem_unmap_full = gst_clapper_gdk_mem_unmap_full; + alloc->mem_copy = gst_clapper_gdk_mem_copy; + alloc->mem_share = gst_clapper_gdk_mem_share; + alloc->mem_is_span = gst_clapper_gdk_mem_is_span; + + GST_OBJECT_FLAG_SET (self, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); +} + +static void +gst_clapper_gdk_allocator_class_init (GstClapperGdkAllocatorClass *klass) +{ + GstAllocatorClass *allocator_class = (GstAllocatorClass *) klass; + + allocator_class->alloc = NULL; + allocator_class->free = GST_DEBUG_FUNCPTR (gst_clapper_gdk_allocator_free); + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergdkallocator", 0, + "Clapper Gdk Allocator"); +} + +void +gst_clapper_gdk_memory_init_once (void) +{ + static gsize _alloc_init = 0; + + if (g_once_init_enter (&_alloc_init)) { + _gst_clapper_gdk_allocator = GST_ALLOCATOR_CAST ( + g_object_new (GST_TYPE_CLAPPER_GDK_ALLOCATOR, NULL)); + gst_object_ref_sink (_gst_clapper_gdk_allocator); + + gst_allocator_register (GST_CLAPPER_GDK_MEMORY_TYPE_NAME, _gst_clapper_gdk_allocator); + g_once_init_leave (&_alloc_init, 1); + } +} + +gboolean +gst_is_clapper_gdk_memory (GstMemory *memory) +{ + return (memory != NULL && memory->allocator != NULL + && GST_IS_CLAPPER_GDK_ALLOCATOR (memory->allocator)); +} + +GstMemory * +gst_clapper_gdk_allocator_alloc (GstClapperGdkAllocator *self, const GstVideoInfo *info) +{ + GstClapperGdkMemory *mem; + + mem = g_new0 (GstClapperGdkMemory, 1); + mem->info = *info; + + gst_memory_init (GST_MEMORY_CAST (mem), 0, GST_ALLOCATOR_CAST (self), + NULL, GST_VIDEO_INFO_SIZE (info), 0, 0, GST_VIDEO_INFO_SIZE (info)); + + GST_TRACE_OBJECT (self, "Allocated new ClapperGdkMemory: %" GST_PTR_FORMAT, mem); + + return GST_MEMORY_CAST (mem); +} diff --git a/lib/gst/plugin/gstclappergdkmemory.h b/lib/gst/plugin/gstclappergdkmemory.h new file mode 100644 index 00000000..b5dd032a --- /dev/null +++ b/lib/gst/plugin/gstclappergdkmemory.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_GDK_ALLOCATOR (gst_clapper_gdk_allocator_get_type()) +G_DECLARE_FINAL_TYPE (GstClapperGdkAllocator, gst_clapper_gdk_allocator, GST, CLAPPER_GDK_ALLOCATOR, GstAllocator) + +#define GST_CLAPPER_GDK_ALLOCATOR_CAST(obj) ((GstClapperGdkAllocator *)(obj)) +#define GST_CLAPPER_GDK_MEMORY_CAST(mem) ((GstClapperGdkMemory *)(mem)) + +#define GST_CLAPPER_GDK_MEMORY_TYPE_NAME "gst.clapper.gdk.memory" + +#define GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY "memory:ClapperGdkMemory" + +#define GST_CLAPPER_GDK_MEMORY_FORMATS \ + "RGBA64_LE, RGBA64_BE, ABGR, BGRA, " \ + "ARGB, RGBA, BGRx, RGBx, BGR, RGB" \ + +/* Formats that `GdkGLTexture` supports */ +#define GST_CLAPPER_GDK_GL_TEXTURE_FORMATS \ + "RGBA64_LE, RGBA64_BE, RGBA, RGBx, RGB" \ + +typedef struct _GstClapperGdkMemory GstClapperGdkMemory; +struct _GstClapperGdkMemory +{ + GstMemory mem; + + GdkTexture *texture; + GstVideoInfo info; +}; + +struct _GstClapperGdkAllocator +{ + GstAllocator parent; +}; + +void gst_clapper_gdk_memory_init_once (void); + +gboolean gst_is_clapper_gdk_memory (GstMemory *memory); + +GstMemory * gst_clapper_gdk_allocator_alloc (GstClapperGdkAllocator *allocator, const GstVideoInfo *info); + +G_END_DECLS diff --git a/lib/gst/plugin/gstclapperimport.c b/lib/gst/plugin/gstclapperimport.c new file mode 100644 index 00000000..3e48be1c --- /dev/null +++ b/lib/gst/plugin/gstclapperimport.c @@ -0,0 +1,128 @@ +/* + * 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 "gstclapperimport.h" +#include "gstclappergdkmemory.h" +#include "gstgtkutils.h" + +#define GST_CAT_DEFAULT gst_clapper_import_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +static GstStaticPadTemplate gst_clapper_import_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + GST_VIDEO_CAPS_MAKE ("{ " GST_CLAPPER_GDK_MEMORY_FORMATS " }"))); + +static GstStaticPadTemplate gst_clapper_import_src_template = + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY, + "{ " GST_CLAPPER_GDK_MEMORY_FORMATS " }"))); + +#define parent_class gst_clapper_import_parent_class +G_DEFINE_TYPE (GstClapperImport, gst_clapper_import, GST_TYPE_CLAPPER_BASE_IMPORT); +GST_ELEMENT_REGISTER_DEFINE (clapperimport, "clapperimport", GST_RANK_NONE, + GST_TYPE_CLAPPER_IMPORT); + +static GstBufferPool * +gst_clapper_import_create_upstream_pool (GstClapperBaseImport *bi, GstStructure **config) +{ + GstClapperImport *self = GST_CLAPPER_IMPORT_CAST (bi); + GstBufferPool *pool; + + GST_DEBUG_OBJECT (self, "Creating new upstream pool"); + + pool = gst_video_buffer_pool_new (); + *config = gst_buffer_pool_get_config (pool); + + return pool; +} + +static void +video_frame_unmap_and_free (GstVideoFrame *frame) +{ + gst_video_frame_unmap (frame); + g_slice_free (GstVideoFrame, frame); +} + +static GstFlowReturn +gst_clapper_import_transform (GstBaseTransform *bt, + GstBuffer *in_buf, GstBuffer *out_buf) +{ + GstClapperBaseImport *bi = GST_CLAPPER_BASE_IMPORT_CAST (bt); + GstVideoFrame *frame; + GstMapInfo info; + GstMemory *memory; + + frame = g_slice_new (GstVideoFrame); + + if (!gst_clapper_base_import_map_buffers (bi, in_buf, out_buf, + GST_MAP_READ, GST_MAP_WRITE, frame, &info, &memory)) { + g_slice_free (GstVideoFrame, frame); + + return GST_FLOW_ERROR; + } + + /* Keep frame data alive as long as necessary, + * unmap only after bytes are destroyed */ + GST_CLAPPER_GDK_MEMORY_CAST (memory)->texture = gst_video_frame_into_gdk_texture ( + frame, (GDestroyNotify) video_frame_unmap_and_free); + + gst_memory_unmap (memory, &info); + + return GST_FLOW_OK; +} + +static void +gst_clapper_import_init (GstClapperImport *self) +{ +} + +static void +gst_clapper_import_class_init (GstClapperImportClass *klass) +{ + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstBaseTransformClass *gstbasetransform_class = (GstBaseTransformClass *) klass; + GstClapperBaseImportClass *bi_class = (GstClapperBaseImportClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimport", 0, + "Clapper Import"); + + gstbasetransform_class->transform = gst_clapper_import_transform; + + bi_class->create_upstream_pool = gst_clapper_import_create_upstream_pool; + + gst_element_class_set_metadata (gstelement_class, + "Clapper import", + "Filter/Video", "Imports RAW video data into ClapperGdkMemory", + "Rafał Dzięgiel "); + + gst_element_class_add_static_pad_template (gstelement_class, + &gst_clapper_import_sink_template); + gst_element_class_add_static_pad_template (gstelement_class, + &gst_clapper_import_src_template); +} diff --git a/lib/gst/plugin/gstclapperimport.h b/lib/gst/plugin/gstclapperimport.h new file mode 100644 index 00000000..790af14c --- /dev/null +++ b/lib/gst/plugin/gstclapperimport.h @@ -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. + */ + +#pragma once + +#include "gstclapperbaseimport.h" + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_IMPORT (gst_clapper_import_get_type()) +G_DECLARE_FINAL_TYPE (GstClapperImport, gst_clapper_import, GST, CLAPPER_IMPORT, GstClapperBaseImport) + +#define GST_CLAPPER_IMPORT_CAST(obj) ((GstClapperImport *)(obj)) + +struct _GstClapperImport +{ + GstClapperBaseImport parent; +}; + +GST_ELEMENT_REGISTER_DECLARE (clapperimport); + +G_END_DECLS diff --git a/lib/gst/plugin/gstclapperpaintable.c b/lib/gst/plugin/gstclapperpaintable.c new file mode 100644 index 00000000..dd082e36 --- /dev/null +++ b/lib/gst/plugin/gstclapperpaintable.c @@ -0,0 +1,635 @@ +/* + * 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 "gstclapperpaintable.h" +#include "gstclappergdkmemory.h" +#include "gstgtkutils.h" + +#define DEFAULT_PAR_N 1 +#define DEFAULT_PAR_D 1 + +#define GST_CAT_DEFAULT gst_clapper_paintable_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +typedef struct +{ + GdkTexture *texture; + GstVideoOverlayRectangle *rectangle; + + gint x, y; + guint width, height; + + gboolean used; +} GstClapperGdkOverlay; + +static void +gst_clapper_gdk_overlay_free (GstClapperGdkOverlay *overlay) +{ + GST_TRACE ("Freeing overlay: %" GST_PTR_FORMAT, overlay); + + g_object_unref (overlay->texture); + gst_video_overlay_rectangle_unref (overlay->rectangle); + g_slice_free (GstClapperGdkOverlay, overlay); +} + +static void gst_clapper_paintable_iface_init (GdkPaintableInterface *iface); +static void gst_clapper_paintable_dispose (GObject *object); +static void gst_clapper_paintable_finalize (GObject *object); + +#define parent_class gst_clapper_paintable_parent_class +G_DEFINE_TYPE_WITH_CODE (GstClapperPaintable, gst_clapper_paintable, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + gst_clapper_paintable_iface_init)); + +static void +gst_clapper_paintable_class_init (GstClapperPaintableClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperpaintable", 0, + "Clapper Paintable"); + + gobject_class->dispose = gst_clapper_paintable_dispose; + gobject_class->finalize = gst_clapper_paintable_finalize; +} + +static void +gst_clapper_paintable_init (GstClapperPaintable *self) +{ + self->par_n = DEFAULT_PAR_N; + self->par_d = DEFAULT_PAR_D; + + g_mutex_init (&self->lock); + gst_video_info_init (&self->v_info); + g_weak_ref_init (&self->widget, NULL); + + self->overlays = g_ptr_array_new_with_free_func ( + (GDestroyNotify) gst_clapper_gdk_overlay_free); + + gdk_rgba_parse (&self->bg, "black"); +} + +static void +gst_clapper_paintable_dispose (GObject *object) +{ + GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object); + + GST_CLAPPER_PAINTABLE_LOCK (self); + + if (self->draw_id > 0) { + g_source_remove (self->draw_id); + self->draw_id = 0; + } + + GST_CLAPPER_PAINTABLE_UNLOCK (self); + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); +} + +static void +gst_clapper_paintable_finalize (GObject *object) +{ + GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object); + + GST_TRACE ("Finalize"); + + GST_CLAPPER_PAINTABLE_LOCK (self); + + g_weak_ref_clear (&self->widget); + + gst_clear_buffer (&self->pending_buffer); + gst_clear_buffer (&self->buffer); + + g_ptr_array_unref (self->overlays); + + GST_CLAPPER_PAINTABLE_UNLOCK (self); + + g_mutex_clear (&self->lock); + + GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static gboolean +calculate_display_par (GstClapperPaintable *self, GstVideoInfo *info) +{ + gint width, height, par_n, par_d, req_par_n, req_par_d; + gboolean success; + + width = GST_VIDEO_INFO_WIDTH (info); + height = GST_VIDEO_INFO_HEIGHT (info); + + /* Cannot apply aspect ratio if there is no video */ + if (width == 0 || height == 0) + return FALSE; + + par_n = GST_VIDEO_INFO_PAR_N (info); + par_d = GST_VIDEO_INFO_PAR_D (info); + + req_par_n = self->par_n; + req_par_d = self->par_d; + + if (par_n == 0) + par_n = 1; + + /* Use defaults if user set zero */ + if (req_par_n == 0 || req_par_d == 0) { + req_par_n = DEFAULT_PAR_N; + req_par_d = DEFAULT_PAR_D; + } + + GST_LOG_OBJECT (self, "PAR: %u/%u, DAR: %u/%u", par_n, par_d, req_par_n, req_par_d); + + if (!(success = gst_video_calculate_display_ratio (&self->display_ratio_num, + &self->display_ratio_den, width, height, par_n, par_d, + req_par_n, req_par_d))) { + GST_ERROR_OBJECT (self, "Could not calculate display ratio values"); + } + + return success; +} + +static void +invalidate_paintable_size_internal (GstClapperPaintable *self) +{ + gint video_width, video_height; + guint display_ratio_num, display_ratio_den; + + GST_CLAPPER_PAINTABLE_LOCK (self); + + video_width = GST_VIDEO_INFO_WIDTH (&self->v_info); + video_height = GST_VIDEO_INFO_HEIGHT (&self->v_info); + + display_ratio_num = self->display_ratio_num; + display_ratio_den = self->display_ratio_den; + + GST_CLAPPER_PAINTABLE_UNLOCK (self); + + if (video_height % display_ratio_den == 0) { + GST_LOG ("Keeping video height"); + + self->display_width = (guint) + gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den); + self->display_height = video_height; + } else if (video_width % display_ratio_num == 0) { + GST_LOG ("Keeping video width"); + + self->display_width = video_width; + self->display_height = (guint) + gst_util_uint64_scale_int (video_width, display_ratio_den, display_ratio_num); + } else { + GST_LOG ("Approximating while keeping video height"); + + self->display_width = (guint) + gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den); + self->display_height = video_height; + } + + self->display_aspect_ratio = ((gdouble) self->display_width + / (gdouble) self->display_height); + + GST_DEBUG_OBJECT (self, "Invalidate paintable size, display: %dx%d", + self->display_width, self->display_height); + gdk_paintable_invalidate_size ((GdkPaintable *) self); +} + +static gboolean +invalidate_paintable_size_on_main_cb (GstClapperPaintable *self) +{ + GST_CLAPPER_PAINTABLE_LOCK (self); + self->draw_id = 0; + GST_CLAPPER_PAINTABLE_UNLOCK (self); + + invalidate_paintable_size_internal (self); + + return G_SOURCE_REMOVE; +} + +static void +comp_frame_unmap_and_free (GstVideoFrame *frame) +{ + gst_video_frame_unmap (frame); + g_slice_free (GstVideoFrame, frame); +} + +static GstClapperGdkOverlay * +_get_cached_overlay (GPtrArray *overlays, GstVideoOverlayRectangle *rectangle, guint *index) +{ + guint i; + + for (i = 0; i < overlays->len; i++) { + GstClapperGdkOverlay *overlay = g_ptr_array_index (overlays, i); + + if (overlay->rectangle != rectangle) + continue; + + *index = i; + return overlay; + } + + return NULL; +} + +static void +gst_clapper_paintable_prepare_overlays (GstClapperPaintable *self) +{ + GstVideoOverlayCompositionMeta *comp_meta; + guint num_overlays, i; + + /* As long as this is called from main thread, no need to lock here */ + if (G_UNLIKELY (!self->buffer) + || !(comp_meta = gst_buffer_get_video_overlay_composition_meta (self->buffer))) { + guint n_pending = self->overlays->len; + + /* Remove all cached overlays if new buffer does not have any */ + if (n_pending > 0) { + GST_TRACE ("No overlays in buffer, removing all cached ones"); + g_ptr_array_remove_range (self->overlays, 0, n_pending); + } + + return; + } + + GST_LOG_OBJECT (self, "Preparing overlays..."); + + /* Mark all old overlays as unused */ + for (i = 0; i < self->overlays->len; i++) { + GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i); + overlay->used = FALSE; + } + + num_overlays = gst_video_overlay_composition_n_rectangles (comp_meta->overlay); + + for (i = 0; i < num_overlays; i++) { + GdkTexture *texture; + GstBuffer *comp_buffer; + GstVideoFrame *comp_frame; + GstVideoMeta *vmeta; + GstVideoInfo vinfo; + GstVideoOverlayRectangle *rectangle; + GstClapperGdkOverlay *overlay; + GstVideoOverlayFormatFlags flags, alpha_flags = 0; + gint comp_x, comp_y; + guint comp_width, comp_height, cached_index; + + rectangle = gst_video_overlay_composition_get_rectangle (comp_meta->overlay, i); + + if ((overlay = _get_cached_overlay (self->overlays, rectangle, &cached_index))) { + overlay->used = TRUE; + + /* Place overlay at expected position */ + if (i != cached_index) { + GST_LOG ("Rearranging overlay position: %u => %u", cached_index, i); + + overlay = g_ptr_array_steal_index_fast (self->overlays, cached_index); + g_ptr_array_insert (self->overlays, i, overlay); + } + + GST_TRACE ("Reusing cached overlay: %" GST_PTR_FORMAT, overlay); + continue; + } + + if (G_UNLIKELY (!gst_video_overlay_rectangle_get_render_rectangle (rectangle, + &comp_x, &comp_y, &comp_width, &comp_height))) { + GST_WARNING ("Invalid overlay rectangle dimensions: %" GST_PTR_FORMAT, rectangle); + continue; + } + + flags = gst_video_overlay_rectangle_get_flags (rectangle); + + if (flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) + alpha_flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA; + + comp_buffer = gst_video_overlay_rectangle_get_pixels_unscaled_argb (rectangle, alpha_flags); + comp_frame = g_slice_new (GstVideoFrame); + + /* Update overlay video info from video meta */ + if ((vmeta = gst_buffer_get_video_meta (comp_buffer))) { + gst_video_info_set_format (&vinfo, vmeta->format, vmeta->width, vmeta->height); + vinfo.stride[0] = vmeta->stride[0]; + } + + if (G_UNLIKELY (!gst_video_frame_map (comp_frame, &vinfo, comp_buffer, GST_MAP_READ))) { + g_slice_free (GstVideoFrame, comp_frame); + return; + } + + if ((texture = gst_video_frame_into_gdk_texture ( + comp_frame, (GDestroyNotify) comp_frame_unmap_and_free))) { + overlay = g_slice_new (GstClapperGdkOverlay); + overlay->texture = texture; + overlay->rectangle = gst_video_overlay_rectangle_ref (rectangle); + overlay->x = comp_x; + overlay->y = comp_y; + overlay->width = comp_width; + overlay->height = comp_height; + overlay->used = TRUE; + + GST_TRACE ("Created overlay: %" + GST_PTR_FORMAT ", x: %i, y: %i, width: %u, height: %u", + overlay, overlay->x, overlay->y, overlay->width, overlay->height); + + g_ptr_array_insert (self->overlays, i, overlay); + } + } + + /* Remove all overlays that are not going to be used */ + for (i = self->overlays->len; i > 0; i--) { + GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i - 1); + + if (!overlay->used) { + GST_TRACE ("Removing unused cached overlay: %" GST_PTR_FORMAT, overlay); + g_ptr_array_remove (self->overlays, overlay); + } + } + + if (G_UNLIKELY (num_overlays != self->overlays->len)) { + GST_WARNING_OBJECT (self, "Some overlays could not be prepared, %u != %u", + num_overlays, self->overlays->len); + } + + GST_LOG_OBJECT (self, "Prepared overlays: %u", self->overlays->len); +} + +static gboolean +update_paintable_on_main_cb (GstClapperPaintable *self) +{ + gboolean size_changed; + + GST_CLAPPER_PAINTABLE_LOCK (self); + + /* Check if we will need to invalidate size */ + if ((size_changed = self->pending_resize)) + self->pending_resize = FALSE; + + gst_clear_buffer (&self->buffer); + self->buffer = self->pending_buffer; + self->pending_buffer = NULL; + + self->draw_id = 0; + + GST_CLAPPER_PAINTABLE_UNLOCK (self); + + gst_clapper_paintable_prepare_overlays (self); + + if (size_changed) + invalidate_paintable_size_internal (self); + + GST_LOG_OBJECT (self, "Invalidate paintable contents"); + gdk_paintable_invalidate_contents ((GdkPaintable *) self); + + return G_SOURCE_REMOVE; +} + +GstClapperPaintable * +gst_clapper_paintable_new (void) +{ + return g_object_new (GST_TYPE_CLAPPER_PAINTABLE, NULL); +} + +void +gst_clapper_paintable_set_widget (GstClapperPaintable *self, GtkWidget *widget) +{ + g_weak_ref_set (&self->widget, widget); +} + +void +gst_clapper_paintable_set_buffer (GstClapperPaintable *self, GstBuffer *buffer) +{ + GST_CLAPPER_PAINTABLE_LOCK (self); + + if (self->draw_id > 0) { + GST_CLAPPER_PAINTABLE_UNLOCK (self); + GST_TRACE ("Already have pending buffer, skipping %" GST_PTR_FORMAT, buffer); + + return; + } + + gst_buffer_replace (&self->pending_buffer, buffer); + self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT, + (GSourceFunc) update_paintable_on_main_cb, self, NULL); + + GST_CLAPPER_PAINTABLE_UNLOCK (self); +} + +gboolean +gst_clapper_paintable_set_video_info (GstClapperPaintable *self, GstVideoInfo *v_info) +{ + GST_CLAPPER_PAINTABLE_LOCK (self); + + if (gst_video_info_is_equal (&self->v_info, v_info)) { + GST_CLAPPER_PAINTABLE_UNLOCK (self); + return TRUE; + } + + /* Reject info if values would cause integer overflow */ + if (G_UNLIKELY (!calculate_display_par (self, v_info))) { + GST_CLAPPER_PAINTABLE_UNLOCK (self); + return FALSE; + } + + self->pending_resize = TRUE; + self->v_info = *v_info; + + GST_CLAPPER_PAINTABLE_UNLOCK (self); + + return TRUE; +} + +void +gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *self, + gint par_n, gint par_d) +{ + gboolean success; + + GST_CLAPPER_PAINTABLE_LOCK (self); + + /* No change */ + if (self->par_n == par_n && self->par_d == par_d) { + GST_CLAPPER_PAINTABLE_UNLOCK (self); + return; + } + + self->par_n = par_n; + self->par_d = par_d; + + /* Check if we can accept new values. This will update + * display `ratio_num` and `ratio_den` only when successful */ + success = calculate_display_par (self, &self->v_info); + + /* If paintable update is queued, wait for it, otherwise invalidate + * size only for change to be applied even when paused */ + if (!success || self->draw_id > 0) { + GST_CLAPPER_PAINTABLE_UNLOCK (self); + return; + } + + self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT, + (GSourceFunc) invalidate_paintable_size_on_main_cb, self, NULL); + + GST_CLAPPER_PAINTABLE_UNLOCK (self); + + return; +} + +/* + * GdkPaintableInterface + */ +static void +gst_clapper_paintable_snapshot_internal (GstClapperPaintable *self, + GdkSnapshot *snapshot, gdouble width, gdouble height, + gint widget_width, gint widget_height) +{ + GstMemory *memory; + GstMapInfo info; + gfloat scale_x, scale_y; + guint i; + + scale_x = (gfloat) width / self->display_width; + scale_y = (gfloat) height / self->display_height; + + /* Apply black borders when keeping aspect ratio */ + if (scale_x == scale_y || abs (scale_x - scale_y) <= FLT_EPSILON) { + if (widget_height - height > 0) { + gdouble bar_height = (widget_height - height) / 2; + + gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, -bar_height)); + gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, height, width, bar_height + 0.5)); + } else if (widget_width - width > 0) { + gdouble bar_width = (widget_width - width) / 2; + + gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, -bar_width, height)); + gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (width, 0, bar_width + 0.5, height)); + } + } + + /* Buffer is accessed only from main thread, so no locking required */ + if (!self->buffer) { + gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height)); + return; + } + + GST_TRACE ("Snapshot %" GST_PTR_FORMAT, self->buffer); + + memory = gst_buffer_peek_memory (self->buffer, 0); + + /* If we cannot map, just draw black */ + if (G_UNLIKELY (!gst_memory_map (memory, &info, GST_MAP_READ))) { + gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height)); + GST_WARNING_OBJECT (self, "Could not map %" GST_PTR_FORMAT, self->buffer); + + return; + } + + gtk_snapshot_append_texture (snapshot, + GST_CLAPPER_GDK_MEMORY_CAST (memory)->texture, + &GRAPHENE_RECT_INIT (0, 0, width, height)); + gst_memory_unmap (memory, &info); + + /* FIXME: Draw black BG here when import format has-alpha */ + //gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height)); + + /* Finally append prepared overlays */ + for (i = 0; i < self->overlays->len; i++) { + GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i); + + gtk_snapshot_append_texture (snapshot, overlay->texture, + &GRAPHENE_RECT_INIT (overlay->x * scale_x, overlay->y * scale_y, + overlay->width * scale_x, overlay->height * scale_y)); + } +} + +static void +gst_clapper_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, gdouble width, gdouble height) +{ + GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); + GtkWidget *widget; + gint widget_width = 0, widget_height = 0; + + if ((widget = g_weak_ref_get (&self->widget))) { + widget_width = gtk_widget_get_width (widget); + widget_height = gtk_widget_get_height (widget); + + g_object_unref (widget); + } + + gst_clapper_paintable_snapshot_internal (self, snapshot, + width, height, widget_width, widget_height); +} + +static GdkPaintable * +gst_clapper_paintable_get_current_image (GdkPaintable *paintable) +{ + GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); + + if (self->buffer) { + GtkSnapshot *snapshot; + GdkPaintable *ret; + + snapshot = gtk_snapshot_new (); + + /* Snapshot without widget size in order to get + * paintable without black borders */ + gst_clapper_paintable_snapshot_internal (self, snapshot, + self->display_width, self->display_height, 0, 0); + + if ((ret = gtk_snapshot_free_to_paintable (snapshot, NULL))) + return ret; + } + + return gdk_paintable_new_empty (0, 0); +} + +static gint +gst_clapper_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); + + return self->display_width; +} + +static gint +gst_clapper_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); + + return self->display_height; +} + +static gdouble +gst_clapper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) +{ + GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); + + return self->display_aspect_ratio; +} + +static void +gst_clapper_paintable_iface_init (GdkPaintableInterface *iface) +{ + iface->snapshot = gst_clapper_paintable_snapshot; + iface->get_current_image = gst_clapper_paintable_get_current_image; + iface->get_intrinsic_width = gst_clapper_paintable_get_intrinsic_width; + iface->get_intrinsic_height = gst_clapper_paintable_get_intrinsic_height; + iface->get_intrinsic_aspect_ratio = gst_clapper_paintable_get_intrinsic_aspect_ratio; +} diff --git a/lib/gst/plugin/gstclapperpaintable.h b/lib/gst/plugin/gstclapperpaintable.h new file mode 100644 index 00000000..7b79e10b --- /dev/null +++ b/lib/gst/plugin/gstclapperpaintable.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_PAINTABLE (gst_clapper_paintable_get_type()) +G_DECLARE_FINAL_TYPE (GstClapperPaintable, gst_clapper_paintable, GST, CLAPPER_PAINTABLE, GObject) + +#define GST_CLAPPER_PAINTABLE_CAST(obj) ((GstClapperPaintable *)(obj)) + +#define GST_CLAPPER_PAINTABLE_GET_LOCK(obj) (&GST_CLAPPER_PAINTABLE_CAST(obj)->lock) +#define GST_CLAPPER_PAINTABLE_LOCK(obj) g_mutex_lock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj)) +#define GST_CLAPPER_PAINTABLE_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj)) + +struct _GstClapperPaintable +{ + GObject parent; + + GMutex lock; + + GstBuffer *pending_buffer, *buffer; + GPtrArray *overlays; + GstVideoInfo v_info; + + GdkRGBA bg; + + GWeakRef widget; + + /* Sink properties */ + gint par_n, par_d; + + /* Resize */ + gboolean pending_resize; + guint display_ratio_num; + guint display_ratio_den; + + /* GdkPaintableInterface */ + gint display_width; + gint display_height; + gdouble display_aspect_ratio; + + /* Pending draw signal id */ + guint draw_id; +}; + +GstClapperPaintable * gst_clapper_paintable_new (void); +void gst_clapper_paintable_set_widget (GstClapperPaintable *paintable, GtkWidget *widget); +void gst_clapper_paintable_set_buffer (GstClapperPaintable *paintable, GstBuffer *buffer); +gboolean gst_clapper_paintable_set_video_info (GstClapperPaintable *paintable, GstVideoInfo *v_info); +void gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *paintable, gint par_n, gint par_d); + +G_END_DECLS diff --git a/lib/gst/plugin/gstclappersink.c b/lib/gst/plugin/gstclappersink.c new file mode 100644 index 00000000..cf2bef4a --- /dev/null +++ b/lib/gst/plugin/gstclappersink.c @@ -0,0 +1,838 @@ +/* + * 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" +#include "gstclappergdkmemory.h" +#include "gstclappergdkbufferpool.h" +#include "gstgtkutils.h" + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_PAR_N 1 +#define DEFAULT_PAR_D 1 +#define DEFAULT_KEEP_LAST_FRAME FALSE + +#define WINDOW_CSS_CLASS_NAME "clappersinkwindow" + +enum +{ + PROP_0, + PROP_WIDGET, + PROP_FORCE_ASPECT_RATIO, + PROP_PIXEL_ASPECT_RATIO, + PROP_KEEP_LAST_FRAME, + PROP_LAST +}; + +#define GST_CAT_DEFAULT gst_clapper_sink_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +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 (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY, + "{ " GST_CLAPPER_GDK_MEMORY_FORMATS " }") + "; " + GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY ", " + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, + "{ " GST_CLAPPER_GDK_MEMORY_FORMATS " }"))); + +static void gst_clapper_sink_navigation_interface_init ( + GstNavigationInterface *iface); + +#define parent_class gst_clapper_sink_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_ELEMENT_REGISTER_DEFINE (clappersink, "clappersink", GST_RANK_NONE, + GST_TYPE_CLAPPER_SINK); + +static void +window_clear_no_lock (GstClapperSink *self) +{ + if (!self->window) + return; + + GST_TRACE_OBJECT (self, "Window clear"); + + if (self->window_destroy_id) { + g_signal_handler_disconnect (self->window, self->window_destroy_id); + self->window_destroy_id = 0; + } + self->window = NULL; +} + +static void +widget_clear_no_lock (GstClapperSink *self) +{ + if (!self->widget) + return; + + GST_TRACE_OBJECT (self, "Widget clear"); + + if (self->widget_destroy_id) { + g_signal_handler_disconnect (self->widget, self->widget_destroy_id); + self->widget_destroy_id = 0; + } + g_clear_object (&self->widget); +} + +static void +widget_destroy_cb (GtkWidget *widget, GstClapperSink *self) +{ + GST_CLAPPER_SINK_LOCK (self); + widget_clear_no_lock (self); + GST_CLAPPER_SINK_UNLOCK (self); +} + +static void +window_destroy_cb (GtkWidget *window, GstClapperSink *self) +{ + GST_DEBUG_OBJECT (self, "Window destroy"); + + GST_CLAPPER_SINK_LOCK (self); + + widget_clear_no_lock (self); + window_clear_no_lock (self); + + GST_CLAPPER_SINK_UNLOCK (self); +} + +static void +calculate_stream_coords (GstClapperSink *self, GtkWidget *widget, + gdouble x, gdouble y, gdouble *stream_x, gdouble *stream_y) +{ + GstVideoRectangle result; + gint scaled_width, scaled_height, scale_factor; + gint video_width, video_height; + gboolean force_aspect_ratio; + + GST_CLAPPER_SINK_LOCK (self); + + video_width = GST_VIDEO_INFO_WIDTH (&self->v_info); + video_height = GST_VIDEO_INFO_HEIGHT (&self->v_info); + force_aspect_ratio = self->force_aspect_ratio; + + GST_CLAPPER_SINK_UNLOCK (self); + + scale_factor = gtk_widget_get_scale_factor (widget); + scaled_width = gtk_widget_get_width (widget) * scale_factor; + scaled_height = gtk_widget_get_height (widget) * scale_factor; + + if (force_aspect_ratio) { + GstVideoRectangle src, dst; + + src.x = 0; + src.y = 0; + src.w = gdk_paintable_get_intrinsic_width ((GdkPaintable *) self->paintable); + src.h = gdk_paintable_get_intrinsic_height ((GdkPaintable *) self->paintable); + + dst.x = 0; + dst.y = 0; + dst.w = scaled_width; + dst.h = scaled_height; + + gst_video_center_rect (&src, &dst, &result, TRUE); + } else { + result.x = 0; + result.y = 0; + result.w = scaled_width; + result.h = scaled_height; + } + + /* Display coordinates to stream coordinates */ + *stream_x = (result.w > 0) + ? (x - result.x) / result.w * video_width + : 0; + *stream_y = (result.h > 0) + ? (y - result.y) / result.h * video_height + : 0; + + /* Clip to stream size */ + *stream_x = CLAMP (*stream_x, 0, video_width); + *stream_y = CLAMP (*stream_y, 0, video_height); + + GST_LOG ("Transform coords %fx%f => %fx%f", x, y, *stream_x, *stream_y); +} + +static void +gst_clapper_sink_widget_motion_event (GtkEventControllerMotion *motion, + gdouble x, gdouble y, GstClapperSink *self) +{ + GtkWidget *widget; + gdouble stream_x, stream_y; + + if ((x == self->last_pos_x && y == self->last_pos_y) + || GST_STATE (self) < GST_STATE_PLAYING) + return; + + self->last_pos_x = x; + self->last_pos_y = y; + + widget = gtk_event_controller_get_widget ((GtkEventController *) motion); + calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y); + GST_LOG ("Event \"mouse-move\", x: %f, y: %f", stream_x, stream_y); + + gst_navigation_send_mouse_event ((GstNavigation *) self, "mouse-move", + 0, stream_x, stream_y); +} + +static void +gst_clapper_sink_widget_button_event (GtkGestureClick *click, + gint n_press, gdouble x, gdouble y, GstClapperSink *self) +{ + GtkWidget *widget; + GdkEvent *event; + gdouble stream_x, stream_y; + GdkEventType event_type; + const gchar *event_name; + + if (GST_STATE (self) < GST_STATE_PLAYING) + return; + + event = gtk_event_controller_get_current_event ((GtkEventController *) click); + event_type = gdk_event_get_event_type (event); + + /* FIXME: Touchscreen handling should probably use new touch events from GStreamer 1.22 */ + event_name = (event_type == GDK_BUTTON_PRESS || event_type == GDK_TOUCH_BEGIN) + ? "mouse-button-press" + : (event_type == GDK_BUTTON_RELEASE || event_type == GDK_TOUCH_END) + ? "mouse-button-release" + : NULL; + + /* Can be NULL on touch */ + if (!event_name) + return; + + widget = gtk_event_controller_get_widget ((GtkEventController *) click); + calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y); + GST_LOG ("Event \"%s\", x: %f, y: %f", event_name, stream_x, stream_y); + + /* Gesture is set to handle only primary button, so we do not have to check */ + gst_navigation_send_mouse_event ((GstNavigation *) self, event_name, + 1, stream_x, stream_y); +} + +/* Must call from main thread only with a lock */ +static GtkWidget * +gst_clapper_sink_get_widget (GstClapperSink *self) +{ + if (G_UNLIKELY (!self->widget)) { + GtkEventController *controller; + GtkGesture *gesture; + + /* Make sure GTK is initialized */ + if (!gtk_init_check ()) { + GST_ERROR_OBJECT (self, "Could not ensure GTK initialization"); + return NULL; + } + + self->widget = gtk_picture_new (); + + /* Otherwise widget in grid will appear as a 1x1px + * video which might be misleading for users */ + gtk_widget_set_hexpand (self->widget, TRUE); + gtk_widget_set_vexpand (self->widget, TRUE); + + gtk_widget_set_focusable (self->widget, TRUE); + gtk_widget_set_can_focus (self->widget, TRUE); + + controller = gtk_event_controller_motion_new (); + g_signal_connect (controller, "motion", + G_CALLBACK (gst_clapper_sink_widget_motion_event), self); + gtk_widget_add_controller (self->widget, controller); + + gesture = gtk_gesture_click_new (); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 1); + g_signal_connect (gesture, "pressed", + G_CALLBACK (gst_clapper_sink_widget_button_event), self); + g_signal_connect (gesture, "released", + G_CALLBACK (gst_clapper_sink_widget_button_event), self); + gtk_widget_add_controller (self->widget, GTK_EVENT_CONTROLLER (gesture)); + + /* TODO: Implement touch events once we depend on GStreamer 1.22 */ + + /* Take floating ref */ + g_object_ref_sink (self->widget); + + /* Set back pointer */ + gst_clapper_paintable_set_widget (self->paintable, self->widget); + + /* Set earlier remembered property */ + gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget), + self->force_aspect_ratio); + + gtk_picture_set_paintable (GTK_PICTURE (self->widget), GDK_PAINTABLE (self->paintable)); + + self->widget_destroy_id = g_signal_connect (self->widget, + "destroy", G_CALLBACK (widget_destroy_cb), self); + } + + return self->widget; +} + +static GtkWidget * +gst_clapper_sink_obtain_widget (GstClapperSink *self) +{ + GtkWidget *widget; + + GST_CLAPPER_SINK_LOCK (self); + widget = gst_clapper_sink_get_widget (self); + if (widget) + g_object_ref (widget); + GST_CLAPPER_SINK_UNLOCK (self); + + return widget; +} + +static void +gst_clapper_sink_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); + + GST_CLAPPER_SINK_LOCK (self); + + switch (prop_id) { + case PROP_WIDGET: + if (self->widget) { + g_value_set_object (value, self->widget); + } else { + GtkWidget *widget; + + GST_CLAPPER_SINK_UNLOCK (self); + widget = gst_gtk_invoke_on_main ((GThreadFunc) gst_clapper_sink_obtain_widget, self); + GST_CLAPPER_SINK_LOCK (self); + + g_value_set_object (value, widget); + g_object_unref (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; + case PROP_KEEP_LAST_FRAME: + g_value_set_boolean (value, self->keep_last_frame); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_CLAPPER_SINK_UNLOCK (self); +} + +static void +gst_clapper_sink_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); + + GST_CLAPPER_SINK_LOCK (self); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + self->force_aspect_ratio = g_value_get_boolean (value); + + if (self->widget) { + gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget), + self->force_aspect_ratio); + } + break; + case PROP_PIXEL_ASPECT_RATIO: + self->par_n = gst_value_get_fraction_numerator (value); + self->par_d = gst_value_get_fraction_denominator (value); + + gst_clapper_paintable_set_pixel_aspect_ratio (self->paintable, + self->par_n, self->par_d); + break; + case PROP_KEEP_LAST_FRAME: + self->keep_last_frame = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_CLAPPER_SINK_UNLOCK (self); +} + +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 (event)) { + GstPad *pad; + + pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink)); + + if (G_LIKELY (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 gboolean +gst_clapper_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); + GstBufferPool *pool = NULL; + GstCaps *caps; + GstVideoInfo info; + guint size; + gboolean need_pool; + + gst_query_parse_allocation (query, &caps, &need_pool); + + if (!caps) { + GST_DEBUG_OBJECT (self, "No caps specified"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_DEBUG_OBJECT (self, "Invalid caps specified"); + return FALSE; + } + + /* Normal size of a frame */ + size = GST_VIDEO_INFO_SIZE (&info); + + if (need_pool) { + GstStructure *config; + + GST_DEBUG_OBJECT (self, "Creating new pool"); + + pool = gst_clapper_gdk_buffer_pool_new (); + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_set_params (config, caps, size, 2, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + gst_object_unref (pool); + + GST_DEBUG_OBJECT (self, "Failed to set config"); + return FALSE; + } + } + + gst_query_add_allocation_pool (query, pool, size, 2, 0); + if (pool) + gst_object_unref (pool); + + gst_query_add_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL); + + /* We also support various metadata */ + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + + return TRUE; +} + +static gboolean +gst_clapper_sink_start_on_main (GstClapperSink *self) +{ + GtkWidget *widget; + + GST_CLAPPER_SINK_LOCK (self); + + /* Make sure widget is created */ + if (!(widget = gst_clapper_sink_get_widget (self))) { + GST_CLAPPER_SINK_UNLOCK (self); + + return FALSE; + } + + /* When no toplevel window, make our own */ + if (G_UNLIKELY (!gtk_widget_get_root (widget) && !self->window)) { + GtkWidget *toplevel, *parent; + GtkCssProvider *provider; + 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; + + self->window = (GtkWindow *) gtk_window_new (); + gtk_widget_add_css_class (GTK_WIDGET (self->window), WINDOW_CSS_CLASS_NAME); + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, + "." WINDOW_CSS_CLASS_NAME " { background: none; }", -1); + gtk_style_context_add_provider_for_display ( + gdk_display_get_default (), GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + 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); + } + + GST_CLAPPER_SINK_UNLOCK (self); + + return TRUE; +} + +static gboolean +window_present_on_main_idle (GtkWindow *window) +{ + GST_INFO ("Presenting window"); + gtk_window_present (window); + + return G_SOURCE_REMOVE; +} + +static gboolean +gst_clapper_sink_start (GstBaseSink *bsink) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); + + GST_INFO_OBJECT (self, "Start"); + + if (G_UNLIKELY (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_clapper_sink_start_on_main, self)))) { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("GtkWidget could not be created"), (NULL)); + + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_clapper_sink_stop_on_main (GstClapperSink *self) +{ + GtkWindow *window = NULL; + + GST_CLAPPER_SINK_LOCK (self); + if (self->window) + window = g_object_ref (self->window); + GST_CLAPPER_SINK_UNLOCK (self); + + if (window) { + gtk_window_destroy (window); + g_object_unref (window); + } + + return TRUE; +} + +static gboolean +gst_clapper_sink_stop (GstBaseSink *bsink) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); + gboolean has_window; + + GST_INFO_OBJECT (self, "Stop"); + + GST_CLAPPER_SINK_LOCK (self); + has_window = (self->window != NULL); + GST_CLAPPER_SINK_UNLOCK (self); + + if (G_UNLIKELY (has_window)) { + return (! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) + gst_clapper_sink_stop_on_main, self)); + } + + return TRUE; +} + +static GstStateChangeReturn +gst_clapper_sink_change_state (GstElement *element, GstStateChange transition) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (element); + + 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))); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_CLAPPER_SINK_LOCK (self); + if (!self->keep_last_frame) + gst_clapper_paintable_set_buffer (self->paintable, NULL); + GST_CLAPPER_SINK_UNLOCK (self); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + GST_CLAPPER_SINK_LOCK (self); + if (G_UNLIKELY (self->window && !self->presented_window)) { + g_idle_add_full (G_PRIORITY_DEFAULT, + (GSourceFunc) window_present_on_main_idle, + g_object_ref (self->window), (GDestroyNotify) g_object_unref); + self->presented_window = TRUE; + } + GST_CLAPPER_SINK_UNLOCK (self); + break; + default: + break; + } + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + +static void +gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer, + GstClockTime *start, GstClockTime *end) +{ + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + return; + + *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); + gint fps_n, fps_d; + + GST_CLAPPER_SINK_LOCK (self); + fps_n = GST_VIDEO_INFO_FPS_N (&self->v_info); + fps_d = GST_VIDEO_INFO_FPS_D (&self->v_info); + GST_CLAPPER_SINK_UNLOCK (self); + + if (fps_n > 0) + *end = *start + gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); + } +} + +static GstCaps * +gst_clapper_sink_get_caps (GstBaseSink *bsink, GstCaps *filter) +{ + GstCaps *result, *tmp; + + tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); + + if (filter) { + GST_DEBUG ("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; + } + GST_DEBUG ("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); + gboolean res; + + GST_INFO_OBJECT (self, "Set caps: %" GST_PTR_FORMAT, caps); + GST_CLAPPER_SINK_LOCK (self); + + if (!gst_video_info_from_caps (&self->v_info, caps)) { + GST_CLAPPER_SINK_UNLOCK (self); + return FALSE; + } + + if (G_UNLIKELY (!self->widget)) { + GST_CLAPPER_SINK_UNLOCK (self); + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Output widget was destroyed"), (NULL)); + + return FALSE; + } + + res = gst_clapper_paintable_set_video_info (self->paintable, &self->v_info); + GST_CLAPPER_SINK_UNLOCK (self); + + return res; +} + +static GstFlowReturn +gst_clapper_sink_show_frame (GstVideoSink *vsink, GstBuffer *buffer) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink); + + GST_TRACE ("Got %" GST_PTR_FORMAT, buffer); + GST_CLAPPER_SINK_LOCK (self); + + if (G_UNLIKELY (!self->widget)) { + GST_CLAPPER_SINK_UNLOCK (self); + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Output widget was destroyed"), (NULL)); + + return GST_FLOW_ERROR; + } + + gst_clapper_paintable_set_buffer (self->paintable, buffer); + GST_CLAPPER_SINK_UNLOCK (self); + + return GST_FLOW_OK; +} + +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", + 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; + self->keep_last_frame = DEFAULT_KEEP_LAST_FRAME; + + g_mutex_init (&self->lock); + gst_video_info_init (&self->v_info); + + self->paintable = gst_clapper_paintable_new (); +} + +static void +gst_clapper_sink_dispose (GObject *object) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); + + GST_CLAPPER_SINK_LOCK (self); + + window_clear_no_lock (self); + widget_clear_no_lock (self); + + g_clear_object (&self->paintable); + + GST_CLAPPER_SINK_UNLOCK (self); + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); +} + +static void +gst_clapper_sink_finalize (GObject *object) +{ + GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); + + GST_TRACE ("Finalize"); + + g_mutex_clear (&self->lock); + + GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static void +gst_clapper_sink_class_init (GstClapperSinkClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass; + GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappersink", 0, + "Clapper Sink"); + + gobject_class->get_property = gst_clapper_sink_get_property; + gobject_class->set_property = gst_clapper_sink_set_property; + gobject_class->dispose = gst_clapper_sink_dispose; + gobject_class->finalize = gst_clapper_sink_finalize; + + 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)); + + g_object_class_install_property (gobject_class, PROP_KEEP_LAST_FRAME, + g_param_spec_boolean ("keep-last-frame", "Keep last frame", + "Keep showing last video frame after playback instead of black screen", + DEFAULT_KEEP_LAST_FRAME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + 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); +} + +/* + * GstNavigationInterface + */ +static void +gst_clapper_sink_navigation_interface_init (GstNavigationInterface *iface) +{ + /* TODO: Port to "send_event_simple" once we depend on GStreamer 1.22 */ + iface->send_event = gst_clapper_sink_navigation_send_event; +} diff --git a/lib/gst/plugin/gstclappersink.h b/lib/gst/plugin/gstclappersink.h new file mode 100644 index 00000000..66f7ae01 --- /dev/null +++ b/lib/gst/plugin/gstclappersink.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "gstclapperpaintable.h" + +G_BEGIN_DECLS + +#define GST_TYPE_CLAPPER_SINK (gst_clapper_sink_get_type()) +G_DECLARE_FINAL_TYPE (GstClapperSink, gst_clapper_sink, GST, CLAPPER_SINK, GstVideoSink) + +#define GST_CLAPPER_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass)) +#define GST_CLAPPER_SINK_CAST(obj) ((GstClapperSink *)(obj)) + +#define GST_CLAPPER_SINK_GET_LOCK(obj) (&GST_CLAPPER_SINK_CAST(obj)->lock) +#define GST_CLAPPER_SINK_LOCK(obj) g_mutex_lock (GST_CLAPPER_SINK_GET_LOCK(obj)) +#define GST_CLAPPER_SINK_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_SINK_GET_LOCK(obj)) + +struct _GstClapperSink +{ + GstVideoSink parent; + + GMutex lock; + + GstClapperPaintable *paintable; + GstVideoInfo v_info; + + GtkWidget *widget; + GtkWindow *window; + + gboolean presented_window; + + /* Properties */ + gboolean force_aspect_ratio; + gint par_n, par_d; + gboolean keep_last_frame; + + /* Position coords */ + gdouble last_pos_x; + gdouble last_pos_y; + + gulong widget_destroy_id; + gulong window_destroy_id; +}; + +GST_ELEMENT_REGISTER_DECLARE (clappersink); + +G_END_DECLS diff --git a/lib/gst/plugin/gstgtkutils.c b/lib/gst/plugin/gstgtkutils.c new file mode 100644 index 00000000..fca63894 --- /dev/null +++ b/lib/gst/plugin/gstgtkutils.c @@ -0,0 +1,131 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * 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. + */ + +#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; +} + +/* For use with `GdkMemoryTexture` only! */ +GdkMemoryFormat +gst_video_format_to_gdk_memory_format (GstVideoFormat format) +{ + switch (format) { + case GST_VIDEO_FORMAT_RGBA64_LE: + case GST_VIDEO_FORMAT_RGBA64_BE: + return GDK_MEMORY_R16G16B16A16_PREMULTIPLIED; + case GST_VIDEO_FORMAT_RGBA: + return GDK_MEMORY_R8G8B8A8; + case GST_VIDEO_FORMAT_BGRA: + return GDK_MEMORY_B8G8R8A8; + case GST_VIDEO_FORMAT_ARGB: + return GDK_MEMORY_A8R8G8B8; + case GST_VIDEO_FORMAT_ABGR: + return GDK_MEMORY_A8B8G8R8; + case GST_VIDEO_FORMAT_RGBx: + return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; + case GST_VIDEO_FORMAT_BGRx: + return GDK_MEMORY_B8G8R8A8_PREMULTIPLIED; + case GST_VIDEO_FORMAT_RGB: + return GDK_MEMORY_R8G8B8; + case GST_VIDEO_FORMAT_BGR: + return GDK_MEMORY_B8G8R8; + default: + break; + } + + /* This should never happen as long as above switch statement + * is updated when new formats are added to caps */ + g_assert_not_reached (); + + /* Fallback format */ + return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; +} + +GdkTexture * +gst_video_frame_into_gdk_texture (GstVideoFrame *frame, GDestroyNotify free_func) +{ + GdkTexture *texture; + GBytes *bytes; + + 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), + free_func, frame); + + texture = gdk_memory_texture_new ( + GST_VIDEO_FRAME_WIDTH (frame), + GST_VIDEO_FRAME_HEIGHT (frame), + gst_video_format_to_gdk_memory_format (GST_VIDEO_FRAME_FORMAT (frame)), + bytes, + GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0)); + + g_bytes_unref (bytes); + + return texture; +} diff --git a/lib/gst/plugin/gstgtkutils.h b/lib/gst/plugin/gstgtkutils.h new file mode 100644 index 00000000..7f60a432 --- /dev/null +++ b/lib/gst/plugin/gstgtkutils.h @@ -0,0 +1,33 @@ +/* + * GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2015 Thibault Saunier + * 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. + */ + +#pragma once + +#include +#include +#include + +gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data); + +GdkMemoryFormat gst_video_format_to_gdk_memory_format (GstVideoFormat format); + +GdkTexture * gst_video_frame_into_gdk_texture (GstVideoFrame *frame, GDestroyNotify free_func); diff --git a/lib/gst/plugin/gstplugin.c b/lib/gst/plugin/gstplugin.c new file mode 100644 index 00000000..ba170cd7 --- /dev/null +++ b/lib/gst/plugin/gstplugin.c @@ -0,0 +1,44 @@ +/* + * 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 "gstclapperimport.h" +#include "gstclappersink.h" +#include "gstclappergdkmemory.h" + +static gboolean +plugin_init (GstPlugin *plugin) +{ + gboolean res = FALSE; + + res |= GST_ELEMENT_REGISTER (clapperimport, plugin); + res |= GST_ELEMENT_REGISTER (clappersink, plugin); + + if (res) + gst_clapper_gdk_memory_init_once (); + + 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/meson.build b/lib/gst/plugin/meson.build new file mode 100644 index 00000000..7f4ce76e --- /dev/null +++ b/lib/gst/plugin/meson.build @@ -0,0 +1,67 @@ +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 + +if not gtk4_dep.version().version_compare('>=4.6.0') + if gst_clapper_plugin_option.enabled() + error('GTK4 version on this system is too old, plugin needs 4.6.0+') + else + subdir_done() + endif +endif + +gst_clapper_plugin_sources = [ + 'gstclappergdkmemory.c', + 'gstclappergdkbufferpool.c', + 'gstclapperbaseimport.c', + 'gstclapperimport.c', + 'gstclappersink.c', + 'gstclapperpaintable.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..5b73ca87 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.20.0' api_version = '1.0' libversion = meson.project_version() @@ -132,7 +132,7 @@ cdata.set('SIZEOF_SHORT', cc.sizeof('short')) cdata.set('SIZEOF_VOIDP', cc.sizeof('void*')) cdata.set_quoted('VERSION', libversion) -cdata.set_quoted('PACKAGE', 'gst-plugins-clapper') +cdata.set_quoted('PACKAGE', 'clapper') cdata.set_quoted('PACKAGE_VERSION', libversion) cdata.set_quoted('PACKAGE_BUGREPORT', 'https://github.com/Rafostar/clapper/issues/new') cdata.set_quoted('PACKAGE_NAME', 'GStreamer Clapper Libs') @@ -184,7 +184,7 @@ foreach extra_arg : warning_flags endif endforeach -cdata.set_quoted('GST_PACKAGE_NAME', 'GStreamer Plugins Clapper') +cdata.set_quoted('GST_PACKAGE_NAME', 'gst-plugin-clapper') cdata.set_quoted('GST_PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') # Mandatory GST deps @@ -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,