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