A plugin to add an alpha channel to I420 video. Can optionally do chroma keying.
[platform/upstream/gst-plugins-good.git] / gst / videomixer / videomixer.c
1 /* Generic video mixer plugin
2  * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <gst/gst.h>
25 #include <string.h>
26
27 GST_DEBUG_CATEGORY_STATIC (gst_videomixer_debug);
28 #define GST_CAT_DEFAULT gst_videomixer_debug
29
30 #define GST_TYPE_VIDEO_MIXER_PAD (gst_videomixer_pad_get_type())
31 #define GST_VIDEO_MIXER_PAD(obj) \
32         (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIDEO_MIXER_PAD, GstVideoMixerPad))
33 #define GST_VIDEO_MIXER_PAD_CLASS(klass) \
34         (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIDEO_MIXER_PAD, GstVideoMixerPadiClass))
35 #define GST_IS_VIDEO_MIXER_PAD(obj) \
36         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_MIXER_PAD))
37 #define GST_IS_VIDEO_MIXER_PAD_CLASS(obj) \
38         (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_MIXER_PAD))
39
40 typedef struct _GstVideoMixerPad GstVideoMixerPad;
41 typedef struct _GstVideoMixerPadClass GstVideoMixerPadClass;
42
43 static void gst_videomixer_pad_base_init (gpointer g_class);
44 static void gst_videomixer_pad_class_init (GstVideoMixerPadClass * klass);
45 static void gst_videomixer_pad_init (GstVideoMixerPad * mixerpad);
46
47 static void gst_videomixer_pad_get_property (GObject * object, guint prop_id,
48     GValue * value, GParamSpec * pspec);
49 static void gst_videomixer_pad_set_property (GObject * object, guint prop_id,
50     const GValue * value, GParamSpec * pspec);
51
52 #define DEFAULT_PAD_ZORDER 0
53 #define DEFAULT_PAD_XPOS   0
54 #define DEFAULT_PAD_YPOS   0
55 #define DEFAULT_PAD_ALPHA  1.0
56 enum
57 {
58   ARG_PAD_0,
59   ARG_PAD_ZORDER,
60   ARG_PAD_XPOS,
61   ARG_PAD_YPOS,
62   ARG_PAD_ALPHA,
63 };
64
65 /* all information needed for one video stream */
66 struct _GstVideoMixerPad
67 {
68   GstRealPad parent;            /* subclass the pad */
69
70   GstBuffer *buffer;            /* the queued buffer for this pad */
71   gboolean eos;
72
73   gint64 queued;
74
75   guint in_width, in_height;
76   gdouble in_framerate;
77
78   gint xpos, ypos;
79   guint zorder;
80   gint blend_mode;
81   gdouble alpha;
82 };
83
84 struct _GstVideoMixerPadClass
85 {
86   GstRealPadClass parent_class;
87 };
88
89 GType
90 gst_videomixer_pad_get_type (void)
91 {
92   static GType videomixer_pad_type = 0;
93
94   if (!videomixer_pad_type) {
95     static const GTypeInfo videomixer_pad_info = {
96       sizeof (GstVideoMixerPadClass),
97       gst_videomixer_pad_base_init,
98       NULL,
99       (GClassInitFunc) gst_videomixer_pad_class_init,
100       NULL,
101       NULL,
102       sizeof (GstVideoMixerPad),
103       0,
104       (GInstanceInitFunc) gst_videomixer_pad_init,
105     };
106
107     videomixer_pad_type =
108         g_type_register_static (GST_TYPE_REAL_PAD,
109         "GstVideoMixerPad", &videomixer_pad_info, 0);
110   }
111   return videomixer_pad_type;
112 }
113
114 static void
115 gst_videomixer_pad_base_init (gpointer g_class)
116 {
117 }
118
119 static void
120 gst_videomixer_pad_class_init (GstVideoMixerPadClass * klass)
121 {
122   GObjectClass *gobject_class;
123
124   gobject_class = (GObjectClass *) klass;
125
126   gobject_class->set_property =
127       GST_DEBUG_FUNCPTR (gst_videomixer_pad_set_property);
128   gobject_class->get_property =
129       GST_DEBUG_FUNCPTR (gst_videomixer_pad_get_property);
130
131   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PAD_ZORDER,
132       g_param_spec_uint ("zorder", "Z-Order", "Z Order of the picture",
133           0, 10000, DEFAULT_PAD_ZORDER, G_PARAM_READWRITE));
134   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PAD_XPOS,
135       g_param_spec_int ("xpos", "X Position", "X Position of the picture",
136           G_MININT, G_MAXINT, DEFAULT_PAD_XPOS, G_PARAM_READWRITE));
137   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PAD_YPOS,
138       g_param_spec_int ("ypos", "Y Position", "Y Position of the picture",
139           G_MININT, G_MAXINT, DEFAULT_PAD_YPOS, G_PARAM_READWRITE));
140   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PAD_ALPHA,
141       g_param_spec_double ("alpha", "Alpha", "Alpha of the picture",
142           0.0, 1.0, DEFAULT_PAD_ALPHA, G_PARAM_READWRITE));
143 }
144
145 static const GstEventMask *
146 gst_videomixer_pad_get_sink_event_masks (GstPad * pad)
147 {
148   static const GstEventMask gst_videomixer_sink_event_masks[] = {
149     {GST_EVENT_EOS, 0},
150     {0,}
151   };
152
153   return gst_videomixer_sink_event_masks;
154 }
155
156 static void
157 gst_videomixer_pad_get_property (GObject * object, guint prop_id,
158     GValue * value, GParamSpec * pspec)
159 {
160   GstVideoMixerPad *pad;
161
162   g_return_if_fail (GST_IS_VIDEO_MIXER_PAD (object));
163
164   pad = GST_VIDEO_MIXER_PAD (object);
165
166   switch (prop_id) {
167     case ARG_PAD_ZORDER:
168       g_value_set_uint (value, pad->zorder);
169       break;
170     case ARG_PAD_XPOS:
171       g_value_set_int (value, pad->xpos);
172       break;
173     case ARG_PAD_YPOS:
174       g_value_set_int (value, pad->ypos);
175       break;
176     case ARG_PAD_ALPHA:
177       g_value_set_double (value, pad->alpha);
178       break;
179     default:
180       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
181       break;
182   }
183 }
184
185 static void
186 gst_videomixer_pad_set_property (GObject * object, guint prop_id,
187     const GValue * value, GParamSpec * pspec)
188 {
189   GstVideoMixerPad *pad;
190
191   g_return_if_fail (GST_IS_PAD (object));
192
193   pad = GST_VIDEO_MIXER_PAD (object);
194
195   switch (prop_id) {
196     case ARG_PAD_ZORDER:
197       pad->zorder = g_value_get_uint (value);
198       break;
199     case ARG_PAD_XPOS:
200       pad->xpos = g_value_get_int (value);
201       break;
202     case ARG_PAD_YPOS:
203       pad->ypos = g_value_get_int (value);
204       break;
205     case ARG_PAD_ALPHA:
206       pad->alpha = g_value_get_double (value);
207       break;
208     default:
209       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
210       break;
211   }
212 }
213
214 #define GST_TYPE_VIDEO_MIXER (gst_videomixer_get_type())
215 #define GST_VIDEO_MIXER(obj) \
216         (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIDEO_MIXER, GstVideoMixer))
217 #define GST_VIDEO_MIXER_CLASS(klass) \
218         (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIDEO_MIXER, GstVideoMixerClass))
219 #define GST_IS_VIDEO_MIXER(obj) \
220         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_MIXER))
221 #define GST_IS_VIDEO_MIXER_CLASS(obj) \
222         (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_MIXER))
223
224 typedef struct _GstVideoMixer GstVideoMixer;
225 typedef struct _GstVideoMixerClass GstVideoMixerClass;
226
227 GType gst_videomixer_get_type (void);
228
229 typedef enum
230 {
231   VIDEO_MIXER_BACKGROUND_CHECKER,
232   VIDEO_MIXER_BACKGROUND_BLACK,
233   VIDEO_MIXER_BACKGROUND_WHITE,
234 }
235 GstVideoMixerBackground;
236
237 struct _GstVideoMixer
238 {
239   GstElement element;
240
241   /* pad */
242   GstPad *srcpad;
243
244   /* sinkpads, a GSList of GstVideoMixerPads */
245   GSList *sinkpads;
246   gint numpads;
247
248   /* the master pad */
249   GstVideoMixerPad *master;
250
251   gint in_width, in_height;
252   gint out_width, out_height;
253
254   GstVideoMixerBackground background;
255
256   gdouble in_framerate;
257 };
258
259 struct _GstVideoMixerClass
260 {
261   GstElementClass parent_class;
262 };
263
264 static GstPadLinkReturn
265 gst_videomixer_pad_sinkconnect (GstPad * pad, const GstCaps * vscaps)
266 {
267   GstVideoMixer *mix;
268   GstVideoMixerPad *mixpad;
269   GstStructure *structure;
270
271   mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
272   mixpad = GST_VIDEO_MIXER_PAD (pad);
273
274   GST_DEBUG ("videomixer: sinkconnect triggered on %s", gst_pad_get_name (pad));
275
276   structure = gst_caps_get_structure (vscaps, 0);
277
278   gst_structure_get_int (structure, "width", &mixpad->in_width);
279   gst_structure_get_int (structure, "height", &mixpad->in_height);
280   gst_structure_get_double (structure, "framerate", &mixpad->in_framerate);
281
282   mixpad->xpos = 0;
283   mixpad->ypos = 0;
284
285   mix->in_width = MAX (mix->in_width, mixpad->in_width);
286   mix->in_height = MAX (mix->in_height, mixpad->in_height);
287   mix->in_framerate = mixpad->in_framerate;
288
289   return GST_PAD_LINK_OK;
290 }
291
292 static void
293 gst_videomixer_pad_link (GstPad * pad, GstPad * peer, gpointer data)
294 {
295   //GstVideoMixer *videomixer = GST_VIDEO_MIXER (data);
296   const gchar *padname = gst_pad_get_name (pad);
297
298   GST_DEBUG ("pad '%s' connected", padname);
299 }
300
301 static void
302 gst_videomixer_pad_unlink (GstPad * pad, GstPad * peer, gpointer data)
303 {
304   //GstVideoMixer *videomixer = GST_VIDEO_MIXER (data);
305   const gchar *padname = gst_pad_get_name (pad);
306
307   GST_DEBUG ("pad '%s' unlinked", padname);
308 }
309
310 static void
311 gst_videomixer_pad_init (GstVideoMixerPad * mixerpad)
312 {
313   g_signal_connect (mixerpad, "linked",
314       G_CALLBACK (gst_videomixer_pad_link), (gpointer) mixerpad);
315   g_signal_connect (mixerpad, "unlinked",
316       G_CALLBACK (gst_videomixer_pad_unlink), (gpointer) mixerpad);
317
318   /* setup some pad functions */
319   gst_pad_set_link_function (GST_PAD (mixerpad),
320       gst_videomixer_pad_sinkconnect);
321   gst_pad_set_event_mask_function (GST_PAD (mixerpad),
322       gst_videomixer_pad_get_sink_event_masks);
323
324   mixerpad->zorder = DEFAULT_PAD_ZORDER;
325   mixerpad->xpos = DEFAULT_PAD_XPOS;
326   mixerpad->ypos = DEFAULT_PAD_YPOS;
327   mixerpad->alpha = DEFAULT_PAD_ALPHA;
328 }
329
330
331
332 /* elementfactory information */
333 static GstElementDetails gst_videomixer_details =
334 GST_ELEMENT_DETAILS ("video mixer",
335     "Filter/Editor/Video",
336     "Mix multiple video streams",
337     "Wim Taymans <wim@fluendo.com>");
338
339 /* VideoMixer signals and args */
340 enum
341 {
342   /* FILL ME */
343   LAST_SIGNAL
344 };
345
346 #define DEFAULT_BACKGROUND VIDEO_MIXER_BACKGROUND_CHECKER
347 enum
348 {
349   ARG_0,
350   ARG_BACKGROUND,
351 };
352
353 #define GST_TYPE_VIDEO_MIXER_BACKGROUND (gst_video_mixer_background_get_type())
354 static GType
355 gst_video_mixer_background_get_type (void)
356 {
357   static GType video_mixer_background_type = 0;
358   static GEnumValue video_mixer_background[] = {
359     {VIDEO_MIXER_BACKGROUND_CHECKER, "0", "Checker pattern"},
360     {VIDEO_MIXER_BACKGROUND_BLACK, "1", "Black"},
361     {VIDEO_MIXER_BACKGROUND_WHITE, "2", "White"},
362     {0, NULL, NULL},
363   };
364
365   if (!video_mixer_background_type) {
366     video_mixer_background_type =
367         g_enum_register_static ("GstVideoMixerBackground",
368         video_mixer_background);
369   }
370   return video_mixer_background_type;
371 }
372
373 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
374     GST_PAD_SRC,
375     GST_PAD_ALWAYS,
376     GST_STATIC_CAPS ("video/x-raw-yuv,"
377         "format = (fourcc) I420,"
378         "width = (int) [ 16, 4096 ],"
379         "height = (int) [ 16, 4096 ]," "framerate = (double) [ 0, max ]")
380     );
381
382 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%d",
383     GST_PAD_SINK,
384     GST_PAD_REQUEST,
385     GST_STATIC_CAPS ("video/x-raw-yuv,"
386         "format = (fourcc) AYUV,"
387         "width = (int) [ 16, 4096 ],"
388         "height = (int) [ 16, 4096 ]," "framerate = (double) [ 0, max ]")
389     );
390
391 static void gst_videomixer_base_init (gpointer g_class);
392 static void gst_videomixer_class_init (GstVideoMixerClass * klass);
393 static void gst_videomixer_init (GstVideoMixer * videomixer);
394
395 static void gst_videomixer_loop (GstElement * element);
396 static gboolean gst_videomixer_handle_src_event (GstPad * pad,
397     GstEvent * event);
398 static GstPad *gst_videomixer_request_new_pad (GstElement * element,
399     GstPadTemplate * templ, const gchar * name);
400 static void gst_videomixer_set_property (GObject * object, guint prop_id,
401     const GValue * value, GParamSpec * pspec);
402 static void gst_videomixer_get_property (GObject * object, guint prop_id,
403     GValue * value, GParamSpec * pspec);
404 static GstElementStateReturn gst_videomixer_change_state (GstElement * element);
405
406 static GstElementClass *parent_class = NULL;
407
408 /*static guint gst_videomixer_signals[LAST_SIGNAL] = { 0 }; */
409
410 GType
411 gst_videomixer_get_type (void)
412 {
413   static GType videomixer_type = 0;
414
415   if (!videomixer_type) {
416     static const GTypeInfo videomixer_info = {
417       sizeof (GstVideoMixerClass),
418       gst_videomixer_base_init,
419       NULL,
420       (GClassInitFunc) gst_videomixer_class_init,
421       NULL,
422       NULL,
423       sizeof (GstVideoMixer),
424       0,
425       (GInstanceInitFunc) gst_videomixer_init,
426     };
427
428     videomixer_type =
429         g_type_register_static (GST_TYPE_ELEMENT, "GstVideoMixer",
430         &videomixer_info, 0);
431   }
432   return videomixer_type;
433 }
434
435 static void
436 gst_videomixer_base_init (gpointer g_class)
437 {
438   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
439
440   gst_element_class_add_pad_template (element_class,
441       gst_static_pad_template_get (&src_factory));
442   gst_element_class_add_pad_template (element_class,
443       gst_static_pad_template_get (&sink_factory));
444
445   gst_element_class_set_details (element_class, &gst_videomixer_details);
446 }
447
448 static void
449 gst_videomixer_class_init (GstVideoMixerClass * klass)
450 {
451   GObjectClass *gobject_class;
452   GstElementClass *gstelement_class;
453
454   gobject_class = (GObjectClass *) klass;
455   gstelement_class = (GstElementClass *) klass;
456
457   parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
458
459   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BACKGROUND,
460       g_param_spec_enum ("background", "Background", "Background type",
461           GST_TYPE_VIDEO_MIXER_BACKGROUND,
462           DEFAULT_BACKGROUND, G_PARAM_READWRITE));
463
464   gstelement_class->request_new_pad = gst_videomixer_request_new_pad;
465
466   gstelement_class->change_state = gst_videomixer_change_state;
467
468   gstelement_class->get_property = gst_videomixer_get_property;
469   gstelement_class->set_property = gst_videomixer_set_property;
470 }
471
472 static void
473 gst_videomixer_init (GstVideoMixer * mix)
474 {
475   GstElementClass *klass = GST_ELEMENT_GET_CLASS (mix);
476
477   mix->srcpad =
478       gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
479           "src"), "src");
480   gst_pad_set_event_function (mix->srcpad, gst_videomixer_handle_src_event);
481   gst_element_add_pad (GST_ELEMENT (mix), mix->srcpad);
482
483   GST_FLAG_SET (GST_ELEMENT (mix), GST_ELEMENT_EVENT_AWARE);
484
485   mix->sinkpads = NULL;
486   mix->background = DEFAULT_BACKGROUND;
487   mix->in_width = 0;
488   mix->in_height = 0;
489   mix->out_width = 0;
490   mix->out_height = 0;
491
492   gst_element_set_loop_function (GST_ELEMENT (mix), gst_videomixer_loop);
493 }
494
495 static GstPad *
496 gst_videomixer_request_new_pad (GstElement * element,
497     GstPadTemplate * templ, const gchar * req_name)
498 {
499   GstVideoMixer *mix;
500   GstPad *newpad;
501   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
502
503   g_return_val_if_fail (templ != NULL, NULL);
504
505   if (templ->direction != GST_PAD_SINK) {
506     g_warning ("videomixer: request pad that is not a SINK pad\n");
507     return NULL;
508   }
509
510   g_return_val_if_fail (GST_IS_VIDEO_MIXER (element), NULL);
511
512   mix = GST_VIDEO_MIXER (element);
513
514   if (templ == gst_element_class_get_pad_template (klass, "sink_%d")) {
515     gchar *name;
516     GstVideoMixerPad *mixpad;
517
518     /* create new pad with the name */
519     name = g_strdup_printf ("sink_%02d", mix->numpads);
520     newpad =
521         gst_pad_custom_new_from_template (GST_TYPE_VIDEO_MIXER_PAD, templ,
522         name);
523     g_free (name);
524
525     mixpad = GST_VIDEO_MIXER_PAD (newpad);
526
527     mixpad->zorder = mix->numpads;
528     mix->numpads++;
529     if (mix->numpads == 1) {
530       mix->master = mixpad;
531     }
532     mix->sinkpads = g_slist_append (mix->sinkpads, newpad);
533   } else {
534     g_warning ("videomixer: this is not our template!\n");
535     return NULL;
536   }
537
538   /* dd the pad to the element */
539   gst_element_add_pad (element, newpad);
540
541   return newpad;
542 }
543
544 /* handle events */
545 static gboolean
546 gst_videomixer_handle_src_event (GstPad * pad, GstEvent * event)
547 {
548   GstVideoMixer *mix;
549   GstEventType type;
550
551   mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
552
553   type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
554
555   switch (type) {
556     case GST_EVENT_SEEK:
557       /* disable seeking for now */
558       return FALSE;
559     default:
560       break;
561   }
562
563   return gst_pad_event_default (pad, event);
564 }
565
566 #define BLEND_NORMAL(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)     \
567         Y = ((Y1*(255-alpha))+(Y2*alpha))>>8;           \
568         U = ((U1*(255-alpha))+(U2*alpha))>>8;           \
569         V = ((V1*(255-alpha))+(V2*alpha))>>8;
570
571 #define BLEND_ADD(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)                \
572         Y = Y1+((Y2*alpha)>>8);                                 \
573         U = U1+(((127*(255-alpha)+(U2*alpha)))>>8)-127;         \
574         V = V1+(((127*(255-alpha)+(V2*alpha)))>>8)-127;         \
575         if (Y>255) {                                            \
576           gint mult = MAX (0, 288-Y);                           \
577           U = ((U*mult) + (127*(32-mult)))>>5;                  \
578           V = ((V*mult) + (127*(32-mult)))>>5;                  \
579           Y = 255;                                              \
580         }                                                       \
581         U = MIN (U,255)                                         \
582         V = MIN (V,255)
583
584 #define BLEND_SUBTRACT(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)           \
585         Y = Y1-((Y2*alpha)>>8);                                 \
586         U = U1+(((127*(255-alpha)+(U2*alpha)))>>8)-127;         \
587         V = V1+(((127*(255-alpha)+(V2*alpha)))>>8)-127;         \
588         if (Y<0) {                                              \
589           gint mult = MIN (32, -Y);                             \
590           U = ((U*(32-mult)) + (127*mult))>>5;                  \
591           V = ((V*(32-mult)) + (127*mult))>>5;                  \
592           Y = 0;                                                \
593         }
594
595 #define BLEND_DARKEN(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)     \
596         if (Y1 < Y2) {                                  \
597           Y = Y1; U = U1; V = V1;                       \
598         }                                               \
599         else {                                          \
600           Y = ((Y1*(255-alpha))+(Y2*alpha))>>8;         \
601           U = ((U1*(255-alpha))+(U2*alpha))>>8;         \
602           V = ((V1*(255-alpha))+(V2*alpha))>>8;         \
603         }
604
605 #define BLEND_LIGHTEN(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)    \
606         if (Y1 > Y2) {                                  \
607           Y = Y1; U = U1; V = V1;                       \
608         }                                               \
609         else {                                          \
610           Y = ((Y1*(255-alpha))+(Y2*alpha))>>8;         \
611           U = ((U1*(255-alpha))+(U2*alpha))>>8;         \
612           V = ((V1*(255-alpha))+(V2*alpha))>>8;         \
613         }
614
615 #define BLEND_MULTIPLY(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)                   \
616         Y = (Y1*(256*(255-alpha) +(Y2*alpha)))>>16;                     \
617         U = ((U1*(255-alpha)*256)+(alpha*(U1*Y2+128*(256-Y2))))>>16;    \
618         V = ((V1*(255-alpha)*256)+(alpha*(V1*Y2+128*(256-Y2))))>>16;
619
620 #define BLEND_DIFFERENCE(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)         \
621         Y = ABS((gint)Y1-(gint)Y2)+127;                         \
622         U = ABS((gint)U1-(gint)U2)+127;                         \
623         V = ABS((gint)V1-(gint)V2)+127;                         \
624         Y = ((Y*alpha)+(Y1*(255-alpha)))>>8;                    \
625         U = ((U*alpha)+(U1*(255-alpha)))>>8;                    \
626         V = ((V*alpha)+(V1*(255-alpha)))>>8;                    \
627         if (Y>255) {                                            \
628           gint mult = MAX (0, 288-Y);                           \
629           U = ((U*mult) + (127*(32-mult)))>>5;                  \
630           V = ((V*mult) + (127*(32-mult)))>>5;                  \
631           Y = 255;                                              \
632         } else if (Y<0) {                                       \
633           gint mult = MIN (32, -Y);                             \
634           U = ((U*(32-mult)) + (127*mult))>>5;                  \
635           V = ((V*(32-mult)) + (127*mult))>>5;                  \
636           Y = 0;                                                \
637         }                                                       \
638         U = CLAMP(U, 0, 255);                                   \
639         V = CLAMP(V, 0, 255);
640
641 #define BLEND_EXCLUSION(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)          \
642         Y = ((gint)(Y1^0xff)*Y2+(gint)(Y2^0xff)*Y1)>>8;         \
643         U = ((gint)(U1^0xff)*Y2+(gint)(Y2^0xff)*U1)>>8;         \
644         V = ((gint)(V1^0xff)*Y2+(gint)(Y2^0xff)*V1)>>8;         \
645         Y = ((Y*alpha)+(Y1*(255-alpha)))>>8;                    \
646         U = ((U*alpha)+(U1*(255-alpha)))>>8;                    \
647         V = ((V*alpha)+(V1*(255-alpha)))>>8;                    \
648         if (Y>255) {                                            \
649           gint mult = MAX (0, 288-Y);                           \
650           U = ((U*mult) + (127*(32-mult)))>>5;                  \
651           V = ((V*mult) + (127*(32-mult)))>>5;                  \
652           Y = 255;                                              \
653         } else if (Y<0) {                                       \
654           gint mult = MIN (32, -Y);                             \
655           U = ((U*(32-mult)) + (127*mult))>>5;                  \
656           V = ((V*(32-mult)) + (127*mult))>>5;                  \
657           Y = 0;                                                \
658         }                                                       \
659         U = CLAMP(U, 0, 255);                                   \
660         V = CLAMP(V, 0, 255);
661
662 #define BLEND_SOFTLIGHT(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)          \
663         Y = (gint)Y1+(gint)Y2 - 127;                            \
664         U = (gint)U1+(gint)U2 - 127;                            \
665         V = (gint)V1+(gint)V2 - 127;                            \
666         Y = ((Y*alpha)+(Y1*(255-alpha)))>>8;                    \
667         U = ((U*alpha)+(U1*(255-alpha)))>>8;                    \
668         V = ((V*alpha)+(V1*(255-alpha)))>>8;                    \
669         if (Y>255) {                                            \
670           gint mult = MAX (0, 288-Y);                           \
671           U = ((U*mult) + (127*(32-mult)))>>5;                  \
672           V = ((V*mult) + (127*(32-mult)))>>5;                  \
673           Y = 255;                                              \
674         } else if (Y<0) {                                       \
675           gint mult = MIN (32, -Y);                             \
676           U = ((U*(32-mult)) + (127*mult))>>5;                  \
677           V = ((V*(32-mult)) + (127*mult))>>5;                  \
678           Y = 0;                                                \
679         }                                                       \
680
681 #define BLEND_HARDLIGHT(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)          \
682         Y = (gint)Y1+(gint)Y2*2 - 255;                          \
683         U = (gint)U1+(gint)U2 - 127;                            \
684         V = (gint)V1+(gint)V2 - 127;                            \
685         Y = ((Y*alpha)+(Y1*(255-alpha)))>>8;                    \
686         U = ((U*alpha)+(U1*(255-alpha)))>>8;                    \
687         V = ((V*alpha)+(V1*(255-alpha)))>>8;                    \
688         if (Y>255) {                                            \
689           gint mult = MAX (0, 288-Y);                           \
690           U = ((U*mult) + (127*(32-mult)))>>5;                  \
691           V = ((V*mult) + (127*(32-mult)))>>5;                  \
692           Y = 255;                                              \
693         } else if (Y<0) {                                       \
694           gint mult = MIN (32, -Y);                             \
695           U = ((U*(32-mult)) + (127*mult))>>5;                  \
696           V = ((V*(32-mult)) + (127*mult))>>5;                  \
697           Y = 0;                                                \
698         }                                                       \
699
700 #define BLEND_MODE BLEND_NORMAL
701 #if 0
702 #define BLEND_MODE BLEND_ADD
703 #define BLEND_MODE BLEND_SUBTRACT
704 #define BLEND_MODE BLEND_DARKEN
705 #define BLEND_MODE BLEND_LIGHTEN
706 #define BLEND_MODE BLEND_MULTIPLY
707 #define BLEND_MODE BLEND_DIFFERENCE
708 #define BLEND_MODE BLEND_EXCLUSION
709 #define BLEND_MODE BLEND_SOFTLIGHT
710 #define BLEND_MODE BLEND_HARDLIGHT
711 #endif
712
713 /* note that this function does packing conversion and blending at the
714  * same time */
715 static void
716 gst_videomixer_blend_ayuv_i420 (guint8 * src, gint xpos, gint ypos,
717     gint src_width, gint src_height, gdouble src_alpha,
718     guint8 * dest, gint dest_width, gint dest_height)
719 {
720   gint dest_size;
721   gint alpha, b_alpha;
722   guint8 *destY1, *destY2, *destU, *destV;
723   gint accumU;
724   gint accumV;
725   gint i, j;
726   gint src_stride;
727   gint src_add, destY_add, destC_add;
728   guint8 *src1, *src2;
729   gint Y, U, V;
730
731   src_stride = src_width * 4;
732   dest_size = dest_width * dest_height;
733
734   b_alpha = (gint) (src_alpha * 255);
735
736   /* adjust src pointers for negative sizes */
737   if (xpos < 0) {
738     src += -xpos * 4;
739     src_width -= -xpos;
740     xpos = 0;
741   }
742   if (ypos < 0) {
743     src += -ypos * src_stride;
744     src_height -= -ypos;
745     ypos = 0;
746   }
747   /* adjust width/height if the src is bigger than dest */
748   if (xpos + src_width > dest_width) {
749     src_width = dest_width - xpos;
750   }
751   if (ypos + src_height > dest_height) {
752     src_height = dest_height - ypos;
753   }
754
755   src_add = 2 * src_stride - (4 * src_width);
756   destY_add = 2 * dest_width - (src_width);
757   destC_add = dest_width / 2 - (src_width / 2);
758
759   destY1 = dest + xpos + (ypos * dest_width);
760   destY2 = destY1 + dest_width;
761   destU = dest + dest_size + xpos / 2 + (ypos / 2 * dest_width / 2);
762   destV = destU + dest_size / 4;
763
764   src1 = src;
765   src2 = src + src_stride;
766
767   /* we convert a square of 2x2 samples to generate 4 Luma and 2 chroma samples */
768   for (i = 0; i < src_height / 2; i++) {
769     for (j = 0; j < src_width / 2; j++) {
770       alpha = (src1[0] * b_alpha) >> 8;
771       BLEND_MODE (destY1[0], destU[0], destV[0], src1[1], src1[2], src1[3],
772           alpha, Y, U, V);
773       destY1[0] = Y;
774       accumU = U;
775       accumV = V;
776       alpha = (src1[4] * b_alpha) >> 8;
777       BLEND_MODE (destY1[1], destU[0], destV[0], src1[5], src1[6], src1[7],
778           alpha, Y, U, V);
779       destY1[1] = Y;
780       accumU += U;
781       accumV += V;
782       alpha = (src2[0] * b_alpha) >> 8;
783       BLEND_MODE (destY2[0], destU[0], destV[0], src2[1], src2[2], src2[3],
784           alpha, Y, U, V);
785       destY2[0] = Y;
786       accumU += U;
787       accumV += V;
788       alpha = (src2[4] * b_alpha) >> 8;
789       BLEND_MODE (destY2[1], destU[0], destV[0], src2[5], src2[6], src2[7],
790           alpha, Y, U, V);
791       destY2[1] = Y;
792       accumU += U;
793       accumV += V;
794
795       /* take the average of the 4 chroma samples to get the final value */
796       destU[0] = accumU / 4;
797       destV[0] = accumV / 4;
798
799       src1 += 8;
800       src2 += 8;
801       destY1 += 2;
802       destY2 += 2;
803       destU += 1;
804       destV += 1;
805     }
806     src1 += src_add;
807     src2 += src_add;
808     destY1 += destY_add;
809     destY2 += destY_add;
810     destU += destC_add;
811     destV += destC_add;
812   }
813 }
814
815 #undef BLEND_MODE
816
817 /* fill a buffer with a checkerboard pattern */
818 static void
819 gst_videomixer_fill_checker (guint8 * dest, gint width, gint height)
820 {
821   gint size = width * height;
822   gint i, j;
823   static int tab[] = { 80, 160, 80, 160 };
824
825   for (i = 0; i < height; i++) {
826     for (j = 0; j < width; j++) {
827       *dest++ = tab[((i & 0x8) >> 3) + ((j & 0x8) >> 3)];
828     }
829   }
830   memset (dest, 128, size / 2);
831 }
832
833 static void
834 gst_videomixer_fill_color (guint8 * dest, gint width, gint height,
835     gint colY, gint colU, gint colV)
836 {
837   gint size = width * height;
838
839   memset (dest, colY, size);
840   memset (dest + size, colU, size / 4);
841   memset (dest + size + size / 4, colV, size / 4);
842 }
843
844 /* try to get a buffer on all pads. As long as the queued value is
845  * negative, we skip buffers */
846 static gboolean
847 gst_videomixer_fill_queues (GstVideoMixer * mix)
848 {
849   GSList *walk;
850   gboolean eos = TRUE;
851
852   /* loop over all pads and fill it with a buffer */
853   walk = mix->sinkpads;
854   while (walk) {
855     GstVideoMixerPad *pad = GST_VIDEO_MIXER_PAD (walk->data);
856
857     walk = g_slist_next (walk);
858
859     GST_DEBUG ("looking at pad %s", gst_pad_get_name (GST_PAD (pad)));
860
861     /* don't care about eos pads */
862     if (pad->eos) {
863       GST_DEBUG ("pad %s in eos, skipping", gst_pad_get_name (GST_PAD (pad)));
864       continue;
865     }
866
867     GST_DEBUG ("pad %s: buffer %p, queued %lld  ",
868         gst_pad_get_name (GST_PAD (pad)), pad->buffer, pad->queued);
869
870     /* this pad is in need of a new buffer */
871     if (pad->buffer == NULL) {
872       GstData *data;
873       GstBuffer *buffer;
874
875       /* as long as not enough buffers have been queued */
876       while (pad->queued <= 0 && !pad->eos) {
877         data = gst_pad_pull (GST_PAD (pad));
878         if (GST_IS_EVENT (data)) {
879           GstEvent *event = GST_EVENT (data);
880
881           switch (GST_EVENT_TYPE (event)) {
882             case GST_EVENT_EOS:
883               GST_DEBUG ("videomixer: EOS on pad %s",
884                   gst_pad_get_name (GST_PAD (pad)));
885               /* mark pad eos */
886               pad->eos = TRUE;
887               gst_event_unref (event);
888               break;
889             default:
890               gst_pad_event_default (GST_PAD (pad), GST_EVENT (data));
891           }
892         } else {
893           guint64 duration;
894
895           buffer = GST_BUFFER (data);
896           duration = GST_BUFFER_DURATION (buffer);
897           /* no duration on the buffer, use the framerate */
898           if (duration == -1)
899             duration = GST_SECOND / pad->in_framerate;
900           pad->queued += duration;
901           /* this buffer will need to be mixed */
902           if (pad->queued > 0) {
903             pad->buffer = buffer;
904           } else {
905             /* skip buffer, it's too old */
906             gst_buffer_unref (buffer);
907           }
908         }
909         GST_DEBUG ("pad %s: in loop, buffer %p, queued %lld  ",
910             gst_pad_get_name (GST_PAD (pad)), pad->buffer, pad->queued);
911       }
912     }
913     if (pad->buffer != NULL) {
914       /* got a buffer somewhere so were not eos */
915       eos = FALSE;
916     }
917   }
918   return eos;
919 }
920
921 /* blend all buffers present on the pads */
922 static void
923 gst_videomixer_blend_buffers (GstVideoMixer * mix, GstBuffer * outbuf)
924 {
925   GSList *walk;
926
927   walk = mix->sinkpads;
928   while (walk) {
929     GstVideoMixerPad *pad = GST_VIDEO_MIXER_PAD (walk->data);
930
931     walk = g_slist_next (walk);
932
933     if (pad->buffer != NULL) {
934       gst_videomixer_blend_ayuv_i420 (GST_BUFFER_DATA (pad->buffer),
935           pad->xpos, pad->ypos,
936           pad->in_width, pad->in_height,
937           pad->alpha,
938           GST_BUFFER_DATA (outbuf), mix->out_width, mix->out_height);
939     }
940   }
941 }
942
943 /* remove buffers from the queue that were expired in the
944  * interval of the master, we also prepare the queued value
945  * in the pad so that we can skip and fill buffers later on */
946 static void
947 gst_videomixer_update_queues (GstVideoMixer * mix)
948 {
949   GSList *walk;
950   guint64 interval;
951
952   interval = mix->master->queued;
953   if (interval <= 0) {
954     interval = GST_SECOND / mix->in_framerate;
955   }
956
957   walk = mix->sinkpads;
958   while (walk) {
959     GstVideoMixerPad *pad = GST_VIDEO_MIXER_PAD (walk->data);
960
961     walk = g_slist_next (walk);
962
963     if (pad->buffer != NULL) {
964       pad->queued -= interval;
965       GST_DEBUG ("queued now %s %lld", gst_pad_get_name (GST_PAD (pad)),
966           pad->queued);
967       if (pad->queued <= 0) {
968         gst_buffer_unref (pad->buffer);
969         pad->buffer = NULL;
970       }
971     }
972   }
973 }
974
975 /*
976  * The basic idea is to get a buffer on all pads and mix them together.
977  * Based on the framerate, buffers are removed from the queues to make room
978  * for a new buffer. 
979  */
980 static void
981 gst_videomixer_loop (GstElement * element)
982 {
983   GstVideoMixer *mix;
984   GstBuffer *outbuf;
985   gint outsize;
986   gint new_width, new_height;
987   gboolean eos;
988
989   mix = GST_VIDEO_MIXER (element);
990
991   eos = gst_videomixer_fill_queues (mix);
992   if (eos) {
993     gst_pad_push (mix->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
994     gst_element_set_eos (GST_ELEMENT (mix));
995     return;
996   }
997
998   new_width = mix->in_width;
999   new_height = mix->in_height;
1000
1001   if (new_width != mix->out_width ||
1002       new_height != mix->out_height || !GST_PAD_CAPS (mix->srcpad)) {
1003     GstCaps *newcaps;
1004
1005     newcaps =
1006         gst_caps_copy (gst_pad_get_negotiated_caps (GST_PAD (mix->master)));
1007     gst_caps_set_simple (newcaps, "format", GST_TYPE_FOURCC,
1008         GST_STR_FOURCC ("I420"), "width", G_TYPE_INT, new_width, "height",
1009         G_TYPE_INT, new_height, NULL);
1010
1011     if (!gst_pad_try_set_caps (mix->srcpad, newcaps)) {
1012       GST_ELEMENT_ERROR (mix, CORE, NEGOTIATION, (NULL), (NULL));
1013       return;
1014     }
1015
1016     mix->out_width = new_width;
1017     mix->out_height = new_height;
1018   }
1019
1020   outsize = 3 * (mix->out_width * mix->out_height) / 2;
1021   outbuf = gst_pad_alloc_buffer (mix->srcpad, GST_BUFFER_OFFSET_NONE, outsize);
1022   switch (mix->background) {
1023     case VIDEO_MIXER_BACKGROUND_CHECKER:
1024       gst_videomixer_fill_checker (GST_BUFFER_DATA (outbuf),
1025           new_width, new_height);
1026       break;
1027     case VIDEO_MIXER_BACKGROUND_BLACK:
1028       gst_videomixer_fill_color (GST_BUFFER_DATA (outbuf),
1029           new_width, new_height, 16, 128, 128);
1030       break;
1031     case VIDEO_MIXER_BACKGROUND_WHITE:
1032       gst_videomixer_fill_color (GST_BUFFER_DATA (outbuf),
1033           new_width, new_height, 240, 128, 128);
1034       break;
1035   }
1036
1037   gst_videomixer_blend_buffers (mix, outbuf);
1038
1039   gst_videomixer_update_queues (mix);
1040
1041   gst_pad_push (mix->srcpad, GST_DATA (outbuf));
1042 }
1043
1044 static void
1045 gst_videomixer_get_property (GObject * object,
1046     guint prop_id, GValue * value, GParamSpec * pspec)
1047 {
1048   GstVideoMixer *mix = GST_VIDEO_MIXER (object);
1049
1050   switch (prop_id) {
1051     case ARG_BACKGROUND:
1052       g_value_set_enum (value, mix->background);
1053       break;
1054     default:
1055       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1056       break;
1057   }
1058 }
1059
1060 static void
1061 gst_videomixer_set_property (GObject * object,
1062     guint prop_id, const GValue * value, GParamSpec * pspec)
1063 {
1064   GstVideoMixer *mix = GST_VIDEO_MIXER (object);
1065
1066   switch (prop_id) {
1067     case ARG_BACKGROUND:
1068       mix->background = g_value_get_enum (value);
1069       break;
1070     default:
1071       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1072       break;
1073   }
1074 }
1075
1076 static GstElementStateReturn
1077 gst_videomixer_change_state (GstElement * element)
1078 {
1079   GstVideoMixer *mix;
1080   gint transition = GST_STATE_TRANSITION (element);
1081
1082   g_return_val_if_fail (GST_IS_VIDEO_MIXER (element), GST_STATE_FAILURE);
1083
1084   mix = GST_VIDEO_MIXER (element);
1085
1086   switch (transition) {
1087     case GST_STATE_NULL_TO_READY:
1088     case GST_STATE_READY_TO_PAUSED:
1089       break;
1090     case GST_STATE_PAUSED_TO_PLAYING:
1091     case GST_STATE_PLAYING_TO_PAUSED:
1092     case GST_STATE_PAUSED_TO_READY:
1093     case GST_STATE_READY_TO_NULL:
1094       break;
1095   }
1096
1097   if (GST_ELEMENT_CLASS (parent_class)->change_state)
1098     return GST_ELEMENT_CLASS (parent_class)->change_state (element);
1099
1100   return GST_STATE_SUCCESS;
1101 }
1102
1103 static gboolean
1104 plugin_init (GstPlugin * plugin)
1105 {
1106   GST_DEBUG_CATEGORY_INIT (gst_videomixer_debug, "videomixer", 0,
1107       "video mixer");
1108
1109   return gst_element_register (plugin, "videomixer", GST_RANK_PRIMARY,
1110       GST_TYPE_VIDEO_MIXER);
1111 }
1112
1113 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1114     GST_VERSION_MINOR,
1115     "videomixer",
1116     "Video mixer", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)