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, ×tamp);
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