| /* 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 |
| * |
| * Compositor can accept AYUV, 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: |
| * |
| * <itemizedlist> |
| * <listitem> |
| * "xpos": The x-coordinate position of the top-left corner of the picture |
| * (#gint) |
| * </listitem> |
| * <listitem> |
| * "ypos": The y-coordinate position of the top-left corner of the picture |
| * (#gint) |
| * </listitem> |
| * <listitem> |
| * "width": The width of the picture; the input will be scaled if necessary |
| * (#gint) |
| * </listitem> |
| * <listitem> |
| * "height": The height of the picture; the input will be scaled if necessary |
| * (#gint) |
| * </listitem> |
| * <listitem> |
| * "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) |
| * </listitem> |
| * <listitem> |
| * "zorder": The z-order position of the picture in the composition; between |
| * 0 and 10000. (#guint) |
| * </listitem> |
| * </itemizedlist> |
| * |
| * <refsect2> |
| * <title>Sample pipelines</title> |
| * |[ |
| * 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 overlayed 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 demostrate 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) |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| |
| #include "compositor.h" |
| #include "compositorpad.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, 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)) |
| ); |
| |
| #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 |
| enum |
| { |
| PROP_PAD_0, |
| PROP_PAD_XPOS, |
| PROP_PAD_YPOS, |
| PROP_PAD_WIDTH, |
| PROP_PAD_HEIGHT, |
| PROP_PAD_ALPHA |
| }; |
| |
| G_DEFINE_TYPE (GstCompositorPad, gst_compositor_pad, |
| GST_TYPE_VIDEO_AGGREGATOR_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; |
| 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); |
| break; |
| case PROP_PAD_HEIGHT: |
| pad->height = g_value_get_int (value); |
| break; |
| 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 gboolean |
| gst_compositor_pad_set_info (GstVideoAggregatorPad * pad, |
| GstVideoAggregator * vagg G_GNUC_UNUSED, |
| GstVideoInfo * current_info, GstVideoInfo * wanted_info) |
| { |
| GstCompositorPad *cpad = GST_COMPOSITOR_PAD (pad); |
| gchar *colorimetry, *best_colorimetry; |
| const gchar *chroma, *best_chroma; |
| gint width, height; |
| |
| if (!current_info->finfo) |
| return TRUE; |
| |
| if (GST_VIDEO_INFO_FORMAT (current_info) == GST_VIDEO_FORMAT_UNKNOWN) |
| return TRUE; |
| |
| if (cpad->convert) |
| gst_video_converter_free (cpad->convert); |
| |
| cpad->convert = NULL; |
| |
| colorimetry = gst_video_colorimetry_to_string (&(current_info->colorimetry)); |
| chroma = gst_video_chroma_to_string (current_info->chroma_site); |
| |
| best_colorimetry = |
| gst_video_colorimetry_to_string (&(wanted_info->colorimetry)); |
| best_chroma = gst_video_chroma_to_string (wanted_info->chroma_site); |
| |
| if (cpad->width > 0) |
| width = cpad->width; |
| else |
| width = current_info->width; |
| |
| if (cpad->height > 0) |
| height = cpad->height; |
| else |
| height = current_info->height; |
| |
| if (GST_VIDEO_INFO_FORMAT (wanted_info) != |
| GST_VIDEO_INFO_FORMAT (current_info) |
| || g_strcmp0 (colorimetry, best_colorimetry) |
| || g_strcmp0 (chroma, best_chroma) |
| || width != current_info->width || height != current_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 (wanted_info), |
| width, height); |
| tmp_info.chroma_site = wanted_info->chroma_site; |
| tmp_info.colorimetry = wanted_info->colorimetry; |
| tmp_info.par_n = current_info->par_n; |
| tmp_info.par_d = current_info->par_d; |
| tmp_info.fps_n = current_info->fps_n; |
| tmp_info.fps_d = current_info->fps_d; |
| tmp_info.flags = current_info->flags; |
| tmp_info.interlace_mode = current_info->interlace_mode; |
| |
| GST_DEBUG_OBJECT (pad, "This pad will be converted from %d to %d", |
| GST_VIDEO_INFO_FORMAT (current_info), |
| GST_VIDEO_INFO_FORMAT (&tmp_info)); |
| cpad->convert = gst_video_converter_new (current_info, &tmp_info, NULL); |
| cpad->conversion_info = tmp_info; |
| if (!cpad->convert) { |
| g_free (colorimetry); |
| g_free (best_colorimetry); |
| GST_WARNING_OBJECT (pad, "No path found for conversion"); |
| return FALSE; |
| } |
| } else { |
| cpad->conversion_info = *current_info; |
| GST_DEBUG_OBJECT (pad, "This pad will not need conversion"); |
| } |
| g_free (colorimetry); |
| g_free (best_colorimetry); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad, |
| GstVideoAggregator * vagg) |
| { |
| GstCompositorPad *cpad = GST_COMPOSITOR_PAD (pad); |
| guint outsize; |
| GstVideoFrame *converted_frame; |
| GstBuffer *converted_buf = NULL; |
| GstVideoFrame *frame; |
| static GstAllocationParams params = { 0, 15, 0, 0, }; |
| gint width, height; |
| |
| if (!pad->buffer) |
| return TRUE; |
| |
| frame = g_slice_new0 (GstVideoFrame); |
| |
| if (!gst_video_frame_map (frame, &pad->buffer_vinfo, pad->buffer, |
| GST_MAP_READ)) { |
| GST_WARNING_OBJECT (vagg, "Could not map input buffer"); |
| return FALSE; |
| } |
| |
| if (cpad->width > 0) |
| width = cpad->width; |
| else |
| width = GST_VIDEO_FRAME_WIDTH (frame); |
| |
| if (cpad->height > 0) |
| height = cpad->height; |
| else |
| height = GST_VIDEO_FRAME_HEIGHT (frame); |
| |
| /* The only thing that can change here is the width |
| * and height, otherwise set_info would've been called */ |
| if (cpad->conversion_info.width != width || |
| cpad->conversion_info.height != height) { |
| gchar *colorimetry, *wanted_colorimetry; |
| const gchar *chroma, *wanted_chroma; |
| |
| /* We might end up with no converter afterwards if |
| * the only reason for conversion was a different |
| * width or height |
| */ |
| if (cpad->convert) |
| gst_video_converter_free (cpad->convert); |
| cpad->convert = NULL; |
| |
| colorimetry = gst_video_colorimetry_to_string (&frame->info.colorimetry); |
| chroma = gst_video_chroma_to_string (frame->info.chroma_site); |
| |
| wanted_colorimetry = |
| gst_video_colorimetry_to_string (&cpad->conversion_info.colorimetry); |
| wanted_chroma = |
| gst_video_chroma_to_string (cpad->conversion_info.chroma_site); |
| |
| if (GST_VIDEO_INFO_FORMAT (&frame->info) != |
| GST_VIDEO_INFO_FORMAT (&cpad->conversion_info) |
| || g_strcmp0 (colorimetry, wanted_colorimetry) |
| || g_strcmp0 (chroma, wanted_chroma) |
| || width != GST_VIDEO_FRAME_WIDTH (frame) |
| || height != GST_VIDEO_FRAME_HEIGHT (frame)) { |
| GstVideoInfo tmp_info; |
| |
| gst_video_info_set_format (&tmp_info, cpad->conversion_info.finfo->format, |
| width, height); |
| tmp_info.chroma_site = cpad->conversion_info.chroma_site; |
| tmp_info.colorimetry = cpad->conversion_info.colorimetry; |
| tmp_info.par_n = cpad->conversion_info.par_n; |
| tmp_info.par_d = cpad->conversion_info.par_d; |
| tmp_info.fps_n = cpad->conversion_info.fps_n; |
| tmp_info.fps_d = cpad->conversion_info.fps_d; |
| tmp_info.flags = cpad->conversion_info.flags; |
| tmp_info.interlace_mode = cpad->conversion_info.interlace_mode; |
| |
| GST_DEBUG_OBJECT (pad, "This pad will be converted from %d to %d", |
| GST_VIDEO_INFO_FORMAT (&frame->info), |
| GST_VIDEO_INFO_FORMAT (&tmp_info)); |
| cpad->convert = gst_video_converter_new (&frame->info, &tmp_info, NULL); |
| cpad->conversion_info = tmp_info; |
| |
| if (!cpad->convert) { |
| GST_WARNING_OBJECT (pad, "No path found for conversion"); |
| g_free (colorimetry); |
| g_free (wanted_colorimetry); |
| gst_video_frame_unmap (frame); |
| g_slice_free (GstVideoFrame, frame); |
| return FALSE; |
| } |
| } else { |
| cpad->conversion_info.width = width; |
| cpad->conversion_info.height = height; |
| } |
| |
| g_free (colorimetry); |
| g_free (wanted_colorimetry); |
| } |
| |
| if (cpad->alpha == 0.0) { |
| GST_DEBUG_OBJECT (vagg, "Pad has alpha 0.0, not converting frame"); |
| converted_frame = NULL; |
| gst_video_frame_unmap (frame); |
| g_slice_free (GstVideoFrame, frame); |
| } else if (cpad->convert) { |
| gint converted_size; |
| |
| converted_frame = g_slice_new0 (GstVideoFrame); |
| |
| /* We wait until here to set the conversion infos, in case vagg->info changed */ |
| converted_size = cpad->conversion_info.size; |
| outsize = GST_VIDEO_INFO_SIZE (&vagg->info); |
| converted_size = converted_size > outsize ? converted_size : outsize; |
| converted_buf = gst_buffer_new_allocate (NULL, converted_size, ¶ms); |
| |
| if (!gst_video_frame_map (converted_frame, &(cpad->conversion_info), |
| converted_buf, GST_MAP_READWRITE)) { |
| GST_WARNING_OBJECT (vagg, "Could not map converted frame"); |
| |
| g_slice_free (GstVideoFrame, converted_frame); |
| gst_video_frame_unmap (frame); |
| g_slice_free (GstVideoFrame, frame); |
| return FALSE; |
| } |
| |
| gst_video_converter_frame (cpad->convert, frame, converted_frame); |
| cpad->converted_buffer = converted_buf; |
| gst_video_frame_unmap (frame); |
| g_slice_free (GstVideoFrame, frame); |
| } else { |
| converted_frame = frame; |
| } |
| |
| pad->aggregated_frame = converted_frame; |
| |
| return TRUE; |
| } |
| |
| static void |
| gst_compositor_pad_clean_frame (GstVideoAggregatorPad * pad, |
| GstVideoAggregator * vagg) |
| { |
| GstCompositorPad *cpad = GST_COMPOSITOR_PAD (pad); |
| |
| if (pad->aggregated_frame) { |
| gst_video_frame_unmap (pad->aggregated_frame); |
| g_slice_free (GstVideoFrame, pad->aggregated_frame); |
| pad->aggregated_frame = NULL; |
| } |
| |
| if (cpad->converted_buffer) { |
| gst_buffer_unref (cpad->converted_buffer); |
| cpad->converted_buffer = NULL; |
| } |
| } |
| |
| static void |
| gst_compositor_pad_finalize (GObject * object) |
| { |
| GstCompositorPad *pad = GST_COMPOSITOR_PAD (object); |
| |
| if (pad->convert) |
| gst_video_converter_free (pad->convert); |
| pad->convert = NULL; |
| |
| G_OBJECT_CLASS (gst_compositor_pad_parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_compositor_pad_class_init (GstCompositorPadClass * klass) |
| { |
| GObjectClass *gobject_class = (GObjectClass *) klass; |
| GstVideoAggregatorPadClass *vaggpadclass = |
| (GstVideoAggregatorPadClass *) klass; |
| |
| gobject_class->set_property = gst_compositor_pad_set_property; |
| gobject_class->get_property = gst_compositor_pad_get_property; |
| gobject_class->finalize = gst_compositor_pad_finalize; |
| |
| 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)); |
| |
| vaggpadclass->set_info = GST_DEBUG_FUNCPTR (gst_compositor_pad_set_info); |
| vaggpadclass->prepare_frame = |
| GST_DEBUG_FUNCPTR (gst_compositor_pad_prepare_frame); |
| vaggpadclass->clean_frame = |
| GST_DEBUG_FUNCPTR (gst_compositor_pad_clean_frame); |
| } |
| |
| 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; |
| } |
| |
| |
| /* GstCompositor */ |
| #define DEFAULT_BACKGROUND COMPOSITOR_BACKGROUND_CHECKER |
| enum |
| { |
| PROP_0, |
| PROP_BACKGROUND |
| }; |
| |
| #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; |
| } |
| |
| 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 (GstCompositor, gst_compositor, GST_TYPE_VIDEO_AGGREGATOR); |
| |
| 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_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 * |
| _update_caps (GstVideoAggregator * vagg, GstCaps * caps) |
| { |
| GList *l; |
| gint best_width = -1, best_height = -1; |
| GstVideoInfo info; |
| GstCaps *ret = NULL; |
| |
| gst_video_info_from_caps (&info, caps); |
| |
| 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; |
| |
| if (compositor_pad->width > 0) |
| width = compositor_pad->width; |
| else |
| width = GST_VIDEO_INFO_WIDTH (&vaggpad->info); |
| |
| if (compositor_pad->height > 0) |
| height = compositor_pad->height; |
| else |
| height = GST_VIDEO_INFO_HEIGHT (&vaggpad->info); |
| |
| 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; |
| } |
| GST_OBJECT_UNLOCK (vagg); |
| |
| if (best_width > 0 && best_height > 0) { |
| info.width = best_width; |
| info.height = best_height; |
| if (set_functions (GST_COMPOSITOR (vagg), &info)) |
| ret = gst_video_info_to_caps (&info); |
| } |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_compositor_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf) |
| { |
| GList *l; |
| GstCompositor *self = GST_COMPOSITOR (vagg); |
| BlendFunction composite; |
| GstVideoFrame out_frame, *outframe; |
| |
| 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; |
| /* default to blending */ |
| composite = self->blend; |
| switch (self->background) { |
| case COMPOSITOR_BACKGROUND_CHECKER: |
| self->fill_checker (outframe); |
| break; |
| case COMPOSITOR_BACKGROUND_BLACK: |
| self->fill_color (outframe, 16, 128, 128); |
| break; |
| case COMPOSITOR_BACKGROUND_WHITE: |
| self->fill_color (outframe, 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) { |
| guint8 *pdata; |
| gsize rowsize, plane_stride; |
| |
| 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_FRAME_COMP_HEIGHT (outframe, plane); |
| for (i = 0; i < height; ++i) { |
| memset (pdata, 0, rowsize); |
| pdata += plane_stride; |
| } |
| } |
| |
| /* use overlay to keep background transparent */ |
| composite = self->overlay; |
| break; |
| } |
| } |
| |
| GST_OBJECT_LOCK (vagg); |
| for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) { |
| GstVideoAggregatorPad *pad = l->data; |
| GstCompositorPad *compo_pad = GST_COMPOSITOR_PAD (pad); |
| |
| if (pad->aggregated_frame != NULL) { |
| composite (pad->aggregated_frame, compo_pad->xpos, compo_pad->ypos, |
| compo_pad->alpha, outframe); |
| } |
| } |
| GST_OBJECT_UNLOCK (vagg); |
| |
| gst_video_frame_unmap (outframe); |
| |
| return GST_FLOW_OK; |
| } |
| |
| 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); |
| } |
| } |
| |
| /* 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; |
| |
| agg_class->sinkpads_type = GST_TYPE_COMPOSITOR_PAD; |
| agg_class->sink_query = _sink_query; |
| videoaggregator_class->update_caps = _update_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_pad_template (gstelement_class, |
| gst_static_pad_template_get (&src_factory)); |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&sink_factory)); |
| |
| 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>"); |
| } |
| |
| static void |
| gst_compositor_init (GstCompositor * self) |
| { |
| self->background = DEFAULT_BACKGROUND; |
| /* initialize variables */ |
| } |
| |
| /* 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) |