[MOVED FROM BAD 15/29] shapewipe: Add support for ARGB video input/output
[platform/upstream/gstreamer.git] / gst / shapewipe / gstshapewipe.c
1 /* GStreamer
2  * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
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-shapewipe
22  *
23  * The shapewipe element provides custom transitions on video streams
24  * based on a grayscale bitmap. The state of the transition can be
25  * controlled by the position property and an optional blended border
26  * can be added by the border property.
27  *
28  * Transition bitmaps can be downloaded from the
29  * <ulink url="http://cinelerra.org/transitions.php">Cinelerra transition</ulink>
30  * page.
31  *
32  * <refsect2>
33  * <title>Example launch line</title>
34  * |[
35  * gst-launch -v videotestsrc ! video/x-raw-yuv,width=640,height=480 ! shapewipe position=0.5 name=shape ! videomixer name=mixer ! ffmpegcolorspace ! autovideosink     filesrc location=mask.png ! typefind ! decodebin2 ! ffmpegcolorspace ! videoscale ! queue ! shape.mask_sink    videotestsrc pattern=snow ! video/x-raw-yuv,width=640,height=480 ! queue ! mixer.
36  * ]| This pipeline adds the transition from mask.png with position 0.5 to an SMPTE test screen and snow.
37  * </refsect2>
38  */
39
40
41 #ifdef HAVE_CONFIG_H
42 #  include "config.h"
43 #endif
44
45 #include <string.h>
46
47 #include <gst/gst.h>
48 #include <gst/controller/gstcontroller.h>
49
50 #include "gstshapewipe.h"
51
52 static void gst_shape_wipe_finalize (GObject * object);
53 static void gst_shape_wipe_get_property (GObject * object, guint prop_id,
54     GValue * value, GParamSpec * pspec);
55 static void gst_shape_wipe_set_property (GObject * object, guint prop_id,
56     const GValue * value, GParamSpec * pspec);
57
58 static void gst_shape_wipe_reset (GstShapeWipe * self);
59 static void gst_shape_wipe_update_qos (GstShapeWipe * self, gdouble proportion,
60     GstClockTimeDiff diff, GstClockTime time);
61 static void gst_shape_wipe_reset_qos (GstShapeWipe * self);
62 static void gst_shape_wipe_read_qos (GstShapeWipe * self, gdouble * proportion,
63     GstClockTime * time);
64
65 static GstStateChangeReturn gst_shape_wipe_change_state (GstElement * element,
66     GstStateChange transition);
67
68 static GstFlowReturn gst_shape_wipe_video_sink_chain (GstPad * pad,
69     GstBuffer * buffer);
70 static gboolean gst_shape_wipe_video_sink_event (GstPad * pad,
71     GstEvent * event);
72 static gboolean gst_shape_wipe_video_sink_setcaps (GstPad * pad,
73     GstCaps * caps);
74 static GstCaps *gst_shape_wipe_video_sink_getcaps (GstPad * pad);
75 static GstFlowReturn gst_shape_wipe_video_sink_bufferalloc (GstPad * pad,
76     guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf);
77 static gboolean gst_shape_wipe_video_sink_query (GstPad * pad,
78     GstQuery * query);
79 static GstFlowReturn gst_shape_wipe_mask_sink_chain (GstPad * pad,
80     GstBuffer * buffer);
81 static gboolean gst_shape_wipe_mask_sink_event (GstPad * pad, GstEvent * event);
82 static gboolean gst_shape_wipe_mask_sink_setcaps (GstPad * pad, GstCaps * caps);
83 static GstCaps *gst_shape_wipe_mask_sink_getcaps (GstPad * pad);
84 static gboolean gst_shape_wipe_src_event (GstPad * pad, GstEvent * event);
85 static GstCaps *gst_shape_wipe_src_getcaps (GstPad * pad);
86 static gboolean gst_shape_wipe_src_query (GstPad * pad, GstQuery * query);
87
88 enum
89 {
90   PROP_0,
91   PROP_POSITION,
92   PROP_BORDER
93 };
94
95 static GstStaticPadTemplate video_sink_pad_template =
96     GST_STATIC_PAD_TEMPLATE ("video_sink",
97     GST_PAD_SINK,
98     GST_PAD_ALWAYS,
99     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") " ; " GST_VIDEO_CAPS_ARGB));
100
101 static GstStaticPadTemplate mask_sink_pad_template =
102     GST_STATIC_PAD_TEMPLATE ("mask_sink",
103     GST_PAD_SINK,
104     GST_PAD_ALWAYS,
105     GST_STATIC_CAPS ("video/x-raw-gray, "
106         "bpp = 8, "
107         "depth = 8, "
108         "width = " GST_VIDEO_SIZE_RANGE ", "
109         "height = " GST_VIDEO_SIZE_RANGE ", " "framerate = 0/1 ; "
110         "video/x-raw-gray, " "bpp = 16, " "depth = 16, "
111         "endianness = BYTE_ORDER, " "width = " GST_VIDEO_SIZE_RANGE ", "
112         "height = " GST_VIDEO_SIZE_RANGE ", " "framerate = 0/1"));
113
114 static GstStaticPadTemplate src_pad_template =
115     GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
116     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") " ; " GST_VIDEO_CAPS_ARGB));
117
118 GST_DEBUG_CATEGORY_STATIC (gst_shape_wipe_debug);
119 #define GST_CAT_DEFAULT gst_shape_wipe_debug
120
121 GST_BOILERPLATE (GstShapeWipe, gst_shape_wipe, GstElement, GST_TYPE_ELEMENT);
122
123 static void
124 gst_shape_wipe_base_init (gpointer g_class)
125 {
126   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
127
128   gst_element_class_set_details_simple (gstelement_class,
129       "Shape Wipe transition filter",
130       "Filter/Editor/Video",
131       "Adds a shape wipe transition to a video stream",
132       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
133
134   gst_element_class_add_pad_template (gstelement_class,
135       gst_static_pad_template_get (&video_sink_pad_template));
136   gst_element_class_add_pad_template (gstelement_class,
137       gst_static_pad_template_get (&mask_sink_pad_template));
138   gst_element_class_add_pad_template (gstelement_class,
139       gst_static_pad_template_get (&src_pad_template));
140 }
141
142 static void
143 gst_shape_wipe_class_init (GstShapeWipeClass * klass)
144 {
145   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
146   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
147
148   gobject_class->finalize = gst_shape_wipe_finalize;
149   gobject_class->set_property = gst_shape_wipe_set_property;
150   gobject_class->get_property = gst_shape_wipe_get_property;
151
152   g_object_class_install_property (gobject_class, PROP_POSITION,
153       g_param_spec_float ("position", "Position", "Position of the mask",
154           0.0, 1.0, 0.0,
155           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
156   g_object_class_install_property (gobject_class, PROP_BORDER,
157       g_param_spec_float ("border", "Border", "Border of the mask",
158           0.0, 1.0, 0.0,
159           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
160
161   gstelement_class->change_state =
162       GST_DEBUG_FUNCPTR (gst_shape_wipe_change_state);
163 }
164
165 static void
166 gst_shape_wipe_init (GstShapeWipe * self, GstShapeWipeClass * g_class)
167 {
168   self->video_sinkpad =
169       gst_pad_new_from_static_template (&video_sink_pad_template, "video_sink");
170   gst_pad_set_chain_function (self->video_sinkpad,
171       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_chain));
172   gst_pad_set_event_function (self->video_sinkpad,
173       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_event));
174   gst_pad_set_setcaps_function (self->video_sinkpad,
175       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_setcaps));
176   gst_pad_set_getcaps_function (self->video_sinkpad,
177       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_getcaps));
178   gst_pad_set_bufferalloc_function (self->video_sinkpad,
179       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_bufferalloc));
180   gst_pad_set_query_function (self->video_sinkpad,
181       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_query));
182   gst_element_add_pad (GST_ELEMENT (self), self->video_sinkpad);
183
184   self->mask_sinkpad =
185       gst_pad_new_from_static_template (&mask_sink_pad_template, "mask_sink");
186   gst_pad_set_chain_function (self->mask_sinkpad,
187       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_chain));
188   gst_pad_set_event_function (self->mask_sinkpad,
189       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_event));
190   gst_pad_set_setcaps_function (self->mask_sinkpad,
191       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_setcaps));
192   gst_pad_set_getcaps_function (self->mask_sinkpad,
193       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_getcaps));
194   gst_element_add_pad (GST_ELEMENT (self), self->mask_sinkpad);
195
196   self->srcpad = gst_pad_new_from_static_template (&src_pad_template, "src");
197   gst_pad_set_event_function (self->srcpad,
198       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_event));
199   gst_pad_set_getcaps_function (self->srcpad,
200       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_getcaps));
201   gst_pad_set_query_function (self->srcpad,
202       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_query));
203   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
204
205   self->mask_mutex = g_mutex_new ();
206   self->mask_cond = g_cond_new ();
207
208   gst_shape_wipe_reset (self);
209 }
210
211 static void
212 gst_shape_wipe_get_property (GObject * object, guint prop_id,
213     GValue * value, GParamSpec * pspec)
214 {
215   GstShapeWipe *self = GST_SHAPE_WIPE (object);
216
217   switch (prop_id) {
218     case PROP_POSITION:
219       g_value_set_float (value, self->mask_position);
220       break;
221     case PROP_BORDER:
222       g_value_set_float (value, self->mask_border);
223       break;
224     default:
225       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
226       break;
227   }
228 }
229
230 static void
231 gst_shape_wipe_set_property (GObject * object, guint prop_id,
232     const GValue * value, GParamSpec * pspec)
233 {
234   GstShapeWipe *self = GST_SHAPE_WIPE (object);
235
236   switch (prop_id) {
237     case PROP_POSITION:
238       self->mask_position = g_value_get_float (value);
239       break;
240     case PROP_BORDER:
241       self->mask_border = g_value_get_float (value);
242       break;
243     default:
244       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
245       break;
246   }
247 }
248
249 static void
250 gst_shape_wipe_finalize (GObject * object)
251 {
252   GstShapeWipe *self = GST_SHAPE_WIPE (object);
253
254   gst_shape_wipe_reset (self);
255
256   if (self->mask_cond)
257     g_cond_free (self->mask_cond);
258   self->mask_cond = NULL;
259
260   if (self->mask_mutex)
261     g_mutex_free (self->mask_mutex);
262   self->mask_mutex = NULL;
263
264   G_OBJECT_CLASS (parent_class)->finalize (object);
265 }
266
267 static void
268 gst_shape_wipe_reset (GstShapeWipe * self)
269 {
270   if (self->mask)
271     gst_buffer_unref (self->mask);
272   self->mask = NULL;
273
274   g_cond_signal (self->mask_cond);
275
276   self->fmt = GST_VIDEO_FORMAT_UNKNOWN;
277   self->width = self->height = 0;
278   self->mask_position = 0.0;
279   self->mask_border = 0.0;
280   self->mask_bpp = 0;
281
282   gst_segment_init (&self->segment, GST_FORMAT_TIME);
283
284   gst_shape_wipe_reset_qos (self);
285   self->frame_duration = 0;
286 }
287
288 static GstFlowReturn
289 gst_shape_wipe_video_sink_bufferalloc (GstPad * pad, guint64 offset, guint size,
290     GstCaps * caps, GstBuffer ** buf)
291 {
292   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
293   GstFlowReturn ret = GST_FLOW_OK;
294
295   GST_DEBUG_OBJECT (pad, "Allocating buffer with offset 0x%" G_GINT64_MODIFIER
296       "x and size %u with caps: %" GST_PTR_FORMAT, offset, size, caps);
297
298   *buf = NULL;
299
300   ret = gst_pad_alloc_buffer (self->srcpad, offset, size, caps, buf);
301
302   gst_object_unref (self);
303
304   return ret;
305 }
306
307 static gboolean
308 gst_shape_wipe_video_sink_setcaps (GstPad * pad, GstCaps * caps)
309 {
310   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
311   gboolean ret = TRUE;
312   GstStructure *s;
313   GstVideoFormat fmt;
314   gint width, height;
315   gint fps_n, fps_d;
316
317   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
318
319   s = gst_caps_get_structure (caps, 0);
320
321   if (!gst_video_format_parse_caps (caps, &fmt, &width, &height) ||
322       !gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) {
323     ret = FALSE;
324     goto done;
325   }
326
327   self->fmt = fmt;
328   if (self->width != width || self->height != height) {
329     g_mutex_lock (self->mask_mutex);
330     self->width = width;
331     self->height = height;
332
333     if (self->mask)
334       gst_buffer_unref (self->mask);
335     self->mask = NULL;
336     g_mutex_unlock (self->mask_mutex);
337   }
338
339   if (fps_n != 0)
340     self->frame_duration = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n);
341   else
342     self->frame_duration = 0;
343
344   ret = gst_pad_set_caps (self->srcpad, caps);
345
346 done:
347   gst_object_unref (self);
348
349   return ret;
350 }
351
352 static GstCaps *
353 gst_shape_wipe_video_sink_getcaps (GstPad * pad)
354 {
355   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
356   GstCaps *ret, *tmp;
357
358   if (GST_PAD_CAPS (pad))
359     return gst_caps_copy (GST_PAD_CAPS (pad));
360
361   tmp = gst_pad_peer_get_caps (self->srcpad);
362   if (tmp) {
363     ret = gst_caps_intersect (tmp, gst_pad_get_pad_template_caps (pad));
364     gst_caps_unref (tmp);
365   } else {
366     ret = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
367   }
368
369   tmp = gst_pad_peer_get_caps (pad);
370   if (tmp) {
371     GstCaps *intersection;
372
373     intersection = gst_caps_intersect (tmp, ret);
374     gst_caps_unref (tmp);
375     gst_caps_unref (ret);
376     ret = intersection;
377   }
378
379   if (self->height && self->width) {
380     guint i, n;
381
382     n = gst_caps_get_size (ret);
383     for (i = 0; i < n; i++) {
384       GstStructure *s = gst_caps_get_structure (ret, i);
385
386       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
387           G_TYPE_INT, self->height, NULL);
388     }
389   }
390
391   tmp = gst_pad_peer_get_caps (self->mask_sinkpad);
392   if (tmp) {
393     GstCaps *intersection, *tmp2;
394     guint i, n;
395
396     tmp = gst_caps_make_writable (tmp);
397
398     tmp2 = gst_caps_copy (gst_pad_get_pad_template_caps (self->mask_sinkpad));
399
400     intersection = gst_caps_intersect (tmp, tmp2);
401     gst_caps_unref (tmp);
402     gst_caps_unref (tmp2);
403     tmp = intersection;
404
405     n = gst_caps_get_size (tmp);
406
407     tmp2 = gst_caps_new_empty ();
408     for (i = 0; i < n; i++) {
409       GstStructure *s = gst_caps_get_structure (tmp, i);
410       GstStructure *c;
411
412       gst_structure_remove_fields (s, "format", "bpp", "depth", "endianness",
413           "framerate", "red_mask", "green_mask", "blue_mask", "alpha_mask",
414           NULL);
415       gst_structure_set_name (s, "video/x-raw-yuv");
416       c = gst_structure_copy (s);
417       gst_structure_set_name (c, "video/x-raw-rgb");
418       gst_caps_append_structure (tmp2, c);
419     }
420     gst_caps_append (tmp, tmp2);
421
422     intersection = gst_caps_intersect (tmp, ret);
423     gst_caps_unref (tmp);
424     gst_caps_unref (ret);
425     ret = intersection;
426   }
427
428   gst_object_unref (self);
429
430   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
431
432   return ret;
433 }
434
435 static gboolean
436 gst_shape_wipe_mask_sink_setcaps (GstPad * pad, GstCaps * caps)
437 {
438   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
439   gboolean ret = TRUE;
440   GstStructure *s;
441   gint width, height, bpp;
442
443   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
444
445   s = gst_caps_get_structure (caps, 0);
446
447   if (!gst_structure_get_int (s, "width", &width) ||
448       !gst_structure_get_int (s, "height", &height) ||
449       !gst_structure_get_int (s, "bpp", &bpp)) {
450     ret = FALSE;
451     goto done;
452   }
453
454   if ((self->width != width || self->height != height) &&
455       self->width > 0 && self->height > 0) {
456     GST_ERROR_OBJECT (pad, "Mask caps must have the same width/height "
457         "as the video caps");
458     ret = FALSE;
459     goto done;
460   } else {
461     self->width = width;
462     self->height = height;
463   }
464
465   self->mask_bpp = bpp;
466
467 done:
468   gst_object_unref (self);
469
470   return ret;
471 }
472
473 static GstCaps *
474 gst_shape_wipe_mask_sink_getcaps (GstPad * pad)
475 {
476   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
477   GstCaps *ret, *tmp;
478   guint i, n;
479
480   if (GST_PAD_CAPS (pad))
481     return gst_caps_copy (GST_PAD_CAPS (pad));
482
483   tmp = gst_pad_peer_get_caps (self->video_sinkpad);
484   if (tmp) {
485     ret =
486         gst_caps_intersect (tmp,
487         gst_pad_get_pad_template_caps (self->video_sinkpad));
488     gst_caps_unref (tmp);
489   } else {
490     ret = gst_caps_copy (gst_pad_get_pad_template_caps (self->video_sinkpad));
491   }
492
493   tmp = gst_pad_peer_get_caps (self->srcpad);
494   if (tmp) {
495     GstCaps *intersection;
496
497     intersection = gst_caps_intersect (ret, tmp);
498     gst_caps_unref (ret);
499     gst_caps_unref (tmp);
500     ret = intersection;
501   }
502
503   n = gst_caps_get_size (ret);
504   tmp = gst_caps_new_empty ();
505   for (i = 0; i < n; i++) {
506     GstStructure *s = gst_caps_get_structure (ret, i);
507     GstStructure *t;
508
509     gst_structure_set_name (s, "video/x-raw-gray");
510     gst_structure_remove_fields (s, "format", "framerate", "bpp", "depth",
511         "endianness", "framerate", "red_mask", "green_mask", "blue_mask",
512         "alpha_mask", NULL);
513
514     if (self->width && self->height)
515       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
516           G_TYPE_INT, self->height, NULL);
517
518     gst_structure_set (s, "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
519
520     t = gst_structure_copy (s);
521
522     gst_structure_set (s, "bpp", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16,
523         "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
524     gst_structure_set (t, "bpp", G_TYPE_INT, 8, "depth", G_TYPE_INT, 8, NULL);
525
526     gst_caps_append_structure (tmp, t);
527   }
528   gst_caps_append (ret, tmp);
529
530   tmp = gst_pad_peer_get_caps (pad);
531   if (tmp) {
532     GstCaps *intersection;
533
534     intersection = gst_caps_intersect (tmp, ret);
535     gst_caps_unref (tmp);
536     gst_caps_unref (ret);
537     ret = intersection;
538   }
539
540   gst_object_unref (self);
541
542   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
543
544   return ret;
545 }
546
547 static GstCaps *
548 gst_shape_wipe_src_getcaps (GstPad * pad)
549 {
550   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
551   GstCaps *ret, *tmp;
552
553   if (GST_PAD_CAPS (pad))
554     return gst_caps_copy (GST_PAD_CAPS (pad));
555   else if (GST_PAD_CAPS (self->video_sinkpad))
556     return gst_caps_copy (GST_PAD_CAPS (self->video_sinkpad));
557
558   tmp = gst_pad_peer_get_caps (self->video_sinkpad);
559   if (tmp) {
560     ret =
561         gst_caps_intersect (tmp,
562         gst_pad_get_pad_template_caps (self->video_sinkpad));
563     gst_caps_unref (tmp);
564   } else {
565     ret = gst_caps_copy (gst_pad_get_pad_template_caps (self->video_sinkpad));
566   }
567
568   tmp = gst_pad_peer_get_caps (pad);
569   if (tmp) {
570     GstCaps *intersection;
571
572     intersection = gst_caps_intersect (tmp, ret);
573     gst_caps_unref (tmp);
574     gst_caps_unref (ret);
575     ret = intersection;
576   }
577
578   if (self->height && self->width) {
579     guint i, n;
580
581     n = gst_caps_get_size (ret);
582     for (i = 0; i < n; i++) {
583       GstStructure *s = gst_caps_get_structure (ret, i);
584
585       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
586           G_TYPE_INT, self->height, NULL);
587     }
588   }
589
590   tmp = gst_pad_peer_get_caps (self->mask_sinkpad);
591   if (tmp) {
592     GstCaps *intersection, *tmp2;
593     guint i, n;
594
595     tmp = gst_caps_make_writable (tmp);
596     tmp2 = gst_caps_copy (gst_pad_get_pad_template_caps (self->mask_sinkpad));
597
598     intersection = gst_caps_intersect (tmp, tmp2);
599     gst_caps_unref (tmp);
600     gst_caps_unref (tmp2);
601
602     tmp = intersection;
603     n = gst_caps_get_size (tmp);
604
605     tmp2 = gst_caps_new_empty ();
606     for (i = 0; i < n; i++) {
607       GstStructure *s = gst_caps_get_structure (tmp, i);
608       GstStructure *c;
609
610       gst_structure_remove_fields (s, "format", "bpp", "depth", "endianness",
611           "framerate", "red_mask", "green_mask", "blue_mask", "alpha_mask",
612           NULL);
613       gst_structure_set_name (s, "video/x-raw-yuv");
614       c = gst_structure_copy (s);
615
616       gst_caps_append_structure (tmp2, c);
617     }
618     gst_caps_append (tmp, tmp2);
619
620     intersection = gst_caps_intersect (tmp, ret);
621     gst_caps_unref (tmp);
622     gst_caps_unref (ret);
623     ret = intersection;
624   }
625
626   gst_object_unref (self);
627
628   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
629
630   return ret;
631 }
632
633 static gboolean
634 gst_shape_wipe_video_sink_query (GstPad * pad, GstQuery * query)
635 {
636   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
637   gboolean ret;
638   GstPad *peer = gst_pad_get_peer (self->srcpad);
639
640   GST_DEBUG_OBJECT (pad, "Handling query of type '%s'",
641       gst_query_type_get_name (GST_QUERY_TYPE (query)));
642
643   if (!peer) {
644     GST_INFO_OBJECT (pad, "No peer yet");
645     ret = FALSE;
646   } else {
647     ret = gst_pad_query (peer, query);
648     gst_object_unref (peer);
649   }
650
651   gst_object_unref (self);
652   return ret;
653 }
654
655 static gboolean
656 gst_shape_wipe_src_query (GstPad * pad, GstQuery * query)
657 {
658   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
659   gboolean ret;
660   GstPad *peer = gst_pad_get_peer (self->video_sinkpad);
661
662   GST_DEBUG_OBJECT (pad, "Handling query of type '%s'",
663       gst_query_type_get_name (GST_QUERY_TYPE (query)));
664
665   if (!peer) {
666     GST_INFO_OBJECT (pad, "No peer yet");
667     ret = FALSE;
668   } else {
669     ret = gst_pad_query (peer, query);
670     gst_object_unref (peer);
671   }
672
673   gst_object_unref (self);
674   return ret;
675 }
676
677 static void
678 gst_shape_wipe_update_qos (GstShapeWipe * self, gdouble proportion,
679     GstClockTimeDiff diff, GstClockTime timestamp)
680 {
681   GST_OBJECT_LOCK (self);
682   self->proportion = proportion;
683   if (G_LIKELY (timestamp != GST_CLOCK_TIME_NONE)) {
684     if (G_UNLIKELY (diff > 0))
685       self->earliest_time = timestamp + 2 * diff + self->frame_duration;
686     else
687       self->earliest_time = timestamp + diff;
688   } else {
689     self->earliest_time = GST_CLOCK_TIME_NONE;
690   }
691   GST_OBJECT_UNLOCK (self);
692 }
693
694 static void
695 gst_shape_wipe_reset_qos (GstShapeWipe * self)
696 {
697   gst_shape_wipe_update_qos (self, 0.5, 0, GST_CLOCK_TIME_NONE);
698 }
699
700 static void
701 gst_shape_wipe_read_qos (GstShapeWipe * self, gdouble * proportion,
702     GstClockTime * time)
703 {
704   GST_OBJECT_LOCK (self);
705   *proportion = self->proportion;
706   *time = self->earliest_time;
707   GST_OBJECT_UNLOCK (self);
708 }
709
710 /* Perform qos calculations before processing the next frame. Returns TRUE if
711  * the frame should be processed, FALSE if the frame can be dropped entirely */
712 static gboolean
713 gst_shape_wipe_do_qos (GstShapeWipe * self, GstClockTime timestamp)
714 {
715   GstClockTime qostime, earliest_time;
716   gdouble proportion;
717
718   /* no timestamp, can't do QoS => process frame */
719   if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (timestamp))) {
720     GST_LOG_OBJECT (self, "invalid timestamp, can't do QoS, process frame");
721     return TRUE;
722   }
723
724   /* get latest QoS observation values */
725   gst_shape_wipe_read_qos (self, &proportion, &earliest_time);
726
727   /* skip qos if we have no observation (yet) => process frame */
728   if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (earliest_time))) {
729     GST_LOG_OBJECT (self, "no observation yet, process frame");
730     return TRUE;
731   }
732
733   /* qos is done on running time */
734   qostime = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME,
735       timestamp);
736
737   /* see how our next timestamp relates to the latest qos timestamp */
738   GST_LOG_OBJECT (self, "qostime %" GST_TIME_FORMAT ", earliest %"
739       GST_TIME_FORMAT, GST_TIME_ARGS (qostime), GST_TIME_ARGS (earliest_time));
740
741   if (qostime != GST_CLOCK_TIME_NONE && qostime <= earliest_time) {
742     GST_DEBUG_OBJECT (self, "we are late, drop frame");
743     return FALSE;
744   }
745
746   GST_LOG_OBJECT (self, "process frame");
747   return TRUE;
748 }
749
750 #define CREATE_AYUV_FUNCTIONS(depth, scale) \
751 static GstFlowReturn \
752 gst_shape_wipe_blend_ayuv_##depth (GstShapeWipe * self, GstBuffer * inbuf, \
753     GstBuffer * maskbuf, GstBuffer * outbuf) \
754 { \
755   const guint##depth *mask = (const guint##depth *) GST_BUFFER_DATA (maskbuf); \
756   const guint8 *input = (const guint8 *) GST_BUFFER_DATA (inbuf); \
757   guint8 *output = (guint8 *) GST_BUFFER_DATA (outbuf); \
758   guint i, j; \
759   guint mask_increment = ((depth == 16) ? GST_ROUND_UP_2 (self->width) : \
760                            GST_ROUND_UP_4 (self->width)) - self->width; \
761   gfloat position = self->mask_position; \
762   gfloat low = position - (self->mask_border / 2.0f); \
763   gfloat high = position + (self->mask_border / 2.0f); \
764   \
765   if (low < 0.0f) { \
766     high = 0.0f; \
767     low = 0.0f; \
768   } \
769   \
770   if (high > 1.0f) { \
771     low = 1.0f; \
772     high = 1.0f; \
773   } \
774   \
775   for (i = 0; i < self->height; i++) { \
776     for (j = 0; j < self->width; j++) { \
777       gfloat in = *mask / scale; \
778       \
779       if (in < low) { \
780         output[0] = 0x00;       /* A */ \
781         output[1] = 0x00;       /* Y */ \
782         output[2] = 0x80;       /* U */ \
783         output[3] = 0x80;       /* V */ \
784       } else if (in >= high) { \
785         output[0] = 0xff;       /* A */ \
786         output[1] = input[1];   /* Y */ \
787         output[2] = input[2];   /* U */ \
788         output[3] = input[3];   /* V */ \
789       } else { \
790         gfloat val = 255.0f * ((in - low) / (high - low)); \
791         \
792         output[0] = CLAMP (val, 0, 255);        /* A */ \
793         output[1] = input[1];   /* Y */ \
794         output[2] = input[2];   /* U */ \
795         output[3] = input[3];   /* V */ \
796       } \
797       \
798       mask++; \
799       input += 4; \
800       output += 4; \
801     } \
802     mask += mask_increment; \
803   } \
804   \
805   return GST_FLOW_OK; \
806 }
807
808 CREATE_AYUV_FUNCTIONS (16, 65536.0f);
809 CREATE_AYUV_FUNCTIONS (8, 256.0f);
810
811 #define CREATE_ARGB_FUNCTIONS(depth, scale) \
812 static GstFlowReturn \
813 gst_shape_wipe_blend_argb_##depth (GstShapeWipe * self, GstBuffer * inbuf, \
814     GstBuffer * maskbuf, GstBuffer * outbuf) \
815 { \
816   const guint##depth *mask = (const guint##depth *) GST_BUFFER_DATA (maskbuf); \
817   const guint8 *input = (const guint8 *) GST_BUFFER_DATA (inbuf); \
818   guint8 *output = (guint8 *) GST_BUFFER_DATA (outbuf); \
819   guint i, j; \
820   guint mask_increment = ((depth == 16) ? GST_ROUND_UP_2 (self->width) : \
821                            GST_ROUND_UP_4 (self->width)) - self->width; \
822   gfloat position = self->mask_position; \
823   gfloat low = position - (self->mask_border / 2.0f); \
824   gfloat high = position + (self->mask_border / 2.0f); \
825   \
826   if (low < 0.0f) { \
827     high = 0.0f; \
828     low = 0.0f; \
829   } \
830   \
831   if (high > 1.0f) { \
832     low = 1.0f; \
833     high = 1.0f; \
834   } \
835   \
836   for (i = 0; i < self->height; i++) { \
837     for (j = 0; j < self->width; j++) { \
838       gfloat in = *mask / scale; \
839       \
840       if (in < low) { \
841         output[0] = 0x00;       /* A */ \
842         output[1] = 0x00;       /* R */ \
843         output[2] = 0x00;       /* G */ \
844         output[3] = 0x00;       /* B */ \
845       } else if (in >= high) { \
846         output[0] = 0xff;       /* A */ \
847         output[1] = input[1];   /* R */ \
848         output[2] = input[2];   /* G */ \
849         output[3] = input[3];   /* B */ \
850       } else { \
851         gfloat val = 255.0f * ((in - low) / (high - low)); \
852         \
853         output[0] = CLAMP (val, 0, 255);        /* A */ \
854         output[1] = input[1];   /* R */ \
855         output[2] = input[2];   /* G */ \
856         output[3] = input[3];   /* B */ \
857       } \
858       \
859       mask++; \
860       input += 4; \
861       output += 4; \
862     } \
863     mask += mask_increment; \
864   } \
865   \
866   return GST_FLOW_OK; \
867 }
868
869 CREATE_ARGB_FUNCTIONS (16, 65536.0f);
870 CREATE_ARGB_FUNCTIONS (8, 256.0f);
871
872 static GstFlowReturn
873 gst_shape_wipe_video_sink_chain (GstPad * pad, GstBuffer * buffer)
874 {
875   GstShapeWipe *self = GST_SHAPE_WIPE (GST_PAD_PARENT (pad));
876   GstFlowReturn ret = GST_FLOW_OK;
877   GstBuffer *mask = NULL, *outbuf = NULL;
878   GstClockTime timestamp;
879   gboolean new_outbuf = FALSE;
880
881   if (G_UNLIKELY (self->fmt == GST_VIDEO_FORMAT_UNKNOWN))
882     return GST_FLOW_NOT_NEGOTIATED;
883
884   timestamp = GST_BUFFER_TIMESTAMP (buffer);
885   timestamp =
886       gst_segment_to_stream_time (&self->segment, GST_FORMAT_TIME, timestamp);
887
888   if (GST_CLOCK_TIME_IS_VALID (timestamp))
889     gst_object_sync_values (G_OBJECT (self), timestamp);
890
891   GST_DEBUG_OBJECT (self,
892       "Blending buffer with timestamp %" GST_TIME_FORMAT " at position %lf",
893       GST_TIME_ARGS (timestamp), self->mask_position);
894
895   g_mutex_lock (self->mask_mutex);
896   if (!self->mask)
897     g_cond_wait (self->mask_cond, self->mask_mutex);
898
899   if (self->mask == NULL) {
900     g_mutex_unlock (self->mask_mutex);
901     gst_buffer_unref (buffer);
902     return GST_FLOW_UNEXPECTED;
903   } else {
904     mask = gst_buffer_ref (self->mask);
905   }
906   g_mutex_unlock (self->mask_mutex);
907
908   if (!gst_shape_wipe_do_qos (self, GST_BUFFER_TIMESTAMP (buffer))) {
909     gst_buffer_unref (buffer);
910     gst_buffer_unref (mask);
911     return GST_FLOW_OK;
912   }
913
914   /* Try to blend inplace, if it's not possible
915    * get a new buffer from downstream.
916    */
917   if (!gst_buffer_is_writable (buffer)) {
918     ret =
919         gst_pad_alloc_buffer_and_set_caps (self->srcpad, GST_BUFFER_OFFSET_NONE,
920         GST_BUFFER_SIZE (buffer), GST_PAD_CAPS (self->srcpad), &outbuf);
921     if (G_UNLIKELY (ret != GST_FLOW_OK)) {
922       gst_buffer_unref (buffer);
923       gst_buffer_unref (mask);
924       return ret;
925     }
926     gst_buffer_copy_metadata (outbuf, buffer, GST_BUFFER_COPY_ALL);
927     new_outbuf = TRUE;
928   } else {
929     outbuf = buffer;
930   }
931
932   if (self->fmt == GST_VIDEO_FORMAT_AYUV && self->mask_bpp == 16)
933     ret = gst_shape_wipe_blend_ayuv_16 (self, buffer, mask, outbuf);
934   else if (self->fmt == GST_VIDEO_FORMAT_AYUV)
935     ret = gst_shape_wipe_blend_ayuv_8 (self, buffer, mask, outbuf);
936   else if (self->fmt == GST_VIDEO_FORMAT_ARGB && self->mask_bpp == 16)
937     ret = gst_shape_wipe_blend_argb_16 (self, buffer, mask, outbuf);
938   else if (self->fmt == GST_VIDEO_FORMAT_ARGB)
939     ret = gst_shape_wipe_blend_argb_8 (self, buffer, mask, outbuf);
940   else
941     g_assert_not_reached ();
942
943   gst_buffer_unref (mask);
944   if (new_outbuf)
945     gst_buffer_unref (buffer);
946
947   if (ret != GST_FLOW_OK) {
948     gst_buffer_unref (outbuf);
949     return ret;
950   }
951
952   ret = gst_pad_push (self->srcpad, outbuf);
953   return ret;
954 }
955
956 static GstFlowReturn
957 gst_shape_wipe_mask_sink_chain (GstPad * pad, GstBuffer * buffer)
958 {
959   GstShapeWipe *self = GST_SHAPE_WIPE (GST_PAD_PARENT (pad));
960   GstFlowReturn ret = GST_FLOW_OK;
961
962   g_mutex_lock (self->mask_mutex);
963   GST_DEBUG_OBJECT (self, "Setting new mask buffer: %" GST_PTR_FORMAT, buffer);
964
965   gst_buffer_replace (&self->mask, buffer);
966   g_cond_signal (self->mask_cond);
967   g_mutex_unlock (self->mask_mutex);
968
969   gst_buffer_unref (buffer);
970
971   return ret;
972 }
973
974 static GstStateChangeReturn
975 gst_shape_wipe_change_state (GstElement * element, GstStateChange transition)
976 {
977   GstShapeWipe *self = GST_SHAPE_WIPE (element);
978   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
979
980   switch (transition) {
981     case GST_STATE_CHANGE_READY_TO_PAUSED:
982     default:
983       break;
984   }
985
986   /* Unblock video sink chain function */
987   if (transition == GST_STATE_CHANGE_PAUSED_TO_READY)
988     g_cond_signal (self->mask_cond);
989
990   if (GST_ELEMENT_CLASS (parent_class)->change_state)
991     ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
992
993   switch (transition) {
994     case GST_STATE_CHANGE_PAUSED_TO_READY:
995       gst_shape_wipe_reset (self);
996       break;
997     default:
998       break;
999   }
1000
1001   return ret;
1002 }
1003
1004 static gboolean
1005 gst_shape_wipe_video_sink_event (GstPad * pad, GstEvent * event)
1006 {
1007   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
1008   gboolean ret;
1009
1010   GST_DEBUG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
1011
1012   switch (GST_EVENT_TYPE (event)) {
1013     case GST_EVENT_NEWSEGMENT:{
1014       GstFormat fmt;
1015       gboolean is_update;
1016       gint64 start, end, base;
1017       gdouble rate;
1018
1019       gst_event_parse_new_segment (event, &is_update, &rate, &fmt, &start,
1020           &end, &base);
1021       if (fmt == GST_FORMAT_TIME) {
1022         GST_DEBUG_OBJECT (pad,
1023             "Got NEWSEGMENT event in GST_FORMAT_TIME, passing on (%"
1024             GST_TIME_FORMAT " - %" GST_TIME_FORMAT ")", GST_TIME_ARGS (start),
1025             GST_TIME_ARGS (end));
1026         gst_segment_set_newsegment (&self->segment, is_update, rate, fmt, start,
1027             end, base);
1028       } else {
1029         gst_segment_init (&self->segment, GST_FORMAT_TIME);
1030       }
1031     }
1032       /* fall through */
1033     case GST_EVENT_FLUSH_STOP:
1034       gst_shape_wipe_reset_qos (self);
1035       /* fall through */
1036     default:
1037       ret = gst_pad_push_event (self->srcpad, event);
1038       break;
1039   }
1040
1041   gst_object_unref (self);
1042   return ret;
1043 }
1044
1045 static gboolean
1046 gst_shape_wipe_mask_sink_event (GstPad * pad, GstEvent * event)
1047 {
1048   GST_DEBUG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
1049
1050   /* Dropping all events here */
1051   gst_event_unref (event);
1052   return TRUE;
1053 }
1054
1055 static gboolean
1056 gst_shape_wipe_src_event (GstPad * pad, GstEvent * event)
1057 {
1058   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
1059   gboolean ret;
1060
1061   switch (GST_EVENT_TYPE (event)) {
1062     case GST_EVENT_QOS:{
1063       GstClockTimeDiff diff;
1064       GstClockTime timestamp;
1065       gdouble proportion;
1066
1067       gst_event_parse_qos (event, &proportion, &diff, &timestamp);
1068
1069       gst_shape_wipe_update_qos (self, proportion, diff, timestamp);
1070     }
1071       /* fall through */
1072     default:
1073       ret = gst_pad_push_event (self->video_sinkpad, event);
1074       break;
1075   }
1076
1077   gst_object_unref (self);
1078   return ret;
1079 }
1080
1081 static gboolean
1082 plugin_init (GstPlugin * plugin)
1083 {
1084   GST_DEBUG_CATEGORY_INIT (gst_shape_wipe_debug, "shapewipe", 0,
1085       "shapewipe element");
1086
1087   gst_controller_init (NULL, NULL);
1088
1089   if (!gst_element_register (plugin, "shapewipe", GST_RANK_NONE,
1090           GST_TYPE_SHAPE_WIPE))
1091     return FALSE;
1092
1093   return TRUE;
1094 }
1095
1096 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1097     GST_VERSION_MINOR,
1098     "shapewipe",
1099     "Shape Wipe transition filter",
1100     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)