gst/alpha/: Re-write the 'alpha' plugin to be BaseTransform based, simplifying some stuff, and making buffer-alloc an...

Original commit message from CVS:
* gst/alpha/Makefile.am:
* gst/alpha/gstalpha.c:
Re-write the 'alpha' plugin to be BaseTransform based, simplifying
some stuff, and making buffer-alloc and resizing work automatically.
No longer crashes on odd frame widths and heights, although there
seems to be a disagreement with ffmpegcolorspace about what size
an AYUV frame with odd height should be.
This commit is contained in:
Jan Schmidt 2008-01-31 00:00:23 +00:00
parent 0465f9b72f
commit e315541000
3 changed files with 303 additions and 385 deletions

View file

@ -1,3 +1,14 @@
2008-01-30 Jan Schmidt <jan.schmidt@sun.com>
* gst/alpha/Makefile.am:
* gst/alpha/gstalpha.c:
Re-write the 'alpha' plugin to be BaseTransform based, simplifying
some stuff, and making buffer-alloc and resizing work automatically.
No longer crashes on odd frame widths and heights, although there
seems to be a disagreement with ffmpegcolorspace about what size
an AYUV frame with odd height should be.
2008-01-29 Wim Taymans <wim.taymans@collabora.co.uk> 2008-01-29 Wim Taymans <wim.taymans@collabora.co.uk>
Patch by: Alessandro Decina <alessandro at nnva dot org> Patch by: Alessandro Decina <alessandro at nnva dot org>

View file

