gstreamer/subprojects/gst-editing-services/ges/ges-smart-video-mixer.c
Thibault Saunier 14d6773aba ges: framepositioner: Expose positioning properties as doubles
Making it possible to properly handle compositors that have those
properties as doubles and handle antialiasing.

Internally we were handling those values as doubles in framepositioner,
so expose new properties so user can set values as doubles also.

This changes the GESFramePositionMeta API but we are still on time for 1.24

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6241>
2024-02-29 00:56:30 +00:00

555 lines
16 KiB
C

/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
/*
* gst-editing-services
*
* Copyright (C) 2013 Mathieu Duponchelle <mduponchelle1@gmail.com>
* gst-editing-services is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gst-editing-services 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.";
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <math.h>
#include "gstframepositioner.h"
#include "ges-frame-composition-meta.h"
#include "ges-types.h"
#include "ges-internal.h"
#include "ges-smart-video-mixer.h"
#include <gst/base/base.h>
#define GES_TYPE_SMART_MIXER_PAD (ges_smart_mixer_pad_get_type ())
typedef struct _GESSmartMixerPad GESSmartMixerPad;
typedef struct _GESSmartMixerPadClass GESSmartMixerPadClass;
GES_DECLARE_TYPE (SmartMixerPad, smart_mixer_pad, SMART_MIXER_PAD);
struct _GESSmartMixerPad
{
GstGhostPad parent;
gdouble alpha;
GstSegment segment;
GParamSpec *width_pspec;
GParamSpec *height_pspec;
GParamSpec *xpos_pspec;
GParamSpec *ypos_pspec;
};
struct _GESSmartMixerPadClass
{
GstGhostPadClass parent_class;
};
enum
{
PROP_PAD_0,
PROP_PAD_ALPHA,
};
G_DEFINE_TYPE (GESSmartMixerPad, ges_smart_mixer_pad, GST_TYPE_GHOST_PAD);
static void
ges_smart_mixer_notify_wrapped_pad (GESSmartMixerPad * self,
GstPad * real_mixer_pad)
{
GObjectClass *klass = G_OBJECT_GET_CLASS (real_mixer_pad);
self->width_pspec = g_object_class_find_property (klass, "width");
self->height_pspec = g_object_class_find_property (klass, "height");
self->xpos_pspec = g_object_class_find_property (klass, "xpos");
self->ypos_pspec = g_object_class_find_property (klass, "ypos");
}
static void
ges_smart_mixer_pad_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GESSmartMixerPad *pad = GES_SMART_MIXER_PAD (object);
switch (prop_id) {
case PROP_PAD_ALPHA:
g_value_set_double (value, pad->alpha);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ges_smart_mixer_pad_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GESSmartMixerPad *pad = GES_SMART_MIXER_PAD (object);
switch (prop_id) {
case PROP_PAD_ALPHA:
pad->alpha = g_value_get_double (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ges_smart_mixer_pad_init (GESSmartMixerPad * self)
{
gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
}
static void
ges_smart_mixer_pad_class_init (GESSmartMixerPadClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
gobject_class->get_property = ges_smart_mixer_pad_get_property;
gobject_class->set_property = ges_smart_mixer_pad_set_property;
g_object_class_install_property (gobject_class, PROP_PAD_ALPHA,
g_param_spec_double ("alpha", "Alpha", "Alpha of the picture", 0.0, 1.0,
1.0,
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
}
G_DEFINE_TYPE (GESSmartMixer, ges_smart_mixer, GST_TYPE_BIN);
#define GET_LOCK(obj) (&((GESSmartMixer*)(obj))->lock)
#define LOCK(obj) (g_mutex_lock (GET_LOCK(obj)))
#define UNLOCK(obj) (g_mutex_unlock (GET_LOCK(obj)))
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-raw")
);
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("video/x-raw")
);
typedef struct _PadInfos
{
gint refcount;
GESSmartMixer *self;
GstPad *mixer_pad;
GstPad *ghostpad;
GstPad *real_mixer_pad;
} PadInfos;
static void
pad_infos_unref (PadInfos * infos)
{
if (g_atomic_int_dec_and_test (&infos->refcount)) {
GST_DEBUG_OBJECT (infos->mixer_pad, "Releasing pad");
if (infos->mixer_pad) {
gst_element_release_request_pad (infos->self->mixer, infos->mixer_pad);
gst_object_unref (infos->mixer_pad);
}
gst_clear_object (&infos->real_mixer_pad);
g_free (infos);
}
}
static PadInfos *
pad_infos_new (void)
{
PadInfos *info = g_new0 (PadInfos, 1);
g_atomic_int_set (&info->refcount, 1);
return info;
}
static PadInfos *
pad_infos_ref (PadInfos * info)
{
g_atomic_int_inc (&info->refcount);
return info;
}
static gboolean
ges_smart_mixer_sinkpad_event_func (GstPad * pad, GstObject * parent,
GstEvent * event)
{
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEGMENT:
{
const GstSegment *seg;
gst_event_parse_segment (event, &seg);
GST_OBJECT_LOCK (pad);
((GESSmartMixerPad *) pad)->segment = *seg;
GST_OBJECT_UNLOCK (pad);
break;
}
default:
break;
}
return gst_pad_event_default (pad, parent, event);
}
GstPad *
ges_smart_mixer_get_mixer_pad (GESSmartMixer * self, GstPad ** mixerpad)
{
PadInfos *info;
GstPad *sinkpad;
sinkpad = gst_element_request_pad_simple (GST_ELEMENT (self), "sink_%u");
if (sinkpad == NULL)
return NULL;
info = g_hash_table_lookup (self->pads_infos, sinkpad);
*mixerpad = gst_object_ref (info->mixer_pad);
return sinkpad;
}
static void
set_pad_properties_from_composition_meta (GstPad * mixer_pad,
GstSample * sample, GESSmartMixerPad * ghost)
{
GESFrameCompositionMeta *meta;
GstBuffer *buf = gst_sample_get_buffer (sample);
GESSmartMixer *self = GES_SMART_MIXER (GST_OBJECT_PARENT (ghost));
meta =
(GESFrameCompositionMeta *) gst_buffer_get_meta (buf,
GES_TYPE_META_FRAME_COMPOSITION);
if (!meta) {
GST_WARNING ("The current source should use a framecomposition");
return;
}
if (!self->is_transition) {
g_object_set (mixer_pad, "alpha", meta->alpha,
"zorder", meta->zorder, NULL);
} else {
gint64 stream_time;
gdouble transalpha;
stream_time = gst_segment_to_stream_time (gst_sample_get_segment (sample),
GST_FORMAT_TIME, GST_BUFFER_PTS (buf));
/* When used in a transition we aggregate the alpha value value if the
* transition pad and the alpha value from upstream frame positioner */
if (GST_CLOCK_TIME_IS_VALID (stream_time))
gst_object_sync_values (GST_OBJECT (ghost), stream_time);
g_object_get (ghost, "alpha", &transalpha, NULL);
g_object_set (mixer_pad, "alpha", meta->alpha * transalpha, NULL);
}
if (G_PARAM_SPEC_VALUE_TYPE (ghost->xpos_pspec) == G_TYPE_INT) {
g_object_set (mixer_pad, "xpos", (gint) round (meta->posx), "ypos",
(gint) round (meta->posy), NULL);
} else if (G_PARAM_SPEC_VALUE_TYPE (ghost->xpos_pspec) == G_TYPE_FLOAT) {
g_object_set (mixer_pad, "xpos", (gfloat) meta->posx, "ypos",
(gfloat) meta->posy, NULL);
} else {
g_object_set (mixer_pad, "xpos", meta->posx, "ypos", meta->posy, NULL);
}
if (meta->width >= 0) {
if (G_PARAM_SPEC_VALUE_TYPE (ghost->width_pspec) == G_TYPE_INT) {
g_object_set (mixer_pad, "width", (gint) round (meta->width), NULL);
} else if (G_PARAM_SPEC_VALUE_TYPE (ghost->width_pspec) == G_TYPE_FLOAT) {
g_object_set (mixer_pad, "width", (gfloat) meta->width, NULL);
} else {
g_object_set (mixer_pad, "width", meta->width, NULL);
}
}
if (meta->height >= 0) {
if (G_PARAM_SPEC_VALUE_TYPE (ghost->height_pspec) == G_TYPE_INT) {
g_object_set (mixer_pad, "height", (gint) round (meta->height), NULL);
} else if (G_PARAM_SPEC_VALUE_TYPE (ghost->height_pspec) == G_TYPE_FLOAT) {
g_object_set (mixer_pad, "height", (gfloat) meta->height, NULL);
} else {
g_object_set (mixer_pad, "height", meta->height, NULL);
}
}
if (self->ABI.abi.has_operator)
g_object_set (mixer_pad, "operator", meta->operator, NULL);
}
/****************************************************
* GstElement vmetods *
****************************************************/
static GstPad *
_request_new_pad (GstElement * element, GstPadTemplate * templ,
const gchar * name, const GstCaps * caps)
{
PadInfos *infos = pad_infos_new ();
GESSmartMixer *self = GES_SMART_MIXER (element);
GstPad *ghost;
gchar *mixer_pad_name;
infos->mixer_pad = gst_element_request_pad (self->mixer,
gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self->mixer),
"sink_%u"), NULL, NULL);
if (infos->mixer_pad == NULL) {
GST_WARNING_OBJECT (element, "Could not get any pad from GstMixer");
pad_infos_unref (infos);
return NULL;
}
/* We can rely on this because the mixer bin uses the same name pad
as the internal mixer when creating the ghost pad. */
mixer_pad_name = gst_pad_get_name (infos->mixer_pad);
infos->real_mixer_pad = gst_element_get_static_pad (self->real_mixer,
mixer_pad_name);
g_free (mixer_pad_name);
if (infos->real_mixer_pad == NULL) {
GST_WARNING_OBJECT (element, "Could not get the real mixer pad");
pad_infos_unref (infos);
return NULL;
}
infos->self = self;
ghost = g_object_new (ges_smart_mixer_pad_get_type (), "name", name,
"direction", GST_PAD_DIRECTION (infos->mixer_pad), NULL);
infos->ghostpad = ghost;
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (ghost), infos->mixer_pad);
ges_smart_mixer_notify_wrapped_pad (GES_SMART_MIXER_PAD (ghost),
infos->real_mixer_pad);
gst_pad_set_active (ghost, TRUE);
if (!gst_element_add_pad (GST_ELEMENT (self), ghost))
goto could_not_add;
gst_pad_set_event_function (GST_PAD (ghost),
ges_smart_mixer_sinkpad_event_func);
LOCK (self);
g_hash_table_insert (self->pads_infos, ghost, infos);
g_hash_table_insert (self->pads_infos, infos->mixer_pad,
pad_infos_ref (infos));
g_hash_table_insert (self->pads_infos, infos->real_mixer_pad,
pad_infos_ref (infos));
UNLOCK (self);
GST_DEBUG_OBJECT (self, "Returning new pad %" GST_PTR_FORMAT, ghost);
return ghost;
could_not_add:
{
GST_ERROR_OBJECT (self, "could not add pad");
pad_infos_unref (infos);
return NULL;
}
}
static PadInfos *
ges_smart_mixer_find_pad_info (GESSmartMixer * self, GstPad * pad)
{
PadInfos *info;
LOCK (self);
info = g_hash_table_lookup (self->pads_infos, pad);
UNLOCK (self);
if (info)
pad_infos_ref (info);
return info;
}
static void
_release_pad (GstElement * element, GstPad * pad)
{
GstPad *peer;
GESSmartMixer *self = GES_SMART_MIXER (element);
PadInfos *info = ges_smart_mixer_find_pad_info (self, pad);
GST_DEBUG_OBJECT (element, "Releasing pad %" GST_PTR_FORMAT, pad);
LOCK (element);
g_hash_table_remove (GES_SMART_MIXER (element)->pads_infos, pad);
g_hash_table_remove (GES_SMART_MIXER (element)->pads_infos, info->mixer_pad);
g_hash_table_remove (GES_SMART_MIXER (element)->pads_infos,
info->real_mixer_pad);
peer = gst_pad_get_peer (pad);
if (peer) {
gst_pad_unlink (peer, pad);
gst_object_unref (peer);
}
gst_pad_set_active (pad, FALSE);
gst_element_remove_pad (element, pad);
UNLOCK (element);
pad_infos_unref (info);
}
static gboolean
compositor_sync_properties_with_meta (GstElement * compositor,
GstPad * sinkpad, GESSmartMixer * self)
{
PadInfos *info = ges_smart_mixer_find_pad_info (self, sinkpad);
GstSample *sample;
if (!info) {
GST_WARNING_OBJECT (self, "Couldn't find pad info?!");
return TRUE;
}
sample = gst_aggregator_peek_next_sample (GST_AGGREGATOR (compositor),
GST_AGGREGATOR_PAD (sinkpad));
if (sample) {
set_pad_properties_from_composition_meta (sinkpad,
sample, GES_SMART_MIXER_PAD (info->ghostpad));
gst_sample_unref (sample);
} else {
GST_INFO_OBJECT (sinkpad, "No sample set!");
}
pad_infos_unref (info);
return TRUE;
}
static void
ges_smart_mixer_samples_selected_cb (GstElement * compositor,
GstSegment * segment, GstClockTime pts, GstClockTime dts,
GstClockTime duration, GstStructure * info, GESSmartMixer * self)
{
gst_element_foreach_sink_pad (compositor,
(GstElementForeachPadFunc) compositor_sync_properties_with_meta, self);
}
/****************************************************
* GObject vmethods *
****************************************************/
static void
ges_smart_mixer_dispose (GObject * object)
{
GESSmartMixer *self = GES_SMART_MIXER (object);
if (self->pads_infos != NULL) {
g_hash_table_unref (self->pads_infos);
self->pads_infos = NULL;
}
gst_clear_object (&self->real_mixer);
G_OBJECT_CLASS (ges_smart_mixer_parent_class)->dispose (object);
}
static void
ges_smart_mixer_finalize (GObject * object)
{
GESSmartMixer *self = GES_SMART_MIXER (object);
g_mutex_clear (&self->lock);
G_OBJECT_CLASS (ges_smart_mixer_parent_class)->finalize (object);
}
static void
ges_smart_mixer_constructed (GObject * obj)
{
GstPad *pad;
GstElement *identity, *videoconvert;
GESSmartMixer *self = GES_SMART_MIXER (obj);
gchar *cname = g_strdup_printf ("%s-compositor", GST_OBJECT_NAME (self));
self->mixer =
gst_element_factory_create (ges_get_compositor_factory (), cname);
self->ABI.abi.has_operator =
gst_compositor_operator_get_type_and_default_value (NULL) != G_TYPE_NONE;
g_free (cname);
if (GST_IS_BIN (self->mixer)) {
g_object_get (self->mixer, "mixer", &self->real_mixer, NULL);
g_assert (self->real_mixer);
} else {
self->real_mixer = gst_object_ref (self->mixer);
}
g_object_set (self->real_mixer, "background", 1, "emit-signals", TRUE, NULL);
g_signal_connect (self->real_mixer, "samples-selected",
G_CALLBACK (ges_smart_mixer_samples_selected_cb), self);
/* See https://gitlab.freedesktop.org/gstreamer/gstreamer/issues/310 */
GST_FIXME ("Stop dropping allocation query when it is not required anymore.");
identity = gst_element_factory_make ("identity", NULL);
g_object_set (identity, "drop-allocation", TRUE, NULL);
g_assert (identity);
videoconvert = gst_element_factory_make ("videoconvert", NULL);
g_assert (videoconvert);
gst_bin_add_many (GST_BIN (self), self->mixer, identity, videoconvert, NULL);
gst_element_link_many (self->mixer, identity, videoconvert, NULL);
pad = gst_element_get_static_pad (videoconvert, "src");
self->srcpad = gst_ghost_pad_new ("src", pad);
gst_pad_set_active (self->srcpad, TRUE);
gst_object_unref (pad);
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
}
static void
ges_smart_mixer_class_init (GESSmartMixerClass * klass)
{
/* GstBinClass *parent_class = GST_BIN_CLASS (klass);
*/
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
/* FIXME Make sure the MixerClass doesn get destroy before ourself */
gst_element_class_add_static_pad_template (element_class, &src_template);
gst_element_class_add_static_pad_template (element_class, &sink_template);
gst_element_class_set_static_metadata (element_class, "GES Smart mixer",
"Generic/Audio",
"Use mixer making use of GES information",
"Thibault Saunier <thibault.saunier@collabora.com>");
element_class->request_new_pad = GST_DEBUG_FUNCPTR (_request_new_pad);
element_class->release_pad = GST_DEBUG_FUNCPTR (_release_pad);
object_class->dispose = ges_smart_mixer_dispose;
object_class->finalize = ges_smart_mixer_finalize;
object_class->constructed = ges_smart_mixer_constructed;
}
static void
ges_smart_mixer_init (GESSmartMixer * self)
{
g_mutex_init (&self->lock);
self->pads_infos = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) pad_infos_unref);
}
GstElement *
ges_smart_mixer_new (GESTrack * track)
{
GESSmartMixer *self = g_object_new (GES_TYPE_SMART_MIXER, NULL);
/* FIXME Make mixer smart and let it properly negotiate caps! */
return GST_ELEMENT (self);
}