[MOVED FROM BAD 12/29] shapewipe: Proxy queries on the video pads to the correct...
[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
60 static GstStateChangeReturn gst_shape_wipe_change_state (GstElement * element,
61     GstStateChange transition);
62
63 static GstFlowReturn gst_shape_wipe_video_sink_chain (GstPad * pad,
64     GstBuffer * buffer);
65 static gboolean gst_shape_wipe_video_sink_event (GstPad * pad,
66     GstEvent * event);
67 static gboolean gst_shape_wipe_video_sink_setcaps (GstPad * pad,
68     GstCaps * caps);
69 static GstCaps *gst_shape_wipe_video_sink_getcaps (GstPad * pad);
70 static GstFlowReturn gst_shape_wipe_video_sink_bufferalloc (GstPad * pad,
71     guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf);
72 static gboolean gst_shape_wipe_video_sink_query (GstPad * pad,
73     GstQuery * query);
74 static GstFlowReturn gst_shape_wipe_mask_sink_chain (GstPad * pad,
75     GstBuffer * buffer);
76 static gboolean gst_shape_wipe_mask_sink_event (GstPad * pad, GstEvent * event);
77 static gboolean gst_shape_wipe_mask_sink_setcaps (GstPad * pad, GstCaps * caps);
78 static GstCaps *gst_shape_wipe_mask_sink_getcaps (GstPad * pad);
79 static gboolean gst_shape_wipe_src_event (GstPad * pad, GstEvent * event);
80 static GstCaps *gst_shape_wipe_src_getcaps (GstPad * pad);
81 static gboolean gst_shape_wipe_src_query (GstPad * pad, GstQuery * query);
82
83 enum
84 {
85   PROP_0,
86   PROP_POSITION,
87   PROP_BORDER
88 };
89
90 static GstStaticPadTemplate video_sink_pad_template =
91 GST_STATIC_PAD_TEMPLATE ("video_sink",
92     GST_PAD_SINK,
93     GST_PAD_ALWAYS,
94     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV")));
95
96 static GstStaticPadTemplate mask_sink_pad_template =
97     GST_STATIC_PAD_TEMPLATE ("mask_sink",
98     GST_PAD_SINK,
99     GST_PAD_ALWAYS,
100     GST_STATIC_CAPS ("video/x-raw-gray, "
101         "bpp = 8, "
102         "depth = 8, "
103         "width = " GST_VIDEO_SIZE_RANGE ", "
104         "height = " GST_VIDEO_SIZE_RANGE ", " "framerate = 0/1 ; "
105         "video/x-raw-gray, " "bpp = 16, " "depth = 16, "
106         "endianness = BYTE_ORDER, " "width = " GST_VIDEO_SIZE_RANGE ", "
107         "height = " GST_VIDEO_SIZE_RANGE ", " "framerate = 0/1"));
108
109 static GstStaticPadTemplate src_pad_template =
110 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
111     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV")));
112
113 GST_DEBUG_CATEGORY_STATIC (gst_shape_wipe_debug);
114 #define GST_CAT_DEFAULT gst_shape_wipe_debug
115
116 GST_BOILERPLATE (GstShapeWipe, gst_shape_wipe, GstElement, GST_TYPE_ELEMENT);
117
118 static void
119 gst_shape_wipe_base_init (gpointer g_class)
120 {
121   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
122
123   gst_element_class_set_details_simple (gstelement_class,
124       "Shape Wipe transition filter",
125       "Filter/Editor/Video",
126       "Adds a shape wipe transition to a video stream",
127       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
128
129   gst_element_class_add_pad_template (gstelement_class,
130       gst_static_pad_template_get (&video_sink_pad_template));
131   gst_element_class_add_pad_template (gstelement_class,
132       gst_static_pad_template_get (&mask_sink_pad_template));
133   gst_element_class_add_pad_template (gstelement_class,
134       gst_static_pad_template_get (&src_pad_template));
135 }
136
137 static void
138 gst_shape_wipe_class_init (GstShapeWipeClass * klass)
139 {
140   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
141   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
142
143   gobject_class->finalize = gst_shape_wipe_finalize;
144   gobject_class->set_property = gst_shape_wipe_set_property;
145   gobject_class->get_property = gst_shape_wipe_get_property;
146
147   g_object_class_install_property (gobject_class, PROP_POSITION,
148       g_param_spec_float ("position", "Position", "Position of the mask",
149           0.0, 1.0, 0.0,
150           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
151   g_object_class_install_property (gobject_class, PROP_BORDER,
152       g_param_spec_float ("border", "Border", "Border of the mask",
153           0.0, 1.0, 0.0,
154           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
155
156   gstelement_class->change_state =
157       GST_DEBUG_FUNCPTR (gst_shape_wipe_change_state);
158 }
159
160 static void
161 gst_shape_wipe_init (GstShapeWipe * self, GstShapeWipeClass * g_class)
162 {
163   self->video_sinkpad =
164       gst_pad_new_from_static_template (&video_sink_pad_template, "video_sink");
165   gst_pad_set_chain_function (self->video_sinkpad,
166       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_chain));
167   gst_pad_set_event_function (self->video_sinkpad,
168       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_event));
169   gst_pad_set_setcaps_function (self->video_sinkpad,
170       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_setcaps));
171   gst_pad_set_getcaps_function (self->video_sinkpad,
172       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_getcaps));
173   gst_pad_set_bufferalloc_function (self->video_sinkpad,
174       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_bufferalloc));
175   gst_pad_set_query_function (self->video_sinkpad,
176       GST_DEBUG_FUNCPTR (gst_shape_wipe_video_sink_query));
177   gst_element_add_pad (GST_ELEMENT (self), self->video_sinkpad);
178
179   self->mask_sinkpad =
180       gst_pad_new_from_static_template (&mask_sink_pad_template, "mask_sink");
181   gst_pad_set_chain_function (self->mask_sinkpad,
182       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_chain));
183   gst_pad_set_event_function (self->mask_sinkpad,
184       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_event));
185   gst_pad_set_setcaps_function (self->mask_sinkpad,
186       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_setcaps));
187   gst_pad_set_getcaps_function (self->mask_sinkpad,
188       GST_DEBUG_FUNCPTR (gst_shape_wipe_mask_sink_getcaps));
189   gst_element_add_pad (GST_ELEMENT (self), self->mask_sinkpad);
190
191   self->srcpad = gst_pad_new_from_static_template (&src_pad_template, "src");
192   gst_pad_set_event_function (self->srcpad,
193       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_event));
194   gst_pad_set_getcaps_function (self->srcpad,
195       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_getcaps));
196   gst_pad_set_query_function (self->srcpad,
197       GST_DEBUG_FUNCPTR (gst_shape_wipe_src_query));
198   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
199
200   self->mask_mutex = g_mutex_new ();
201   self->mask_cond = g_cond_new ();
202
203   gst_shape_wipe_reset (self);
204 }
205
206 static void
207 gst_shape_wipe_get_property (GObject * object, guint prop_id,
208     GValue * value, GParamSpec * pspec)
209 {
210   GstShapeWipe *self = GST_SHAPE_WIPE (object);
211
212   switch (prop_id) {
213     case PROP_POSITION:
214       g_value_set_float (value, self->mask_position);
215       break;
216     case PROP_BORDER:
217       g_value_set_float (value, self->mask_border);
218       break;
219     default:
220       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
221       break;
222   }
223 }
224
225 static void
226 gst_shape_wipe_set_property (GObject * object, guint prop_id,
227     const GValue * value, GParamSpec * pspec)
228 {
229   GstShapeWipe *self = GST_SHAPE_WIPE (object);
230
231   switch (prop_id) {
232     case PROP_POSITION:
233       self->mask_position = g_value_get_float (value);
234       break;
235     case PROP_BORDER:
236       self->mask_border = g_value_get_float (value);
237       break;
238     default:
239       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
240       break;
241   }
242 }
243
244 static void
245 gst_shape_wipe_finalize (GObject * object)
246 {
247   GstShapeWipe *self = GST_SHAPE_WIPE (object);
248
249   gst_shape_wipe_reset (self);
250
251   if (self->mask_cond)
252     g_cond_free (self->mask_cond);
253   self->mask_cond = NULL;
254
255   if (self->mask_mutex)
256     g_mutex_free (self->mask_mutex);
257   self->mask_mutex = NULL;
258
259   G_OBJECT_CLASS (parent_class)->finalize (object);
260 }
261
262 static void
263 gst_shape_wipe_reset (GstShapeWipe * self)
264 {
265   if (self->mask)
266     gst_buffer_unref (self->mask);
267   self->mask = NULL;
268
269   g_cond_signal (self->mask_cond);
270
271   self->width = self->height = 0;
272   self->mask_position = 0.0;
273   self->mask_border = 0.0;
274   self->mask_bpp = 0;
275
276   gst_segment_init (&self->segment, GST_FORMAT_TIME);
277 }
278
279 static GstFlowReturn
280 gst_shape_wipe_video_sink_bufferalloc (GstPad * pad, guint64 offset, guint size,
281     GstCaps * caps, GstBuffer ** buf)
282 {
283   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
284   GstFlowReturn ret = GST_FLOW_OK;
285
286   GST_DEBUG_OBJECT (pad, "Allocating buffer with offset 0x%" G_GINT64_MODIFIER
287       "x and size %u with caps: %" GST_PTR_FORMAT, offset, size, caps);
288
289   *buf = NULL;
290
291   ret = gst_pad_alloc_buffer (self->srcpad, offset, size, caps, buf);
292
293   gst_object_unref (self);
294
295   return ret;
296 }
297
298 static gboolean
299 gst_shape_wipe_video_sink_setcaps (GstPad * pad, GstCaps * caps)
300 {
301   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
302   gboolean ret = TRUE;
303   GstStructure *s;
304   gint width, height;
305
306   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
307
308   s = gst_caps_get_structure (caps, 0);
309
310   if (!gst_structure_get_int (s, "width", &width) ||
311       !gst_structure_get_int (s, "height", &height)) {
312     ret = FALSE;
313     goto done;
314   }
315
316   if (self->width != width || self->height != height) {
317     g_mutex_lock (self->mask_mutex);
318     self->width = width;
319     self->height = height;
320
321     if (self->mask)
322       gst_buffer_unref (self->mask);
323     self->mask = NULL;
324     g_mutex_unlock (self->mask_mutex);
325   }
326
327   ret = gst_pad_set_caps (self->srcpad, caps);
328
329 done:
330   gst_object_unref (self);
331
332   return ret;
333 }
334
335 static GstCaps *
336 gst_shape_wipe_video_sink_getcaps (GstPad * pad)
337 {
338   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
339   GstCaps *ret, *tmp;
340
341   if (GST_PAD_CAPS (pad))
342     return gst_caps_copy (GST_PAD_CAPS (pad));
343
344   tmp = gst_pad_peer_get_caps (self->srcpad);
345   if (tmp) {
346     ret = gst_caps_intersect (tmp, gst_pad_get_pad_template_caps (pad));
347     gst_caps_unref (tmp);
348   } else {
349     ret = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
350   }
351
352   tmp = gst_pad_peer_get_caps (pad);
353   if (tmp) {
354     GstCaps *intersection;
355
356     intersection = gst_caps_intersect (tmp, ret);
357     gst_caps_unref (tmp);
358     gst_caps_unref (ret);
359     ret = intersection;
360   }
361
362   if (self->height && self->width) {
363     guint i, n;
364
365     n = gst_caps_get_size (ret);
366     for (i = 0; i < n; i++) {
367       GstStructure *s = gst_caps_get_structure (ret, i);
368
369       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
370           G_TYPE_INT, self->height, NULL);
371     }
372   }
373
374   tmp = gst_pad_peer_get_caps (self->mask_sinkpad);
375   if (tmp) {
376     GstCaps *intersection, *tmp2;
377     guint i, n;
378
379     tmp = gst_caps_make_writable (tmp);
380
381     tmp2 = gst_caps_copy (gst_pad_get_pad_template_caps (self->mask_sinkpad));
382
383     intersection = gst_caps_intersect (tmp, tmp2);
384     gst_caps_unref (tmp);
385     gst_caps_unref (tmp2);
386     tmp = intersection;
387
388     n = gst_caps_get_size (tmp);
389
390     for (i = 0; i < n; i++) {
391       GstStructure *s = gst_caps_get_structure (tmp, i);
392
393       gst_structure_remove_fields (s, "bpp", "depth", "endianness", "framerate",
394           NULL);
395       gst_structure_set_name (s, "video/x-raw-yuv");
396     }
397
398     intersection = gst_caps_intersect (tmp, ret);
399     gst_caps_unref (tmp);
400     gst_caps_unref (ret);
401     ret = intersection;
402   }
403
404   gst_object_unref (self);
405
406   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
407
408   return ret;
409 }
410
411 static gboolean
412 gst_shape_wipe_mask_sink_setcaps (GstPad * pad, GstCaps * caps)
413 {
414   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
415   gboolean ret = TRUE;
416   GstStructure *s;
417   gint width, height, bpp;
418
419   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
420
421   s = gst_caps_get_structure (caps, 0);
422
423   if (!gst_structure_get_int (s, "width", &width) ||
424       !gst_structure_get_int (s, "height", &height) ||
425       !gst_structure_get_int (s, "bpp", &bpp)) {
426     ret = FALSE;
427     goto done;
428   }
429
430   if ((self->width != width || self->height != height) &&
431       self->width > 0 && self->height > 0) {
432     GST_ERROR_OBJECT (pad, "Mask caps must have the same width/height "
433         "as the video caps");
434     ret = FALSE;
435     goto done;
436   } else {
437     self->width = width;
438     self->height = height;
439   }
440
441   self->mask_bpp = bpp;
442
443 done:
444   gst_object_unref (self);
445
446   return ret;
447 }
448
449 static GstCaps *
450 gst_shape_wipe_mask_sink_getcaps (GstPad * pad)
451 {
452   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
453   GstCaps *ret, *tmp;
454   guint i, n;
455
456   if (GST_PAD_CAPS (pad))
457     return gst_caps_copy (GST_PAD_CAPS (pad));
458
459   tmp = gst_pad_peer_get_caps (self->video_sinkpad);
460   if (tmp) {
461     ret =
462         gst_caps_intersect (tmp,
463         gst_pad_get_pad_template_caps (self->video_sinkpad));
464     gst_caps_unref (tmp);
465   } else {
466     ret = gst_caps_copy (gst_pad_get_pad_template_caps (self->video_sinkpad));
467   }
468
469   tmp = gst_pad_peer_get_caps (self->srcpad);
470   if (tmp) {
471     GstCaps *intersection;
472
473     intersection = gst_caps_intersect (ret, tmp);
474     gst_caps_unref (ret);
475     gst_caps_unref (tmp);
476     ret = intersection;
477   }
478
479   n = gst_caps_get_size (ret);
480   tmp = gst_caps_new_empty ();
481   for (i = 0; i < n; i++) {
482     GstStructure *s = gst_caps_get_structure (ret, i);
483     GstStructure *t;
484
485     gst_structure_set_name (s, "video/x-raw-gray");
486     gst_structure_remove_fields (s, "format", "framerate", NULL);
487
488     if (self->width && self->height)
489       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
490           G_TYPE_INT, self->height, NULL);
491
492     gst_structure_set (s, "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
493
494     t = gst_structure_copy (s);
495
496     gst_structure_set (s, "bpp", G_TYPE_INT, 16, "depth", G_TYPE_INT, 16,
497         "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
498     gst_structure_set (t, "bpp", G_TYPE_INT, 8, "depth", G_TYPE_INT, 8, NULL);
499
500     gst_caps_append_structure (tmp, t);
501   }
502   gst_caps_merge (ret, tmp);
503
504   tmp = gst_pad_peer_get_caps (pad);
505   if (tmp) {
506     GstCaps *intersection;
507
508     intersection = gst_caps_intersect (tmp, ret);
509     gst_caps_unref (tmp);
510     gst_caps_unref (ret);
511     ret = intersection;
512   }
513
514   gst_object_unref (self);
515
516   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
517
518   return ret;
519 }
520
521 static GstCaps *
522 gst_shape_wipe_src_getcaps (GstPad * pad)
523 {
524   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
525   GstCaps *ret, *tmp;
526
527   if (GST_PAD_CAPS (pad))
528     return gst_caps_copy (GST_PAD_CAPS (pad));
529   else if (GST_PAD_CAPS (self->video_sinkpad))
530     return gst_caps_copy (GST_PAD_CAPS (self->video_sinkpad));
531
532   tmp = gst_pad_peer_get_caps (self->video_sinkpad);
533   if (tmp) {
534     ret =
535         gst_caps_intersect (tmp,
536         gst_pad_get_pad_template_caps (self->video_sinkpad));
537     gst_caps_unref (tmp);
538   } else {
539     ret = gst_caps_copy (gst_pad_get_pad_template_caps (self->video_sinkpad));
540   }
541
542   tmp = gst_pad_peer_get_caps (pad);
543   if (tmp) {
544     GstCaps *intersection;
545
546     intersection = gst_caps_intersect (tmp, ret);
547     gst_caps_unref (tmp);
548     gst_caps_unref (ret);
549     ret = intersection;
550   }
551
552   if (self->height && self->width) {
553     guint i, n;
554
555     n = gst_caps_get_size (ret);
556     for (i = 0; i < n; i++) {
557       GstStructure *s = gst_caps_get_structure (ret, i);
558
559       gst_structure_set (s, "width", G_TYPE_INT, self->width, "height",
560           G_TYPE_INT, self->height, NULL);
561     }
562   }
563
564   tmp = gst_pad_peer_get_caps (self->mask_sinkpad);
565   if (tmp) {
566     GstCaps *intersection, *tmp2;
567     guint i, n;
568
569     tmp = gst_caps_make_writable (tmp);
570     tmp2 = gst_caps_copy (gst_pad_get_pad_template_caps (self->mask_sinkpad));
571
572     intersection = gst_caps_intersect (tmp, tmp2);
573     gst_caps_unref (tmp);
574     gst_caps_unref (tmp2);
575
576     tmp = intersection;
577     n = gst_caps_get_size (tmp);
578
579     for (i = 0; i < n; i++) {
580       GstStructure *s = gst_caps_get_structure (tmp, i);
581
582       gst_structure_remove_fields (s, "bpp", "depth", "endianness", "framerate",
583           NULL);
584       gst_structure_set_name (s, "video/x-raw-yuv");
585     }
586
587     intersection = gst_caps_intersect (tmp, ret);
588     gst_caps_unref (tmp);
589     gst_caps_unref (ret);
590     ret = intersection;
591   }
592
593   gst_object_unref (self);
594
595   GST_DEBUG_OBJECT (pad, "Returning caps: %" GST_PTR_FORMAT, ret);
596
597   return ret;
598 }
599
600 static gboolean
601 gst_shape_wipe_video_sink_query (GstPad * pad, GstQuery * query)
602 {
603   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
604   gboolean ret;
605   GstPad *peer = gst_pad_get_peer (self->srcpad);
606
607   GST_DEBUG_OBJECT (pad, "Handling query of type '%s'",
608       gst_query_type_get_name (GST_QUERY_TYPE (query)));
609
610   if (!peer) {
611     GST_INFO_OBJECT (pad, "No peer yet");
612     ret = FALSE;
613   } else {
614     ret = gst_pad_query (peer, query);
615     gst_object_unref (peer);
616   }
617
618   gst_object_unref (self);
619   return ret;
620 }
621
622 static gboolean
623 gst_shape_wipe_src_query (GstPad * pad, GstQuery * query)
624 {
625   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
626   gboolean ret;
627   GstPad *peer = gst_pad_get_peer (self->video_sinkpad);
628
629   GST_DEBUG_OBJECT (pad, "Handling query of type '%s'",
630       gst_query_type_get_name (GST_QUERY_TYPE (query)));
631
632   if (!peer) {
633     GST_INFO_OBJECT (pad, "No peer yet");
634     ret = FALSE;
635   } else {
636     ret = gst_pad_query (peer, query);
637     gst_object_unref (peer);
638   }
639
640   gst_object_unref (self);
641   return ret;
642 }
643
644 static GstFlowReturn
645 gst_shape_wipe_blend_16 (GstShapeWipe * self, GstBuffer * inbuf,
646     GstBuffer * maskbuf, GstBuffer * outbuf)
647 {
648   const guint16 *mask = (const guint16 *) GST_BUFFER_DATA (maskbuf);
649   const guint8 *input = (const guint8 *) GST_BUFFER_DATA (inbuf);
650   guint8 *output = (guint8 *) GST_BUFFER_DATA (outbuf);
651   guint i, j;
652   guint mask_increment = GST_ROUND_UP_2 (self->width) - self->width;
653   gfloat position = self->mask_position;
654   gfloat low = position - (self->mask_border / 2.0f);
655   gfloat high = position + (self->mask_border / 2.0f);
656
657   if (low < 0.0f) {
658     high = 0.0f;
659     low = 0.0f;
660   }
661
662   if (high > 1.0f) {
663     low = 1.0f;
664     high = 1.0f;
665   }
666
667   for (i = 0; i < self->height; i++) {
668     for (j = 0; j < self->width; j++) {
669       gfloat in = *mask / 65536.0f;
670
671       if (in < low) {
672         output[0] = 0x00;       /* A */
673         output[1] = 0x00;       /* Y */
674         output[2] = 0x80;       /* U */
675         output[3] = 0x80;       /* V */
676       } else if (in >= high) {
677         output[0] = 0xff;       /* A */
678         output[1] = input[1];   /* Y */
679         output[2] = input[2];   /* U */
680         output[3] = input[3];   /* V */
681       } else {
682         gfloat val = 255.0f * ((in - low) / (high - low));
683
684         output[0] = CLAMP (val, 0, 255);        /* A */
685         output[1] = input[1];   /* Y */
686         output[2] = input[2];   /* U */
687         output[3] = input[3];   /* V */
688       }
689
690       mask++;
691       input += 4;
692       output += 4;
693     }
694     mask += mask_increment;
695   }
696
697   return GST_FLOW_OK;
698 }
699
700 static GstFlowReturn
701 gst_shape_wipe_blend_8 (GstShapeWipe * self, GstBuffer * inbuf,
702     GstBuffer * maskbuf, GstBuffer * outbuf)
703 {
704   const guint8 *mask = (const guint8 *) GST_BUFFER_DATA (maskbuf);
705   const guint8 *input = (const guint8 *) GST_BUFFER_DATA (inbuf);
706   guint8 *output = (guint8 *) GST_BUFFER_DATA (outbuf);
707   guint i, j;
708   guint mask_increment = GST_ROUND_UP_4 (self->width) - self->width;
709   gfloat position = self->mask_position;
710   gfloat low = position - (self->mask_border / 2.0f);
711   gfloat high = position + (self->mask_border / 2.0f);
712
713   if (low < 0.0f) {
714     high = 0.0f;
715     low = 0.0f;
716   }
717
718   if (high > 1.0f) {
719     low = 1.0f;
720     high = 1.0f;
721   }
722
723   for (i = 0; i < self->height; i++) {
724     for (j = 0; j < self->width; j++) {
725       gfloat in = *mask / 256.0f;
726
727       if (in < low) {
728         output[0] = 0x00;       /* A */
729         output[1] = 0x00;       /* Y */
730         output[2] = 0x80;       /* U */
731         output[3] = 0x80;       /* V */
732       } else if (in >= high) {
733         output[0] = 0xff;       /* A */
734         output[1] = input[1];   /* Y */
735         output[2] = input[2];   /* U */
736         output[3] = input[3];   /* V */
737       } else {
738         gfloat val = 255.0f * ((in - low) / (high - low));
739
740         output[0] = CLAMP (val, 0, 255);        /* A */
741         output[1] = input[1];   /* Y */
742         output[2] = input[2];   /* U */
743         output[3] = input[3];   /* V */
744       }
745
746       mask++;
747       input += 4;
748       output += 4;
749     }
750     mask += mask_increment;
751   }
752
753   return GST_FLOW_OK;
754 }
755
756 static GstFlowReturn
757 gst_shape_wipe_video_sink_chain (GstPad * pad, GstBuffer * buffer)
758 {
759   GstShapeWipe *self = GST_SHAPE_WIPE (GST_PAD_PARENT (pad));
760   GstFlowReturn ret = GST_FLOW_OK;
761   GstBuffer *mask = NULL, *outbuf = NULL;
762   GstClockTime timestamp;
763   gboolean new_outbuf = FALSE;
764
765   timestamp = GST_BUFFER_TIMESTAMP (buffer);
766   timestamp =
767       gst_segment_to_stream_time (&self->segment, GST_FORMAT_TIME, timestamp);
768
769   if (GST_CLOCK_TIME_IS_VALID (timestamp))
770     gst_object_sync_values (G_OBJECT (self), timestamp);
771
772   GST_DEBUG_OBJECT (self,
773       "Blending buffer with timestamp %" GST_TIME_FORMAT " at position %lf",
774       GST_TIME_ARGS (timestamp), self->mask_position);
775
776   g_mutex_lock (self->mask_mutex);
777   if (!self->mask)
778     g_cond_wait (self->mask_cond, self->mask_mutex);
779
780   if (self->mask == NULL) {
781     g_mutex_unlock (self->mask_mutex);
782     gst_buffer_unref (buffer);
783     return GST_FLOW_UNEXPECTED;
784   } else {
785     mask = gst_buffer_ref (self->mask);
786   }
787   g_mutex_unlock (self->mask_mutex);
788
789   /* Try to blend inplace, if it's not possible
790    * get a new buffer from downstream.
791    */
792   if (!gst_buffer_is_writable (buffer)) {
793     ret =
794         gst_pad_alloc_buffer_and_set_caps (self->srcpad, GST_BUFFER_OFFSET_NONE,
795         GST_BUFFER_SIZE (buffer), GST_PAD_CAPS (self->srcpad), &outbuf);
796     if (G_UNLIKELY (ret != GST_FLOW_OK)) {
797       gst_buffer_unref (buffer);
798       gst_buffer_unref (mask);
799       return ret;
800     }
801     new_outbuf = TRUE;
802   } else {
803     outbuf = buffer;
804   }
805
806   if (self->mask_bpp == 16)
807     ret = gst_shape_wipe_blend_16 (self, buffer, mask, outbuf);
808   else
809     ret = gst_shape_wipe_blend_8 (self, buffer, mask, outbuf);
810
811   gst_buffer_unref (mask);
812   if (new_outbuf)
813     gst_buffer_unref (buffer);
814
815   if (ret != GST_FLOW_OK) {
816     gst_buffer_unref (outbuf);
817     return ret;
818   }
819
820   ret = gst_pad_push (self->srcpad, outbuf);
821   return ret;
822 }
823
824 static GstFlowReturn
825 gst_shape_wipe_mask_sink_chain (GstPad * pad, GstBuffer * buffer)
826 {
827   GstShapeWipe *self = GST_SHAPE_WIPE (GST_PAD_PARENT (pad));
828   GstFlowReturn ret = GST_FLOW_OK;
829
830   g_mutex_lock (self->mask_mutex);
831   GST_DEBUG_OBJECT (self, "Setting new mask buffer: %" GST_PTR_FORMAT, buffer);
832
833   gst_buffer_replace (&self->mask, buffer);
834   g_cond_signal (self->mask_cond);
835   g_mutex_unlock (self->mask_mutex);
836
837   gst_buffer_unref (buffer);
838
839   return ret;
840 }
841
842 static GstStateChangeReturn
843 gst_shape_wipe_change_state (GstElement * element, GstStateChange transition)
844 {
845   GstShapeWipe *self = GST_SHAPE_WIPE (element);
846   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
847
848   switch (transition) {
849     case GST_STATE_CHANGE_READY_TO_PAUSED:
850     default:
851       break;
852   }
853
854   /* Unblock video sink chain function */
855   if (transition == GST_STATE_CHANGE_PAUSED_TO_READY)
856     g_cond_signal (self->mask_cond);
857
858   if (GST_ELEMENT_CLASS (parent_class)->change_state)
859     ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
860
861   switch (transition) {
862     case GST_STATE_CHANGE_PAUSED_TO_READY:
863       gst_shape_wipe_reset (self);
864       break;
865     default:
866       break;
867   }
868
869   return ret;
870 }
871
872 static gboolean
873 gst_shape_wipe_video_sink_event (GstPad * pad, GstEvent * event)
874 {
875   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
876   gboolean ret;
877
878   GST_DEBUG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
879
880   switch (GST_EVENT_TYPE (event)) {
881     case GST_EVENT_NEWSEGMENT:{
882       GstFormat fmt;
883       gboolean is_update;
884       gint64 start, end, base;
885       gdouble rate;
886
887       gst_event_parse_new_segment (event, &is_update, &rate, &fmt, &start,
888           &end, &base);
889       if (fmt == GST_FORMAT_TIME) {
890         GST_DEBUG_OBJECT (pad,
891             "Got NEWSEGMENT event in GST_FORMAT_TIME, passing on (%"
892             GST_TIME_FORMAT " - %" GST_TIME_FORMAT ")", GST_TIME_ARGS (start),
893             GST_TIME_ARGS (end));
894         gst_segment_set_newsegment (&self->segment, is_update, rate, fmt, start,
895             end, base);
896       } else {
897         gst_segment_init (&self->segment, GST_FORMAT_TIME);
898       }
899     }
900       /* fall through */
901     default:
902       ret = gst_pad_push_event (self->srcpad, event);
903       break;
904   }
905
906   gst_object_unref (self);
907   return ret;
908 }
909
910 static gboolean
911 gst_shape_wipe_mask_sink_event (GstPad * pad, GstEvent * event)
912 {
913   GST_DEBUG_OBJECT (pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
914
915   /* Dropping all events here */
916   gst_event_unref (event);
917   return TRUE;
918 }
919
920 static gboolean
921 gst_shape_wipe_src_event (GstPad * pad, GstEvent * event)
922 {
923   GstShapeWipe *self = GST_SHAPE_WIPE (gst_pad_get_parent (pad));
924   gboolean ret;
925
926   switch (GST_EVENT_TYPE (event)) {
927     default:
928       ret = gst_pad_push_event (self->video_sinkpad, event);
929       break;
930   }
931
932   gst_object_unref (self);
933   return ret;
934 }
935
936 static gboolean
937 plugin_init (GstPlugin * plugin)
938 {
939   GST_DEBUG_CATEGORY_INIT (gst_shape_wipe_debug, "shapewipe", 0,
940       "shapewipe element");
941
942   gst_controller_init (NULL, NULL);
943
944   if (!gst_element_register (plugin, "shapewipe", GST_RANK_NONE,
945           GST_TYPE_SHAPE_WIPE))
946     return FALSE;
947
948   return TRUE;
949 }
950
951 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
952     GST_VERSION_MINOR,
953     "shapewipe",
954     "Shape Wipe transition filter",
955     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)