{ \
s_alpha = MIN (255, s_alpha); \
switch (mode) { \
- case COMPOSITOR_BLEND_MODE_NORMAL:\
+ case COMPOSITOR_BLEND_MODE_SOURCE:\
+ compositor_orc_source_##name (dest, dest_stride, src, src_stride, \
+ s_alpha, src_width, src_height); \
+ break;\
+ case COMPOSITOR_BLEND_MODE_OVER:\
compositor_orc_overlay_##name (dest, dest_stride, src, src_stride, \
s_alpha, src_width, src_height); \
break;\
- case COMPOSITOR_BLEND_MODE_ADDITIVE:\
+ case COMPOSITOR_BLEND_MODE_ADD:\
compositor_orc_overlay_##name##_addition (dest, dest_stride, src, src_stride, \
s_alpha, src_width, src_height); \
break;\
}\
}
-#define BLEND_A32_LOOP_WITH_MODE(name) \
+#define BLEND_A32_LOOP(name) \
static inline void \
_blend_loop_##name (guint8 * dest, const guint8 * src, gint src_height, \
gint src_width, gint src_stride, gint dest_stride, guint s_alpha, \
GstCompositorBlendMode mode) \
{ \
s_alpha = MIN (255, s_alpha); \
- compositor_orc_blend_##name (dest, dest_stride, src, src_stride, \
- s_alpha, src_width, src_height); \
+ switch (mode) { \
+ case COMPOSITOR_BLEND_MODE_SOURCE:\
+ compositor_orc_source_##name (dest, dest_stride, src, src_stride, \
+ s_alpha, src_width, src_height); \
+ break;\
+ case COMPOSITOR_BLEND_MODE_OVER:\
+ case COMPOSITOR_BLEND_MODE_ADD:\
+ /* both modes are the same for opaque background */ \
+ compositor_orc_blend_##name (dest, dest_stride, src, src_stride, \
+ s_alpha, src_width, src_height); \
+ break;\
+ }\
}
OVERLAY_A32_LOOP (argb);
OVERLAY_A32_LOOP (bgra);
-BLEND_A32_LOOP_WITH_MODE (argb);
-BLEND_A32_LOOP_WITH_MODE (bgra);
+BLEND_A32_LOOP (argb);
+BLEND_A32_LOOP (bgra);
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
BLEND_A32 (argb, blend, _blend_loop_argb);
inline static void \
_blend_##format_name (const guint8 * src, guint8 * dest, \
gint src_stride, gint dest_stride, gint src_width, gint src_height, \
- gdouble src_alpha) \
+ gdouble src_alpha, GstCompositorBlendMode mode) \
{ \
gint i; \
gint b_alpha; \
\
+ /* in source mode we just have to copy over things */ \
+ if (mode == COMPOSITOR_BLEND_MODE_SOURCE) { \
+ src_alpha = 1.0; \
+ } \
+ \
/* If it's completely transparent... we just return */ \
if (G_UNLIKELY (src_alpha == 0.0)) { \
GST_INFO ("Fast copy (alpha == 0.0)"); \
b_dest + comp_xpos + comp_ypos * dest_comp_rowstride, \
src_comp_rowstride, \
dest_comp_rowstride, src_comp_width, src_comp_height, \
- src_alpha); \
+ src_alpha, mode); \
\
b_src = GST_VIDEO_FRAME_COMP_DATA (srcframe, 1); \
b_dest = GST_VIDEO_FRAME_COMP_DATA (destframe, 1); \
b_dest + comp_xpos + comp_ypos * dest_comp_rowstride, \
src_comp_rowstride, \
dest_comp_rowstride, src_comp_width, src_comp_height, \
- src_alpha); \
+ src_alpha, mode); \
\
b_src = GST_VIDEO_FRAME_COMP_DATA (srcframe, 2); \
b_dest = GST_VIDEO_FRAME_COMP_DATA (destframe, 2); \
b_dest + comp_xpos + comp_ypos * dest_comp_rowstride, \
src_comp_rowstride, \
dest_comp_rowstride, src_comp_width, src_comp_height, \
- src_alpha); \
+ src_alpha, mode); \
}
#define PLANAR_YUV_FILL_CHECKER(format_name, format_enum, MEMSET) \
inline static void \
_blend_##format_name (const guint8 * src, guint8 * dest, \
gint src_stride, gint dest_stride, gint src_width, gint src_height, \
- gdouble src_alpha) \
+ gdouble src_alpha, GstCompositorBlendMode mode) \
{ \
gint i; \
gint b_alpha; \
\
+ /* in source mode we just have to copy over things */ \
+ if (mode == COMPOSITOR_BLEND_MODE_SOURCE) { \
+ src_alpha = 1.0; \
+ } \
+ \
/* If it's completely transparent... we just return */ \
if (G_UNLIKELY (src_alpha == 0.0)) { \
GST_INFO ("Fast copy (alpha == 0.0)"); \
b_dest + comp_xpos + comp_ypos * dest_comp_rowstride, \
src_comp_rowstride, \
dest_comp_rowstride, src_comp_width, src_comp_height, \
- src_alpha); \
+ src_alpha, mode); \
\
b_src = GST_VIDEO_FRAME_PLANE_DATA (srcframe, 1); \
b_dest = GST_VIDEO_FRAME_PLANE_DATA (destframe, 1); \
b_dest + comp_xpos * 2 + comp_ypos * dest_comp_rowstride, \
src_comp_rowstride, \
dest_comp_rowstride, 2 * src_comp_width, src_comp_height, \
- src_alpha); \
+ src_alpha, mode); \
}
#define NV_YUV_FILL_CHECKER(format_name, MEMSET) \
} \
\
dest = dest + bpp * xpos + (ypos * dest_stride); \
+ \
+ /* in source mode we just have to copy over things */ \
+ if (mode == COMPOSITOR_BLEND_MODE_SOURCE) { \
+ src_alpha = 1.0; \
+ } \
+ \
/* If it's completely transparent... we just return */ \
if (G_UNLIKELY (src_alpha == 0.0)) { \
GST_INFO ("Fast copy (alpha == 0.0)"); \
} \
\
dest = dest + 2 * xpos + (ypos * dest_stride); \
+ \
+ /* in source mode we just have to copy over things */ \
+ if (mode == COMPOSITOR_BLEND_MODE_SOURCE) { \
+ src_alpha = 1.0; \
+ } \
+ \
/* If it's completely transparent... we just return */ \
if (G_UNLIKELY (src_alpha == 0.0)) { \
GST_INFO ("Fast copy (alpha == 0.0)"); \
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 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_CROSSFADE_RATIO 0.0
+#define DEFAULT_PAD_OPERATOR COMPOSITOR_OPERATOR_OVER
enum
{
PROP_PAD_0,
PROP_PAD_WIDTH,
PROP_PAD_HEIGHT,
PROP_PAD_ALPHA,
- PROP_PAD_CROSSFADE_RATIO,
+ PROP_PAD_OPERATOR,
};
G_DEFINE_TYPE (GstCompositorPad, gst_compositor_pad,
case PROP_PAD_ALPHA:
g_value_set_double (value, pad->alpha);
break;
- case PROP_PAD_CROSSFADE_RATIO:
- g_value_set_double (value, pad->crossfade);
+ case PROP_PAD_OPERATOR:
+ g_value_set_enum (value, pad->op);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
case PROP_PAD_ALPHA:
pad->alpha = g_value_get_double (value);
break;
- case PROP_PAD_CROSSFADE_RATIO:
- pad->crossfade = g_value_get_double (value);
+ case PROP_PAD_OPERATOR:
+ pad->op = g_value_get_enum (value);
gst_video_aggregator_pad_set_needs_alpha (GST_VIDEO_AGGREGATOR_PAD (pad),
- pad->crossfade > 0.0);
+ pad->op == COMPOSITOR_OPERATOR_ADD);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
GST_VIDEO_INFO_PAR_D (&vagg->info), &width, &height);
if (cpad->alpha == 0.0) {
- GST_DEBUG_OBJECT (vagg, "Pad has alpha 0.0, not converting frame");
+ GST_DEBUG_OBJECT (pad, "Pad has alpha 0.0, not converting frame");
goto done;
}
GST_VIDEO_INFO_WIDTH (&vagg->info), GST_VIDEO_INFO_HEIGHT (&vagg->info));
if (frame_rect.w == 0 || frame_rect.h == 0) {
- GST_DEBUG_OBJECT (vagg, "Resulting frame is zero-width or zero-height "
+ 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 we are crossfading the pad one way or another */
- l = g_list_find (GST_ELEMENT (vagg)->sinkpads, pad);
- if ((l->prev && GST_COMPOSITOR_PAD (l->prev->data)->crossfade > 0.0) ||
- (GST_COMPOSITOR_PAD (pad)->crossfade > 0.0)) {
- GST_DEBUG_OBJECT (pad, "Is being crossfaded with previous pad");
- l = NULL;
- } else {
- l = l->next;
- }
-
/* 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) {
GstVideoRectangle frame2_rect;
GstVideoAggregatorPad *pad2 = l->data;
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_CROSSFADE_RATIO,
- g_param_spec_double ("crossfade-ratio", "Crossfade ratio",
- "The crossfade ratio to use while crossfading with the following pad",
- 0.0, 1.0, DEFAULT_PAD_CROSSFADE_RATIO,
+ 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 =
compo_pad->xpos = DEFAULT_PAD_XPOS;
compo_pad->ypos = DEFAULT_PAD_YPOS;
compo_pad->alpha = DEFAULT_PAD_ALPHA;
- compo_pad->crossfade = DEFAULT_PAD_CROSSFADE_RATIO;
+ compo_pad->op = DEFAULT_PAD_OPERATOR;
}
return GST_AGGREGATOR_CLASS (parent_class)->negotiated_src_caps (agg, caps);
}
-/* Fills frame with transparent pixels if @nframe is NULL otherwise copy @frame
- * properties and fill @nframes with transparent pixels */
-static GstFlowReturn
-gst_compositor_fill_transparent (GstCompositor * self, GstVideoFrame * frame,
- GstVideoFrame * nframe)
-{
- guint plane, num_planes, height, i;
-
- if (nframe) {
- GstBuffer *cbuffer = gst_buffer_copy_deep (frame->buffer);
-
- if (!gst_video_frame_map (nframe, &frame->info, cbuffer, GST_MAP_WRITE)) {
- GST_WARNING_OBJECT (self, "Could not map output buffer");
- gst_buffer_unref (cbuffer);
- return GST_FLOW_ERROR;
- }
-
- /* the last reference is owned by the frame and released once the frame
- * is unmapped. We leak it if we don't unref here */
- gst_buffer_unref (cbuffer);
- } else {
- nframe = frame;
- }
-
- num_planes = GST_VIDEO_FRAME_N_PLANES (nframe);
- for (plane = 0; plane < num_planes; ++plane) {
- guint8 *pdata;
- gsize rowsize, plane_stride;
-
- pdata = GST_VIDEO_FRAME_PLANE_DATA (nframe, plane);
- plane_stride = GST_VIDEO_FRAME_PLANE_STRIDE (nframe, plane);
- rowsize = GST_VIDEO_FRAME_COMP_WIDTH (nframe, plane)
- * GST_VIDEO_FRAME_COMP_PSTRIDE (nframe, plane);
- height = GST_VIDEO_FRAME_COMP_HEIGHT (nframe, plane);
- for (i = 0; i < height; ++i) {
- memset (pdata, 0, rowsize);
- pdata += plane_stride;
- }
- }
-
- return GST_FLOW_OK;
-}
-
-/* WITH GST_OBJECT_LOCK !!
- * Returns: %TRUE if outframe is allready ready to be used as we are using
- * a transparent background and all pads have already been crossfaded
- * %FALSE otherwise
- */
-static gboolean
-gst_compositor_crossfade_frames (GstCompositor * self, GstVideoFrame * outframe)
-{
- GList *l;
- gboolean all_crossfading = FALSE;
- GstVideoAggregator *vagg = GST_VIDEO_AGGREGATOR (self);
-
- if (self->background == COMPOSITOR_BACKGROUND_TRANSPARENT) {
-
- all_crossfading = TRUE;
- for (l = GST_ELEMENT (self)->sinkpads; l; l = l->next) {
- GstCompositorPad *compo_pad = GST_COMPOSITOR_PAD (l->data);
-
- if (compo_pad->crossfade == 0.0 && l->next &&
- GST_COMPOSITOR_PAD (l->next->data)->crossfade == 0.0) {
- all_crossfading = FALSE;
-
- break;
- }
- }
- }
-
- for (l = GST_ELEMENT (self)->sinkpads; l; l = l->next) {
- GstVideoAggregatorPad *pad = l->data;
- GstCompositorPad *compo_pad = GST_COMPOSITOR_PAD (pad);
- GstVideoAggregatorPadClass *pad_class =
- GST_VIDEO_AGGREGATOR_PAD_GET_CLASS (compo_pad);
- GstVideoFrame *prepared_frame =
- gst_video_aggregator_pad_get_prepared_frame (pad);
-
- if (compo_pad->crossfade > 0.0 && prepared_frame) {
- gfloat alpha = compo_pad->crossfade * compo_pad->alpha;
- GstVideoAggregatorPad *npad = l->next ? l->next->data : NULL;
- GstVideoFrame *next_prepared_frame;
- GstVideoFrame nframe;
-
- next_prepared_frame =
- npad ? gst_video_aggregator_pad_get_prepared_frame (npad) : NULL;
-
- if (!all_crossfading) {
- gst_compositor_fill_transparent (self, outframe, &nframe);
- } else {
- nframe = *outframe;
- }
-
- self->overlay (prepared_frame,
- compo_pad->crossfaded ? 0 : compo_pad->xpos,
- compo_pad->crossfaded ? 0 : compo_pad->ypos,
- alpha, &nframe, COMPOSITOR_BLEND_MODE_ADDITIVE);
-
- if (npad && next_prepared_frame) {
- GstCompositorPad *next_compo_pad = GST_COMPOSITOR_PAD (npad);
-
- alpha = (1.0 - compo_pad->crossfade) * next_compo_pad->alpha;
- self->overlay (next_prepared_frame, next_compo_pad->xpos,
- next_compo_pad->ypos, alpha, &nframe,
- COMPOSITOR_BLEND_MODE_ADDITIVE);
-
- /* Replace frame with current frame */
- pad_class->clean_frame (npad, vagg, next_prepared_frame);
- if (!all_crossfading)
- *next_prepared_frame = nframe;
- next_compo_pad->crossfaded = TRUE;
-
- /* Frame is now consumed, clean it up */
- pad_class->clean_frame (pad, vagg, prepared_frame);
- } else {
- GST_LOG_OBJECT (self, "Simply fading out as no following pad found");
- pad_class->clean_frame (pad, vagg, prepared_frame);
- if (!all_crossfading)
- *prepared_frame = nframe;
- compo_pad->crossfaded = TRUE;
- }
- }
- }
-
- if (all_crossfading)
- for (l = GST_ELEMENT (self)->sinkpads; l; l = l->next)
- GST_COMPOSITOR_PAD (l->data)->crossfaded = FALSE;
-
- return all_crossfading;
-}
-
static GstFlowReturn
gst_compositor_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf)
{
self->fill_color (outframe, 240, 128, 128);
break;
case COMPOSITOR_BACKGROUND_TRANSPARENT:
- gst_compositor_fill_transparent (self, outframe, NULL);
+ {
+ 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);
- /* First mix the crossfade frames as required */
- if (!gst_compositor_crossfade_frames (self, outframe)) {
- 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);
-
- if (prepared_frame != NULL) {
- composite (prepared_frame,
- compo_pad->crossfaded ? 0 : compo_pad->xpos,
- compo_pad->crossfaded ? 0 : compo_pad->ypos, compo_pad->alpha,
- outframe, COMPOSITOR_BLEND_MODE_NORMAL);
- compo_pad->crossfaded = FALSE;
- }
+ 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_OPERATOR_SOURCE;
+ break;
+ case COMPOSITOR_OPERATOR_OVER:
+ blend_mode = COMPOSITOR_OPERATOR_OVER;
+ break;
+ case COMPOSITOR_OPERATOR_ADD:
+ blend_mode = COMPOSITOR_OPERATOR_ADD;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (prepared_frame != NULL) {
+ composite (prepared_frame,
+ compo_pad->xpos,
+ compo_pad->ypos, compo_pad->alpha, outframe, blend_mode);
}
}
GST_OBJECT_UNLOCK (vagg);
/**
* Simple crossfade example using the compositor element.
*
- * Takes a list of uri/path to video files and crossfade them
- * for 10 seconds and returns.
+ * Takes two video files and crossfades them for 10 seconds and returns.
*/
#include <stdlib.h>
{
GstElement *compositor;
guint z_order;
- gboolean is_last;
} VideoInfo;
static gchar *
{
GstPad *sinkpad =
gst_element_get_request_pad (GST_ELEMENT (info->compositor), "sink_%u");
+ GstControlSource *control_source;
+ gboolean is_last = info->z_order == 1;
- if (!info->is_last) {
- GstControlSource *control_source;
+ control_source = gst_interpolation_control_source_new ();
- control_source = gst_interpolation_control_source_new ();
+ gst_util_set_object_arg (G_OBJECT (sinkpad), "operator",
+ info->z_order == 0 ? "source" : "add");
+ gst_object_add_control_binding (GST_OBJECT (sinkpad),
+ gst_direct_control_binding_new_absolute (GST_OBJECT (sinkpad), "alpha",
+ control_source));
- g_object_set (sinkpad, "crossfade-ratio", 1.0, NULL);
- gst_object_add_control_binding (GST_OBJECT (sinkpad),
- gst_direct_control_binding_new_absolute (GST_OBJECT (sinkpad),
- "crossfade-ratio", control_source));
+ g_object_set (control_source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL);
- g_object_set (control_source, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL);
-
- gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
- (control_source), 0, 1.0);
- gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
- (control_source), 10 * GST_SECOND, 0.0);
- }
+ gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
+ (control_source), 0, is_last ? 0.0 : 1.0);
+ gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
+ (control_source), 10 * GST_SECOND, is_last ? 1.0 : 0.0);
g_object_set (sinkpad, "zorder", info->z_order, NULL);
gst_pad_link (pad, sinkpad);
GstElement *compositor, *sink, *pipeline;
GstBus *bus;
- if (argc < 2) {
- g_error ("At least 1 valid video file paths/urls need to " "be provided");
+ if (argc != 3) {
+ g_error ("Need to provide 2 input videos");
return -1;
}
gst_bin_add_many (GST_BIN (pipeline), compositor, sink, NULL);
g_assert (gst_element_link (compositor, sink));
- for (i = 1; i < argc; i++) {
+ for (i = 1; i < 3; i++) {
gchar *uri = ensure_uri (argv[i]);
VideoInfo *info = g_malloc0 (sizeof (VideoInfo));
GstElement *uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
info->compositor = compositor;
info->z_order = i - 1;
- info->is_last = (i == (argc - 1)) && (argc > 2);
g_signal_connect (uridecodebin, "pad-added", (GCallback) _pad_added_cb,
info);