gstreamer/gst/compositor/compositor.c
Matthew Waters d7abf832af compositor: blend with multiple threads
Increases the throughput of compositing by using more CPU cycles across
multiple threads.  Simple cases (the output contains one pixel from at
most one input) can have up to a 70% increase in throughput.  Not so
simple cases are limited by the region with the most number of
composite operations.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/755>
2020-09-14 14:00:58 +10:00

1456 lines
46 KiB
C

/* Video compositor plugin
* Copyright (C) 2004, 2008 Wim Taymans <wim@fluendo.com>
* Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
* Copyright (C) 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>
* Copyright (C) 2014 Thibault Saunier <tsaunier@gnome.org>
*
* 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 <string.h>
#include "compositor.h"
#ifdef DISABLE_ORC
#define orc_memset memset
#else
#include <orc/orcfunctions.h>
#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 (FORMATS))
);
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 0
#define DEFAULT_PAD_HEIGHT 0
#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 (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;
}
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 (!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 (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 (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)->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 (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;
}
/* GstCompositor */
#define DEFAULT_BACKGROUND COMPOSITOR_BACKGROUND_CHECKER
enum
{
PROP_0,
PROP_BACKGROUND,
};
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;
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;
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));
static gboolean
set_functions (GstCompositor * self, 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 (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;
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);
pdata += y_start * 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));
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 <wim@fluendo.com>, "
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
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;
}
/* 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 (plugin, "compositor", GST_RANK_PRIMARY + 1,
GST_TYPE_COMPOSITOR);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
compositor,
"Compositor", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
GST_PACKAGE_ORIGIN)