/* Video compositor plugin * Copyright (C) 2004, 2008 Wim Taymans * Copyright (C) 2010 Sebastian Dröge * Copyright (C) 2014 Mathieu Duponchelle * Copyright (C) 2014 Thibault Saunier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:element-compositor * @title: compositor * * Compositor can accept AYUV, VUYA, ARGB and BGRA video streams. For each of the requested * sink pads it will compare the incoming geometry and framerate to define the * output parameters. Indeed output video frames will have the geometry of the * biggest incoming video stream and the framerate of the fastest incoming one. * * Compositor will do colorspace conversion. * * Individual parameters for each input stream can be configured on the * #GstCompositorPad: * * * "xpos": The x-coordinate position of the top-left corner of the picture (#gint) * * "ypos": The y-coordinate position of the top-left corner of the picture (#gint) * * "width": The width of the picture; the input will be scaled if necessary (#gint) * * "height": The height of the picture; the input will be scaled if necessary (#gint) * * "alpha": The transparency of the picture; between 0.0 and 1.0. The blending * is a simple copy when fully-transparent (0.0) and fully-opaque (1.0). (#gdouble) * * "zorder": The z-order position of the picture in the composition (#guint) * * ## Sample pipelines * |[ * gst-launch-1.0 \ * videotestsrc pattern=1 ! \ * video/x-raw,format=AYUV,framerate=\(fraction\)10/1,width=100,height=100 ! \ * videobox border-alpha=0 top=-70 bottom=-70 right=-220 ! \ * compositor name=comp sink_0::alpha=0.7 sink_1::alpha=0.5 ! \ * videoconvert ! xvimagesink \ * videotestsrc ! \ * video/x-raw,format=AYUV,framerate=\(fraction\)5/1,width=320,height=240 ! comp. * ]| A pipeline to demonstrate compositor used together with videobox. * This should show a 320x240 pixels video test source with some transparency * showing the background checker pattern. Another video test source with just * the snow pattern of 100x100 pixels is overlaid on top of the first one on * the left vertically centered with a small transparency showing the first * video test source behind and the checker pattern under it. Note that the * framerate of the output video is 10 frames per second. * |[ * gst-launch-1.0 videotestsrc pattern=1 ! \ * video/x-raw, framerate=\(fraction\)10/1, width=100, height=100 ! \ * compositor name=comp ! videoconvert ! ximagesink \ * videotestsrc ! \ * video/x-raw, framerate=\(fraction\)5/1, width=320, height=240 ! comp. * ]| A pipeline to demonstrate bgra comping. (This does not demonstrate alpha blending). * |[ * gst-launch-1.0 videotestsrc pattern=1 ! \ * video/x-raw,format =I420, framerate=\(fraction\)10/1, width=100, height=100 ! \ * compositor name=comp ! videoconvert ! ximagesink \ * videotestsrc ! \ * video/x-raw,format=I420, framerate=\(fraction\)5/1, width=320, height=240 ! comp. * ]| A pipeline to test I420 * |[ * gst-launch-1.0 compositor name=comp sink_1::alpha=0.5 sink_1::xpos=50 sink_1::ypos=50 ! \ * videoconvert ! ximagesink \ * videotestsrc pattern=snow timestamp-offset=3000000000 ! \ * "video/x-raw,format=AYUV,width=640,height=480,framerate=(fraction)30/1" ! \ * timeoverlay ! queue2 ! comp. \ * videotestsrc pattern=smpte ! \ * "video/x-raw,format=AYUV,width=800,height=600,framerate=(fraction)10/1" ! \ * timeoverlay ! queue2 ! comp. * ]| A pipeline to demonstrate synchronized compositing (the second stream starts after 3 seconds) * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "compositor.h" #ifdef DISABLE_ORC #define orc_memset memset #else #include #endif GST_DEBUG_CATEGORY_STATIC (gst_compositor_debug); #define GST_CAT_DEFAULT gst_compositor_debug #define FORMATS " { AYUV, VUYA, BGRA, ARGB, RGBA, ABGR, Y444, Y42B, YUY2, UYVY, "\ " YVYU, I420, YV12, NV12, NV21, Y41B, RGB, BGR, xRGB, xBGR, "\ " RGBx, BGRx } " static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (FORMATS)) ); static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL)) ); static void gst_compositor_child_proxy_init (gpointer g_iface, gpointer iface_data); #define GST_TYPE_COMPOSITOR_OPERATOR (gst_compositor_operator_get_type()) static GType gst_compositor_operator_get_type (void) { static GType compositor_operator_type = 0; static const GEnumValue compositor_operator[] = { {COMPOSITOR_OPERATOR_SOURCE, "Source", "source"}, {COMPOSITOR_OPERATOR_OVER, "Over", "over"}, {COMPOSITOR_OPERATOR_ADD, "Add", "add"}, {0, NULL, NULL}, }; if (!compositor_operator_type) { compositor_operator_type = g_enum_register_static ("GstCompositorOperator", compositor_operator); } return compositor_operator_type; } #define GST_TYPE_COMPOSITOR_BACKGROUND (gst_compositor_background_get_type()) static GType gst_compositor_background_get_type (void) { static GType compositor_background_type = 0; static const GEnumValue compositor_background[] = { {COMPOSITOR_BACKGROUND_CHECKER, "Checker pattern", "checker"}, {COMPOSITOR_BACKGROUND_BLACK, "Black", "black"}, {COMPOSITOR_BACKGROUND_WHITE, "White", "white"}, {COMPOSITOR_BACKGROUND_TRANSPARENT, "Transparent Background to enable further compositing", "transparent"}, {0, NULL, NULL}, }; if (!compositor_background_type) { compositor_background_type = g_enum_register_static ("GstCompositorBackground", compositor_background); } return compositor_background_type; } #define DEFAULT_PAD_XPOS 0 #define DEFAULT_PAD_YPOS 0 #define DEFAULT_PAD_WIDTH -1 #define DEFAULT_PAD_HEIGHT -1 #define DEFAULT_PAD_ALPHA 1.0 #define DEFAULT_PAD_OPERATOR COMPOSITOR_OPERATOR_OVER enum { PROP_PAD_0, PROP_PAD_XPOS, PROP_PAD_YPOS, PROP_PAD_WIDTH, PROP_PAD_HEIGHT, PROP_PAD_ALPHA, PROP_PAD_OPERATOR, }; G_DEFINE_TYPE (GstCompositorPad, gst_compositor_pad, GST_TYPE_VIDEO_AGGREGATOR_CONVERT_PAD); static void gst_compositor_pad_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstCompositorPad *pad = GST_COMPOSITOR_PAD (object); switch (prop_id) { case PROP_PAD_XPOS: g_value_set_int (value, pad->xpos); break; case PROP_PAD_YPOS: g_value_set_int (value, pad->ypos); break; case PROP_PAD_WIDTH: g_value_set_int (value, pad->width); break; case PROP_PAD_HEIGHT: g_value_set_int (value, pad->height); break; case PROP_PAD_ALPHA: g_value_set_double (value, pad->alpha); break; case PROP_PAD_OPERATOR: g_value_set_enum (value, pad->op); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_compositor_pad_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstCompositorPad *pad = GST_COMPOSITOR_PAD (object); switch (prop_id) { case PROP_PAD_XPOS: pad->xpos = g_value_get_int (value); break; case PROP_PAD_YPOS: pad->ypos = g_value_get_int (value); break; case PROP_PAD_WIDTH: pad->width = g_value_get_int (value); gst_video_aggregator_convert_pad_update_conversion_info (GST_VIDEO_AGGREGATOR_CONVERT_PAD (pad)); break; case PROP_PAD_HEIGHT: pad->height = g_value_get_int (value); gst_video_aggregator_convert_pad_update_conversion_info (GST_VIDEO_AGGREGATOR_CONVERT_PAD (pad)); break; case PROP_PAD_ALPHA: pad->alpha = g_value_get_double (value); break; case PROP_PAD_OPERATOR: pad->op = g_value_get_enum (value); gst_video_aggregator_pad_set_needs_alpha (GST_VIDEO_AGGREGATOR_PAD (pad), pad->op == COMPOSITOR_OPERATOR_ADD); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void _mixer_pad_get_output_size (GstCompositor * comp, GstCompositorPad * comp_pad, gint out_par_n, gint out_par_d, gint * width, gint * height) { GstVideoAggregatorPad *vagg_pad = GST_VIDEO_AGGREGATOR_PAD (comp_pad); gint pad_width, pad_height; guint dar_n, dar_d; /* FIXME: Anything better we can do here? */ if (!vagg_pad->info.finfo || vagg_pad->info.finfo->format == GST_VIDEO_FORMAT_UNKNOWN) { GST_DEBUG_OBJECT (comp_pad, "Have no caps yet"); *width = 0; *height = 0; return; } if (comp->zero_size_is_unscaled) { pad_width = comp_pad->width <= 0 ? GST_VIDEO_INFO_WIDTH (&vagg_pad->info) : comp_pad->width; pad_height = comp_pad->height <= 0 ? GST_VIDEO_INFO_HEIGHT (&vagg_pad->info) : comp_pad->height; } else { pad_width = comp_pad->width < 0 ? GST_VIDEO_INFO_WIDTH (&vagg_pad->info) : comp_pad->width; pad_height = comp_pad->height < 0 ? GST_VIDEO_INFO_HEIGHT (&vagg_pad->info) : comp_pad->height; } if (pad_width == 0 || pad_height == 0) { *width = 0; *height = 0; return; } if (!gst_video_calculate_display_ratio (&dar_n, &dar_d, pad_width, pad_height, GST_VIDEO_INFO_PAR_N (&vagg_pad->info), GST_VIDEO_INFO_PAR_D (&vagg_pad->info), out_par_n, out_par_d)) { GST_WARNING_OBJECT (comp_pad, "Cannot calculate display aspect ratio"); *width = *height = 0; return; } GST_LOG_OBJECT (comp_pad, "scaling %ux%u by %u/%u (%u/%u / %u/%u)", pad_width, pad_height, dar_n, dar_d, GST_VIDEO_INFO_PAR_N (&vagg_pad->info), GST_VIDEO_INFO_PAR_D (&vagg_pad->info), out_par_n, out_par_d); /* Pick either height or width, whichever is an integer multiple of the * display aspect ratio. However, prefer preserving the height to account * for interlaced video. */ if (pad_height % dar_n == 0) { pad_width = gst_util_uint64_scale_int (pad_height, dar_n, dar_d); } else if (pad_width % dar_d == 0) { pad_height = gst_util_uint64_scale_int (pad_width, dar_d, dar_n); } else { pad_width = gst_util_uint64_scale_int (pad_height, dar_n, dar_d); } *width = pad_width; *height = pad_height; } /* Test whether rectangle2 contains rectangle 1 (geometrically) */ static gboolean is_rectangle_contained (const GstVideoRectangle rect1, const GstVideoRectangle rect2) { if ((rect2.x <= rect1.x) && (rect2.y <= rect1.y) && ((rect2.x + rect2.w) >= (rect1.x + rect1.w)) && ((rect2.y + rect2.h) >= (rect1.y + rect1.h))) return TRUE; return FALSE; } static GstVideoRectangle clamp_rectangle (gint x, gint y, gint w, gint h, gint outer_width, gint outer_height) { gint x2 = x + w; gint y2 = y + h; GstVideoRectangle clamped; /* Clamp the x/y coordinates of this frame to the output boundaries to cover * the case where (say, with negative xpos/ypos or w/h greater than the output * size) the non-obscured portion of the frame could be outside the bounds of * the video itself and hence not visible at all */ clamped.x = CLAMP (x, 0, outer_width); clamped.y = CLAMP (y, 0, outer_height); clamped.w = CLAMP (x2, 0, outer_width) - clamped.x; clamped.h = CLAMP (y2, 0, outer_height) - clamped.y; return clamped; } /* Call this with the lock taken */ static gboolean _pad_obscures_rectangle (GstVideoAggregator * vagg, GstVideoAggregatorPad * pad, const GstVideoRectangle rect) { GstVideoRectangle pad_rect; GstCompositorPad *cpad = GST_COMPOSITOR_PAD (pad); /* No buffer to obscure the rectangle with */ if (!gst_video_aggregator_pad_has_current_buffer (pad)) return FALSE; /* Can't obscure if we introduce alpha or if the format has an alpha * component as we'd have to inspect every pixel to know if the frame is * opaque, so assume it doesn't obscure */ if (cpad->alpha != 1.0 || GST_VIDEO_INFO_HAS_ALPHA (&pad->info)) return FALSE; pad_rect.x = cpad->xpos; pad_rect.y = cpad->ypos; /* Handle pixel and display aspect ratios to find the actual size */ _mixer_pad_get_output_size (GST_COMPOSITOR (vagg), cpad, GST_VIDEO_INFO_PAR_N (&vagg->info), GST_VIDEO_INFO_PAR_D (&vagg->info), &(pad_rect.w), &(pad_rect.h)); if (!is_rectangle_contained (rect, pad_rect)) return FALSE; GST_DEBUG_OBJECT (pad, "Pad %s %ix%i@(%i,%i) obscures rect %ix%i@(%i,%i)", GST_PAD_NAME (pad), pad_rect.w, pad_rect.h, pad_rect.x, pad_rect.y, rect.w, rect.h, rect.x, rect.y); return TRUE; } static gboolean gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad, GstVideoAggregator * vagg, GstBuffer * buffer, GstVideoFrame * prepared_frame) { GstCompositorPad *cpad = GST_COMPOSITOR_PAD (pad); gint width, height; gboolean frame_obscured = FALSE; GList *l; /* The rectangle representing this frame, clamped to the video's boundaries. * Due to the clamping, this is different from the frame width/height above. */ GstVideoRectangle frame_rect; /* There's three types of width/height here: * 1. GST_VIDEO_FRAME_WIDTH/HEIGHT: * The frame width/height (same as pad->info.height/width; * see gst_video_frame_map()) * 2. cpad->width/height: * The optional pad property for scaling the frame (if zero, the video is * left unscaled) * 3. conversion_info.width/height: * Equal to cpad->width/height if it's set, otherwise it's the pad * width/height. See ->set_info() * */ _mixer_pad_get_output_size (GST_COMPOSITOR (vagg), cpad, GST_VIDEO_INFO_PAR_N (&vagg->info), GST_VIDEO_INFO_PAR_D (&vagg->info), &width, &height); if (cpad->alpha == 0.0) { GST_DEBUG_OBJECT (pad, "Pad has alpha 0.0, not converting frame"); goto done; } frame_rect = clamp_rectangle (cpad->xpos, cpad->ypos, width, height, GST_VIDEO_INFO_WIDTH (&vagg->info), GST_VIDEO_INFO_HEIGHT (&vagg->info)); if (frame_rect.w == 0 || frame_rect.h == 0) { GST_DEBUG_OBJECT (pad, "Resulting frame is zero-width or zero-height " "(w: %i, h: %i), skipping", frame_rect.w, frame_rect.h); goto done; } GST_OBJECT_LOCK (vagg); /* Check if this frame is obscured by a higher-zorder frame * TODO: Also skip a frame if it's obscured by a combination of * higher-zorder frames */ l = g_list_find (GST_ELEMENT (vagg)->sinkpads, pad); /* The pad might've just been removed */ if (l) l = l->next; for (; l; l = l->next) { if (_pad_obscures_rectangle (vagg, l->data, frame_rect)) { frame_obscured = TRUE; break; } } GST_OBJECT_UNLOCK (vagg); if (frame_obscured) goto done; return GST_VIDEO_AGGREGATOR_PAD_CLASS (gst_compositor_pad_parent_class)->prepare_frame (pad, vagg, buffer, prepared_frame); done: return TRUE; } static void gst_compositor_pad_create_conversion_info (GstVideoAggregatorConvertPad * pad, GstVideoAggregator * vagg, GstVideoInfo * conversion_info) { GstCompositorPad *cpad = GST_COMPOSITOR_PAD (pad); gint width, height; GST_VIDEO_AGGREGATOR_CONVERT_PAD_CLASS (gst_compositor_pad_parent_class)->create_conversion_info (pad, vagg, conversion_info); if (!conversion_info->finfo) return; _mixer_pad_get_output_size (GST_COMPOSITOR (vagg), cpad, GST_VIDEO_INFO_PAR_N (&vagg->info), GST_VIDEO_INFO_PAR_D (&vagg->info), &width, &height); /* The only thing that can change here is the width * and height, otherwise set_info would've been called */ if (GST_VIDEO_INFO_WIDTH (conversion_info) != width || GST_VIDEO_INFO_HEIGHT (conversion_info) != height) { GstVideoInfo tmp_info; /* Initialize with the wanted video format and our original width and * height as we don't want to rescale. Then copy over the wanted * colorimetry, and chroma-site and our current pixel-aspect-ratio * and other relevant fields. */ gst_video_info_set_format (&tmp_info, GST_VIDEO_INFO_FORMAT (conversion_info), width, height); tmp_info.chroma_site = conversion_info->chroma_site; tmp_info.colorimetry = conversion_info->colorimetry; tmp_info.par_n = conversion_info->par_n; tmp_info.par_d = conversion_info->par_d; tmp_info.fps_n = conversion_info->fps_n; tmp_info.fps_d = conversion_info->fps_d; tmp_info.flags = conversion_info->flags; tmp_info.interlace_mode = conversion_info->interlace_mode; *conversion_info = tmp_info; } } static void gst_compositor_pad_class_init (GstCompositorPadClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstVideoAggregatorPadClass *vaggpadclass = (GstVideoAggregatorPadClass *) klass; GstVideoAggregatorConvertPadClass *vaggcpadclass = (GstVideoAggregatorConvertPadClass *) klass; gobject_class->set_property = gst_compositor_pad_set_property; gobject_class->get_property = gst_compositor_pad_get_property; g_object_class_install_property (gobject_class, PROP_PAD_XPOS, g_param_spec_int ("xpos", "X Position", "X Position of the picture", G_MININT, G_MAXINT, DEFAULT_PAD_XPOS, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PAD_YPOS, g_param_spec_int ("ypos", "Y Position", "Y Position of the picture", G_MININT, G_MAXINT, DEFAULT_PAD_YPOS, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PAD_WIDTH, g_param_spec_int ("width", "Width", "Width of the picture", G_MININT, G_MAXINT, DEFAULT_PAD_WIDTH, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PAD_HEIGHT, g_param_spec_int ("height", "Height", "Height of the picture", G_MININT, G_MAXINT, DEFAULT_PAD_HEIGHT, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PAD_ALPHA, g_param_spec_double ("alpha", "Alpha", "Alpha of the picture", 0.0, 1.0, DEFAULT_PAD_ALPHA, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PAD_OPERATOR, g_param_spec_enum ("operator", "Operator", "Blending operator to use for blending this pad over the previous ones", GST_TYPE_COMPOSITOR_OPERATOR, DEFAULT_PAD_OPERATOR, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); vaggpadclass->prepare_frame = GST_DEBUG_FUNCPTR (gst_compositor_pad_prepare_frame); vaggcpadclass->create_conversion_info = GST_DEBUG_FUNCPTR (gst_compositor_pad_create_conversion_info); } static void gst_compositor_pad_init (GstCompositorPad * compo_pad) { compo_pad->xpos = DEFAULT_PAD_XPOS; compo_pad->ypos = DEFAULT_PAD_YPOS; compo_pad->alpha = DEFAULT_PAD_ALPHA; compo_pad->op = DEFAULT_PAD_OPERATOR; compo_pad->width = DEFAULT_PAD_WIDTH; compo_pad->height = DEFAULT_PAD_HEIGHT; } /* GstCompositor */ #define DEFAULT_BACKGROUND COMPOSITOR_BACKGROUND_CHECKER #define DEFAULT_ZERO_SIZE_IS_UNSCALED TRUE enum { PROP_0, PROP_BACKGROUND, PROP_ZERO_SIZE_IS_UNSCALED, }; static void gst_compositor_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstCompositor *self = GST_COMPOSITOR (object); switch (prop_id) { case PROP_BACKGROUND: g_value_set_enum (value, self->background); break; case PROP_ZERO_SIZE_IS_UNSCALED: g_value_set_boolean (value, self->zero_size_is_unscaled); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_compositor_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstCompositor *self = GST_COMPOSITOR (object); switch (prop_id) { case PROP_BACKGROUND: self->background = g_value_get_enum (value); break; case PROP_ZERO_SIZE_IS_UNSCALED: self->zero_size_is_unscaled = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } #define gst_compositor_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstCompositor, gst_compositor, GST_TYPE_VIDEO_AGGREGATOR, G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY, gst_compositor_child_proxy_init)); GST_ELEMENT_REGISTER_DEFINE (compositor, "compositor", GST_RANK_PRIMARY + 1, GST_TYPE_COMPOSITOR); static gboolean set_functions (GstCompositor * self, const GstVideoInfo * info) { gboolean ret = FALSE; self->blend = NULL; self->overlay = NULL; self->fill_checker = NULL; self->fill_color = NULL; switch (GST_VIDEO_INFO_FORMAT (info)) { case GST_VIDEO_FORMAT_AYUV: self->blend = gst_compositor_blend_ayuv; self->overlay = gst_compositor_overlay_ayuv; self->fill_checker = gst_compositor_fill_checker_ayuv; self->fill_color = gst_compositor_fill_color_ayuv; ret = TRUE; break; case GST_VIDEO_FORMAT_VUYA: self->blend = gst_compositor_blend_vuya; self->overlay = gst_compositor_overlay_vuya; self->fill_checker = gst_compositor_fill_checker_vuya; self->fill_color = gst_compositor_fill_color_vuya; ret = TRUE; break; case GST_VIDEO_FORMAT_ARGB: self->blend = gst_compositor_blend_argb; self->overlay = gst_compositor_overlay_argb; self->fill_checker = gst_compositor_fill_checker_argb; self->fill_color = gst_compositor_fill_color_argb; ret = TRUE; break; case GST_VIDEO_FORMAT_BGRA: self->blend = gst_compositor_blend_bgra; self->overlay = gst_compositor_overlay_bgra; self->fill_checker = gst_compositor_fill_checker_bgra; self->fill_color = gst_compositor_fill_color_bgra; ret = TRUE; break; case GST_VIDEO_FORMAT_ABGR: self->blend = gst_compositor_blend_abgr; self->overlay = gst_compositor_overlay_abgr; self->fill_checker = gst_compositor_fill_checker_abgr; self->fill_color = gst_compositor_fill_color_abgr; ret = TRUE; break; case GST_VIDEO_FORMAT_RGBA: self->blend = gst_compositor_blend_rgba; self->overlay = gst_compositor_overlay_rgba; self->fill_checker = gst_compositor_fill_checker_rgba; self->fill_color = gst_compositor_fill_color_rgba; ret = TRUE; break; case GST_VIDEO_FORMAT_Y444: self->blend = gst_compositor_blend_y444; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_y444; self->fill_color = gst_compositor_fill_color_y444; ret = TRUE; break; case GST_VIDEO_FORMAT_Y42B: self->blend = gst_compositor_blend_y42b; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_y42b; self->fill_color = gst_compositor_fill_color_y42b; ret = TRUE; break; case GST_VIDEO_FORMAT_YUY2: self->blend = gst_compositor_blend_yuy2; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_yuy2; self->fill_color = gst_compositor_fill_color_yuy2; ret = TRUE; break; case GST_VIDEO_FORMAT_UYVY: self->blend = gst_compositor_blend_uyvy; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_uyvy; self->fill_color = gst_compositor_fill_color_uyvy; ret = TRUE; break; case GST_VIDEO_FORMAT_YVYU: self->blend = gst_compositor_blend_yvyu; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_yvyu; self->fill_color = gst_compositor_fill_color_yvyu; ret = TRUE; break; case GST_VIDEO_FORMAT_I420: self->blend = gst_compositor_blend_i420; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_i420; self->fill_color = gst_compositor_fill_color_i420; ret = TRUE; break; case GST_VIDEO_FORMAT_YV12: self->blend = gst_compositor_blend_yv12; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_yv12; self->fill_color = gst_compositor_fill_color_yv12; ret = TRUE; break; case GST_VIDEO_FORMAT_NV12: self->blend = gst_compositor_blend_nv12; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_nv12; self->fill_color = gst_compositor_fill_color_nv12; ret = TRUE; break; case GST_VIDEO_FORMAT_NV21: self->blend = gst_compositor_blend_nv21; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_nv21; self->fill_color = gst_compositor_fill_color_nv21; ret = TRUE; break; case GST_VIDEO_FORMAT_Y41B: self->blend = gst_compositor_blend_y41b; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_y41b; self->fill_color = gst_compositor_fill_color_y41b; ret = TRUE; break; case GST_VIDEO_FORMAT_RGB: self->blend = gst_compositor_blend_rgb; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_rgb; self->fill_color = gst_compositor_fill_color_rgb; ret = TRUE; break; case GST_VIDEO_FORMAT_BGR: self->blend = gst_compositor_blend_bgr; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_bgr; self->fill_color = gst_compositor_fill_color_bgr; ret = TRUE; break; case GST_VIDEO_FORMAT_xRGB: self->blend = gst_compositor_blend_xrgb; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_xrgb; self->fill_color = gst_compositor_fill_color_xrgb; ret = TRUE; break; case GST_VIDEO_FORMAT_xBGR: self->blend = gst_compositor_blend_xbgr; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_xbgr; self->fill_color = gst_compositor_fill_color_xbgr; ret = TRUE; break; case GST_VIDEO_FORMAT_RGBx: self->blend = gst_compositor_blend_rgbx; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_rgbx; self->fill_color = gst_compositor_fill_color_rgbx; ret = TRUE; break; case GST_VIDEO_FORMAT_BGRx: self->blend = gst_compositor_blend_bgrx; self->overlay = self->blend; self->fill_checker = gst_compositor_fill_checker_bgrx; self->fill_color = gst_compositor_fill_color_bgrx; ret = TRUE; break; default: break; } return ret; } static GstCaps * _fixate_caps (GstAggregator * agg, GstCaps * caps) { GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (agg); GList *l; gint best_width = -1, best_height = -1; gint best_fps_n = -1, best_fps_d = -1; gint par_n, par_d; gdouble best_fps = 0.; GstCaps *ret = NULL; GstStructure *s; ret = gst_caps_make_writable (caps); /* we need this to calculate how large to make the output frame */ s = gst_caps_get_structure (ret, 0); if (gst_structure_has_field (s, "pixel-aspect-ratio")) { gst_structure_fixate_field_nearest_fraction (s, "pixel-aspect-ratio", 1, 1); gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d); } else { par_n = par_d = 1; } GST_OBJECT_LOCK (vagg); for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) { GstVideoAggregatorPad *vaggpad = l->data; GstCompositorPad *compositor_pad = GST_COMPOSITOR_PAD (vaggpad); gint this_width, this_height; gint width, height; gint fps_n, fps_d; gdouble cur_fps; fps_n = GST_VIDEO_INFO_FPS_N (&vaggpad->info); fps_d = GST_VIDEO_INFO_FPS_D (&vaggpad->info); _mixer_pad_get_output_size (GST_COMPOSITOR (vagg), compositor_pad, par_n, par_d, &width, &height); if (width == 0 || height == 0) continue; this_width = width + MAX (compositor_pad->xpos, 0); this_height = height + MAX (compositor_pad->ypos, 0); if (best_width < this_width) best_width = this_width; if (best_height < this_height) best_height = this_height; if (fps_d == 0) cur_fps = 0.0; else gst_util_fraction_to_double (fps_n, fps_d, &cur_fps); if (best_fps < cur_fps) { best_fps = cur_fps; best_fps_n = fps_n; best_fps_d = fps_d; } } GST_OBJECT_UNLOCK (vagg); if (best_fps_n <= 0 || best_fps_d <= 0 || best_fps == 0.0) { best_fps_n = 25; best_fps_d = 1; best_fps = 25.0; } gst_structure_fixate_field_nearest_int (s, "width", best_width); gst_structure_fixate_field_nearest_int (s, "height", best_height); gst_structure_fixate_field_nearest_fraction (s, "framerate", best_fps_n, best_fps_d); ret = gst_caps_fixate (ret); return ret; } static gpointer gst_parallelized_task_thread_func (gpointer data) { GstParallelizedTaskThread *self = data; g_mutex_lock (&self->runner->lock); self->runner->n_done++; if (self->runner->n_done == self->runner->n_threads - 1) g_cond_signal (&self->runner->cond_done); do { gint idx; while (self->runner->n_todo == -1 && !self->runner->quit) g_cond_wait (&self->runner->cond_todo, &self->runner->lock); if (self->runner->quit) break; idx = self->runner->n_todo--; g_assert (self->runner->n_todo >= -1); g_mutex_unlock (&self->runner->lock); g_assert (self->runner->func != NULL); self->runner->func (self->runner->task_data[idx]); g_mutex_lock (&self->runner->lock); self->runner->n_done++; if (self->runner->n_done == self->runner->n_threads - 1) g_cond_signal (&self->runner->cond_done); } while (TRUE); g_mutex_unlock (&self->runner->lock); return NULL; } static void gst_parallelized_task_runner_free (GstParallelizedTaskRunner * self) { guint i; g_mutex_lock (&self->lock); self->quit = TRUE; g_cond_broadcast (&self->cond_todo); g_mutex_unlock (&self->lock); for (i = 1; i < self->n_threads; i++) { if (!self->threads[i].thread) continue; g_thread_join (self->threads[i].thread); } g_mutex_clear (&self->lock); g_cond_clear (&self->cond_todo); g_cond_clear (&self->cond_done); g_free (self->threads); g_free (self); } static GstParallelizedTaskRunner * gst_parallelized_task_runner_new (guint n_threads) { GstParallelizedTaskRunner *self; guint i; GError *err = NULL; if (n_threads == 0) n_threads = g_get_num_processors (); self = g_new0 (GstParallelizedTaskRunner, 1); self->n_threads = n_threads; self->threads = g_new0 (GstParallelizedTaskThread, n_threads); self->quit = FALSE; self->n_todo = -1; self->n_done = 0; g_mutex_init (&self->lock); g_cond_init (&self->cond_todo); g_cond_init (&self->cond_done); /* Set when scheduling a job */ self->func = NULL; self->task_data = NULL; for (i = 0; i < n_threads; i++) { self->threads[i].runner = self; self->threads[i].idx = i; /* First thread is the one calling run() */ if (i > 0) { self->threads[i].thread = g_thread_try_new ("compositor-blend", gst_parallelized_task_thread_func, &self->threads[i], &err); if (!self->threads[i].thread) goto error; } } g_mutex_lock (&self->lock); while (self->n_done < self->n_threads - 1) g_cond_wait (&self->cond_done, &self->lock); self->n_done = 0; g_mutex_unlock (&self->lock); return self; error: { GST_ERROR ("Failed to start thread %u: %s", i, err->message); g_clear_error (&err); gst_parallelized_task_runner_free (self); return NULL; } } static void gst_parallelized_task_runner_run (GstParallelizedTaskRunner * self, GstParallelizedTaskFunc func, gpointer * task_data) { guint n_threads = self->n_threads; self->func = func; self->task_data = task_data; if (n_threads > 1) { g_mutex_lock (&self->lock); self->n_todo = self->n_threads - 2; self->n_done = 0; g_cond_broadcast (&self->cond_todo); g_mutex_unlock (&self->lock); } self->func (self->task_data[self->n_threads - 1]); if (n_threads > 1) { g_mutex_lock (&self->lock); while (self->n_done < self->n_threads - 1) g_cond_wait (&self->cond_done, &self->lock); self->n_done = 0; g_mutex_unlock (&self->lock); } self->func = NULL; self->task_data = NULL; } static gboolean _negotiated_caps (GstAggregator * agg, GstCaps * caps) { GstCompositor *compositor = GST_COMPOSITOR (agg); GstVideoInfo v_info; guint n_threads; GST_DEBUG_OBJECT (agg, "Negotiated caps %" GST_PTR_FORMAT, caps); if (!gst_video_info_from_caps (&v_info, caps)) return FALSE; if (!set_functions (compositor, &v_info)) { GST_ERROR_OBJECT (agg, "Failed to setup vfuncs"); return FALSE; } n_threads = g_get_num_processors (); /* Magic number of 200 lines */ if (GST_VIDEO_INFO_HEIGHT (&v_info) / n_threads < 200) n_threads = (GST_VIDEO_INFO_HEIGHT (&v_info) + 199) / 200; if (n_threads < 1) n_threads = 1; /* XXX: implement better thread count change */ if (compositor->blend_runner && compositor->blend_runner->n_threads != n_threads) { gst_parallelized_task_runner_free (compositor->blend_runner); compositor->blend_runner = NULL; } if (!compositor->blend_runner) compositor->blend_runner = gst_parallelized_task_runner_new (n_threads); return GST_AGGREGATOR_CLASS (parent_class)->negotiated_src_caps (agg, caps); } static gboolean _should_draw_background (GstVideoAggregator * vagg) { GstVideoRectangle bg_rect; gboolean draw = TRUE; GList *l; bg_rect.x = bg_rect.y = 0; GST_OBJECT_LOCK (vagg); bg_rect.w = GST_VIDEO_INFO_WIDTH (&vagg->info); bg_rect.h = GST_VIDEO_INFO_HEIGHT (&vagg->info); /* Check if the background is completely obscured by a pad * TODO: Also skip if it's obscured by a combination of pads */ for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) { if (_pad_obscures_rectangle (vagg, l->data, bg_rect)) { draw = FALSE; break; } } GST_OBJECT_UNLOCK (vagg); return draw; } static gboolean frames_can_copy (const GstVideoFrame * frame1, const GstVideoFrame * frame2) { if (GST_VIDEO_FRAME_FORMAT (frame1) != GST_VIDEO_FRAME_FORMAT (frame2)) return FALSE; if (GST_VIDEO_FRAME_HEIGHT (frame1) != GST_VIDEO_FRAME_HEIGHT (frame2)) return FALSE; if (GST_VIDEO_FRAME_WIDTH (frame1) != GST_VIDEO_FRAME_WIDTH (frame2)) return FALSE; return TRUE; } struct CompositePadInfo { GstVideoFrame *prepared_frame; GstCompositorPad *pad; GstCompositorBlendMode blend_mode; }; struct CompositeTask { GstCompositor *compositor; GstVideoFrame *out_frame; guint dst_line_start; guint dst_line_end; gboolean draw_background; guint n_pads; struct CompositePadInfo *pads_info; }; static void _draw_background (GstCompositor * comp, GstVideoFrame * outframe, guint y_start, guint y_end, BlendFunction * composite) { *composite = comp->blend; switch (comp->background) { case COMPOSITOR_BACKGROUND_CHECKER: comp->fill_checker (outframe, y_start, y_end); break; case COMPOSITOR_BACKGROUND_BLACK: comp->fill_color (outframe, y_start, y_end, 16, 128, 128); break; case COMPOSITOR_BACKGROUND_WHITE: comp->fill_color (outframe, y_start, y_end, 240, 128, 128); break; case COMPOSITOR_BACKGROUND_TRANSPARENT: { guint i, plane, num_planes, height; num_planes = GST_VIDEO_FRAME_N_PLANES (outframe); for (plane = 0; plane < num_planes; ++plane) { const GstVideoFormatInfo *info; guint8 *pdata; gsize rowsize, plane_stride; gint yoffset; info = outframe->info.finfo; pdata = GST_VIDEO_FRAME_PLANE_DATA (outframe, plane); plane_stride = GST_VIDEO_FRAME_PLANE_STRIDE (outframe, plane); rowsize = GST_VIDEO_FRAME_COMP_WIDTH (outframe, plane) * GST_VIDEO_FRAME_COMP_PSTRIDE (outframe, plane); height = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info, plane, (y_end - y_start)); yoffset = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info, plane, y_start); pdata += yoffset * plane_stride; for (i = 0; i < height; ++i) { memset (pdata, 0, rowsize); pdata += plane_stride; } } /* use overlay to keep background transparent */ *composite = comp->overlay; break; } } } static void blend_pads (struct CompositeTask *comp) { BlendFunction composite; guint i; composite = comp->compositor->blend; if (comp->draw_background) { _draw_background (comp->compositor, comp->out_frame, comp->dst_line_start, comp->dst_line_end, &composite); } for (i = 0; i < comp->n_pads; i++) { composite (comp->pads_info[i].prepared_frame, comp->pads_info[i].pad->xpos, comp->pads_info[i].pad->ypos, comp->pads_info[i].pad->alpha, comp->out_frame, comp->dst_line_start, comp->dst_line_end, comp->pads_info[i].blend_mode); } } static GstFlowReturn gst_compositor_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf) { GstCompositor *compositor = GST_COMPOSITOR (vagg); GList *l; GstVideoFrame out_frame, *outframe; gboolean draw_background; guint drawn_a_pad = FALSE; struct CompositePadInfo *pads_info; guint i, n_pads = 0; if (!gst_video_frame_map (&out_frame, &vagg->info, outbuf, GST_MAP_WRITE)) { GST_WARNING_OBJECT (vagg, "Could not map output buffer"); return GST_FLOW_ERROR; } outframe = &out_frame; /* If one of the frames to be composited completely obscures the background, * don't bother drawing the background at all. We can also always use the * 'blend' BlendFunction in that case because it only changes if we have to * overlay on top of a transparent background. */ draw_background = _should_draw_background (vagg); GST_OBJECT_LOCK (vagg); for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) { GstVideoAggregatorPad *pad = l->data; GstVideoFrame *prepared_frame = gst_video_aggregator_pad_get_prepared_frame (pad); if (prepared_frame) n_pads++; } pads_info = g_newa (struct CompositePadInfo, n_pads); n_pads = 0; for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) { GstVideoAggregatorPad *pad = l->data; GstCompositorPad *compo_pad = GST_COMPOSITOR_PAD (pad); GstVideoFrame *prepared_frame = gst_video_aggregator_pad_get_prepared_frame (pad); GstCompositorBlendMode blend_mode = COMPOSITOR_BLEND_MODE_OVER; switch (compo_pad->op) { case COMPOSITOR_OPERATOR_SOURCE: blend_mode = COMPOSITOR_BLEND_MODE_SOURCE; break; case COMPOSITOR_OPERATOR_OVER: blend_mode = COMPOSITOR_BLEND_MODE_OVER; break; case COMPOSITOR_OPERATOR_ADD: blend_mode = COMPOSITOR_BLEND_MODE_ADD; break; default: g_assert_not_reached (); break; } if (prepared_frame != NULL) { /* If this is the first pad we're drawing, and we didn't draw the * background, and @prepared_frame has the same format, height, and width * as @outframe, then we can just copy it as-is. Subsequent pads (if any) * will be composited on top of it. */ if (!drawn_a_pad && !draw_background && frames_can_copy (prepared_frame, outframe)) { gst_video_frame_copy (outframe, prepared_frame); } else { pads_info[n_pads].pad = compo_pad; pads_info[n_pads].prepared_frame = prepared_frame; pads_info[n_pads].blend_mode = blend_mode; n_pads++; } drawn_a_pad = TRUE; } } { guint n_threads, lines_per_thread; guint out_height; struct CompositeTask *tasks; struct CompositeTask **tasks_p; n_threads = compositor->blend_runner->n_threads; tasks = g_newa (struct CompositeTask, n_threads); tasks_p = g_newa (struct CompositeTask *, n_threads); out_height = GST_VIDEO_FRAME_HEIGHT (outframe); lines_per_thread = (out_height + n_threads - 1) / n_threads; for (i = 0; i < n_threads; i++) { tasks[i].compositor = compositor; tasks[i].n_pads = n_pads; tasks[i].pads_info = pads_info; tasks[i].out_frame = outframe; tasks[i].draw_background = draw_background; /* This is a dumb split of the work by number of output lines. * If there is a section of the output that reads from a lot of source * pads, then that thread will consume more time. Maybe tracking and * splitting on the source fill rate would produce better results. */ tasks[i].dst_line_start = i * lines_per_thread; tasks[i].dst_line_end = MIN ((i + 1) * lines_per_thread, out_height); tasks_p[i] = &tasks[i]; } gst_parallelized_task_runner_run (compositor->blend_runner, (GstParallelizedTaskFunc) blend_pads, (gpointer *) tasks_p); } GST_OBJECT_UNLOCK (vagg); gst_video_frame_unmap (outframe); return GST_FLOW_OK; } static GstPad * gst_compositor_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps) { GstPad *newpad; newpad = (GstPad *) GST_ELEMENT_CLASS (parent_class)->request_new_pad (element, templ, req_name, caps); if (newpad == NULL) goto could_not_create; gst_child_proxy_child_added (GST_CHILD_PROXY (element), G_OBJECT (newpad), GST_OBJECT_NAME (newpad)); return newpad; could_not_create: { GST_DEBUG_OBJECT (element, "could not create/add pad"); return NULL; } } static void gst_compositor_release_pad (GstElement * element, GstPad * pad) { GstCompositor *compositor; compositor = GST_COMPOSITOR (element); GST_DEBUG_OBJECT (compositor, "release pad %s:%s", GST_DEBUG_PAD_NAME (pad)); gst_child_proxy_child_removed (GST_CHILD_PROXY (compositor), G_OBJECT (pad), GST_OBJECT_NAME (pad)); GST_ELEMENT_CLASS (parent_class)->release_pad (element, pad); } static gboolean _sink_query (GstAggregator * agg, GstAggregatorPad * bpad, GstQuery * query) { switch (GST_QUERY_TYPE (query)) { case GST_QUERY_ALLOCATION:{ GstCaps *caps; GstVideoInfo info; GstBufferPool *pool; guint size; GstStructure *structure; gst_query_parse_allocation (query, &caps, NULL); if (caps == NULL) return FALSE; if (!gst_video_info_from_caps (&info, caps)) return FALSE; size = GST_VIDEO_INFO_SIZE (&info); pool = gst_video_buffer_pool_new (); structure = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_set_params (structure, caps, size, 0, 0); if (!gst_buffer_pool_set_config (pool, structure)) { gst_object_unref (pool); return FALSE; } gst_query_add_allocation_pool (query, pool, size, 0, 0); gst_object_unref (pool); gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); return TRUE; } default: return GST_AGGREGATOR_CLASS (parent_class)->sink_query (agg, bpad, query); } } static void gst_compositor_finalize (GObject * object) { GstCompositor *compositor = GST_COMPOSITOR (object); if (compositor->blend_runner) gst_parallelized_task_runner_free (compositor->blend_runner); compositor->blend_runner = NULL; G_OBJECT_CLASS (parent_class)->finalize (object); } /* GObject boilerplate */ static void gst_compositor_class_init (GstCompositorClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstElementClass *gstelement_class = (GstElementClass *) klass; GstVideoAggregatorClass *videoaggregator_class = (GstVideoAggregatorClass *) klass; GstAggregatorClass *agg_class = (GstAggregatorClass *) klass; gobject_class->get_property = gst_compositor_get_property; gobject_class->set_property = gst_compositor_set_property; gobject_class->finalize = gst_compositor_finalize; gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_compositor_request_new_pad); gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_compositor_release_pad); agg_class->sink_query = _sink_query; agg_class->fixate_src_caps = _fixate_caps; agg_class->negotiated_src_caps = _negotiated_caps; videoaggregator_class->aggregate_frames = gst_compositor_aggregate_frames; g_object_class_install_property (gobject_class, PROP_BACKGROUND, g_param_spec_enum ("background", "Background", "Background type", GST_TYPE_COMPOSITOR_BACKGROUND, DEFAULT_BACKGROUND, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * compositor:zero-size-is-unscaled: * * Whether a pad with height or width 0 should be left unscaled * in that dimension, or simply not composited in. Setting it to * %FALSE might be useful when animating those properties. * * Since: 1.20 */ g_object_class_install_property (gobject_class, PROP_ZERO_SIZE_IS_UNSCALED, g_param_spec_boolean ("zero-size-is-unscaled", "Zero size is unscaled", "If TRUE, then input video is unscaled in that dimension " "if width or height is 0 (for backwards compatibility)", DEFAULT_ZERO_SIZE_IS_UNSCALED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gst_element_class_add_static_pad_template_with_gtype (gstelement_class, &src_factory, GST_TYPE_AGGREGATOR_PAD); gst_element_class_add_static_pad_template_with_gtype (gstelement_class, &sink_factory, GST_TYPE_COMPOSITOR_PAD); gst_element_class_set_static_metadata (gstelement_class, "Compositor", "Filter/Editor/Video/Compositor", "Composite multiple video streams", "Wim Taymans , " "Sebastian Dröge "); gst_type_mark_as_plugin_api (GST_TYPE_COMPOSITOR_PAD, 0); gst_type_mark_as_plugin_api (GST_TYPE_COMPOSITOR_OPERATOR, 0); gst_type_mark_as_plugin_api (GST_TYPE_COMPOSITOR_BACKGROUND, 0); } static void gst_compositor_init (GstCompositor * self) { /* initialize variables */ self->background = DEFAULT_BACKGROUND; self->zero_size_is_unscaled = DEFAULT_ZERO_SIZE_IS_UNSCALED; } /* GstChildProxy implementation */ static GObject * gst_compositor_child_proxy_get_child_by_index (GstChildProxy * child_proxy, guint index) { GstCompositor *compositor = GST_COMPOSITOR (child_proxy); GObject *obj = NULL; GST_OBJECT_LOCK (compositor); obj = g_list_nth_data (GST_ELEMENT_CAST (compositor)->sinkpads, index); if (obj) gst_object_ref (obj); GST_OBJECT_UNLOCK (compositor); return obj; } static guint gst_compositor_child_proxy_get_children_count (GstChildProxy * child_proxy) { guint count = 0; GstCompositor *compositor = GST_COMPOSITOR (child_proxy); GST_OBJECT_LOCK (compositor); count = GST_ELEMENT_CAST (compositor)->numsinkpads; GST_OBJECT_UNLOCK (compositor); GST_INFO_OBJECT (compositor, "Children Count: %d", count); return count; } static void gst_compositor_child_proxy_init (gpointer g_iface, gpointer iface_data) { GstChildProxyInterface *iface = g_iface; iface->get_child_by_index = gst_compositor_child_proxy_get_child_by_index; iface->get_children_count = gst_compositor_child_proxy_get_children_count; } /* Element registration */ static gboolean plugin_init (GstPlugin * plugin) { GST_DEBUG_CATEGORY_INIT (gst_compositor_debug, "compositor", 0, "compositor"); gst_compositor_init_blend (); return GST_ELEMENT_REGISTER (compositor, plugin); } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, compositor, "Compositor", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)