1 /* GStreamer
2 * Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
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-overlaycomposition
22 *
23 * The overlaycomposition element renders an overlay using an application
24 * provided draw function.
25 *
26 * A more interesting example can be found at
27 * https://cgit.freedesktop.org/gstreamer/gst-plugins-base/tree/tests/examples/overlaycomposition/overlaycomposition.c
28 *
29 * <refsect2>
30 * <title>Example code</title>
31 * |[
32 *
33 * #include <gst/gst.h>
34 * #include <gst/video/video.h>
35 *
36 * ...
37 *
38 * typedef struct {
39 * gboolean valid;
40 * GstVideoInfo info;
41 * } OverlayState;
42 *
43 * ...
44 *
45 * static void
46 * prepare_overlay (GstElement * overlay, GstCaps * caps, gint window_width,
47 * gint window_height, gpointer user_data)
48 * {
49 * OverlayState *s = (OverlayState *)user_data;
50 *
51 * if (gst_video_info_from_caps (&s->info, caps))
52 * s->valid = TRUE;
53 * }
54 *
55 * static GstVideoOverlayComposition *
56 * draw_overlay (GstElement * overlay, GstSample * sample, gpointer user_data)
57 * {
58 * OverlayState *s = (OverlayState *)user_data;
59 * GstBuffer *buffer;
60 * GstVideoOverlayRectangle *rect;
61 * GstVideoOverlayComposition *comp;
62 * GstVideoInfo info;
63 * GstVideoFrame frame;
64 * gint x, y;
65 * guint8 *data;
66 *
67 * if (!s->valid)
68 * return NULL;
69 *
70 * gst_video_info_set_format (&info, GST_VIDEO_FORMAT_BGRA, 16, 16);
71 * buffer = gst_buffer_new_and_alloc (info.size);
72 * gst_buffer_add_video_meta (buffer, GST_VIDEO_FRAME_FLAG_NONE,
73 * GST_VIDEO_INFO_FORMAT(&info),
74 * GST_VIDEO_INFO_WIDTH(&info),
75 * GST_VIDEO_INFO_HEIGHT(&info));
76 *
77 * gst_video_frame_map (&frame, &info, buffer, GST_MAP_WRITE);
78 *
79 * // Overlay a half-transparent blue 16x16 rectangle in the middle
80 * // of the frame
81 * data = GST_VIDEO_FRAME_PLANE_DATA(&frame, 0);
82 * for (y = 0; y < 16; y++) {
83 * guint8 *line = &data[y * GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0)];
84 * for (x = 0; x < 16; x++) {
85 * guint8 *pixel = &line[x * 4];
86 *
87 * pixel[0] = 255;
88 * pixel[1] = 0;
89 * pixel[2] = 0;
90 * pixel[3] = 127;
91 * }
92 * }
93 *
94 * gst_video_frame_unmap (&frame);
95 * rect = gst_video_overlay_rectangle_new_raw (buffer,
96 * s->info.width / 2 - 8,
97 * s->info.height / 2 - 8,
98 * 16, 16,
99 * GST_VIDEO_OVERLAY_FORMAT_FLAG_NONE);
100 * comp = gst_video_overlay_composition_new (rect);
101 * gst_video_overlay_rectangle_unref (rect);
102 * gst_buffer_unref (buffer);
103 *
104 * return comp;
105 * }
106 *
107 * ...
108 *
109 * overlay = gst_element_factory_make ("overlaycomposition", "overlay");
110 *
111 * g_signal_connect (overlay, "draw", G_CALLBACK (draw_overlay),
112 * overlay_state);
113 * g_signal_connect (overlay, "caps-changed",
114 * G_CALLBACK (prepare_overlay), overlay_state);
115 * ...
116 *
117 * ]|
118 * </refsect2>
119 */
120
121 #if HAVE_CONFIG_H
122 #include "config.h"
123 #endif
124
125 #include <string.h>
126
127 #include "gstoverlaycomposition.h"
128
129 GST_DEBUG_CATEGORY_STATIC (gst_overlay_composition_debug);
130 #define GST_CAT_DEFAULT gst_overlay_composition_debug
131
132 #define OVERLAY_COMPOSITION_CAPS GST_VIDEO_CAPS_MAKE (GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS)
133
134 #define ALL_CAPS OVERLAY_COMPOSITION_CAPS ";" \
135 GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
136
137 enum
138 {
139 SIGNAL_CAPS_CHANGED,
140 SIGNAL_DRAW,
141 LAST_SIGNAL
142 };
143
144 static guint overlay_composition_signals[LAST_SIGNAL];
145
146 static GstStaticCaps overlay_composition_caps =
147 GST_STATIC_CAPS (OVERLAY_COMPOSITION_CAPS);
148
149 static gboolean
can_blend_caps(GstCaps * incaps)150 can_blend_caps (GstCaps * incaps)
151 {
152 gboolean ret;
153 GstCaps *caps;
154
155 caps = gst_static_caps_get (&overlay_composition_caps);
156 ret = gst_caps_is_subset (incaps, caps);
157 gst_caps_unref (caps);
158
159 return ret;
160 }
161
162 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
163 GST_PAD_SRC,
164 GST_PAD_ALWAYS,
165 GST_STATIC_CAPS (ALL_CAPS)
166 );
167
168 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
169 GST_PAD_SINK,
170 GST_PAD_ALWAYS,
171 GST_STATIC_CAPS (ALL_CAPS)
172 );
173
174 #define parent_class gst_overlay_composition_parent_class
175 G_DEFINE_TYPE (GstOverlayComposition, gst_overlay_composition,
176 GST_TYPE_ELEMENT);
177
178 static GstFlowReturn gst_overlay_composition_sink_chain (GstPad * pad,
179 GstObject * parent, GstBuffer * buffer);
180 static gboolean gst_overlay_composition_sink_event (GstPad * pad,
181 GstObject * parent, GstEvent * event);
182 static gboolean gst_overlay_composition_sink_query (GstPad * pad,
183 GstObject * parent, GstQuery * query);
184 static gboolean gst_overlay_composition_src_query (GstPad * pad,
185 GstObject * parent, GstQuery * query);
186
187 static GstStateChangeReturn gst_overlay_composition_change_state (GstElement *
188 element, GstStateChange transition);
189
190 static void
gst_overlay_composition_class_init(GstOverlayCompositionClass * klass)191 gst_overlay_composition_class_init (GstOverlayCompositionClass * klass)
192 {
193 GstElementClass *gstelement_class = (GstElementClass *) klass;
194
195 GST_DEBUG_CATEGORY_INIT (gst_overlay_composition_debug, "overlaycomposition",
196 0, "Overlay Composition");
197
198 gst_element_class_set_static_metadata (gstelement_class,
199 "Overlay Composition", "Filter/Editor/Video",
200 "Overlay Composition", "Sebastian Dröge <sebastian@centricular.com>");
201
202 gst_element_class_add_pad_template (gstelement_class,
203 gst_static_pad_template_get (&src_template));
204 gst_element_class_add_pad_template (gstelement_class,
205 gst_static_pad_template_get (&sink_template));
206
207 gstelement_class->change_state = gst_overlay_composition_change_state;
208
209 /**
210 * GstOverlayComposition::draw:
211 * @overlay: Overlay element emitting the signal.
212 * @sample: #GstSample containing the current buffer, caps and segment.
213 *
214 * This signal is emitted when the overlay should be drawn.
215 *
216 * Returns: #GstVideoOverlayComposition or %NULL
217 */
218 overlay_composition_signals[SIGNAL_DRAW] =
219 g_signal_new ("draw",
220 G_TYPE_FROM_CLASS (klass),
221 0,
222 0,
223 NULL,
224 NULL,
225 g_cclosure_marshal_generic,
226 GST_TYPE_VIDEO_OVERLAY_COMPOSITION, 1, GST_TYPE_SAMPLE);
227
228 /**
229 * GstOverlayComposition::caps-changed:
230 * @overlay: Overlay element emitting the signal.
231 * @caps: The #GstCaps of the element.
232 * @window_width: The window render width of downstream, or 0.
233 * @window_height: The window render height of downstream, or 0.
234 *
235 * This signal is emitted when the caps of the element has changed.
236 *
237 * The window width and height define the resolution at which the frame is
238 * going to be rendered in the end by e.g. a video sink (i.e. the window
239 * size).
240 */
241 overlay_composition_signals[SIGNAL_CAPS_CHANGED] =
242 g_signal_new ("caps-changed",
243 G_TYPE_FROM_CLASS (klass),
244 0,
245 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 3, GST_TYPE_CAPS,
246 G_TYPE_UINT, G_TYPE_UINT);
247 }
248
249 static void
gst_overlay_composition_init(GstOverlayComposition * self)250 gst_overlay_composition_init (GstOverlayComposition * self)
251 {
252 self->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
253 gst_pad_set_chain_function (self->sinkpad,
254 GST_DEBUG_FUNCPTR (gst_overlay_composition_sink_chain));
255 gst_pad_set_event_function (self->sinkpad,
256 GST_DEBUG_FUNCPTR (gst_overlay_composition_sink_event));
257 gst_pad_set_query_function (self->sinkpad,
258 GST_DEBUG_FUNCPTR (gst_overlay_composition_sink_query));
259 gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
260
261 self->srcpad = gst_pad_new_from_static_template (&src_template, "src");
262 gst_pad_set_query_function (self->srcpad,
263 GST_DEBUG_FUNCPTR (gst_overlay_composition_src_query));
264 gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
265 }
266
267 static GstStateChangeReturn
gst_overlay_composition_change_state(GstElement * element,GstStateChange transition)268 gst_overlay_composition_change_state (GstElement * element,
269 GstStateChange transition)
270 {
271 GstOverlayComposition *self = GST_OVERLAY_COMPOSITION (element);
272 GstStateChangeReturn state_ret;
273
274 switch (transition) {
275 default:
276 break;
277 }
278
279 state_ret =
280 GST_ELEMENT_CLASS (gst_overlay_composition_parent_class)->change_state
281 (element, transition);
282 if (state_ret == GST_STATE_CHANGE_FAILURE)
283 return state_ret;
284
285 switch (transition) {
286 case GST_STATE_CHANGE_READY_TO_PAUSED:
287 gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
288 break;
289 case GST_STATE_CHANGE_PAUSED_TO_READY:
290 memset (&self->info, 0, sizeof (self->info));
291 self->window_width = self->window_height = 0;
292 self->attach_compo_to_buffer = FALSE;
293 if (self->sample) {
294 gst_sample_unref (self->sample);
295 self->sample = NULL;
296 }
297 gst_caps_replace (&self->caps, NULL);
298 gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
299 break;
300 default:
301 break;
302 }
303
304 return state_ret;
305 }
306
307 /* Based on gstbasetextoverlay.c */
308 static gboolean
gst_overlay_composition_negotiate(GstOverlayComposition * self,GstCaps * caps)309 gst_overlay_composition_negotiate (GstOverlayComposition * self, GstCaps * caps)
310 {
311 gboolean upstream_has_meta = FALSE;
312 gboolean caps_has_meta = FALSE;
313 gboolean alloc_has_meta = FALSE;
314 gboolean attach = FALSE;
315 gboolean ret = TRUE;
316 guint width, height;
317 GstCapsFeatures *f;
318 GstCaps *overlay_caps;
319 GstQuery *query;
320 guint alloc_index;
321
322 GST_DEBUG_OBJECT (self, "performing negotiation");
323
324 /* Clear any pending reconfigure to avoid negotiating twice */
325 gst_pad_check_reconfigure (self->srcpad);
326
327 self->window_width = self->window_height = 0;
328
329 if (!caps)
330 caps = gst_pad_get_current_caps (self->sinkpad);
331 else
332 gst_caps_ref (caps);
333
334 if (!caps || gst_caps_is_empty (caps))
335 goto no_format;
336
337 /* Check if upstream caps have meta */
338 if ((f = gst_caps_get_features (caps, 0))) {
339 upstream_has_meta = gst_caps_features_contains (f,
340 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
341 }
342
343 /* Initialize dimensions */
344 width = self->info.width;
345 height = self->info.height;
346
347 if (upstream_has_meta) {
348 overlay_caps = gst_caps_ref (caps);
349 } else {
350 GstCaps *peercaps;
351
352 /* BaseTransform requires caps for the allocation query to work */
353 overlay_caps = gst_caps_copy (caps);
354 f = gst_caps_get_features (overlay_caps, 0);
355 gst_caps_features_add (f,
356 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
357
358 /* Then check if downstream accept overlay composition in caps */
359 /* FIXME: We should probably check if downstream *prefers* the
360 * overlay meta, and only enforce usage of it if we can't handle
361 * the format ourselves and thus would have to drop the overlays.
362 * Otherwise we should prefer what downstream wants here.
363 */
364 peercaps = gst_pad_peer_query_caps (self->srcpad, overlay_caps);
365 caps_has_meta = !gst_caps_is_empty (peercaps);
366 gst_caps_unref (peercaps);
367
368 GST_DEBUG_OBJECT (self, "caps have overlay meta %d", caps_has_meta);
369 }
370
371 if (upstream_has_meta || caps_has_meta) {
372 /* Send caps immediatly, it's needed by GstBaseTransform to get a reply
373 * from allocation query */
374 ret = gst_pad_set_caps (self->srcpad, overlay_caps);
375
376 /* First check if the allocation meta has compositon */
377 query = gst_query_new_allocation (overlay_caps, FALSE);
378
379 if (!gst_pad_peer_query (self->srcpad, query)) {
380 /* no problem, we use the query defaults */
381 GST_DEBUG_OBJECT (self, "ALLOCATION query failed");
382
383 /* In case we were flushing, mark reconfigure and fail this method,
384 * will make it retry */
385 if (GST_PAD_IS_FLUSHING (self->srcpad))
386 ret = FALSE;
387 }
388
389 alloc_has_meta = gst_query_find_allocation_meta (query,
390 GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, &alloc_index);
391
392 GST_DEBUG_OBJECT (self, "sink alloc has overlay meta %d", alloc_has_meta);
393
394 if (alloc_has_meta) {
395 const GstStructure *params;
396
397 gst_query_parse_nth_allocation_meta (query, alloc_index, ¶ms);
398 if (params) {
399 if (gst_structure_get (params, "width", G_TYPE_UINT, &width,
400 "height", G_TYPE_UINT, &height, NULL)) {
401 GST_DEBUG_OBJECT (self, "received window size: %dx%d", width, height);
402 g_assert (width != 0 && height != 0);
403 }
404 }
405 }
406
407 gst_query_unref (query);
408 }
409
410 /* Update render size if needed */
411 self->window_width = width;
412 self->window_height = height;
413
414 /* For backward compatibility, we will prefer blitting if downstream
415 * allocation does not support the meta. In other case we will prefer
416 * attaching, and will fail the negotiation in the unlikely case we are
417 * force to blit, but format isn't supported. */
418
419 if (upstream_has_meta) {
420 attach = TRUE;
421 } else if (caps_has_meta) {
422 if (alloc_has_meta) {
423 attach = TRUE;
424 } else {
425 /* Don't attach unless we cannot handle the format */
426 attach = !can_blend_caps (caps);
427 }
428 } else {
429 ret = can_blend_caps (caps);
430 }
431
432 /* If we attach, then pick the overlay caps */
433 if (attach) {
434 GST_DEBUG_OBJECT (self, "Using caps %" GST_PTR_FORMAT, overlay_caps);
435 /* Caps where already sent */
436 } else if (ret) {
437 GST_DEBUG_OBJECT (self, "Using caps %" GST_PTR_FORMAT, caps);
438 ret = gst_pad_set_caps (self->srcpad, caps);
439 }
440
441 self->attach_compo_to_buffer = attach;
442
443 if (!ret) {
444 GST_DEBUG_OBJECT (self, "negotiation failed, schedule reconfigure");
445 gst_pad_mark_reconfigure (self->srcpad);
446 }
447
448 g_signal_emit (self, overlay_composition_signals[SIGNAL_CAPS_CHANGED], 0,
449 caps, self->window_width, self->window_height, NULL);
450
451 gst_caps_unref (overlay_caps);
452 gst_caps_unref (caps);
453
454 return ret;
455
456 no_format:
457 {
458 if (caps)
459 gst_caps_unref (caps);
460 gst_pad_mark_reconfigure (self->srcpad);
461 return FALSE;
462 }
463 }
464
465 static gboolean
gst_overlay_composition_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)466 gst_overlay_composition_sink_event (GstPad * pad, GstObject * parent,
467 GstEvent * event)
468 {
469 GstOverlayComposition *self = GST_OVERLAY_COMPOSITION (parent);
470 gboolean ret = FALSE;
471
472 switch (GST_EVENT_TYPE (event)) {
473 case GST_EVENT_SEGMENT:
474 gst_event_copy_segment (event, &self->segment);
475 ret = gst_pad_event_default (pad, parent, event);
476 break;
477 case GST_EVENT_CAPS:{
478 GstCaps *caps;
479
480 gst_event_parse_caps (event, &caps);
481 if (!gst_video_info_from_caps (&self->info, caps)) {
482 gst_event_unref (event);
483 ret = FALSE;
484 break;
485 }
486
487 if (!gst_overlay_composition_negotiate (self, caps)) {
488 gst_event_unref (event);
489 ret = FALSE;
490 break;
491 }
492
493 gst_caps_replace (&self->caps, caps);
494
495 ret = TRUE;
496 gst_event_unref (event);
497
498 break;
499 }
500 case GST_EVENT_FLUSH_STOP:
501 gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
502 ret = gst_pad_event_default (pad, parent, event);
503 break;
504 default:
505 ret = gst_pad_event_default (pad, parent, event);
506 break;
507 }
508
509 return ret;
510 }
511
512 /* Based on gstbasetextoverlay.c */
513 /**
514 * add_feature_and_intersect:
515 *
516 * Creates a new #GstCaps containing the (given caps +
517 * given caps feature) + (given caps intersected by the
518 * given filter).
519 *
520 * Returns: the new #GstCaps
521 */
522 static GstCaps *
add_feature_and_intersect(GstCaps * caps,const gchar * feature,GstCaps * filter)523 add_feature_and_intersect (GstCaps * caps,
524 const gchar * feature, GstCaps * filter)
525 {
526 int i, caps_size;
527 GstCaps *new_caps;
528
529 new_caps = gst_caps_copy (caps);
530
531 caps_size = gst_caps_get_size (new_caps);
532 for (i = 0; i < caps_size; i++) {
533 GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
534
535 if (!gst_caps_features_is_any (features)) {
536 gst_caps_features_add (features, feature);
537 }
538 }
539
540 gst_caps_append (new_caps, gst_caps_intersect_full (caps,
541 filter, GST_CAPS_INTERSECT_FIRST));
542
543 return new_caps;
544 }
545
546 /* Based on gstbasetextoverlay.c */
547 /* intersect_by_feature:
548 *
549 * Creates a new #GstCaps based on the following filtering rule.
550 *
551 * For each individual caps contained in given caps, if the
552 * caps uses the given caps feature, keep a version of the caps
553 * with the feature and an another one without. Otherwise, intersect
554 * the caps with the given filter.
555 *
556 * Returns: the new #GstCaps
557 */
558 static GstCaps *
intersect_by_feature(GstCaps * caps,const gchar * feature,GstCaps * filter)559 intersect_by_feature (GstCaps * caps, const gchar * feature, GstCaps * filter)
560 {
561 int i, caps_size;
562 GstCaps *new_caps;
563
564 new_caps = gst_caps_new_empty ();
565
566 caps_size = gst_caps_get_size (caps);
567 for (i = 0; i < caps_size; i++) {
568 GstStructure *caps_structure = gst_caps_get_structure (caps, i);
569 GstCapsFeatures *caps_features =
570 gst_caps_features_copy (gst_caps_get_features (caps, i));
571 GstCaps *filtered_caps;
572 GstCaps *simple_caps =
573 gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
574 gst_caps_set_features (simple_caps, 0, caps_features);
575
576 if (gst_caps_features_contains (caps_features, feature)) {
577 gst_caps_append (new_caps, gst_caps_copy (simple_caps));
578
579 gst_caps_features_remove (caps_features, feature);
580 filtered_caps = gst_caps_ref (simple_caps);
581 } else {
582 filtered_caps = gst_caps_intersect_full (simple_caps, filter,
583 GST_CAPS_INTERSECT_FIRST);
584 }
585
586 gst_caps_unref (simple_caps);
587 gst_caps_append (new_caps, filtered_caps);
588 }
589
590 return new_caps;
591 }
592
593 /* Based on gstbasetextoverlay.c */
594 static GstCaps *
gst_overlay_composition_sink_query_caps(GstOverlayComposition * self,GstCaps * filter)595 gst_overlay_composition_sink_query_caps (GstOverlayComposition * self,
596 GstCaps * filter)
597 {
598 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
599
600 if (filter) {
601 /* filter caps + composition feature + filter caps
602 * filtered by the software caps. */
603 GstCaps *sw_caps = gst_static_caps_get (&overlay_composition_caps);
604 overlay_filter = add_feature_and_intersect (filter,
605 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
606 gst_caps_unref (sw_caps);
607
608 GST_DEBUG_OBJECT (self->sinkpad, "overlay filter %" GST_PTR_FORMAT,
609 overlay_filter);
610 }
611
612 peer_caps = gst_pad_peer_query_caps (self->srcpad, overlay_filter);
613
614 if (overlay_filter)
615 gst_caps_unref (overlay_filter);
616
617 if (peer_caps) {
618
619 GST_DEBUG_OBJECT (self->sinkpad, "peer caps %" GST_PTR_FORMAT, peer_caps);
620
621 if (gst_caps_is_any (peer_caps)) {
622 /* if peer returns ANY caps, return filtered src pad template caps */
623 caps = gst_caps_copy (gst_pad_get_pad_template_caps (self->srcpad));
624 } else {
625
626 /* duplicate caps which contains the composition into one version with
627 * the meta and one without. Filter the other caps by the software caps */
628 GstCaps *sw_caps = gst_static_caps_get (&overlay_composition_caps);
629 caps = intersect_by_feature (peer_caps,
630 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
631 gst_caps_unref (sw_caps);
632 }
633
634 gst_caps_unref (peer_caps);
635
636 } else {
637 /* no peer, our padtemplate is enough then */
638 caps = gst_pad_get_pad_template_caps (self->sinkpad);
639 }
640
641 if (filter) {
642 GstCaps *intersection = gst_caps_intersect_full (filter, caps,
643 GST_CAPS_INTERSECT_FIRST);
644 gst_caps_unref (caps);
645 caps = intersection;
646 }
647
648 GST_DEBUG_OBJECT (self->sinkpad, "returning %" GST_PTR_FORMAT, caps);
649
650 return caps;
651 }
652
653 /* Based on gstbasetextoverlay.c */
654 static GstCaps *
gst_overlay_composition_src_query_caps(GstOverlayComposition * self,GstCaps * filter)655 gst_overlay_composition_src_query_caps (GstOverlayComposition * self,
656 GstCaps * filter)
657 {
658 GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
659
660 if (filter) {
661 /* duplicate filter caps which contains the composition into one version
662 * with the meta and one without. Filter the other caps by the software
663 * caps */
664 GstCaps *sw_caps = gst_static_caps_get (&overlay_composition_caps);
665 overlay_filter =
666 intersect_by_feature (filter,
667 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
668 gst_caps_unref (sw_caps);
669 }
670
671 peer_caps = gst_pad_peer_query_caps (self->sinkpad, overlay_filter);
672
673 if (overlay_filter)
674 gst_caps_unref (overlay_filter);
675
676 if (peer_caps) {
677
678 GST_DEBUG_OBJECT (self->srcpad, "peer caps %" GST_PTR_FORMAT, peer_caps);
679
680 if (gst_caps_is_any (peer_caps)) {
681
682 /* if peer returns ANY caps, return filtered sink pad template caps */
683 caps = gst_caps_copy (gst_pad_get_pad_template_caps (self->sinkpad));
684
685 } else {
686
687 /* return upstream caps + composition feature + upstream caps
688 * filtered by the software caps. */
689 GstCaps *sw_caps = gst_static_caps_get (&overlay_composition_caps);
690 caps = add_feature_and_intersect (peer_caps,
691 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
692 gst_caps_unref (sw_caps);
693 }
694
695 gst_caps_unref (peer_caps);
696
697 } else {
698 /* no peer, our padtemplate is enough then */
699 caps = gst_pad_get_pad_template_caps (self->srcpad);
700 }
701
702 if (filter) {
703 GstCaps *intersection;
704
705 intersection =
706 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
707 gst_caps_unref (caps);
708 caps = intersection;
709 }
710 GST_DEBUG_OBJECT (self->srcpad, "returning %" GST_PTR_FORMAT, caps);
711
712 return caps;
713 }
714
715 static gboolean
gst_overlay_composition_sink_query(GstPad * pad,GstObject * parent,GstQuery * query)716 gst_overlay_composition_sink_query (GstPad * pad, GstObject * parent,
717 GstQuery * query)
718 {
719 GstOverlayComposition *self = GST_OVERLAY_COMPOSITION (parent);
720 gboolean ret = FALSE;
721
722 switch (GST_QUERY_TYPE (query)) {
723 case GST_QUERY_CAPS:{
724 GstCaps *filter, *caps;
725
726 gst_query_parse_caps (query, &filter);
727 caps = gst_overlay_composition_sink_query_caps (self, filter);
728 gst_query_set_caps_result (query, caps);
729 gst_caps_unref (caps);
730 ret = TRUE;
731 break;
732 }
733 default:
734 ret = gst_pad_query_default (pad, parent, query);
735 break;
736 }
737
738 return ret;
739 }
740
741 static gboolean
gst_overlay_composition_src_query(GstPad * pad,GstObject * parent,GstQuery * query)742 gst_overlay_composition_src_query (GstPad * pad, GstObject * parent,
743 GstQuery * query)
744 {
745 GstOverlayComposition *self = GST_OVERLAY_COMPOSITION (parent);
746 gboolean ret = FALSE;
747
748 switch (GST_QUERY_TYPE (query)) {
749 case GST_QUERY_CAPS:{
750 GstCaps *filter, *caps;
751
752 gst_query_parse_caps (query, &filter);
753 caps = gst_overlay_composition_src_query_caps (self, filter);
754 gst_query_set_caps_result (query, caps);
755 gst_caps_unref (caps);
756 ret = TRUE;
757 break;
758 }
759 default:
760 ret = gst_pad_query_default (pad, parent, query);
761 break;
762 }
763
764 return ret;
765 }
766
767 static GstFlowReturn
gst_overlay_composition_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)768 gst_overlay_composition_sink_chain (GstPad * pad, GstObject * parent,
769 GstBuffer * buffer)
770 {
771 GstOverlayComposition *self = GST_OVERLAY_COMPOSITION (parent);
772 GstVideoOverlayComposition *compo = NULL;
773 GstVideoOverlayCompositionMeta *upstream_compo_meta;
774
775 if (gst_pad_check_reconfigure (self->srcpad)) {
776 if (!gst_overlay_composition_negotiate (self, NULL)) {
777 gst_pad_mark_reconfigure (self->srcpad);
778 gst_buffer_unref (buffer);
779 GST_OBJECT_LOCK (self->srcpad);
780 if (GST_PAD_IS_FLUSHING (self->srcpad)) {
781 GST_OBJECT_UNLOCK (self->srcpad);
782 return GST_FLOW_FLUSHING;
783 }
784 GST_OBJECT_UNLOCK (self->srcpad);
785 return GST_FLOW_NOT_NEGOTIATED;
786 }
787 }
788
789 if (!self->sample) {
790 self->sample = gst_sample_new (buffer, self->caps, &self->segment, NULL);
791 } else {
792 self->sample = gst_sample_make_writable (self->sample);
793 gst_sample_set_buffer (self->sample, buffer);
794 gst_sample_set_caps (self->sample, self->caps);
795 gst_sample_set_segment (self->sample, &self->segment);
796 }
797
798 g_signal_emit (self, overlay_composition_signals[SIGNAL_DRAW], 0,
799 self->sample, &compo);
800
801 /* Don't store the buffer in the sample any longer, otherwise it will not
802 * be writable below as we have one reference in the sample and one in
803 * this function.
804 *
805 * If the sample is not writable itself then the application kept an
806 * reference itself.
807 */
808 if (gst_sample_is_writable (self->sample)) {
809 gst_sample_set_buffer (self->sample, NULL);
810 }
811
812 if (!compo) {
813 GST_DEBUG_OBJECT (self->sinkpad,
814 "Application did not provide an overlay composition");
815 return gst_pad_push (self->srcpad, buffer);
816 }
817
818 /* If upstream attached a meta, we can safely add our own things
819 * in it. Upstream must've checked that downstream supports it */
820 upstream_compo_meta = gst_buffer_get_video_overlay_composition_meta (buffer);
821 if (upstream_compo_meta) {
822 GstVideoOverlayComposition *merged_compo =
823 gst_video_overlay_composition_copy (upstream_compo_meta->overlay);
824 guint i, n;
825
826 GST_DEBUG_OBJECT (self->sinkpad,
827 "Appending to upstream overlay composition");
828
829 n = gst_video_overlay_composition_n_rectangles (compo);
830 for (i = 0; i < n; i++) {
831 GstVideoOverlayRectangle *rect =
832 gst_video_overlay_composition_get_rectangle (compo, i);
833 gst_video_overlay_composition_add_rectangle (merged_compo, rect);
834 }
835
836 gst_video_overlay_composition_unref (compo);
837 gst_video_overlay_composition_unref (upstream_compo_meta->overlay);
838 upstream_compo_meta->overlay = merged_compo;
839 } else if (self->attach_compo_to_buffer) {
840 GST_DEBUG_OBJECT (self->sinkpad, "Attaching as meta");
841
842 buffer = gst_buffer_make_writable (buffer);
843 gst_buffer_add_video_overlay_composition_meta (buffer, compo);
844 gst_video_overlay_composition_unref (compo);
845 } else {
846 GstVideoFrame frame;
847
848 buffer = gst_buffer_make_writable (buffer);
849 if (!gst_video_frame_map (&frame, &self->info, buffer, GST_MAP_READWRITE)) {
850 gst_video_overlay_composition_unref (compo);
851 goto map_failed;
852 }
853
854 gst_video_overlay_composition_blend (compo, &frame);
855
856 gst_video_frame_unmap (&frame);
857 gst_video_overlay_composition_unref (compo);
858 }
859
860 return gst_pad_push (self->srcpad, buffer);
861
862 map_failed:
863 {
864 GST_ERROR_OBJECT (self->sinkpad, "Failed to map buffer");
865 gst_buffer_unref (buffer);
866 return GST_FLOW_ERROR;
867 }
868 }
869
870 static gboolean
plugin_init(GstPlugin * plugin)871 plugin_init (GstPlugin * plugin)
872 {
873 return gst_element_register (plugin, "overlaycomposition", GST_RANK_NONE,
874 GST_TYPE_OVERLAY_COMPOSITION);
875 }
876
877 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
878 GST_VERSION_MINOR,
879 overlaycomposition,
880 "Renders overlays on top of video frames",
881 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
882