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