@ -3,7 +3,8 @@ plugin_LTLIBRARIES = libgstalpha.la libgstalphacolor.la
libgstalpha_la_SOURCES = gstalpha.c libgstalpha_la_SOURCES = gstalpha.c
libgstalpha_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \ libgstalpha_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \
$(GST_BASE_CFLAGS) $(GST_CFLAGS) $(GST_CONTROLLER_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(GST_CONTROLLER_CFLAGS)
libgstalpha_la_LIBADD = $(GST_LIBS) $(LIBM) $(GST_CONTROLLER_LIBS) libgstalpha_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstvideo-$(GST_MAJORMINOR) \
$(GST_LIBS) $(LIBM) $(GST_CONTROLLER_LIBS)
libgstalpha_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstalpha_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
libgstalphacolor_la_SOURCES = gstalphacolor.c libgstalphacolor_la_SOURCES = gstalphacolor.c

View file

@ -1,5 +1,8 @@
/* GStreamer /* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) <2007> Wim Taymans <wim.taymans@collabora.co.uk>
* Copyright (C) <2007> Edward Hervey <edward.hervey@collabora.co.uk>
* Copyright (C) <2007> Jan Schmidt <thaytan@noraisin.net>
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public * modify it under the terms of the GNU Library General Public
@ -21,6 +24,7 @@
#include "config.h" #include "config.h"
#endif #endif
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/base/gstbasetransform.h>
#include <gst/video/video.h> #include <gst/video/video.h>
#include <gst/controller/gstcontroller.h> #include <gst/controller/gstcontroller.h>
@ -55,24 +59,16 @@ typedef enum
} }
GstAlphaMethod; GstAlphaMethod;
#define ROUND_UP_2(x) (((x) + 1) & ~1) GST_DEBUG_CATEGORY_STATIC (gst_alpha_debug);
#define ROUND_UP_4(x) (((x) + 3) & ~3)
#define ROUND_UP_8(x) (((x) + 7) & ~7)
#define GST_CAT_DEFAULT gst_alpha_debug #define GST_CAT_DEFAULT gst_alpha_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _GstAlpha struct _GstAlpha
{ {
GstElement element; GstBaseTransform parent;
/* pads */
GstPad *sinkpad;
GstPad *srcpad;
/* caps */ /* caps */
gint in_width, in_height; GstVideoFormat format;
gint out_width, out_height; gint width, height;
gboolean ayuv; gboolean ayuv;
gdouble alpha; gdouble alpha;
@ -95,22 +91,21 @@ struct _GstAlpha
guint8 accept_angle_ctg; guint8 accept_angle_ctg;
guint8 one_over_kc; guint8 one_over_kc;
guint8 kfgy_scale; guint8 kfgy_scale;
GstSegment segment;
}; };
struct _GstAlphaClass struct _GstAlphaClass
{ {
GstElementClass parent_class; GstBaseTransformClass parent_class;
}; };
/* elementfactory information */ /* elementfactory information */
static const GstElementDetails gst_alpha_details = static const GstElementDetails gst_alpha_details =
GST_ELEMENT_DETAILS ("Alpha filter", GST_ELEMENT_DETAILS ("Alpha filter",
"Filter/Effect/Video", "Filter/Effect/Video",
"Adds an alpha channel to video", "Adds an alpha channel to video - uniform or via chroma-keying",
"Wim Taymans <wim@fluendo.com>"); "Wim Taymans <wim@fluendo.com>\n"
"Edward Hervey <edward.hervey@collabora.co.uk>\n"
"Jan Schmidt <thaytan@noraisin.net>");
/* Alpha signals and args */ /* Alpha signals and args */
enum enum
@ -151,15 +146,20 @@ static GstStaticPadTemplate gst_alpha_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink", GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK, GST_PAD_SINK,
GST_PAD_ALWAYS, GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") ";" GST_VIDEO_CAPS_YUV ("I420")
";" GST_VIDEO_CAPS_YUV ("I420")
) )
); );
static gboolean gst_alpha_start (GstBaseTransform * trans);
static gboolean gst_alpha_get_unit_size (GstBaseTransform * btrans,
GstCaps * caps, guint * size);
static GstCaps *gst_alpha_transform_caps (GstBaseTransform * btrans,
GstPadDirection direction, GstCaps * caps);
static gboolean gst_alpha_set_caps (GstBaseTransform * btrans,
GstCaps * incaps, GstCaps * outcaps);
static GstFlowReturn gst_alpha_transform (GstBaseTransform * btrans,
GstBuffer * in, GstBuffer * out);
static void gst_alpha_base_init (gpointer g_class);
static void gst_alpha_class_init (GstAlphaClass * klass);
static void gst_alpha_init (GstAlpha * alpha);
static void gst_alpha_init_params (GstAlpha * alpha); static void gst_alpha_init_params (GstAlpha * alpha);
static void gst_alpha_set_property (GObject * object, guint prop_id, static void gst_alpha_set_property (GObject * object, guint prop_id,
@ -167,15 +167,8 @@ static void gst_alpha_set_property (GObject * object, guint prop_id,
static void gst_alpha_get_property (GObject * object, guint prop_id, static void gst_alpha_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec); GValue * value, GParamSpec * pspec);
static gboolean gst_alpha_sink_setcaps (GstPad * pad, GstCaps * caps); GST_BOILERPLATE (GstAlpha, gst_alpha, GstBaseTransform,
static GstFlowReturn gst_alpha_chain (GstPad * pad, GstBuffer * buffer); GST_TYPE_BASE_TRANSFORM);
static gboolean gst_alpha_sink_event (GstPad * pad, GstEvent * event);
static GstStateChangeReturn gst_alpha_change_state (GstElement * element,
GstStateChange transition);
static GstElementClass *parent_class = NULL;
#define GST_TYPE_ALPHA_METHOD (gst_alpha_method_get_type()) #define GST_TYPE_ALPHA_METHOD (gst_alpha_method_get_type())
static GType static GType
@ -196,32 +189,6 @@ gst_alpha_method_get_type (void)
return alpha_method_type; return alpha_method_type;
} }
/* static guint gst_alpha_signals[LAST_SIGNAL] = { 0 }; */
GType
gst_alpha_get_type (void)
{
static GType alpha_type = 0;
if (!alpha_type) {
static const GTypeInfo alpha_info = {
sizeof (GstAlphaClass),
gst_alpha_base_init,
NULL,
(GClassInitFunc) gst_alpha_class_init,
NULL,
NULL,
sizeof (GstAlpha),
0,
(GInstanceInitFunc) gst_alpha_init,
};
alpha_type =
g_type_register_static (GST_TYPE_ELEMENT, "GstAlpha", &alpha_info, 0);
}
return alpha_type;
}
static void static void
gst_alpha_base_init (gpointer g_class) gst_alpha_base_init (gpointer g_class)
{ {
@ -233,17 +200,18 @@ gst_alpha_base_init (gpointer g_class)
gst_static_pad_template_get (&gst_alpha_sink_template)); gst_static_pad_template_get (&gst_alpha_sink_template));
gst_element_class_add_pad_template (element_class, gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_alpha_src_template)); gst_static_pad_template_get (&gst_alpha_src_template));
GST_DEBUG_CATEGORY_INIT (gst_alpha_debug, "alpha", 0,
"alpha - Element for adding alpha channel to streams");
} }
static void static void
gst_alpha_class_init (GstAlphaClass * klass) gst_alpha_class_init (GstAlphaClass * klass)
{ {
GObjectClass *gobject_class; GObjectClass *gobject_class;
GstElementClass *gstelement_class; GstBaseTransformClass *btrans_class;
gobject_class = (GObjectClass *) klass; gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass; btrans_class = (GstBaseTransformClass *) klass;
parent_class = g_type_class_peek_parent (klass);
gobject_class->set_property = gst_alpha_set_property; gobject_class->set_property = gst_alpha_set_property;
gobject_class->get_property = gst_alpha_get_property; gobject_class->get_property = gst_alpha_get_property;
@ -277,24 +245,16 @@ gst_alpha_class_init (GstAlphaClass * klass)
0.0, 64.0, DEFAULT_NOISE_LEVEL, 0.0, 64.0, DEFAULT_NOISE_LEVEL,
(GParamFlags) G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); (GParamFlags) G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
gstelement_class->change_state = gst_alpha_change_state; btrans_class->start = GST_DEBUG_FUNCPTR (gst_alpha_start);
btrans_class->transform = GST_DEBUG_FUNCPTR (gst_alpha_transform);
btrans_class->get_unit_size = GST_DEBUG_FUNCPTR (gst_alpha_get_unit_size);
btrans_class->transform_caps = GST_DEBUG_FUNCPTR (gst_alpha_transform_caps);
btrans_class->set_caps = GST_DEBUG_FUNCPTR (gst_alpha_set_caps);
} }
static void static void
gst_alpha_init (GstAlpha * alpha) gst_alpha_init (GstAlpha * alpha, GstAlphaClass * klass)
{ {
/* create the sink and src pads */
alpha->sinkpad =
gst_pad_new_from_static_template (&gst_alpha_sink_template, "sink");
gst_element_add_pad (GST_ELEMENT (alpha), alpha->sinkpad);
gst_pad_set_chain_function (alpha->sinkpad, gst_alpha_chain);
gst_pad_set_setcaps_function (alpha->sinkpad, gst_alpha_sink_setcaps);
gst_pad_set_event_function (alpha->sinkpad, gst_alpha_sink_event);
alpha->srcpad =
gst_pad_new_from_static_template (&gst_alpha_src_template, "src");
gst_element_add_pad (GST_ELEMENT (alpha), alpha->srcpad);
alpha->alpha = DEFAULT_ALPHA; alpha->alpha = DEFAULT_ALPHA;
alpha->method = DEFAULT_METHOD; alpha->method = DEFAULT_METHOD;
alpha->target_r = DEFAULT_TARGET_R; alpha->target_r = DEFAULT_TARGET_R;
@ -302,8 +262,6 @@ gst_alpha_init (GstAlpha * alpha)
alpha->target_b = DEFAULT_TARGET_B; alpha->target_b = DEFAULT_TARGET_B;
alpha->angle = DEFAULT_ANGLE; alpha->angle = DEFAULT_ANGLE;
alpha->noise_level = DEFAULT_NOISE_LEVEL; alpha->noise_level = DEFAULT_NOISE_LEVEL;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "alpha", 0, "Alpha adding element");
} }
/* do we need this function? */ /* do we need this function? */
@ -403,33 +361,77 @@ gst_alpha_get_property (GObject * object, guint prop_id, GValue * value,
} }
static gboolean static gboolean
gst_alpha_sink_setcaps (GstPad * pad, GstCaps * caps) gst_alpha_get_unit_size (GstBaseTransform * btrans,
GstCaps * caps, guint * size)
{ {
GstAlpha *alpha; GstAlpha *alpha = GST_ALPHA (btrans);
GstVideoFormat format;
gint width, height;
if (!gst_video_format_parse_caps (caps, &format, &width, &height))
return FALSE;
*size = gst_video_format_get_size (format, width, height);
GST_DEBUG_OBJECT (alpha, "unit size = %d for format %d w %d height %d",
*size, format, width, height);
return TRUE;
}
static GstCaps *
gst_alpha_transform_caps (GstBaseTransform * btrans,
GstPadDirection direction, GstCaps * caps)
{
GstCaps *ret;
GstStructure *structure; GstStructure *structure;
gboolean ret; gint i;
guint32 fourcc;
alpha = GST_ALPHA (GST_PAD_PARENT (pad)); ret = gst_caps_copy (caps);
structure = gst_caps_get_structure (caps, 0);
if (gst_structure_get_fourcc (structure, "format", &fourcc)) { /* When going from the SINK pad to the src, we just need to make sure the
switch (fourcc) { * format is AYUV */
case GST_MAKE_FOURCC ('I', '4', '2', '0'): if (direction == GST_PAD_SINK) {
alpha->ayuv = FALSE; for (i = 0; i < gst_caps_get_size (ret); i++) {
break; structure = gst_caps_get_structure (ret, i);
case GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'): gst_structure_set (structure, "format",
alpha->ayuv = TRUE; GST_TYPE_FOURCC, GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'), NULL);
break;
default:
return FALSE;
} }
} else { } else {
return FALSE; GstCaps *ayuv_caps;
/* In the other direction, prepend a copy of the caps with format AYUV,
* and set the first to I420 */
ayuv_caps = gst_caps_copy (ret);
for (i = 0; i < gst_caps_get_size (ret); i++) {
structure = gst_caps_get_structure (ret, i);
gst_structure_set (structure, "format",
GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'), NULL);
}
gst_caps_append (ret, ayuv_caps);
} }
ret = gst_structure_get_int (structure, "width", &alpha->in_width); gst_caps_do_simplify (ret);
ret &= gst_structure_get_int (structure, "height", &alpha->in_height);
return ret;
}
static gboolean
gst_alpha_set_caps (GstBaseTransform * btrans,
GstCaps * incaps, GstCaps * outcaps)
{
GstAlpha *alpha = GST_ALPHA (btrans);
if (!gst_video_format_parse_caps (incaps, &alpha->format,
&alpha->width, &alpha->height))
return FALSE;
if (alpha->format == GST_VIDEO_FORMAT_AYUV)
alpha->ayuv = TRUE;
else
alpha->ayuv = FALSE;
return TRUE; return TRUE;
} }
@ -439,28 +441,20 @@ gst_alpha_set_ayuv (guint8 * src, guint8 * dest, gint width, gint height,
gdouble alpha) gdouble alpha)
{ {
gint b_alpha = (gint) (alpha * 255); gint b_alpha = (gint) (alpha * 255);
gint i, j; gint y, x;
gint size; gint size;
gint stride; gint stride;
gint wrap;
width = ROUND_UP_2 (width); stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_AYUV, 0, width);
height = ROUND_UP_2 (height); size = gst_video_format_get_size (GST_VIDEO_FORMAT_AYUV, width, height);
stride = ROUND_UP_4 (width); for (y = 0; y < height; y++) {
size = stride * height; for (x = 0; x < width; x++) {
wrap = stride - width;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
*dest++ = (*src++ * b_alpha) >> 8; *dest++ = (*src++ * b_alpha) >> 8;
*dest++ = *src++; *dest++ = *src++;
*dest++ = *src++; *dest++ = *src++;
*dest++ = *src++; *dest++ = *src++;
} }
src += wrap;
dest += wrap;
} }
} }
@ -473,24 +467,23 @@ gst_alpha_set_i420 (guint8 * src, guint8 * dest, gint width, gint height,
guint8 *srcU; guint8 *srcU;
guint8 *srcV; guint8 *srcV;
gint i, j; gint i, j;
gint size, size2; gint src_wrap, src_uv_wrap;
gint stride, stride2; gint y_stride, uv_stride;
gint wrap, wrap2; gboolean odd_width;
width = ROUND_UP_2 (width); y_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0, width);
height = ROUND_UP_2 (height); uv_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1, width);
stride = ROUND_UP_4 (width); src_wrap = y_stride - width;
size = stride * height; src_uv_wrap = uv_stride - (width / 2);
stride2 = ROUND_UP_8 (width) / 2;
size2 = stride2 * height / 2;
wrap = stride - 2 * (width / 2);
wrap2 = stride2 - width / 2;
srcY = src; srcY = src;
srcU = srcY + size; srcU = src + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420,
srcV = srcU + size2; 1, width, height);
srcV = src + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420,
2, width, height);
odd_width = (width % 2 != 0);
for (i = 0; i < height; i++) { for (i = 0; i < height; i++) {
for (j = 0; j < width / 2; j++) { for (j = 0; j < width / 2; j++) {
@ -503,14 +496,21 @@ gst_alpha_set_i420 (guint8 * src, guint8 * dest, gint width, gint height,
*dest++ = *srcU++; *dest++ = *srcU++;
*dest++ = *srcV++; *dest++ = *srcV++;
} }
/* Might have one odd column left to do */
if (odd_width) {
*dest++ = b_alpha;
*dest++ = *srcY++;
*dest++ = *srcU;
*dest++ = *srcV;
}
if (i % 2 == 0) { if (i % 2 == 0) {
srcU -= width / 2; srcU -= width / 2;
srcV -= width / 2; srcV -= width / 2;
} else { } else {
srcU += wrap2; srcU += src_uv_wrap;
srcV += wrap2; srcV += src_uv_wrap;
} }
srcY += wrap; srcY += src_wrap;
} }
} }
@ -523,23 +523,12 @@ gst_alpha_chroma_key_ayuv (guint8 * src, guint8 * dest, gint width, gint height,
guint8 *dest1; guint8 *dest1;
gint i, j; gint i, j;
gint x, z, u, v, y, a; gint x, z, u, v, y, a;
gint size;
gint stride;
gint wrap;
gint tmp, tmp1; gint tmp, tmp1;
gint x1, y1; gint x1, y1;
width = ROUND_UP_2 (width);
height = ROUND_UP_2 (height);
stride = ROUND_UP_4 (width);
size = stride * height;
src1 = src; src1 = src;
dest1 = dest; dest1 = dest;
wrap = stride - width;
for (i = 0; i < height; i++) { for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) { for (j = 0; j < width; j++) {
a = *src1++ * (alpha->alpha); a = *src1++ * (alpha->alpha);
@ -618,8 +607,119 @@ gst_alpha_chroma_key_ayuv (guint8 * src, guint8 * dest, gint width, gint height,
*dest1++ = u; *dest1++ = u;
*dest1++ = v; *dest1++ = v;
} }
dest1 += wrap; }
src1 += wrap; }
static void
gst_alpha_chromakey_row_i420 (GstAlpha * alpha, guint8 * dest1, guint8 * dest2,
guint8 * srcY1, guint8 * srcY2, guint8 * srcU, guint8 * srcV, gint width)
{
gint xpos;
gint b_alpha;
gint x, z, u, v, y11, y12, y21, y22, a;
gint tmp, tmp1;
gint x1, y1;
a = 255 * alpha->alpha;
for (xpos = 0; xpos < width / 2; xpos++) {
y11 = *srcY1++;
y12 = *srcY1++;
y21 = *srcY2++;
y22 = *srcY2++;
u = *srcU++ - 128;
v = *srcV++ - 128;
/* Convert foreground to XZ coords where X direction is defined by
the key color */
tmp = ((short) u * alpha->cb + (short) v * alpha->cr) >> 7;
x = CLAMP (tmp, -128, 127);
tmp = ((short) v * alpha->cb - (short) u * alpha->cr) >> 7;
z = CLAMP (tmp, -128, 127);
/* WARNING: accept angle should never be set greater than "somewhat less
than 90 degrees" to avoid dealing with negative/infinite tg. In reality,
80 degrees should be enough if foreground is reasonable. If this seems
to be a problem, go to alternative ways of checking point position
(scalar product or line equations). This angle should not be too small
either to avoid infinite ctg (used to suppress foreground without use of
division) */
tmp = ((short) (x) * alpha->accept_angle_tg) >> 4;
tmp = MIN (tmp, 127);
if (abs (z) > tmp) {
/* keep foreground Kfg = 0 */
b_alpha = 255;
} else {
/* Compute Kfg (implicitly) and Kbg, suppress foreground in XZ coord
according to Kfg */
tmp = ((short) (z) * alpha->accept_angle_ctg) >> 4;
tmp = CLAMP (tmp, -128, 127);
x1 = abs (tmp);
y1 = z;
tmp1 = x - x1;
tmp1 = MAX (tmp1, 0);
b_alpha = (((unsigned char) (tmp1) *
(unsigned short) (alpha->one_over_kc)) / 2);
b_alpha = 255 - CLAMP (b_alpha, 0, 255);
b_alpha = (a * b_alpha) >> 8;
tmp = ((unsigned short) (tmp1) * alpha->kfgy_scale) >> 4;
tmp1 = MIN (tmp, 255);
tmp = y11 - tmp1;
y11 = MAX (tmp, 0);
tmp = y12 - tmp1;
y12 = MAX (tmp, 0);
tmp = y21 - tmp1;
y21 = MAX (tmp, 0);
tmp = y22 - tmp1;
y22 = MAX (tmp, 0);
/* Convert suppressed foreground back to CbCr */
tmp = ((char) (x1) * (short) (alpha->cb) -
(char) (y1) * (short) (alpha->cr)) >> 7;
u = CLAMP (tmp, -128, 127);
tmp = ((char) (x1) * (short) (alpha->cr) +
(char) (y1) * (short) (alpha->cb)) >> 7;
v = CLAMP (tmp, -128, 127);
/* Deal with noise. For now, a circle around the key color with
radius of noise_level treated as exact key color. Introduces
sharp transitions.
*/
tmp = z * (short) (z) + (x - alpha->kg) * (short) (x - alpha->kg);
tmp = MIN (tmp, 0xffff);
if (tmp < alpha->noise_level * alpha->noise_level) {
/* Uncomment this if you want total suppression within the noise circle */
b_alpha = 0;
}
}
u += 128;
v += 128;
*dest1++ = b_alpha;
*dest1++ = y11;
*dest1++ = u;
*dest1++ = v;
*dest1++ = b_alpha;
*dest1++ = y12;
*dest1++ = u;
*dest1++ = v;
*dest2++ = b_alpha;
*dest2++ = y21;
*dest2++ = u;
*dest2++ = v;
*dest2++ = b_alpha;
*dest2++ = y22;
*dest2++ = u;
*dest2++ = v;
} }
} }
@ -629,144 +729,44 @@ static void
gst_alpha_chroma_key_i420 (guint8 * src, guint8 * dest, gint width, gint height, gst_alpha_chroma_key_i420 (guint8 * src, guint8 * dest, gint width, gint height,
GstAlpha * alpha) GstAlpha * alpha)
{ {
gint b_alpha;
guint8 *srcY1, *srcY2, *srcU, *srcV; guint8 *srcY1, *srcY2, *srcU, *srcV;
guint8 *dest1, *dest2; guint8 *dest1, *dest2;
gint i, j; gint ypos;
gint x, z, u, v, y11, y12, y21, y22, a; gint dest_stride, src_y_stride, src_uv_stride;
gint size, size2;
gint stride, stride2;
gint wrap, wrap2, wrap3;
gint tmp, tmp1;
gint x1, y1;
width = ROUND_UP_2 (width); dest_stride =
height = ROUND_UP_2 (height); gst_video_format_get_row_stride (GST_VIDEO_FORMAT_AYUV, 0, width);
src_y_stride =
stride = ROUND_UP_4 (width); gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0, width);
size = stride * height; src_uv_stride =
stride2 = ROUND_UP_8 (width) / 2; gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1, width);
size2 = stride2 * height / 2;
srcY1 = src; srcY1 = src;
srcY2 = src + stride; srcY2 = src + src_y_stride;
srcU = srcY1 + size;
srcV = srcU + size2; srcU = src + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420,
1, width, height);
srcV = src + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420,
2, width, height);
dest1 = dest; dest1 = dest;
dest2 = dest + width * 4; dest2 = dest + dest_stride;
wrap = 2 * stride - 2 * (width / 2); /* Redefine Y strides to skip 2 lines at a time ... */
wrap2 = stride2 - width / 2; dest_stride *= 2;
wrap3 = 8 * width - 8 * (width / 2); src_y_stride *= 2;
a = 255 * alpha->alpha; for (ypos = 0; ypos < height / 2; ypos++) {
for (i = 0; i < height / 2; i++) { gst_alpha_chromakey_row_i420 (alpha, dest1, dest2,
for (j = 0; j < width / 2; j++) { srcY1, srcY2, srcU, srcV, width);
y11 = *srcY1++;
y12 = *srcY1++;
y21 = *srcY2++;
y22 = *srcY2++;
u = *srcU++ - 128;
v = *srcV++ - 128;
/* Convert foreground to XZ coords where X direction is defined by dest1 += dest_stride;
the key color */ dest2 += dest_stride;
tmp = ((short) u * alpha->cb + (short) v * alpha->cr) >> 7; srcY1 += src_y_stride;
x = CLAMP (tmp, -128, 127); srcY2 += src_y_stride;
tmp = ((short) v * alpha->cb - (short) u * alpha->cr) >> 7; srcU += src_uv_stride;
z = CLAMP (tmp, -128, 127); srcV += src_uv_stride;
/* WARNING: accept angle should never be set greater than "somewhat less
than 90 degrees" to avoid dealing with negative/infinite tg. In reality,
80 degrees should be enough if foreground is reasonable. If this seems
to be a problem, go to alternative ways of checking point position
(scalar product or line equations). This angle should not be too small
either to avoid infinite ctg (used to suppress foreground without use of
division) */
tmp = ((short) (x) * alpha->accept_angle_tg) >> 4;
tmp = MIN (tmp, 127);
if (abs (z) > tmp) {
/* keep foreground Kfg = 0 */
b_alpha = 255;
} else {
/* Compute Kfg (implicitly) and Kbg, suppress foreground in XZ coord
according to Kfg */
tmp = ((short) (z) * alpha->accept_angle_ctg) >> 4;
tmp = CLAMP (tmp, -128, 127);
x1 = abs (tmp);
y1 = z;
tmp1 = x - x1;
tmp1 = MAX (tmp1, 0);
b_alpha = (((unsigned char) (tmp1) *
(unsigned short) (alpha->one_over_kc)) / 2);
b_alpha = 255 - CLAMP (b_alpha, 0, 255);
b_alpha = (a * b_alpha) >> 8;
tmp = ((unsigned short) (tmp1) * alpha->kfgy_scale) >> 4;
tmp1 = MIN (tmp, 255);
tmp = y11 - tmp1;
y11 = MAX (tmp, 0);
tmp = y12 - tmp1;
y12 = MAX (tmp, 0);
tmp = y21 - tmp1;
y21 = MAX (tmp, 0);
tmp = y22 - tmp1;
y22 = MAX (tmp, 0);
/* Convert suppressed foreground back to CbCr */
tmp = ((char) (x1) * (short) (alpha->cb) -
(char) (y1) * (short) (alpha->cr)) >> 7;
u = CLAMP (tmp, -128, 127);
tmp = ((char) (x1) * (short) (alpha->cr) +
(char) (y1) * (short) (alpha->cb)) >> 7;
v = CLAMP (tmp, -128, 127);
/* Deal with noise. For now, a circle around the key color with
radius of noise_level treated as exact key color. Introduces
sharp transitions.
*/
tmp = z * (short) (z) + (x - alpha->kg) * (short) (x - alpha->kg);
tmp = MIN (tmp, 0xffff);
if (tmp < alpha->noise_level * alpha->noise_level) {
/* Uncomment this if you want total suppression within the noise circle */
b_alpha = 0;
}
}
u += 128;
v += 128;
*dest1++ = b_alpha;
*dest1++ = y11;
*dest1++ = u;
*dest1++ = v;
*dest1++ = b_alpha;
*dest1++ = y12;
*dest1++ = u;
*dest1++ = v;
*dest2++ = b_alpha;
*dest2++ = y21;
*dest2++ = u;
*dest2++ = v;
*dest2++ = b_alpha;
*dest2++ = y22;
*dest2++ = u;
*dest2++ = v;
}
dest1 += wrap3;
dest2 += wrap3;
srcY1 += wrap;
srcY2 += wrap;
srcU += wrap2;
srcV += wrap2;
} }
} }
@ -807,76 +807,29 @@ gst_alpha_init_params (GstAlpha * alpha)
} }
static gboolean static gboolean
gst_alpha_sink_event (GstPad * pad, GstEvent * event) gst_alpha_start (GstBaseTransform * btrans)
{ {
GstAlpha *alpha; GstAlpha *alpha = GST_ALPHA (btrans);
gboolean ret;
alpha = GST_ALPHA (GST_PAD_PARENT (pad)); gst_alpha_init_params (alpha);
switch (GST_EVENT_TYPE (event)) { return TRUE;
case GST_EVENT_FLUSH_STOP:
gst_segment_init (&alpha->segment, GST_FORMAT_UNDEFINED);
break;
case GST_EVENT_NEWSEGMENT:{
GstFormat format;
gdouble rate, arate;
gint64 start, stop, time;
gboolean update;
gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
&start, &stop, &time);
gst_segment_set_newsegment_full (&alpha->segment, update, rate, arate,
format, start, stop, time);
break;
}
default:
break;
}
ret = gst_pad_push_event (alpha->srcpad, event);
return ret;
} }
static GstFlowReturn static GstFlowReturn
gst_alpha_chain (GstPad * pad, GstBuffer * buffer) gst_alpha_transform (GstBaseTransform * btrans, GstBuffer * in, GstBuffer * out)
{ {
GstAlpha *alpha; GstAlpha *alpha = GST_ALPHA (btrans);
GstBuffer *outbuf; gint width, height;
gint new_width, new_height;
GstFlowReturn ret;
GstClockTime timestamp; GstClockTime timestamp;
alpha = GST_ALPHA (GST_PAD_PARENT (pad)); width = alpha->width;
height = alpha->height;
new_width = alpha->in_width; GST_BUFFER_TIMESTAMP (out) = GST_BUFFER_TIMESTAMP (in);
new_height = alpha->in_height; GST_BUFFER_DURATION (out) = GST_BUFFER_DURATION (in);
timestamp = gst_segment_to_stream_time (&btrans->segment, GST_FORMAT_TIME,
if (new_width != alpha->out_width || GST_BUFFER_TIMESTAMP (in));
new_height != alpha->out_height || !GST_PAD_CAPS (alpha->srcpad)) {
GstCaps *newcaps;
newcaps = gst_caps_copy (gst_pad_get_negotiated_caps (alpha->sinkpad));
gst_caps_set_simple (newcaps,
"format", GST_TYPE_FOURCC, GST_STR_FOURCC ("AYUV"),
"width", G_TYPE_INT, new_width, "height", G_TYPE_INT, new_height, NULL);
gst_pad_set_caps (alpha->srcpad, newcaps);
alpha->out_width = new_width;
alpha->out_height = new_height;
}
outbuf =
gst_buffer_new_and_alloc (ROUND_UP_2 (new_width) *
ROUND_UP_2 (new_height) * 4);
gst_buffer_set_caps (outbuf, GST_PAD_CAPS (alpha->srcpad));
GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buffer);
GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buffer);
timestamp = gst_segment_to_stream_time (&alpha->segment, GST_FORMAT_TIME,
GST_BUFFER_TIMESTAMP (buffer));
GST_LOG ("Got stream time of %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp)); GST_LOG ("Got stream time of %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp));
if (GST_CLOCK_TIME_IS_VALID (timestamp)) if (GST_CLOCK_TIME_IS_VALID (timestamp))
gst_object_sync_values (G_OBJECT (alpha), timestamp); gst_object_sync_values (G_OBJECT (alpha), timestamp);
@ -884,76 +837,29 @@ gst_alpha_chain (GstPad * pad, GstBuffer * buffer)
switch (alpha->method) { switch (alpha->method) {
case ALPHA_METHOD_SET: case ALPHA_METHOD_SET:
if (alpha->ayuv) { if (alpha->ayuv) {
gst_alpha_set_ayuv (GST_BUFFER_DATA (buffer), gst_alpha_set_ayuv (GST_BUFFER_DATA (in),
GST_BUFFER_DATA (outbuf), new_width, new_height, alpha->alpha); GST_BUFFER_DATA (out), width, height, alpha->alpha);
} else { } else {
gst_alpha_set_i420 (GST_BUFFER_DATA (buffer), gst_alpha_set_i420 (GST_BUFFER_DATA (in),
GST_BUFFER_DATA (outbuf), new_width, new_height, alpha->alpha); GST_BUFFER_DATA (out), width, height, alpha->alpha);
} }
break; break;
case ALPHA_METHOD_GREEN: case ALPHA_METHOD_GREEN:
case ALPHA_METHOD_BLUE: case ALPHA_METHOD_BLUE:
case ALPHA_METHOD_CUSTOM: case ALPHA_METHOD_CUSTOM:
if (alpha->ayuv) { if (alpha->ayuv) {
gst_alpha_chroma_key_ayuv (GST_BUFFER_DATA (buffer), gst_alpha_chroma_key_ayuv (GST_BUFFER_DATA (in),
GST_BUFFER_DATA (outbuf), new_width, new_height, alpha); GST_BUFFER_DATA (out), width, height, alpha);
} else { } else {
gst_alpha_chroma_key_i420 (GST_BUFFER_DATA (buffer), gst_alpha_chroma_key_i420 (GST_BUFFER_DATA (in),
GST_BUFFER_DATA (outbuf), new_width, new_height, alpha); GST_BUFFER_DATA (out), width, height, alpha);
} }
break; break;
default: default:
break; break;
} }
gst_buffer_unref (buffer); return GST_FLOW_OK;
/* Update last stop position in segment */
if (GST_BUFFER_TIMESTAMP (outbuf) != GST_CLOCK_TIME_NONE) {
GstClockTime last_stop = GST_BUFFER_TIMESTAMP (outbuf);
if (GST_BUFFER_DURATION (outbuf) != GST_CLOCK_TIME_NONE)
last_stop += GST_BUFFER_DURATION (outbuf);
gst_segment_set_last_stop (&alpha->segment, GST_FORMAT_TIME, last_stop);
}
ret = gst_pad_push (alpha->srcpad, outbuf);
return ret;
}
static GstStateChangeReturn
gst_alpha_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn res;
GstAlpha *alpha;
alpha = GST_ALPHA (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
gst_segment_init (&alpha->segment, GST_FORMAT_UNDEFINED);
gst_alpha_init_params (alpha);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
default:
break;
}
res = parent_class->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
case GST_STATE_CHANGE_PAUSED_TO_READY:
case GST_STATE_CHANGE_READY_TO_NULL:
default:
break;
}
return res;
} }
static gboolean static gboolean
@ -967,5 +873,5 @@ plugin_init (GstPlugin * plugin)
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR, GST_VERSION_MINOR,
"alpha", "alpha",
"adds an alpha channel to video", "adds an alpha channel to video - constant or via chroma-keying",
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)