mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 23:32:04 +02:00
Add new GStreamer plugin that consists of multiple elements for Clapper video player. The main difference is that unlike the old one, this does not operate using `GtkGLArea` anymore, but processes and displays `GdkTextures` directly through `GtkPicture` widget. Also this one is installed like any other GStreamer plugin, thus can be used via `gst-launch-1.0` binary or even used by other GTK4 apps if they wish to integrate it. This commit adds new video sink that uses a new kind of memory as input, called `ClapperGdkMemory`. With it comes a simple dedicated memory allocator, buffer pool and a `clapperimport` element that converts system mapped memory frames into `GdkTextures` that are later passed in our `ClapperGdkMemory` for the sink to display.
375 lines
11 KiB
C
Vendored
375 lines
11 KiB
C
Vendored
/*
|
|
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
|
|
*
|
|
* 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;
|
|
}
|