00e843dfc984c3b939f1f939d621b255c01164c2
[platform/upstream/gstreamer.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 /**
21  * SECTION:element-videomixer
22  *
23  * Videomixer can accept AYUV, ARGB and BGRA video streams. For each of the requested
24  * sink pads it will compare the incoming geometry and framerate to define the
25  * output parameters. Indeed output video frames will have the geometry of the
26  * biggest incoming video stream and the framerate of the fastest incoming one.
27  *
28  * All sink pads must be either AYUV, ARGB or BGRA, but a mixture of them is not 
29  * supported. The src pad will have the same colorspace as the sinks. 
30  * No colorspace conversion is done. 
31  * 
32  *
33  * Individual parameters for each input stream can be configured on the
34  * #GstVideoMixerPad.
35  *
36  * <refsect2>
37  * <title>Sample pipelines</title>
38  * |[
39  * gst-launch videotestsrc pattern=1 ! video/x-raw-yuv,format=\(fourcc\)AYUV, framerate=\(fraction\)10/1, width=100, height=100 ! videobox border-alpha=0 alpha=0.5 top=-70 bottom=-70 right=-220 ! videomixer name=mix ! ffmpegcolorspace ! xvimagesink videotestsrc ! video/x-raw-yuv, format=\(fourcc\)AYUV, framerate=\(fraction\)5/1, width=320, height=240 ! alpha alpha=0.7 ! mix.
40  * ]| A pipeline to demonstrate videomixer used together with videobox.
41  * This should show a 320x240 pixels video test source with some transparency
42  * showing the background checker pattern. Another video test source with just
43  * the snow pattern of 100x100 pixels is overlayed on top of the first one on
44  * the left vertically centered with a small transparency showing the first
45  * video test source behind and the checker pattern under it. Note that the
46  * framerate of the output video is 10 frames per second.
47  * |[
48  * gst-launch videotestsrc pattern=1 ! video/x-raw-rgb, framerate=\(fraction\)10/1, width=100, height=100 ! videomixer name=mix ! ffmpegcolorspace ! ximagesink videotestsrc ! video/x-raw-rgb, framerate=\(fraction\)5/1, width=320, height=240 ! mix.
49  * ]| A pipeline to demostrate bgra mixing. (This does not demonstrate alpha blending). 
50  * |[
51  * gst-launch   videotestsrc pattern=1 ! video/x-raw-yuv,format =\(fourcc\)I420, framerate=\(fraction\)10/1, width=100, height=100 ! videomixer name=mix ! ffmpegcolorspace ! ximagesink videotestsrc ! video/x-raw-yuv,format=\(fourcc\)I420, framerate=\(fraction\)5/1, width=320, height=240 ! mix.
52  * ]| A pipeline to test I420
53  * </refsect2>
54  */
55
56 #ifdef HAVE_CONFIG_H
57 #include "config.h"
58 #endif
59
60 #ifdef HAVE_GCC_ASM
61 #if defined(HAVE_CPU_I386) || defined(HAVE_CPU_X86_64)
62 #define BUILD_X86_ASM
63 #endif
64 #endif
65
66 #include <gst/gst.h>
67 #include <gst/base/gstcollectpads.h>
68 #include <gst/controller/gstcontroller.h>
69 #include <gst/video/video.h>
70
71 #ifdef HAVE_STDLIB_H
72 #include <stdlib.h>
73 #endif
74 #ifdef HAVE_STRING_H
75 #include <string.h>
76 #endif
77
78 #include "videomixer.h"
79
80 GST_DEBUG_CATEGORY_STATIC (gst_videomixer_debug);
81 #define GST_CAT_DEFAULT gst_videomixer_debug
82
83 #define GST_VIDEO_MIXER_GET_STATE_LOCK(mix) \
84   (GST_VIDEO_MIXER(mix)->state_lock)
85 #define GST_VIDEO_MIXER_STATE_LOCK(mix) \
86   (g_mutex_lock(GST_VIDEO_MIXER_GET_STATE_LOCK (mix)))
87 #define GST_VIDEO_MIXER_STATE_UNLOCK(mix) \
88   (g_mutex_unlock(GST_VIDEO_MIXER_GET_STATE_LOCK (mix)))
89
90 static GType gst_videomixer_get_type (void);
91
92 static void gst_videomixer_pad_get_property (GObject * object, guint prop_id,
93     GValue * value, GParamSpec * pspec);
94 static void gst_videomixer_pad_set_property (GObject * object, guint prop_id,
95     const GValue * value, GParamSpec * pspec);
96
97 static gboolean gst_videomixer_src_event (GstPad * pad, GstEvent * event);
98 static gboolean gst_videomixer_sink_event (GstPad * pad, GstEvent * event);
99
100 static void gst_videomixer_sort_pads (GstVideoMixer * mix);
101
102 #define DEFAULT_PAD_ZORDER 0
103 #define DEFAULT_PAD_XPOS   0
104 #define DEFAULT_PAD_YPOS   0
105 #define DEFAULT_PAD_ALPHA  1.0
106 enum
107 {
108   PROP_PAD_0,
109   PROP_PAD_ZORDER,
110   PROP_PAD_XPOS,
111   PROP_PAD_YPOS,
112   PROP_PAD_ALPHA
113 };
114
115 GType gst_videomixer_pad_get_type (void);
116 G_DEFINE_TYPE (GstVideoMixerPad, gst_videomixer_pad, GST_TYPE_PAD);
117
118 static void
119 gst_videomixer_pad_class_init (GstVideoMixerPadClass * klass)
120 {
121   GObjectClass *gobject_class = (GObjectClass *) klass;
122
123   gobject_class->set_property = gst_videomixer_pad_set_property;
124   gobject_class->get_property = gst_videomixer_pad_get_property;
125
126   g_object_class_install_property (gobject_class, PROP_PAD_ZORDER,
127       g_param_spec_uint ("zorder", "Z-Order", "Z Order of the picture",
128           0, 10000, DEFAULT_PAD_ZORDER,
129           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
130   g_object_class_install_property (gobject_class, PROP_PAD_XPOS,
131       g_param_spec_int ("xpos", "X Position", "X Position of the picture",
132           G_MININT, G_MAXINT, DEFAULT_PAD_XPOS,
133           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
134   g_object_class_install_property (gobject_class, PROP_PAD_YPOS,
135       g_param_spec_int ("ypos", "Y Position", "Y Position of the picture",
136           G_MININT, G_MAXINT, DEFAULT_PAD_YPOS,
137           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
138   g_object_class_install_property (gobject_class, PROP_PAD_ALPHA,
139       g_param_spec_double ("alpha", "Alpha", "Alpha of the picture", 0.0, 1.0,
140           DEFAULT_PAD_ALPHA,
141           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
142 }
143
144 static void
145 gst_videomixer_pad_get_property (GObject * object, guint prop_id,
146     GValue * value, GParamSpec * pspec)
147 {
148   GstVideoMixerPad *pad = GST_VIDEO_MIXER_PAD (object);
149
150   switch (prop_id) {
151     case PROP_PAD_ZORDER:
152       g_value_set_uint (value, pad->zorder);
153       break;
154     case PROP_PAD_XPOS:
155       g_value_set_int (value, pad->xpos);
156       break;
157     case PROP_PAD_YPOS:
158       g_value_set_int (value, pad->ypos);
159       break;
160     case PROP_PAD_ALPHA:
161       g_value_set_double (value, pad->alpha);
162       break;
163     default:
164       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
165       break;
166   }
167 }
168
169 static void
170 gst_videomixer_pad_set_property (GObject * object, guint prop_id,
171     const GValue * value, GParamSpec * pspec)
172 {
173   GstVideoMixerPad *pad = GST_VIDEO_MIXER_PAD (object);
174   GstVideoMixer *mix = GST_VIDEO_MIXER (gst_pad_get_parent (GST_PAD (pad)));
175
176   switch (prop_id) {
177     case PROP_PAD_ZORDER:
178       GST_VIDEO_MIXER_STATE_LOCK (mix);
179       pad->zorder = g_value_get_uint (value);
180       gst_videomixer_sort_pads (mix);
181       GST_VIDEO_MIXER_STATE_UNLOCK (mix);
182       break;
183     case PROP_PAD_XPOS:
184       pad->xpos = g_value_get_int (value);
185       break;
186     case PROP_PAD_YPOS:
187       pad->ypos = g_value_get_int (value);
188       break;
189     case PROP_PAD_ALPHA:
190       pad->alpha = g_value_get_double (value);
191       break;
192     default:
193       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
194       break;
195   }
196
197   gst_object_unref (mix);
198 }
199
200 static void
201 gst_videomixer_update_qos (GstVideoMixer * mix, gdouble proportion,
202     GstClockTimeDiff diff, GstClockTime timestamp)
203 {
204   GST_DEBUG_OBJECT (mix,
205       "Updating QoS: proportion %lf, diff %s%" GST_TIME_FORMAT ", timestamp %"
206       GST_TIME_FORMAT, proportion, (diff < 0) ? "-" : "",
207       GST_TIME_ARGS (ABS (diff)), GST_TIME_ARGS (timestamp));
208
209   GST_OBJECT_LOCK (mix);
210   mix->proportion = proportion;
211   if (G_LIKELY (timestamp != GST_CLOCK_TIME_NONE)) {
212     if (G_UNLIKELY (diff > 0))
213       mix->earliest_time =
214           timestamp + 2 * diff + gst_util_uint64_scale_int (GST_SECOND,
215           mix->fps_d, mix->fps_n);
216     else
217       mix->earliest_time = timestamp + diff;
218   } else {
219     mix->earliest_time = GST_CLOCK_TIME_NONE;
220   }
221   GST_OBJECT_UNLOCK (mix);
222 }
223
224 static void
225 gst_videomixer_reset_qos (GstVideoMixer * mix)
226 {
227   gst_videomixer_update_qos (mix, 0.5, 0, GST_CLOCK_TIME_NONE);
228 }
229
230 static void
231 gst_videomixer_read_qos (GstVideoMixer * mix, gdouble * proportion,
232     GstClockTime * time)
233 {
234   GST_OBJECT_LOCK (mix);
235   *proportion = mix->proportion;
236   *time = mix->earliest_time;
237   GST_OBJECT_UNLOCK (mix);
238 }
239
240 /* Perform qos calculations before processing the next frame. Returns TRUE if
241  * the frame should be processed, FALSE if the frame can be dropped entirely */
242 static gboolean
243 gst_videomixer_do_qos (GstVideoMixer * mix, GstClockTime timestamp)
244 {
245   GstClockTime qostime, earliest_time;
246   gdouble proportion;
247
248   /* no timestamp, can't do QoS => process frame */
249   if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (timestamp))) {
250     GST_LOG_OBJECT (mix, "invalid timestamp, can't do QoS, process frame");
251     return TRUE;
252   }
253
254   /* get latest QoS observation values */
255   gst_videomixer_read_qos (mix, &proportion, &earliest_time);
256
257   /* skip qos if we have no observation (yet) => process frame */
258   if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (earliest_time))) {
259     GST_LOG_OBJECT (mix, "no observation yet, process frame");
260     return TRUE;
261   }
262
263   /* qos is done on running time */
264   qostime =
265       gst_segment_to_running_time (&mix->segment, GST_FORMAT_TIME, timestamp);
266
267   /* see how our next timestamp relates to the latest qos timestamp */
268   GST_LOG_OBJECT (mix, "qostime %" GST_TIME_FORMAT ", earliest %"
269       GST_TIME_FORMAT, GST_TIME_ARGS (qostime), GST_TIME_ARGS (earliest_time));
270
271   if (qostime != GST_CLOCK_TIME_NONE && qostime <= earliest_time) {
272     GST_DEBUG_OBJECT (mix, "we are late, drop frame");
273     return FALSE;
274   }
275
276   GST_LOG_OBJECT (mix, "process frame");
277   return TRUE;
278 }
279
280 static void
281 gst_videomixer_set_master_geometry (GstVideoMixer * mix)
282 {
283   GSList *walk;
284   gint width = 0, height = 0, fps_n = 0, fps_d = 0, par_n = 0, par_d = 0;
285   GstVideoMixerPad *master = NULL;
286
287   walk = mix->sinkpads;
288   while (walk) {
289     GstVideoMixerPad *mixpad = GST_VIDEO_MIXER_PAD (walk->data);
290
291     walk = g_slist_next (walk);
292
293     /* Biggest input geometry will be our output geometry */
294     width = MAX (width, mixpad->in_width);
295     height = MAX (height, mixpad->in_height);
296
297     /* If mix framerate < mixpad framerate, using fractions */
298     GST_DEBUG_OBJECT (mixpad, "comparing framerate %d/%d to mixpad's %d/%d",
299         fps_n, fps_d, mixpad->fps_n, mixpad->fps_d);
300     if ((!fps_n && !fps_d) ||
301         ((gint64) fps_n * mixpad->fps_d < (gint64) mixpad->fps_n * fps_d)) {
302       fps_n = mixpad->fps_n;
303       fps_d = mixpad->fps_d;
304       par_n = mixpad->par_n;
305       par_d = mixpad->par_d;
306       GST_DEBUG_OBJECT (mixpad, "becomes the master pad");
307       master = mixpad;
308     }
309   }
310
311   /* set results */
312   if (mix->master != master || mix->in_width != width
313       || mix->in_height != height || mix->fps_n != fps_n
314       || mix->fps_d != fps_d || mix->par_n != par_n || mix->par_d != par_d) {
315     mix->setcaps = TRUE;
316     mix->sendseg = TRUE;
317     gst_videomixer_reset_qos (mix);
318     mix->master = master;
319     mix->in_width = width;
320     mix->in_height = height;
321     mix->fps_n = fps_n;
322     mix->fps_d = fps_d;
323     mix->par_n = par_n;
324     mix->par_d = par_d;
325   }
326 }
327
328 static gboolean
329 gst_videomixer_pad_sink_setcaps (GstPad * pad, GstCaps * vscaps)
330 {
331   GstVideoMixer *mix;
332   GstVideoMixerPad *mixpad;
333   GstStructure *structure;
334   gint in_width, in_height;
335   gboolean ret = FALSE;
336   const GValue *framerate, *par;
337
338   GST_INFO_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, vscaps);
339
340   mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
341   mixpad = GST_VIDEO_MIXER_PAD (pad);
342
343   if (!mixpad)
344     goto beach;
345
346   structure = gst_caps_get_structure (vscaps, 0);
347
348   if (!gst_structure_get_int (structure, "width", &in_width)
349       || !gst_structure_get_int (structure, "height", &in_height)
350       || (framerate = gst_structure_get_value (structure, "framerate")) == NULL)
351     goto beach;
352   par = gst_structure_get_value (structure, "pixel-aspect-ratio");
353
354   GST_VIDEO_MIXER_STATE_LOCK (mix);
355   mixpad->fps_n = gst_value_get_fraction_numerator (framerate);
356   mixpad->fps_d = gst_value_get_fraction_denominator (framerate);
357   if (par) {
358     mixpad->par_n = gst_value_get_fraction_numerator (par);
359     mixpad->par_d = gst_value_get_fraction_denominator (par);
360   } else {
361     mixpad->par_n = mixpad->par_d = 1;
362   }
363
364   mixpad->in_width = in_width;
365   mixpad->in_height = in_height;
366
367   gst_videomixer_set_master_geometry (mix);
368   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
369
370   ret = TRUE;
371
372 beach:
373   gst_object_unref (mix);
374
375   return ret;
376 }
377
378 static GstCaps *
379 gst_videomixer_pad_sink_getcaps (GstPad * pad)
380 {
381   GstVideoMixer *mix;
382   GstVideoMixerPad *mixpad;
383   GstCaps *res = NULL;
384   GstCaps *mastercaps;
385   GstStructure *st;
386
387   mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
388   mixpad = GST_VIDEO_MIXER_PAD (pad);
389
390   if (!mixpad)
391     goto beach;
392
393   /* Get downstream allowed caps */
394   res = gst_pad_get_allowed_caps (mix->srcpad);
395   if (G_UNLIKELY (res == NULL)) {
396     res = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
397     goto beach;
398   }
399
400   GST_VIDEO_MIXER_STATE_LOCK (mix);
401
402   /* Return as-is if not other sinkpad set as master */
403   if (mix->master == NULL) {
404     GST_VIDEO_MIXER_STATE_UNLOCK (mix);
405     goto beach;
406   }
407
408   mastercaps = gst_pad_get_fixed_caps_func (GST_PAD (mix->master));
409
410   /* If master pad caps aren't negotiated yet, return downstream
411    * allowed caps */
412   if (!GST_CAPS_IS_SIMPLE (mastercaps)) {
413     GST_VIDEO_MIXER_STATE_UNLOCK (mix);
414     gst_caps_unref (mastercaps);
415     goto beach;
416   }
417
418   gst_caps_unref (res);
419   res = gst_caps_make_writable (mastercaps);
420   st = gst_caps_get_structure (res, 0);
421   gst_structure_set (st, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
422       "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
423       "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
424   if (!gst_structure_has_field (st, "pixel-aspect-ratio"))
425     gst_structure_set (st, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL);
426
427   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
428
429
430 beach:
431   GST_DEBUG_OBJECT (pad, "Returning %" GST_PTR_FORMAT, res);
432
433   return res;
434 }
435
436 /*
437 * We accept the caps if it has the same format as other sink pads in 
438 * the element.
439 */
440 static gboolean
441 gst_videomixer_pad_sink_acceptcaps (GstPad * pad, GstCaps * vscaps)
442 {
443   gboolean ret;
444   GstVideoMixer *mix;
445   GstCaps *acceptedCaps;
446
447   mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
448   GST_DEBUG_OBJECT (pad, "%" GST_PTR_FORMAT, vscaps);
449   GST_VIDEO_MIXER_STATE_LOCK (mix);
450
451   if (mix->master) {
452     acceptedCaps = gst_pad_get_fixed_caps_func (GST_PAD (mix->master));
453     acceptedCaps = gst_caps_make_writable (acceptedCaps);
454     GST_LOG_OBJECT (pad, "master's caps %" GST_PTR_FORMAT, acceptedCaps);
455     if (GST_CAPS_IS_SIMPLE (acceptedCaps)) {
456       GstStructure *s;
457       s = gst_caps_get_structure (acceptedCaps, 0);
458       gst_structure_set (s, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
459           "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
460           "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
461       if (!gst_structure_has_field (s, "pixel-aspect-ratio"))
462         gst_structure_set (s, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
463             NULL);
464     }
465   } else {
466     acceptedCaps = gst_pad_get_fixed_caps_func (pad);
467   }
468
469   GST_INFO_OBJECT (pad, "vscaps: %" GST_PTR_FORMAT, vscaps);
470   GST_INFO_OBJECT (pad, "acceptedCaps: %" GST_PTR_FORMAT, acceptedCaps);
471
472   ret = gst_caps_can_intersect (vscaps, acceptedCaps);
473   GST_INFO_OBJECT (pad, "%saccepted caps %" GST_PTR_FORMAT, (ret ? "" : "not "),
474       vscaps);
475   gst_caps_unref (acceptedCaps);
476   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
477   gst_object_unref (mix);
478   return ret;
479 }
480
481
482
483 static void
484 gst_videomixer_pad_init (GstVideoMixerPad * mixerpad)
485 {
486   /* setup some pad functions */
487   gst_pad_set_setcaps_function (GST_PAD (mixerpad),
488       gst_videomixer_pad_sink_setcaps);
489   gst_pad_set_acceptcaps_function (GST_PAD (mixerpad),
490       GST_DEBUG_FUNCPTR (gst_videomixer_pad_sink_acceptcaps));
491   gst_pad_set_getcaps_function (GST_PAD (mixerpad),
492       gst_videomixer_pad_sink_getcaps);
493
494   mixerpad->zorder = DEFAULT_PAD_ZORDER;
495   mixerpad->xpos = DEFAULT_PAD_XPOS;
496   mixerpad->ypos = DEFAULT_PAD_YPOS;
497   mixerpad->alpha = DEFAULT_PAD_ALPHA;
498 }
499
500 /* VideoMixer signals and args */
501 enum
502 {
503   /* FILL ME */
504   LAST_SIGNAL
505 };
506
507 #define DEFAULT_BACKGROUND VIDEO_MIXER_BACKGROUND_CHECKER
508 enum
509 {
510   PROP_0,
511   PROP_BACKGROUND
512 };
513
514 #define GST_TYPE_VIDEO_MIXER_BACKGROUND (gst_video_mixer_background_get_type())
515 static GType
516 gst_video_mixer_background_get_type (void)
517 {
518   static GType video_mixer_background_type = 0;
519
520   static const GEnumValue video_mixer_background[] = {
521     {VIDEO_MIXER_BACKGROUND_CHECKER, "Checker pattern", "checker"},
522     {VIDEO_MIXER_BACKGROUND_BLACK, "Black", "black"},
523     {VIDEO_MIXER_BACKGROUND_WHITE, "White", "white"},
524     {0, NULL, NULL},
525   };
526
527   if (!video_mixer_background_type) {
528     video_mixer_background_type =
529         g_enum_register_static ("GstVideoMixerBackground",
530         video_mixer_background);
531   }
532   return video_mixer_background_type;
533 }
534
535 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
536     GST_PAD_SRC,
537     GST_PAD_ALWAYS,
538     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") ";" GST_VIDEO_CAPS_BGRA ";"
539         GST_VIDEO_CAPS_ARGB ";" GST_VIDEO_CAPS_RGBA ";" GST_VIDEO_CAPS_ABGR ";"
540         GST_VIDEO_CAPS_YUV ("Y444") ";" GST_VIDEO_CAPS_YUV ("Y42B") ";"
541         GST_VIDEO_CAPS_YUV ("YUY2") ";" GST_VIDEO_CAPS_YUV ("UYVY") ";"
542         GST_VIDEO_CAPS_YUV ("YVYU") ";"
543         GST_VIDEO_CAPS_YUV ("I420") ";" GST_VIDEO_CAPS_YUV ("YV12") ";"
544         GST_VIDEO_CAPS_YUV ("Y41B") ";" GST_VIDEO_CAPS_RGB ";"
545         GST_VIDEO_CAPS_BGR ";" GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR ";"
546         GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx)
547     );
548
549 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%d",
550     GST_PAD_SINK,
551     GST_PAD_REQUEST,
552     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") ";" GST_VIDEO_CAPS_BGRA ";"
553         GST_VIDEO_CAPS_ARGB ";" GST_VIDEO_CAPS_RGBA ";" GST_VIDEO_CAPS_ABGR ";"
554         GST_VIDEO_CAPS_YUV ("Y444") ";" GST_VIDEO_CAPS_YUV ("Y42B") ";"
555         GST_VIDEO_CAPS_YUV ("YUY2") ";" GST_VIDEO_CAPS_YUV ("UYVY") ";"
556         GST_VIDEO_CAPS_YUV ("YVYU") ";"
557         GST_VIDEO_CAPS_YUV ("I420") ";" GST_VIDEO_CAPS_YUV ("YV12") ";"
558         GST_VIDEO_CAPS_YUV ("Y41B") ";" GST_VIDEO_CAPS_RGB ";"
559         GST_VIDEO_CAPS_BGR ";" GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR ";"
560         GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx)
561     );
562
563 static void gst_videomixer_finalize (GObject * object);
564
565 static GstCaps *gst_videomixer_getcaps (GstPad * pad);
566 static gboolean gst_videomixer_setcaps (GstPad * pad, GstCaps * caps);
567 static gboolean gst_videomixer_query (GstPad * pad, GstQuery * query);
568
569 static GstFlowReturn gst_videomixer_collected (GstCollectPads * pads,
570     GstVideoMixer * mix);
571 static GstPad *gst_videomixer_request_new_pad (GstElement * element,
572     GstPadTemplate * templ, const gchar * name);
573 static void gst_videomixer_release_pad (GstElement * element, GstPad * pad);
574
575 static void gst_videomixer_set_property (GObject * object, guint prop_id,
576     const GValue * value, GParamSpec * pspec);
577 static void gst_videomixer_get_property (GObject * object, guint prop_id,
578     GValue * value, GParamSpec * pspec);
579 static GstStateChangeReturn gst_videomixer_change_state (GstElement * element,
580     GstStateChange transition);
581
582 /*static guint gst_videomixer_signals[LAST_SIGNAL] = { 0 }; */
583
584 static void gst_videomixer_child_proxy_init (gpointer g_iface,
585     gpointer iface_data);
586 static void _do_init (GType object_type);
587
588 GST_BOILERPLATE_FULL (GstVideoMixer, gst_videomixer, GstElement,
589     GST_TYPE_ELEMENT, _do_init);
590
591 static void
592 _do_init (GType object_type)
593 {
594   static const GInterfaceInfo child_proxy_info = {
595     (GInterfaceInitFunc) gst_videomixer_child_proxy_init,
596     NULL,
597     NULL
598   };
599
600   g_type_add_interface_static (object_type, GST_TYPE_CHILD_PROXY,
601       &child_proxy_info);
602   GST_INFO ("GstChildProxy interface registered");
603 }
604
605 static GstObject *
606 gst_videomixer_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
607     guint index)
608 {
609   GstVideoMixer *mix = GST_VIDEO_MIXER (child_proxy);
610   GstObject *obj;
611
612   GST_VIDEO_MIXER_STATE_LOCK (mix);
613   if ((obj = g_slist_nth_data (mix->sinkpads, index)))
614     gst_object_ref (obj);
615   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
616   return obj;
617 }
618
619 static guint
620 gst_videomixer_child_proxy_get_children_count (GstChildProxy * child_proxy)
621 {
622   guint count = 0;
623   GstVideoMixer *mix = GST_VIDEO_MIXER (child_proxy);
624
625   GST_VIDEO_MIXER_STATE_LOCK (mix);
626   count = mix->numpads;
627   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
628   GST_INFO_OBJECT (mix, "Children Count: %d", count);
629   return count;
630 }
631
632 static void
633 gst_videomixer_child_proxy_init (gpointer g_iface, gpointer iface_data)
634 {
635   GstChildProxyInterface *iface = g_iface;
636
637   GST_INFO ("intializing child proxy interface");
638   iface->get_child_by_index = gst_videomixer_child_proxy_get_child_by_index;
639   iface->get_children_count = gst_videomixer_child_proxy_get_children_count;
640 }
641
642 static void
643 gst_videomixer_base_init (gpointer g_class)
644 {
645   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
646
647   gst_element_class_add_pad_template (element_class,
648       gst_static_pad_template_get (&src_factory));
649   gst_element_class_add_pad_template (element_class,
650       gst_static_pad_template_get (&sink_factory));
651
652   gst_element_class_set_details_simple (element_class, "Video mixer",
653       "Filter/Editor/Video",
654       "Mix multiple video streams", "Wim Taymans <wim@fluendo.com>");
655 }
656
657 static void
658 gst_videomixer_class_init (GstVideoMixerClass * klass)
659 {
660   GObjectClass *gobject_class = (GObjectClass *) klass;
661   GstElementClass *gstelement_class = (GstElementClass *) klass;
662
663   gobject_class->finalize = gst_videomixer_finalize;
664
665   gobject_class->get_property = gst_videomixer_get_property;
666   gobject_class->set_property = gst_videomixer_set_property;
667
668   g_object_class_install_property (gobject_class, PROP_BACKGROUND,
669       g_param_spec_enum ("background", "Background", "Background type",
670           GST_TYPE_VIDEO_MIXER_BACKGROUND,
671           DEFAULT_BACKGROUND, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
672
673   gstelement_class->request_new_pad =
674       GST_DEBUG_FUNCPTR (gst_videomixer_request_new_pad);
675   gstelement_class->release_pad =
676       GST_DEBUG_FUNCPTR (gst_videomixer_release_pad);
677   gstelement_class->change_state =
678       GST_DEBUG_FUNCPTR (gst_videomixer_change_state);
679
680   /* Register the pad class */
681   (void) (GST_TYPE_VIDEO_MIXER_PAD);
682   /* Register the background enum */
683   (void) (GST_TYPE_VIDEO_MIXER_BACKGROUND);
684 }
685
686 static void
687 gst_videomixer_collect_free (GstVideoMixerCollect * mixcol)
688 {
689   if (mixcol->buffer) {
690     gst_buffer_unref (mixcol->buffer);
691     mixcol->buffer = NULL;
692   }
693 }
694
695 static void
696 gst_videomixer_reset (GstVideoMixer * mix)
697 {
698   GSList *walk;
699
700   mix->in_width = 0;
701   mix->in_height = 0;
702   mix->out_width = 0;
703   mix->out_height = 0;
704   mix->fps_n = mix->fps_d = 0;
705   mix->par_n = mix->par_d = 1;
706   mix->setcaps = FALSE;
707   mix->sendseg = FALSE;
708
709   mix->segment_position = 0;
710   gst_segment_init (&mix->segment, GST_FORMAT_TIME);
711
712   gst_videomixer_reset_qos (mix);
713
714   mix->fmt = GST_VIDEO_FORMAT_UNKNOWN;
715
716   mix->last_ts = 0;
717   mix->last_duration = -1;
718
719   /* clean up collect data */
720   walk = mix->collect->data;
721   while (walk) {
722     GstVideoMixerCollect *data = (GstVideoMixerCollect *) walk->data;
723
724     gst_videomixer_collect_free (data);
725     walk = g_slist_next (walk);
726   }
727
728   mix->next_sinkpad = 0;
729   mix->flush_stop_pending = FALSE;
730 }
731
732 static void
733 gst_videomixer_init (GstVideoMixer * mix, GstVideoMixerClass * g_class)
734 {
735   GstElementClass *klass = GST_ELEMENT_GET_CLASS (mix);
736
737   mix->srcpad =
738       gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
739           "src"), "src");
740   gst_pad_set_getcaps_function (GST_PAD (mix->srcpad),
741       GST_DEBUG_FUNCPTR (gst_videomixer_getcaps));
742   gst_pad_set_setcaps_function (GST_PAD (mix->srcpad),
743       GST_DEBUG_FUNCPTR (gst_videomixer_setcaps));
744   gst_pad_set_query_function (GST_PAD (mix->srcpad),
745       GST_DEBUG_FUNCPTR (gst_videomixer_query));
746   gst_pad_set_event_function (GST_PAD (mix->srcpad),
747       GST_DEBUG_FUNCPTR (gst_videomixer_src_event));
748   gst_element_add_pad (GST_ELEMENT (mix), mix->srcpad);
749
750   mix->collect = gst_collect_pads_new ();
751   mix->background = DEFAULT_BACKGROUND;
752
753   gst_collect_pads_set_function (mix->collect,
754       (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_videomixer_collected),
755       mix);
756
757   mix->state_lock = g_mutex_new ();
758   /* initialize variables */
759   gst_videomixer_reset (mix);
760 }
761
762 static void
763 gst_videomixer_finalize (GObject * object)
764 {
765   GstVideoMixer *mix = GST_VIDEO_MIXER (object);
766
767   gst_object_unref (mix->collect);
768   g_mutex_free (mix->state_lock);
769
770   G_OBJECT_CLASS (parent_class)->finalize (object);
771 }
772
773 static gboolean
774 gst_videomixer_query_duration (GstVideoMixer * mix, GstQuery * query)
775 {
776   gint64 max;
777   gboolean res;
778   GstFormat format;
779   GstIterator *it;
780   gboolean done;
781
782   /* parse format */
783   gst_query_parse_duration (query, &format, NULL);
784
785   max = -1;
786   res = TRUE;
787   done = FALSE;
788
789   /* Take maximum of all durations */
790   it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (mix));
791   while (!done) {
792     GstIteratorResult ires;
793     gpointer item;
794
795     ires = gst_iterator_next (it, &item);
796     switch (ires) {
797       case GST_ITERATOR_DONE:
798         done = TRUE;
799         break;
800       case GST_ITERATOR_OK:
801       {
802         GstPad *pad = GST_PAD_CAST (item);
803         gint64 duration;
804
805         /* ask sink peer for duration */
806         res &= gst_pad_query_peer_duration (pad, &format, &duration);
807         /* take max from all valid return values */
808         if (res) {
809           /* valid unknown length, stop searching */
810           if (duration == -1) {
811             max = duration;
812             done = TRUE;
813           }
814           /* else see if bigger than current max */
815           else if (duration > max)
816             max = duration;
817         }
818         gst_object_unref (pad);
819         break;
820       }
821       case GST_ITERATOR_RESYNC:
822         max = -1;
823         res = TRUE;
824         gst_iterator_resync (it);
825         break;
826       default:
827         res = FALSE;
828         done = TRUE;
829         break;
830     }
831   }
832   gst_iterator_free (it);
833
834   if (res) {
835     /* and store the max */
836     GST_DEBUG_OBJECT (mix, "Total duration in format %s: %"
837         GST_TIME_FORMAT, gst_format_get_name (format), GST_TIME_ARGS (max));
838     gst_query_set_duration (query, format, max);
839   }
840
841   return res;
842 }
843
844 static gboolean
845 gst_videomixer_query_latency (GstVideoMixer * mix, GstQuery * query)
846 {
847   GstClockTime min, max;
848   gboolean live;
849   gboolean res;
850   GstIterator *it;
851   gboolean done;
852
853   res = TRUE;
854   done = FALSE;
855   live = FALSE;
856   min = 0;
857   max = GST_CLOCK_TIME_NONE;
858
859   /* Take maximum of all latency values */
860   it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (mix));
861   while (!done) {
862     GstIteratorResult ires;
863     gpointer item;
864
865     ires = gst_iterator_next (it, &item);
866     switch (ires) {
867       case GST_ITERATOR_DONE:
868         done = TRUE;
869         break;
870       case GST_ITERATOR_OK:
871       {
872         GstPad *pad = GST_PAD_CAST (item);
873
874         GstQuery *peerquery;
875
876         GstClockTime min_cur, max_cur;
877
878         gboolean live_cur;
879
880         peerquery = gst_query_new_latency ();
881
882         /* Ask peer for latency */
883         res &= gst_pad_peer_query (pad, peerquery);
884
885         /* take max from all valid return values */
886         if (res) {
887           gst_query_parse_latency (peerquery, &live_cur, &min_cur, &max_cur);
888
889           if (min_cur > min)
890             min = min_cur;
891
892           if (max_cur != GST_CLOCK_TIME_NONE &&
893               ((max != GST_CLOCK_TIME_NONE && max_cur > max) ||
894                   (max == GST_CLOCK_TIME_NONE)))
895             max = max_cur;
896
897           live = live || live_cur;
898         }
899
900         gst_query_unref (peerquery);
901         gst_object_unref (pad);
902         break;
903       }
904       case GST_ITERATOR_RESYNC:
905         live = FALSE;
906         min = 0;
907         max = GST_CLOCK_TIME_NONE;
908         res = TRUE;
909         gst_iterator_resync (it);
910         break;
911       default:
912         res = FALSE;
913         done = TRUE;
914         break;
915     }
916   }
917   gst_iterator_free (it);
918
919   if (res) {
920     /* store the results */
921     GST_DEBUG_OBJECT (mix, "Calculated total latency: live %s, min %"
922         GST_TIME_FORMAT ", max %" GST_TIME_FORMAT,
923         (live ? "yes" : "no"), GST_TIME_ARGS (min), GST_TIME_ARGS (max));
924     gst_query_set_latency (query, live, min, max);
925   }
926
927   return res;
928 }
929
930 static gboolean
931 gst_videomixer_query (GstPad * pad, GstQuery * query)
932 {
933   GstVideoMixer *mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
934   gboolean res = FALSE;
935
936   switch (GST_QUERY_TYPE (query)) {
937     case GST_QUERY_POSITION:
938     {
939       GstFormat format;
940
941       gst_query_parse_position (query, &format, NULL);
942
943       switch (format) {
944         case GST_FORMAT_TIME:
945           /* FIXME, bring to stream time, might be tricky */
946           gst_query_set_position (query, format, mix->last_ts);
947           res = TRUE;
948           break;
949         default:
950           break;
951       }
952       break;
953     }
954     case GST_QUERY_DURATION:
955       res = gst_videomixer_query_duration (mix, query);
956       break;
957     case GST_QUERY_LATENCY:
958       res = gst_videomixer_query_latency (mix, query);
959       break;
960     default:
961       /* FIXME, needs a custom query handler because we have multiple
962        * sinkpads, send to the master pad until then */
963       res = gst_pad_query (GST_PAD_CAST (mix->master), query);
964       break;
965   }
966
967   gst_object_unref (mix);
968   return res;
969 }
970
971 static GstCaps *
972 gst_videomixer_getcaps (GstPad * pad)
973 {
974   GstVideoMixer *mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
975   GstCaps *caps;
976   GstStructure *structure;
977   int numCaps;
978
979   if (mix->master) {
980     caps =
981         gst_caps_copy (gst_pad_get_pad_template_caps (GST_PAD (mix->master)));
982   } else {
983     caps = gst_caps_copy (gst_pad_get_pad_template_caps (mix->srcpad));
984   }
985
986   numCaps = gst_caps_get_size (caps) - 1;
987   for (; numCaps >= 0; numCaps--) {
988     structure = gst_caps_get_structure (caps, numCaps);
989     if (mix->out_width != 0) {
990       gst_structure_set (structure, "width", G_TYPE_INT, mix->out_width, NULL);
991     }
992     if (mix->out_height != 0) {
993       gst_structure_set (structure, "height", G_TYPE_INT, mix->out_height,
994           NULL);
995     }
996     if (mix->fps_d != 0) {
997       gst_structure_set (structure,
998           "framerate", GST_TYPE_FRACTION, mix->fps_n, mix->fps_d, NULL);
999     }
1000   }
1001
1002   gst_object_unref (mix);
1003
1004   return caps;
1005 }
1006
1007 static gboolean
1008 gst_videomixer_setcaps (GstPad * pad, GstCaps * caps)
1009 {
1010   GstVideoMixer *mixer = GST_VIDEO_MIXER (gst_pad_get_parent_element (pad));
1011   gboolean ret = FALSE;
1012
1013   GST_INFO_OBJECT (mixer, "set src caps: %" GST_PTR_FORMAT, caps);
1014
1015   mixer->blend = NULL;
1016   mixer->fill_checker = NULL;
1017   mixer->fill_color = NULL;
1018
1019   if (!gst_video_format_parse_caps (caps, &mixer->fmt, NULL, NULL))
1020     goto done;
1021
1022   switch (mixer->fmt) {
1023     case GST_VIDEO_FORMAT_AYUV:
1024       mixer->blend = gst_video_mixer_blend_ayuv;
1025       mixer->fill_checker = gst_video_mixer_fill_checker_ayuv;
1026       mixer->fill_color = gst_video_mixer_fill_color_ayuv;
1027       ret = TRUE;
1028       break;
1029     case GST_VIDEO_FORMAT_ARGB:
1030       mixer->blend = gst_video_mixer_blend_argb;
1031       mixer->fill_checker = gst_video_mixer_fill_checker_argb;
1032       mixer->fill_color = gst_video_mixer_fill_color_argb;
1033       ret = TRUE;
1034       break;
1035     case GST_VIDEO_FORMAT_BGRA:
1036       mixer->blend = gst_video_mixer_blend_bgra;
1037       mixer->fill_checker = gst_video_mixer_fill_checker_bgra;
1038       mixer->fill_color = gst_video_mixer_fill_color_bgra;
1039       ret = TRUE;
1040       break;
1041     case GST_VIDEO_FORMAT_ABGR:
1042       mixer->blend = gst_video_mixer_blend_abgr;
1043       mixer->fill_checker = gst_video_mixer_fill_checker_abgr;
1044       mixer->fill_color = gst_video_mixer_fill_color_abgr;
1045       ret = TRUE;
1046       break;
1047     case GST_VIDEO_FORMAT_RGBA:
1048       mixer->blend = gst_video_mixer_blend_rgba;
1049       mixer->fill_checker = gst_video_mixer_fill_checker_rgba;
1050       mixer->fill_color = gst_video_mixer_fill_color_rgba;
1051       ret = TRUE;
1052       break;
1053     case GST_VIDEO_FORMAT_Y444:
1054       mixer->blend = gst_video_mixer_blend_y444;
1055       mixer->fill_checker = gst_video_mixer_fill_checker_y444;
1056       mixer->fill_color = gst_video_mixer_fill_color_y444;
1057       ret = TRUE;
1058       break;
1059     case GST_VIDEO_FORMAT_Y42B:
1060       mixer->blend = gst_video_mixer_blend_y42b;
1061       mixer->fill_checker = gst_video_mixer_fill_checker_y42b;
1062       mixer->fill_color = gst_video_mixer_fill_color_y42b;
1063       ret = TRUE;
1064       break;
1065     case GST_VIDEO_FORMAT_YUY2:
1066       mixer->blend = gst_video_mixer_blend_yuy2;
1067       mixer->fill_checker = gst_video_mixer_fill_checker_yuy2;
1068       mixer->fill_color = gst_video_mixer_fill_color_yuy2;
1069       ret = TRUE;
1070       break;
1071     case GST_VIDEO_FORMAT_UYVY:
1072       mixer->blend = gst_video_mixer_blend_uyvy;
1073       mixer->fill_checker = gst_video_mixer_fill_checker_uyvy;
1074       mixer->fill_color = gst_video_mixer_fill_color_uyvy;
1075       ret = TRUE;
1076       break;
1077     case GST_VIDEO_FORMAT_YVYU:
1078       mixer->blend = gst_video_mixer_blend_yvyu;
1079       mixer->fill_checker = gst_video_mixer_fill_checker_yvyu;
1080       mixer->fill_color = gst_video_mixer_fill_color_yvyu;
1081       ret = TRUE;
1082       break;
1083     case GST_VIDEO_FORMAT_I420:
1084       mixer->blend = gst_video_mixer_blend_i420;
1085       mixer->fill_checker = gst_video_mixer_fill_checker_i420;
1086       mixer->fill_color = gst_video_mixer_fill_color_i420;
1087       ret = TRUE;
1088       break;
1089     case GST_VIDEO_FORMAT_YV12:
1090       mixer->blend = gst_video_mixer_blend_yv12;
1091       mixer->fill_checker = gst_video_mixer_fill_checker_yv12;
1092       mixer->fill_color = gst_video_mixer_fill_color_yv12;
1093       ret = TRUE;
1094       break;
1095     case GST_VIDEO_FORMAT_Y41B:
1096       mixer->blend = gst_video_mixer_blend_y41b;
1097       mixer->fill_checker = gst_video_mixer_fill_checker_y41b;
1098       mixer->fill_color = gst_video_mixer_fill_color_y41b;
1099       ret = TRUE;
1100       break;
1101     case GST_VIDEO_FORMAT_RGB:
1102       mixer->blend = gst_video_mixer_blend_rgb;
1103       mixer->fill_checker = gst_video_mixer_fill_checker_rgb;
1104       mixer->fill_color = gst_video_mixer_fill_color_rgb;
1105       ret = TRUE;
1106       break;
1107     case GST_VIDEO_FORMAT_BGR:
1108       mixer->blend = gst_video_mixer_blend_bgr;
1109       mixer->fill_checker = gst_video_mixer_fill_checker_bgr;
1110       mixer->fill_color = gst_video_mixer_fill_color_bgr;
1111       ret = TRUE;
1112       break;
1113     case GST_VIDEO_FORMAT_xRGB:
1114       mixer->blend = gst_video_mixer_blend_xrgb;
1115       mixer->fill_checker = gst_video_mixer_fill_checker_xrgb;
1116       mixer->fill_color = gst_video_mixer_fill_color_xrgb;
1117       ret = TRUE;
1118       break;
1119     case GST_VIDEO_FORMAT_xBGR:
1120       mixer->blend = gst_video_mixer_blend_xbgr;
1121       mixer->fill_checker = gst_video_mixer_fill_checker_xbgr;
1122       mixer->fill_color = gst_video_mixer_fill_color_xbgr;
1123       ret = TRUE;
1124       break;
1125     case GST_VIDEO_FORMAT_RGBx:
1126       mixer->blend = gst_video_mixer_blend_rgbx;
1127       mixer->fill_checker = gst_video_mixer_fill_checker_rgbx;
1128       mixer->fill_color = gst_video_mixer_fill_color_rgbx;
1129       ret = TRUE;
1130       break;
1131     case GST_VIDEO_FORMAT_BGRx:
1132       mixer->blend = gst_video_mixer_blend_bgrx;
1133       mixer->fill_checker = gst_video_mixer_fill_checker_bgrx;
1134       mixer->fill_color = gst_video_mixer_fill_color_bgrx;
1135       ret = TRUE;
1136       break;
1137     default:
1138       break;
1139   }
1140
1141 done:
1142   gst_object_unref (mixer);
1143
1144   return ret;
1145 }
1146
1147 static GstPad *
1148 gst_videomixer_request_new_pad (GstElement * element,
1149     GstPadTemplate * templ, const gchar * req_name)
1150 {
1151   GstVideoMixer *mix = NULL;
1152   GstVideoMixerPad *mixpad = NULL;
1153   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1154
1155   g_return_val_if_fail (templ != NULL, NULL);
1156
1157   if (G_UNLIKELY (templ->direction != GST_PAD_SINK)) {
1158     g_warning ("videomixer: request pad that is not a SINK pad");
1159     return NULL;
1160   }
1161
1162   g_return_val_if_fail (GST_IS_VIDEO_MIXER (element), NULL);
1163
1164   mix = GST_VIDEO_MIXER (element);
1165
1166   if (templ == gst_element_class_get_pad_template (klass, "sink_%d")) {
1167     gint serial = 0;
1168     gchar *name = NULL;
1169     GstVideoMixerCollect *mixcol = NULL;
1170
1171     GST_VIDEO_MIXER_STATE_LOCK (mix);
1172     if (req_name == NULL || strlen (req_name) < 6
1173         || !g_str_has_prefix (req_name, "sink_")) {
1174       /* no name given when requesting the pad, use next available int */
1175       serial = mix->next_sinkpad++;
1176     } else {
1177       /* parse serial number from requested padname */
1178       serial = atoi (&req_name[5]);
1179       if (serial >= mix->next_sinkpad)
1180         mix->next_sinkpad = serial + 1;
1181     }
1182     /* create new pad with the name */
1183     name = g_strdup_printf ("sink_%d", serial);
1184     mixpad = g_object_new (GST_TYPE_VIDEO_MIXER_PAD, "name", name, "direction",
1185         templ->direction, "template", templ, NULL);
1186     g_free (name);
1187
1188     mixpad->zorder = mix->numpads;
1189     mixpad->xpos = DEFAULT_PAD_XPOS;
1190     mixpad->ypos = DEFAULT_PAD_YPOS;
1191     mixpad->alpha = DEFAULT_PAD_ALPHA;
1192
1193     mixcol = (GstVideoMixerCollect *)
1194         gst_collect_pads_add_pad (mix->collect, GST_PAD (mixpad),
1195         sizeof (GstVideoMixerCollect));
1196
1197     /* FIXME: hacked way to override/extend the event function of
1198      * GstCollectPads; because it sets its own event function giving the
1199      * element no access to events */
1200     mix->collect_event =
1201         (GstPadEventFunction) GST_PAD_EVENTFUNC (GST_PAD (mixpad));
1202     gst_pad_set_event_function (GST_PAD (mixpad),
1203         GST_DEBUG_FUNCPTR (gst_videomixer_sink_event));
1204
1205     /* Keep track of each other */
1206     mixcol->mixpad = mixpad;
1207     mixpad->mixcol = mixcol;
1208
1209     /* Keep an internal list of mixpads for zordering */
1210     mix->sinkpads = g_slist_append (mix->sinkpads, mixpad);
1211     mix->numpads++;
1212     GST_VIDEO_MIXER_STATE_UNLOCK (mix);
1213   } else {
1214     g_warning ("videomixer: this is not our template!");
1215     return NULL;
1216   }
1217
1218   /* add the pad to the element */
1219   gst_element_add_pad (element, GST_PAD (mixpad));
1220   gst_child_proxy_child_added (GST_OBJECT (mix), GST_OBJECT (mixpad));
1221
1222   return GST_PAD (mixpad);
1223 }
1224
1225 static void
1226 gst_videomixer_release_pad (GstElement * element, GstPad * pad)
1227 {
1228   GstVideoMixer *mix = NULL;
1229   GstVideoMixerPad *mixpad;
1230
1231   mix = GST_VIDEO_MIXER (element);
1232   GST_VIDEO_MIXER_STATE_LOCK (mix);
1233   if (G_UNLIKELY (g_slist_find (mix->sinkpads, pad) == NULL)) {
1234     g_warning ("Unknown pad %s", GST_PAD_NAME (pad));
1235     goto error;
1236   }
1237
1238   mixpad = GST_VIDEO_MIXER_PAD (pad);
1239
1240   mix->sinkpads = g_slist_remove (mix->sinkpads, pad);
1241   gst_videomixer_collect_free (mixpad->mixcol);
1242   gst_collect_pads_remove_pad (mix->collect, pad);
1243   gst_child_proxy_child_removed (GST_OBJECT (mix), GST_OBJECT (mixpad));
1244   /* determine possibly new geometry and master */
1245   gst_videomixer_set_master_geometry (mix);
1246   mix->numpads--;
1247   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
1248
1249   gst_element_remove_pad (element, pad);
1250   return;
1251 error:
1252   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
1253 }
1254
1255 static int
1256 pad_zorder_compare (const GstVideoMixerPad * pad1,
1257     const GstVideoMixerPad * pad2)
1258 {
1259   return pad1->zorder - pad2->zorder;
1260 }
1261
1262 static void
1263 gst_videomixer_sort_pads (GstVideoMixer * mix)
1264 {
1265   mix->sinkpads = g_slist_sort (mix->sinkpads,
1266       (GCompareFunc) pad_zorder_compare);
1267 }
1268
1269 /* try to get a buffer on all pads. As long as the queued value is
1270  * negative, we skip buffers */
1271 static gboolean
1272 gst_videomixer_fill_queues (GstVideoMixer * mix)
1273 {
1274   GSList *walk = NULL;
1275   gboolean eos = TRUE;
1276
1277   g_return_val_if_fail (GST_IS_VIDEO_MIXER (mix), FALSE);
1278
1279   /* try to make sure we have a buffer from each usable pad first */
1280   walk = mix->collect->data;
1281   while (walk) {
1282     GstCollectData *data = (GstCollectData *) walk->data;
1283     GstVideoMixerCollect *mixcol = (GstVideoMixerCollect *) data;
1284     GstVideoMixerPad *mixpad = mixcol->mixpad;
1285
1286     walk = g_slist_next (walk);
1287
1288     if (mixcol->buffer == NULL) {
1289       GstBuffer *buf = NULL;
1290
1291       GST_LOG_OBJECT (mix, "we need a new buffer");
1292
1293       buf = gst_collect_pads_peek (mix->collect, data);
1294
1295       if (buf) {
1296         guint64 duration;
1297
1298         mixcol->buffer = buf;
1299         duration = GST_BUFFER_DURATION (mixcol->buffer);
1300
1301         GST_LOG_OBJECT (mix, "we have a buffer with duration %" GST_TIME_FORMAT
1302             ", queued %" GST_TIME_FORMAT, GST_TIME_ARGS (duration),
1303             GST_TIME_ARGS (mixpad->queued));
1304
1305         /* no duration on the buffer, use the framerate */
1306         if (!GST_CLOCK_TIME_IS_VALID (duration)) {
1307           if (mixpad->fps_n == 0) {
1308             duration = GST_CLOCK_TIME_NONE;
1309           } else {
1310             duration =
1311                 gst_util_uint64_scale_int (GST_SECOND, mixpad->fps_d,
1312                 mixpad->fps_n);
1313           }
1314         }
1315         if (GST_CLOCK_TIME_IS_VALID (duration))
1316           mixpad->queued += duration;
1317         else if (!mixpad->queued)
1318           mixpad->queued = GST_CLOCK_TIME_NONE;
1319
1320         GST_LOG_OBJECT (mix, "now queued: %" GST_TIME_FORMAT,
1321             GST_TIME_ARGS (mixpad->queued));
1322       } else {
1323         GST_LOG_OBJECT (mix, "pop returned a NULL buffer");
1324       }
1325     }
1326     if (mix->sendseg && (mixpad == mix->master)) {
1327       GstEvent *event;
1328       gint64 stop, start;
1329       GstSegment *segment = &data->segment;
1330
1331       /* FIXME, use rate/applied_rate as set on all sinkpads.
1332        * - currently we just set rate as received from last seek-event
1333        * We could potentially figure out the duration as well using
1334        * the current segment positions and the stated stop positions.
1335        * Also we just start from stream time 0 which is rather
1336        * weird. For non-synchronized mixing, the time should be
1337        * the min of the stream times of all received segments,
1338        * rationale being that the duration is at least going to
1339        * be as long as the earliest stream we start mixing. This
1340        * would also be correct for synchronized mixing but then
1341        * the later streams would be delayed until the stream times
1342        * match.
1343        */
1344       GST_INFO_OBJECT (mix, "_sending play segment");
1345
1346       start = segment->accum;
1347
1348       /* get the duration of the segment if we can and add it to the accumulated
1349        * time on the segment. */
1350       if (segment->stop != -1 && segment->start != -1)
1351         stop = start + (segment->stop - segment->start);
1352       else
1353         stop = -1;
1354
1355       gst_segment_set_newsegment (&mix->segment, FALSE, segment->rate,
1356           segment->format, start, stop, start + mix->segment_position);
1357       event =
1358           gst_event_new_new_segment_full (FALSE, segment->rate, 1.0,
1359           segment->format, start, stop, start + mix->segment_position);
1360       gst_pad_push_event (mix->srcpad, event);
1361       mix->sendseg = FALSE;
1362     }
1363
1364     if (mixcol->buffer != NULL && GST_CLOCK_TIME_IS_VALID (mixpad->queued)) {
1365       /* got a buffer somewhere so we're not eos */
1366       eos = FALSE;
1367     }
1368   }
1369
1370   return eos;
1371 }
1372
1373 /* blend all buffers present on the pads */
1374 static void
1375 gst_videomixer_blend_buffers (GstVideoMixer * mix, GstBuffer * outbuf)
1376 {
1377   GSList *walk;
1378
1379   walk = mix->sinkpads;
1380   while (walk) {                /* We walk with this list because it's ordered */
1381     GstVideoMixerPad *pad = GST_VIDEO_MIXER_PAD (walk->data);
1382     GstVideoMixerCollect *mixcol = pad->mixcol;
1383
1384     walk = g_slist_next (walk);
1385
1386     if (mixcol->buffer != NULL) {
1387       GstClockTime timestamp;
1388       gint64 stream_time;
1389       GstSegment *seg;
1390
1391       seg = &mixcol->collect.segment;
1392
1393       timestamp = GST_BUFFER_TIMESTAMP (mixcol->buffer);
1394
1395       stream_time =
1396           gst_segment_to_stream_time (seg, GST_FORMAT_TIME, timestamp);
1397
1398       /* sync object properties on stream time */
1399       if (GST_CLOCK_TIME_IS_VALID (stream_time))
1400         gst_object_sync_values (G_OBJECT (pad), stream_time);
1401
1402       mix->blend (GST_BUFFER_DATA (mixcol->buffer),
1403           pad->xpos, pad->ypos, pad->in_width, pad->in_height, pad->alpha,
1404           GST_BUFFER_DATA (outbuf), mix->out_width, mix->out_height);
1405     }
1406   }
1407 }
1408
1409 /* remove buffers from the queue that were expired in the
1410  * interval of the master, we also prepare the queued value
1411  * in the pad so that we can skip and fill buffers later on */
1412 static void
1413 gst_videomixer_update_queues (GstVideoMixer * mix)
1414 {
1415   GSList *walk;
1416   gint64 interval;
1417
1418   interval = mix->master->queued;
1419   if (interval <= 0) {
1420     if (mix->fps_n == 0) {
1421       interval = G_MAXINT64;
1422     } else {
1423       interval = gst_util_uint64_scale_int (GST_SECOND, mix->fps_d, mix->fps_n);
1424     }
1425     GST_LOG_OBJECT (mix, "set interval to %" G_GINT64_FORMAT " nanoseconds",
1426         interval);
1427   }
1428
1429   walk = mix->sinkpads;
1430   while (walk) {
1431     GstVideoMixerPad *pad = GST_VIDEO_MIXER_PAD (walk->data);
1432     GstVideoMixerCollect *mixcol = pad->mixcol;
1433
1434     walk = g_slist_next (walk);
1435
1436     if (mixcol->buffer != NULL) {
1437       pad->queued -= interval;
1438       GST_LOG_OBJECT (pad, "queued now %" G_GINT64_FORMAT, pad->queued);
1439       if (pad->queued <= 0) {
1440         GstBuffer *buffer =
1441             gst_collect_pads_pop (mix->collect, &mixcol->collect);
1442
1443         GST_LOG_OBJECT (pad, "unreffing buffer");
1444         if (buffer)
1445           gst_buffer_unref (buffer);
1446         else
1447           GST_WARNING_OBJECT (pad,
1448               "Buffer was removed by GstCollectPads in the meantime");
1449
1450         gst_buffer_unref (mixcol->buffer);
1451         mixcol->buffer = NULL;
1452       }
1453     }
1454   }
1455 }
1456
1457 static GstFlowReturn
1458 gst_videomixer_collected (GstCollectPads * pads, GstVideoMixer * mix)
1459 {
1460   GstFlowReturn ret = GST_FLOW_OK;
1461   GstBuffer *outbuf = NULL;
1462   size_t outsize = 0;
1463   gboolean eos = FALSE;
1464   GstClockTime timestamp = GST_CLOCK_TIME_NONE;
1465   GstClockTime duration = GST_CLOCK_TIME_NONE;
1466
1467   g_return_val_if_fail (GST_IS_VIDEO_MIXER (mix), GST_FLOW_ERROR);
1468
1469   /* This must be set, otherwise we have no caps */
1470   if (G_UNLIKELY (mix->in_width == 0))
1471     return GST_FLOW_NOT_NEGOTIATED;
1472
1473   if (g_atomic_int_compare_and_exchange (&mix->flush_stop_pending, TRUE, FALSE)) {
1474     GST_DEBUG_OBJECT (mix, "pending flush stop");
1475     gst_pad_push_event (mix->srcpad, gst_event_new_flush_stop ());
1476   }
1477
1478   GST_LOG_OBJECT (mix, "all pads are collected");
1479   GST_VIDEO_MIXER_STATE_LOCK (mix);
1480
1481   eos = gst_videomixer_fill_queues (mix);
1482
1483   if (eos) {
1484     /* Push EOS downstream */
1485     GST_LOG_OBJECT (mix, "all our sinkpads are EOS, pushing downstream");
1486     gst_pad_push_event (mix->srcpad, gst_event_new_eos ());
1487     ret = GST_FLOW_WRONG_STATE;
1488     goto error;
1489   }
1490
1491   /* If geometry has changed we need to set new caps on the buffer */
1492   if (mix->in_width != mix->out_width || mix->in_height != mix->out_height
1493       || mix->setcaps) {
1494     GstCaps *newcaps = NULL;
1495
1496     newcaps = gst_caps_make_writable
1497         (gst_pad_get_negotiated_caps (GST_PAD (mix->master)));
1498     gst_caps_set_simple (newcaps,
1499         "width", G_TYPE_INT, mix->in_width,
1500         "height", G_TYPE_INT, mix->in_height,
1501         "pixel-aspect-ratio", GST_TYPE_FRACTION, mix->par_n, mix->par_d, NULL);
1502
1503     mix->out_width = mix->in_width;
1504     mix->out_height = mix->in_height;
1505     mix->setcaps = FALSE;
1506
1507     /* Calculating out buffer size from input size */
1508     gst_pad_set_caps (mix->srcpad, newcaps);
1509     gst_caps_unref (newcaps);
1510   }
1511
1512   /* Get timestamp & duration */
1513   if (mix->master->mixcol->buffer != NULL) {
1514     GstClockTime in_ts;
1515     GstSegment *seg;
1516     GstVideoMixerCollect *mixcol = mix->master->mixcol;
1517
1518     seg = &mixcol->collect.segment;
1519     in_ts = GST_BUFFER_TIMESTAMP (mixcol->buffer);
1520
1521     timestamp = gst_segment_to_running_time (seg, GST_FORMAT_TIME, in_ts);
1522     duration = GST_BUFFER_DURATION (mixcol->buffer);
1523
1524     mix->last_ts = timestamp;
1525     mix->last_duration = duration;
1526   } else {
1527     timestamp = mix->last_ts;
1528     duration = mix->last_duration;
1529   }
1530
1531   if (GST_CLOCK_TIME_IS_VALID (duration))
1532     mix->last_ts += duration;
1533
1534   if (!gst_videomixer_do_qos (mix, timestamp)) {
1535     gst_videomixer_update_queues (mix);
1536     GST_VIDEO_MIXER_STATE_UNLOCK (mix);
1537     ret = GST_FLOW_OK;
1538     goto beach;
1539   }
1540
1541   /* allocate an output buffer */
1542   outsize =
1543       gst_video_format_get_size (mix->fmt, mix->out_width, mix->out_height);
1544   ret =
1545       gst_pad_alloc_buffer_and_set_caps (mix->srcpad, GST_BUFFER_OFFSET_NONE,
1546       outsize, GST_PAD_CAPS (mix->srcpad), &outbuf);
1547
1548   /* This must be set at this point, otherwise we have no src caps */
1549   g_assert (mix->blend != NULL);
1550
1551   if (ret != GST_FLOW_OK) {
1552     goto error;
1553   }
1554
1555   GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
1556   GST_BUFFER_DURATION (outbuf) = duration;
1557
1558   switch (mix->background) {
1559     case VIDEO_MIXER_BACKGROUND_CHECKER:
1560       mix->fill_checker (GST_BUFFER_DATA (outbuf), mix->out_width,
1561           mix->out_height);
1562       break;
1563     case VIDEO_MIXER_BACKGROUND_BLACK:
1564       mix->fill_color (GST_BUFFER_DATA (outbuf), mix->out_width,
1565           mix->out_height, 16, 128, 128);
1566       break;
1567     case VIDEO_MIXER_BACKGROUND_WHITE:
1568       mix->fill_color (GST_BUFFER_DATA (outbuf), mix->out_width,
1569           mix->out_height, 240, 128, 128);
1570       break;
1571   }
1572
1573   gst_videomixer_blend_buffers (mix, outbuf);
1574
1575   gst_videomixer_update_queues (mix);
1576   GST_VIDEO_MIXER_STATE_UNLOCK (mix);
1577
1578   ret = gst_pad_push (mix->srcpad, outbuf);
1579
1580 beach:
1581   return ret;
1582
1583   /* ERRORS */
1584 error:
1585   {
1586     if (outbuf)
1587       gst_buffer_unref (outbuf);
1588
1589     GST_VIDEO_MIXER_STATE_UNLOCK (mix);
1590     goto beach;
1591   }
1592 }
1593
1594 static gboolean
1595 forward_event_func (GstPad * pad, GValue * ret, GstEvent * event)
1596 {
1597   gst_event_ref (event);
1598   GST_LOG_OBJECT (pad, "About to send event %s", GST_EVENT_TYPE_NAME (event));
1599   if (!gst_pad_push_event (pad, event)) {
1600     g_value_set_boolean (ret, FALSE);
1601     GST_WARNING_OBJECT (pad, "Sending event  %p (%s) failed.",
1602         event, GST_EVENT_TYPE_NAME (event));
1603   } else {
1604     GST_LOG_OBJECT (pad, "Sent event  %p (%s).",
1605         event, GST_EVENT_TYPE_NAME (event));
1606   }
1607   gst_object_unref (pad);
1608   return TRUE;
1609 }
1610
1611 /* forwards the event to all sinkpads, takes ownership of the
1612  * event
1613  *
1614  * Returns: TRUE if the event could be forwarded on all
1615  * sinkpads.
1616  */
1617 static gboolean
1618 forward_event (GstVideoMixer * mix, GstEvent * event)
1619 {
1620   GstIterator *it;
1621   GValue vret = { 0 };
1622
1623   GST_LOG_OBJECT (mix, "Forwarding event %p (%s)", event,
1624       GST_EVENT_TYPE_NAME (event));
1625
1626   g_value_init (&vret, G_TYPE_BOOLEAN);
1627   g_value_set_boolean (&vret, TRUE);
1628   it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (mix));
1629   gst_iterator_fold (it, (GstIteratorFoldFunction) forward_event_func, &vret,
1630       event);
1631   gst_iterator_free (it);
1632   gst_event_unref (event);
1633
1634   return g_value_get_boolean (&vret);
1635 }
1636
1637 static gboolean
1638 gst_videomixer_src_event (GstPad * pad, GstEvent * event)
1639 {
1640   GstVideoMixer *mix = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
1641   gboolean result;
1642
1643   switch (GST_EVENT_TYPE (event)) {
1644     case GST_EVENT_QOS:{
1645       GstClockTimeDiff diff;
1646       GstClockTime timestamp;
1647       gdouble proportion;
1648
1649       gst_event_parse_qos (event, &proportion, &diff, &timestamp);
1650
1651       gst_videomixer_update_qos (mix, proportion, diff, timestamp);
1652       gst_event_unref (event);
1653
1654       /* TODO: The QoS event should be transformed and send upstream */
1655       result = TRUE;
1656       break;
1657     }
1658     case GST_EVENT_SEEK:
1659     {
1660       GstSeekFlags flags;
1661       GstSeekType curtype;
1662       gint64 cur;
1663
1664       /* parse the seek parameters */
1665       gst_event_parse_seek (event, NULL, NULL, &flags, &curtype,
1666           &cur, NULL, NULL);
1667
1668       /* check if we are flushing */
1669       if (flags & GST_SEEK_FLAG_FLUSH) {
1670         /* make sure we accept nothing anymore and return WRONG_STATE */
1671         gst_collect_pads_set_flushing (mix->collect, TRUE);
1672
1673         /* flushing seek, start flush downstream, the flush will be done
1674          * when all pads received a FLUSH_STOP. */
1675         gst_pad_push_event (mix->srcpad, gst_event_new_flush_start ());
1676       }
1677
1678       /* now wait for the collected to be finished and mark a new
1679        * segment */
1680       GST_OBJECT_LOCK (mix->collect);
1681       if (curtype == GST_SEEK_TYPE_SET)
1682         mix->segment_position = cur;
1683       else
1684         mix->segment_position = 0;
1685       mix->sendseg = TRUE;
1686
1687       if (flags & GST_SEEK_FLAG_FLUSH) {
1688         gst_collect_pads_set_flushing (mix->collect, FALSE);
1689
1690         /* we can't send FLUSH_STOP here since upstream could start pushing data
1691          * after we unlock mix->collect.
1692          * We set flush_stop_pending to TRUE instead and send FLUSH_STOP after
1693          * forwarding the seek upstream or from gst_videomixer_collected,
1694          * whichever happens first.
1695          */
1696         mix->flush_stop_pending = TRUE;
1697       }
1698
1699       GST_OBJECT_UNLOCK (mix->collect);
1700       gst_videomixer_reset_qos (mix);
1701
1702       result = forward_event (mix, event);
1703
1704       if (g_atomic_int_compare_and_exchange (&mix->flush_stop_pending,
1705               TRUE, FALSE)) {
1706         GST_DEBUG_OBJECT (mix, "pending flush stop");
1707         gst_pad_push_event (mix->srcpad, gst_event_new_flush_stop ());
1708       }
1709
1710       break;
1711     }
1712     case GST_EVENT_NAVIGATION:
1713       /* navigation is rather pointless. */
1714       result = FALSE;
1715       break;
1716     default:
1717       /* just forward the rest for now */
1718       result = forward_event (mix, event);
1719       break;
1720   }
1721   gst_object_unref (mix);
1722
1723   return result;
1724 }
1725
1726 static gboolean
1727 gst_videomixer_sink_event (GstPad * pad, GstEvent * event)
1728 {
1729   GstVideoMixerPad *vpad = GST_VIDEO_MIXER_PAD (pad);
1730   GstVideoMixer *videomixer = GST_VIDEO_MIXER (gst_pad_get_parent (pad));
1731   gboolean ret;
1732
1733   GST_DEBUG_OBJECT (pad, "Got %s event on pad %s:%s",
1734       GST_EVENT_TYPE_NAME (event), GST_DEBUG_PAD_NAME (pad));
1735
1736   switch (GST_EVENT_TYPE (event)) {
1737     case GST_EVENT_FLUSH_STOP:
1738       /* mark a pending new segment. This event is synchronized
1739        * with the streaming thread so we can safely update the
1740        * variable without races. It's somewhat weird because we
1741        * assume the collectpads forwarded the FLUSH_STOP past us
1742        * and downstream (using our source pad, the bastard!).
1743        */
1744       videomixer->sendseg = TRUE;
1745       videomixer->flush_stop_pending = FALSE;
1746       gst_videomixer_reset_qos (videomixer);
1747
1748       /* Reset pad state after FLUSH_STOP */
1749       if (vpad->mixcol->buffer)
1750         gst_buffer_unref (vpad->mixcol->buffer);
1751       vpad->mixcol->buffer = NULL;
1752       vpad->queued = 0;
1753       break;
1754     case GST_EVENT_NEWSEGMENT:
1755       if (!videomixer->master || vpad == videomixer->master) {
1756         videomixer->sendseg = TRUE;
1757         gst_videomixer_reset_qos (videomixer);
1758       }
1759       break;
1760     default:
1761       break;
1762   }
1763
1764   /* now GstCollectPads can take care of the rest, e.g. EOS */
1765   ret = videomixer->collect_event (pad, event);
1766
1767   gst_object_unref (videomixer);
1768   return ret;
1769 }
1770
1771
1772 static void
1773 gst_videomixer_get_property (GObject * object,
1774     guint prop_id, GValue * value, GParamSpec * pspec)
1775 {
1776   GstVideoMixer *mix = GST_VIDEO_MIXER (object);
1777
1778   switch (prop_id) {
1779     case PROP_BACKGROUND:
1780       g_value_set_enum (value, mix->background);
1781       break;
1782     default:
1783       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1784       break;
1785   }
1786 }
1787
1788 static void
1789 gst_videomixer_set_property (GObject * object,
1790     guint prop_id, const GValue * value, GParamSpec * pspec)
1791 {
1792   GstVideoMixer *mix = GST_VIDEO_MIXER (object);
1793
1794   switch (prop_id) {
1795     case PROP_BACKGROUND:
1796       mix->background = g_value_get_enum (value);
1797       break;
1798     default:
1799       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1800       break;
1801   }
1802 }
1803
1804 static GstStateChangeReturn
1805 gst_videomixer_change_state (GstElement * element, GstStateChange transition)
1806 {
1807   GstVideoMixer *mix;
1808   GstStateChangeReturn ret;
1809
1810   g_return_val_if_fail (GST_IS_VIDEO_MIXER (element), GST_STATE_CHANGE_FAILURE);
1811
1812   mix = GST_VIDEO_MIXER (element);
1813
1814   switch (transition) {
1815     case GST_STATE_CHANGE_READY_TO_PAUSED:
1816       GST_LOG_OBJECT (mix, "starting collectpads");
1817       gst_collect_pads_start (mix->collect);
1818       break;
1819     case GST_STATE_CHANGE_PAUSED_TO_READY:
1820       GST_LOG_OBJECT (mix, "stopping collectpads");
1821       gst_collect_pads_stop (mix->collect);
1822       break;
1823     default:
1824       break;
1825   }
1826
1827   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1828
1829   switch (transition) {
1830     case GST_STATE_CHANGE_PAUSED_TO_READY:
1831       gst_videomixer_reset (mix);
1832       break;
1833     default:
1834       break;
1835   }
1836
1837   return ret;
1838 }
1839
1840 static gboolean
1841 plugin_init (GstPlugin * plugin)
1842 {
1843   GST_DEBUG_CATEGORY_INIT (gst_videomixer_debug, "videomixer", 0,
1844       "video mixer");
1845
1846   gst_video_mixer_init_blend ();
1847
1848   return gst_element_register (plugin, "videomixer", GST_RANK_PRIMARY,
1849       GST_TYPE_VIDEO_MIXER);
1850 }
1851
1852 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1853     GST_VERSION_MINOR,
1854     "videomixer",
1855     "Video mixer", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
1856     GST_PACKAGE_ORIGIN)