1 /*
2 * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-subtitleoverlay
22 * @title: subtitleoverlay
23 *
24 * #GstBin that auto-magically overlays a video stream with subtitles by
25 * autoplugging the required elements.
26 *
27 * It supports raw, timestamped text, different textual subtitle formats and
28 * DVD subpicture subtitles.
29 *
30 * ## Examples
31 * |[
32 * gst-launch-1.0 -v filesrc location=test.mkv ! matroskademux name=demux ! video/x-h264 ! queue ! decodebin ! subtitleoverlay name=overlay ! videoconvert ! autovideosink demux. ! subpicture/x-dvd ! queue ! overlay.
33 * ]|
34 * This will play back the given Matroska file with h264 video and dvd subpicture style subtitles.
35 *
36 */
37
38 #ifdef HAVE_CONFIG_H
39 #include "config.h"
40 #endif
41
42 #include "gstplaybackelements.h"
43 #include "gstsubtitleoverlay.h"
44
45 #include <gst/pbutils/missing-plugins.h>
46 #include <gst/video/video.h>
47 #include <string.h>
48
49 GST_DEBUG_CATEGORY_STATIC (subtitle_overlay_debug);
50 #define GST_CAT_DEFAULT subtitle_overlay_debug
51
52 #define IS_SUBTITLE_CHAIN_IGNORE_ERROR(flow) \
53 G_UNLIKELY (flow == GST_FLOW_ERROR || flow == GST_FLOW_NOT_NEGOTIATED)
54
55 #define IS_VIDEO_CHAIN_IGNORE_ERROR(flow) \
56 G_UNLIKELY (flow == GST_FLOW_ERROR)
57
58 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
59 GST_PAD_SRC,
60 GST_PAD_ALWAYS,
61 GST_STATIC_CAPS_ANY);
62
63 static GstStaticPadTemplate video_sinktemplate =
64 GST_STATIC_PAD_TEMPLATE ("video_sink",
65 GST_PAD_SINK,
66 GST_PAD_ALWAYS,
67 GST_STATIC_CAPS_ANY);
68
69 static GstStaticPadTemplate subtitle_sinktemplate =
70 GST_STATIC_PAD_TEMPLATE ("subtitle_sink",
71 GST_PAD_SINK,
72 GST_PAD_ALWAYS,
73 GST_STATIC_CAPS_ANY);
74
75 enum
76 {
77 PROP_0,
78 PROP_SILENT,
79 PROP_FONT_DESC,
80 PROP_SUBTITLE_ENCODING,
81 PROP_SUBTITLE_TS_OFFSET
82 };
83
84 #define gst_subtitle_overlay_parent_class parent_class
85 G_DEFINE_TYPE (GstSubtitleOverlay, gst_subtitle_overlay, GST_TYPE_BIN);
86
87 static GQuark _subtitle_overlay_event_marker_id = 0;
88
89 #define _do_init \
90 GST_DEBUG_CATEGORY_INIT (subtitle_overlay_debug, "subtitleoverlay", 0, "Subtitle Overlay"); \
91 playback_element_init (plugin); \
92 _subtitle_overlay_event_marker_id = g_quark_from_static_string ("gst-subtitle-overlay-event-marker")
93 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (subtitleoverlay, "subtitleoverlay",
94 GST_RANK_NONE, GST_TYPE_SUBTITLE_OVERLAY, _do_init);
95
96 static void
do_async_start(GstSubtitleOverlay * self)97 do_async_start (GstSubtitleOverlay * self)
98 {
99 if (!self->do_async) {
100 GstMessage *msg = gst_message_new_async_start (GST_OBJECT_CAST (self));
101
102 GST_DEBUG_OBJECT (self, "Posting async-start");
103 GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (self), msg);
104 self->do_async = TRUE;
105 }
106 }
107
108 static void
do_async_done(GstSubtitleOverlay * self)109 do_async_done (GstSubtitleOverlay * self)
110 {
111 if (self->do_async) {
112 GstMessage *msg = gst_message_new_async_done (GST_OBJECT_CAST (self),
113 GST_CLOCK_TIME_NONE);
114
115 GST_DEBUG_OBJECT (self, "Posting async-done");
116 GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (self), msg);
117 self->do_async = FALSE;
118 }
119 }
120
121 static GstPadProbeReturn
122 _pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data);
123
124 static void
block_video(GstSubtitleOverlay * self)125 block_video (GstSubtitleOverlay * self)
126 {
127 if (self->video_block_id != 0)
128 return;
129
130 if (self->video_block_pad) {
131 self->video_block_id =
132 gst_pad_add_probe (self->video_block_pad,
133 GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, _pad_blocked_cb, self, NULL);
134 }
135 }
136
137 static void
unblock_video(GstSubtitleOverlay * self)138 unblock_video (GstSubtitleOverlay * self)
139 {
140 if (self->video_block_id) {
141 gst_pad_remove_probe (self->video_block_pad, self->video_block_id);
142 self->video_sink_blocked = FALSE;
143 self->video_block_id = 0;
144 }
145 }
146
147 static void
block_subtitle(GstSubtitleOverlay * self)148 block_subtitle (GstSubtitleOverlay * self)
149 {
150 if (self->subtitle_block_id != 0)
151 return;
152
153 if (self->subtitle_block_pad) {
154 self->subtitle_block_id =
155 gst_pad_add_probe (self->subtitle_block_pad,
156 GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, _pad_blocked_cb, self, NULL);
157 }
158 }
159
160 static void
unblock_subtitle(GstSubtitleOverlay * self)161 unblock_subtitle (GstSubtitleOverlay * self)
162 {
163 if (self->subtitle_block_id) {
164 gst_pad_remove_probe (self->subtitle_block_pad, self->subtitle_block_id);
165 self->subtitle_sink_blocked = FALSE;
166 self->subtitle_block_id = 0;
167 }
168 }
169
170 static gboolean
pad_supports_caps(GstPad * pad,GstCaps * caps)171 pad_supports_caps (GstPad * pad, GstCaps * caps)
172 {
173 GstCaps *pad_caps;
174 gboolean ret = FALSE;
175
176 pad_caps = gst_pad_query_caps (pad, NULL);
177 if (gst_caps_is_subset (caps, pad_caps))
178 ret = TRUE;
179 gst_caps_unref (pad_caps);
180
181 return ret;
182 }
183
184 static void
gst_subtitle_overlay_finalize(GObject * object)185 gst_subtitle_overlay_finalize (GObject * object)
186 {
187 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (object);
188
189 g_mutex_clear (&self->lock);
190 g_mutex_clear (&self->factories_lock);
191
192 if (self->factories)
193 gst_plugin_feature_list_free (self->factories);
194 self->factories = NULL;
195 gst_caps_replace (&self->factory_caps, NULL);
196
197 if (self->font_desc) {
198 g_free (self->font_desc);
199 self->font_desc = NULL;
200 }
201
202 if (self->encoding) {
203 g_free (self->encoding);
204 self->encoding = NULL;
205 }
206
207 G_OBJECT_CLASS (parent_class)->finalize (object);
208 }
209
210 static gboolean
_is_renderer(GstElementFactory * factory)211 _is_renderer (GstElementFactory * factory)
212 {
213 const gchar *klass, *name;
214
215 klass =
216 gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS);
217 name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory));
218
219 if (klass != NULL) {
220 if (strstr (klass, "Overlay/Subtitle") != NULL ||
221 strstr (klass, "Overlay/SubPicture") != NULL)
222 return TRUE;
223 if (strcmp (name, "textoverlay") == 0)
224 return TRUE;
225 }
226 return FALSE;
227 }
228
229 static gboolean
_is_parser(GstElementFactory * factory)230 _is_parser (GstElementFactory * factory)
231 {
232 const gchar *klass;
233
234 klass =
235 gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS);
236
237 if (klass != NULL && strstr (klass, "Parser/Subtitle") != NULL)
238 return TRUE;
239 return FALSE;
240 }
241
242 static const gchar *const _sub_pad_names[] = { "subpicture", "subpicture_sink",
243 "text", "text_sink",
244 "subtitle_sink", "subtitle", "cc_sink"
245 };
246
247 static gboolean
_is_video_pad(GstPad * pad,gboolean * hw_accelerated)248 _is_video_pad (GstPad * pad, gboolean * hw_accelerated)
249 {
250 GstPad *peer = gst_pad_get_peer (pad);
251 GstCaps *caps;
252 gboolean ret = FALSE;
253 const gchar *name;
254 guint i;
255
256 if (peer) {
257 caps = gst_pad_get_current_caps (peer);
258 if (!caps) {
259 caps = gst_pad_query_caps (peer, NULL);
260 }
261 gst_object_unref (peer);
262 } else {
263 caps = gst_pad_query_caps (pad, NULL);
264 }
265
266 for (i = 0; i < gst_caps_get_size (caps) && !ret; i++) {
267 name = gst_structure_get_name (gst_caps_get_structure (caps, i));
268 if (g_str_equal (name, "video/x-raw")) {
269 ret = TRUE;
270 if (hw_accelerated)
271 *hw_accelerated = FALSE;
272
273 } else if (g_str_has_prefix (name, "video/x-surface")) {
274 ret = TRUE;
275 if (hw_accelerated)
276 *hw_accelerated = TRUE;
277 } else {
278
279 ret = FALSE;
280 if (hw_accelerated)
281 *hw_accelerated = FALSE;
282 }
283 }
284
285 gst_caps_unref (caps);
286
287 return ret;
288 }
289
290 static GstCaps *
_get_sub_caps(GstElementFactory * factory)291 _get_sub_caps (GstElementFactory * factory)
292 {
293 const GList *templates;
294 GList *walk;
295 gboolean is_parser = _is_parser (factory);
296
297 templates = gst_element_factory_get_static_pad_templates (factory);
298 for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
299 GstStaticPadTemplate *templ = walk->data;
300
301 if (templ->direction == GST_PAD_SINK && templ->presence == GST_PAD_ALWAYS) {
302 gboolean found = FALSE;
303
304 if (is_parser) {
305 found = TRUE;
306 } else {
307 guint i;
308
309 for (i = 0; i < G_N_ELEMENTS (_sub_pad_names); i++) {
310 if (strcmp (templ->name_template, _sub_pad_names[i]) == 0) {
311 found = TRUE;
312 break;
313 }
314 }
315 }
316 if (found)
317 return gst_static_caps_get (&templ->static_caps);
318 }
319 }
320 return NULL;
321 }
322
323 static gboolean
_factory_filter(GstPluginFeature * feature,GstCaps ** subcaps)324 _factory_filter (GstPluginFeature * feature, GstCaps ** subcaps)
325 {
326 GstElementFactory *factory;
327 guint rank;
328 const gchar *name;
329 const GList *templates;
330 GList *walk;
331 gboolean is_renderer;
332 GstCaps *templ_caps = NULL;
333 gboolean have_video_sink = FALSE;
334
335 /* we only care about element factories */
336 if (!GST_IS_ELEMENT_FACTORY (feature))
337 return FALSE;
338
339 factory = GST_ELEMENT_FACTORY_CAST (feature);
340
341 /* only select elements with autoplugging rank or textoverlay */
342 name = gst_plugin_feature_get_name (feature);
343 rank = gst_plugin_feature_get_rank (feature);
344 if (strcmp ("textoverlay", name) != 0 && rank < GST_RANK_MARGINAL)
345 return FALSE;
346
347 /* Check if it's a renderer or a parser */
348 if (_is_renderer (factory)) {
349 is_renderer = TRUE;
350 } else if (_is_parser (factory)) {
351 is_renderer = FALSE;
352 } else {
353 return FALSE;
354 }
355
356 /* Check if there's a video sink in case of a renderer */
357 if (is_renderer) {
358 templates = gst_element_factory_get_static_pad_templates (factory);
359 for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
360 GstStaticPadTemplate *templ = walk->data;
361
362 /* we only care about the always-sink templates */
363 if (templ->direction == GST_PAD_SINK && templ->presence == GST_PAD_ALWAYS) {
364 if (strcmp (templ->name_template, "video") == 0 ||
365 strcmp (templ->name_template, "video_sink") == 0) {
366 have_video_sink = TRUE;
367 }
368 }
369 }
370 }
371 templ_caps = _get_sub_caps (factory);
372
373 if (is_renderer && have_video_sink && templ_caps) {
374 GST_DEBUG ("Found renderer element %s (%s) with caps %" GST_PTR_FORMAT,
375 gst_element_factory_get_metadata (factory,
376 GST_ELEMENT_METADATA_LONGNAME),
377 gst_plugin_feature_get_name (feature), templ_caps);
378 *subcaps = gst_caps_merge (*subcaps, templ_caps);
379 return TRUE;
380 } else if (!is_renderer && !have_video_sink && templ_caps) {
381 GST_DEBUG ("Found parser element %s (%s) with caps %" GST_PTR_FORMAT,
382 gst_element_factory_get_metadata (factory,
383 GST_ELEMENT_METADATA_LONGNAME),
384 gst_plugin_feature_get_name (feature), templ_caps);
385 *subcaps = gst_caps_merge (*subcaps, templ_caps);
386 return TRUE;
387 } else {
388 if (templ_caps)
389 gst_caps_unref (templ_caps);
390 return FALSE;
391 }
392 }
393
394 /* Call with factories_lock! */
395 static gboolean
gst_subtitle_overlay_update_factory_list(GstSubtitleOverlay * self)396 gst_subtitle_overlay_update_factory_list (GstSubtitleOverlay * self)
397 {
398 GstRegistry *registry;
399 guint cookie;
400
401 registry = gst_registry_get ();
402 cookie = gst_registry_get_feature_list_cookie (registry);
403 if (!self->factories || self->factories_cookie != cookie) {
404 GstCaps *subcaps;
405 GList *factories;
406
407 subcaps = gst_caps_new_empty ();
408
409 factories = gst_registry_feature_filter (registry,
410 (GstPluginFeatureFilter) _factory_filter, FALSE, &subcaps);
411 GST_DEBUG_OBJECT (self, "Created factory caps: %" GST_PTR_FORMAT, subcaps);
412 gst_caps_replace (&self->factory_caps, subcaps);
413 gst_caps_unref (subcaps);
414 if (self->factories)
415 gst_plugin_feature_list_free (self->factories);
416 self->factories = factories;
417 self->factories_cookie = cookie;
418 }
419
420 return (self->factories != NULL);
421 }
422
423 G_LOCK_DEFINE_STATIC (_factory_caps);
424 static GstCaps *_factory_caps = NULL;
425 static guint32 _factory_caps_cookie = 0;
426
427 GstCaps *
gst_subtitle_overlay_create_factory_caps(void)428 gst_subtitle_overlay_create_factory_caps (void)
429 {
430 GstRegistry *registry;
431 GList *factories;
432 GstCaps *subcaps = NULL;
433 guint cookie;
434
435 registry = gst_registry_get ();
436 cookie = gst_registry_get_feature_list_cookie (registry);
437 G_LOCK (_factory_caps);
438 if (!_factory_caps || _factory_caps_cookie != cookie) {
439 if (_factory_caps)
440 gst_caps_unref (_factory_caps);
441 _factory_caps = gst_caps_new_empty ();
442
443 /* The caps is cached */
444 GST_MINI_OBJECT_FLAG_SET (_factory_caps,
445 GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED);
446
447 factories = gst_registry_feature_filter (registry,
448 (GstPluginFeatureFilter) _factory_filter, FALSE, &_factory_caps);
449 GST_DEBUG ("Created factory caps: %" GST_PTR_FORMAT, _factory_caps);
450 gst_plugin_feature_list_free (factories);
451 _factory_caps_cookie = cookie;
452 }
453 subcaps = gst_caps_ref (_factory_caps);
454 G_UNLOCK (_factory_caps);
455
456 return subcaps;
457 }
458
459 static gboolean
check_factory_for_caps(GstElementFactory * factory,const GstCaps * caps)460 check_factory_for_caps (GstElementFactory * factory, const GstCaps * caps)
461 {
462 GstCaps *fcaps = _get_sub_caps (factory);
463 gboolean ret = (fcaps) ? gst_caps_is_subset (caps, fcaps) : FALSE;
464
465 if (fcaps)
466 gst_caps_unref (fcaps);
467
468 if (ret)
469 gst_object_ref (factory);
470 return ret;
471 }
472
473 static GList *
gst_subtitle_overlay_get_factories_for_caps(const GList * list,const GstCaps * caps)474 gst_subtitle_overlay_get_factories_for_caps (const GList * list,
475 const GstCaps * caps)
476 {
477 const GList *walk = list;
478 GList *result = NULL;
479
480 while (walk) {
481 GstElementFactory *factory = walk->data;
482
483 walk = g_list_next (walk);
484
485 if (check_factory_for_caps (factory, caps)) {
486 result = g_list_prepend (result, factory);
487 }
488 }
489
490 return result;
491 }
492
493 static gint
_sort_by_ranks(GstPluginFeature * f1,GstPluginFeature * f2)494 _sort_by_ranks (GstPluginFeature * f1, GstPluginFeature * f2)
495 {
496 gint diff;
497 const gchar *rname1, *rname2;
498
499 diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1);
500 if (diff != 0)
501 return diff;
502
503 /* If the ranks are the same sort by name to get deterministic results */
504 rname1 = gst_plugin_feature_get_name (f1);
505 rname2 = gst_plugin_feature_get_name (f2);
506
507 diff = strcmp (rname1, rname2);
508
509 return diff;
510 }
511
512 static GstPad *
_get_sub_pad(GstElement * element)513 _get_sub_pad (GstElement * element)
514 {
515 GstPad *pad;
516 guint i;
517
518 for (i = 0; i < G_N_ELEMENTS (_sub_pad_names); i++) {
519 pad = gst_element_get_static_pad (element, _sub_pad_names[i]);
520 if (pad)
521 return pad;
522 }
523 return NULL;
524 }
525
526 static GstPad *
_get_video_pad(GstElement * element)527 _get_video_pad (GstElement * element)
528 {
529 static const gchar *const pad_names[] = { "video", "video_sink" };
530 GstPad *pad;
531 guint i;
532
533 for (i = 0; i < G_N_ELEMENTS (pad_names); i++) {
534 pad = gst_element_get_static_pad (element, pad_names[i]);
535 if (pad)
536 return pad;
537 }
538 return NULL;
539 }
540
541 static gboolean
_create_element(GstSubtitleOverlay * self,GstElement ** element,const gchar * factory_name,GstElementFactory * factory,const gchar * element_name,gboolean mandatory)542 _create_element (GstSubtitleOverlay * self, GstElement ** element,
543 const gchar * factory_name, GstElementFactory * factory,
544 const gchar * element_name, gboolean mandatory)
545 {
546 GstElement *elt;
547
548 g_assert (!factory || !factory_name);
549
550 if (factory_name) {
551 elt = gst_element_factory_make (factory_name, element_name);
552 } else {
553 factory_name =
554 gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory));
555 elt = gst_element_factory_create (factory, element_name);
556 }
557
558 if (G_UNLIKELY (!elt)) {
559 if (!factory) {
560 GstMessage *msg;
561
562 msg =
563 gst_missing_element_message_new (GST_ELEMENT_CAST (self),
564 factory_name);
565 gst_element_post_message (GST_ELEMENT_CAST (self), msg);
566
567 if (mandatory)
568 GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL),
569 ("no '%s' plugin found", factory_name));
570 else
571 GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, (NULL),
572 ("no '%s' plugin found", factory_name));
573 } else {
574 if (mandatory) {
575 GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL),
576 ("can't instantiate '%s'", factory_name));
577 } else {
578 GST_ELEMENT_WARNING (self, CORE, FAILED, (NULL),
579 ("can't instantiate '%s'", factory_name));
580 }
581 }
582
583 return FALSE;
584 }
585
586 if (G_UNLIKELY (gst_element_set_state (elt,
587 GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS)) {
588 gst_object_unref (elt);
589 if (mandatory) {
590 GST_ELEMENT_ERROR (self, CORE, STATE_CHANGE, (NULL),
591 ("failed to set '%s' to READY", factory_name));
592 } else {
593 GST_WARNING_OBJECT (self, "Failed to set '%s' to READY", factory_name);
594 }
595 return FALSE;
596 }
597
598 if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (self), gst_object_ref (elt)))) {
599 gst_element_set_state (elt, GST_STATE_NULL);
600 gst_object_unref (elt);
601 if (mandatory) {
602 GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL),
603 ("failed to add '%s' to subtitleoverlay", factory_name));
604 } else {
605 GST_WARNING_OBJECT (self, "Failed to add '%s' to subtitleoverlay",
606 factory_name);
607 }
608 return FALSE;
609 }
610
611 gst_element_sync_state_with_parent (elt);
612 *element = elt;
613 return TRUE;
614 }
615
616 static void
_remove_element(GstSubtitleOverlay * self,GstElement ** element)617 _remove_element (GstSubtitleOverlay * self, GstElement ** element)
618 {
619 if (*element) {
620 gst_bin_remove (GST_BIN_CAST (self), *element);
621 gst_element_set_state (*element, GST_STATE_NULL);
622 gst_object_unref (*element);
623 *element = NULL;
624 }
625 }
626
627 static gboolean
_setup_passthrough(GstSubtitleOverlay * self)628 _setup_passthrough (GstSubtitleOverlay * self)
629 {
630 GstPad *src, *sink;
631 GstElement *identity;
632
633 GST_DEBUG_OBJECT (self, "Doing video passthrough");
634
635 if (self->passthrough_identity) {
636 GST_DEBUG_OBJECT (self, "Already in passthrough mode");
637 goto out;
638 }
639
640 /* Unlink & destroy everything */
641 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
642 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL);
643 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad), NULL);
644 self->silent_property = NULL;
645 _remove_element (self, &self->post_colorspace);
646 _remove_element (self, &self->overlay);
647 _remove_element (self, &self->parser);
648 _remove_element (self, &self->renderer);
649 _remove_element (self, &self->pre_colorspace);
650 _remove_element (self, &self->passthrough_identity);
651
652 if (G_UNLIKELY (!_create_element (self, &self->passthrough_identity,
653 "identity", NULL, "passthrough-identity", TRUE))) {
654 return FALSE;
655 }
656
657 identity = self->passthrough_identity;
658 g_object_set (G_OBJECT (identity), "silent", TRUE, "signal-handoffs", FALSE,
659 NULL);
660
661 /* Set src ghostpad target */
662 src = gst_element_get_static_pad (self->passthrough_identity, "src");
663 if (G_UNLIKELY (!src)) {
664 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
665 ("Failed to get srcpad from identity"));
666 return FALSE;
667 }
668
669 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
670 src))) {
671 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
672 ("Failed to set srcpad target"));
673 gst_object_unref (src);
674 return FALSE;
675 }
676 gst_object_unref (src);
677
678 sink = gst_element_get_static_pad (self->passthrough_identity, "sink");
679 if (G_UNLIKELY (!sink)) {
680 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
681 ("Failed to get sinkpad from identity"));
682 return FALSE;
683 }
684
685 /* Link sink ghostpads to identity */
686 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
687 (self->video_sinkpad), sink))) {
688 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
689 ("Failed to set video sinkpad target"));
690 gst_object_unref (sink);
691 return FALSE;
692 }
693 gst_object_unref (sink);
694
695 GST_DEBUG_OBJECT (self, "Video passthrough setup successfully");
696
697 out:
698 /* Unblock pads */
699 unblock_video (self);
700 unblock_subtitle (self);
701
702 return TRUE;
703 }
704
705 /* Must be called with subtitleoverlay lock! */
706 static gboolean
_has_property_with_type(GObject * object,const gchar * property,GType type)707 _has_property_with_type (GObject * object, const gchar * property, GType type)
708 {
709 GObjectClass *gobject_class;
710 GParamSpec *pspec;
711
712 gobject_class = G_OBJECT_GET_CLASS (object);
713 pspec = g_object_class_find_property (gobject_class, property);
714 return (pspec && pspec->value_type == type);
715 }
716
717 static void
gst_subtitle_overlay_set_fps(GstSubtitleOverlay * self)718 gst_subtitle_overlay_set_fps (GstSubtitleOverlay * self)
719 {
720 if (!self->parser || self->fps_d == 0)
721 return;
722
723 if (!_has_property_with_type (G_OBJECT (self->parser), "video-fps",
724 GST_TYPE_FRACTION))
725 return;
726
727 GST_DEBUG_OBJECT (self, "Updating video-fps property in parser");
728 g_object_set (self->parser, "video-fps", self->fps_n, self->fps_d, NULL);
729 }
730
731 static void
_update_subtitle_offset(GstSubtitleOverlay * self)732 _update_subtitle_offset (GstSubtitleOverlay * self)
733 {
734 if (self->parser) {
735 GstPad *srcpad = gst_element_get_static_pad (self->parser, "src");
736 GST_DEBUG_OBJECT (self, "setting subtitle offset to %" G_GINT64_FORMAT,
737 self->subtitle_ts_offset);
738 gst_pad_set_offset (srcpad, -self->subtitle_ts_offset);
739 gst_object_unref (srcpad);
740 } else {
741 GST_LOG_OBJECT (self, "no parser, subtitle offset can't be updated");
742 }
743 }
744
745 static const gchar *
_get_silent_property(GstElement * element,gboolean * invert)746 _get_silent_property (GstElement * element, gboolean * invert)
747 {
748 static const struct
749 {
750 const gchar *name;
751 gboolean invert;
752 } properties[] = { {
753 "silent", FALSE}, {
754 "enable", TRUE}};
755 guint i;
756
757 for (i = 0; i < G_N_ELEMENTS (properties); i++) {
758 if (_has_property_with_type (G_OBJECT (element), properties[i].name,
759 G_TYPE_BOOLEAN)) {
760 *invert = properties[i].invert;
761 return properties[i].name;
762 }
763 }
764 return NULL;
765 }
766
767 static gboolean
_setup_parser(GstSubtitleOverlay * self)768 _setup_parser (GstSubtitleOverlay * self)
769 {
770 GstPad *video_peer;
771
772 /* Try to get the latest video framerate */
773 video_peer = gst_pad_get_peer (self->video_sinkpad);
774 if (video_peer) {
775 GstCaps *video_caps;
776 gint fps_n, fps_d;
777
778 video_caps = gst_pad_get_current_caps (video_peer);
779 if (!video_caps) {
780 video_caps = gst_pad_query_caps (video_peer, NULL);
781 if (!gst_caps_is_fixed (video_caps)) {
782 gst_caps_unref (video_caps);
783 video_caps = NULL;
784 }
785 }
786
787 if (video_caps) {
788 GstStructure *st = gst_caps_get_structure (video_caps, 0);
789 if (gst_structure_get_fraction (st, "framerate", &fps_n, &fps_d)) {
790 GST_DEBUG_OBJECT (self, "New video fps: %d/%d", fps_n, fps_d);
791 self->fps_n = fps_n;
792 self->fps_d = fps_d;
793 }
794 }
795
796 if (video_caps)
797 gst_caps_unref (video_caps);
798 gst_object_unref (video_peer);
799 }
800
801 if (_has_property_with_type (G_OBJECT (self->parser), "subtitle-encoding",
802 G_TYPE_STRING))
803 g_object_set (self->parser, "subtitle-encoding", self->encoding, NULL);
804
805 /* Try to set video fps on the parser */
806 gst_subtitle_overlay_set_fps (self);
807
808
809 return TRUE;
810 }
811
812 static gboolean
_setup_renderer(GstSubtitleOverlay * self,GstElement * renderer)813 _setup_renderer (GstSubtitleOverlay * self, GstElement * renderer)
814 {
815 GstElementFactory *factory = gst_element_get_factory (renderer);
816 const gchar *name =
817 gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory));
818
819 if (strcmp (name, "textoverlay") == 0) {
820 /* Set some textoverlay specific properties */
821 gst_util_set_object_arg (G_OBJECT (renderer), "halignment", "center");
822 gst_util_set_object_arg (G_OBJECT (renderer), "valignment", "bottom");
823 g_object_set (G_OBJECT (renderer), "wait-text", FALSE, NULL);
824 if (self->font_desc)
825 g_object_set (G_OBJECT (renderer), "font-desc", self->font_desc, NULL);
826 self->silent_property = "silent";
827 self->silent_property_invert = FALSE;
828 } else {
829 self->silent_property =
830 _get_silent_property (renderer, &self->silent_property_invert);
831 if (_has_property_with_type (G_OBJECT (renderer), "subtitle-encoding",
832 G_TYPE_STRING))
833 g_object_set (renderer, "subtitle-encoding", self->encoding, NULL);
834 if (_has_property_with_type (G_OBJECT (renderer), "font-desc",
835 G_TYPE_STRING))
836 g_object_set (renderer, "font-desc", self->font_desc, NULL);
837 }
838
839 return TRUE;
840 }
841
842 /* subtitle_src==NULL means: use subtitle_sink ghostpad */
843 static gboolean
_link_renderer(GstSubtitleOverlay * self,GstElement * renderer,GstPad * subtitle_src)844 _link_renderer (GstSubtitleOverlay * self, GstElement * renderer,
845 GstPad * subtitle_src)
846 {
847 GstPad *sink, *src;
848 gboolean is_video, is_hw;
849
850 is_video = _is_video_pad (self->video_sinkpad, &is_hw);
851
852 if (is_video) {
853 gboolean render_is_hw;
854
855 /* First check that renderer also supports the video format */
856 sink = _get_video_pad (renderer);
857 if (G_UNLIKELY (!sink)) {
858 GST_WARNING_OBJECT (self, "Can't get video sink from renderer");
859 return FALSE;
860 }
861
862 if (is_video != _is_video_pad (sink, &render_is_hw) ||
863 is_hw != render_is_hw) {
864 GST_DEBUG_OBJECT (self, "Renderer doesn't support %s video",
865 is_hw ? "surface" : "raw");
866 gst_object_unref (sink);
867 return FALSE;
868 }
869 gst_object_unref (sink);
870
871 if (!is_hw) {
872 /* First link everything internally */
873 if (G_UNLIKELY (!_create_element (self, &self->post_colorspace,
874 COLORSPACE, NULL, "post-colorspace", FALSE))) {
875 return FALSE;
876 }
877 src = gst_element_get_static_pad (renderer, "src");
878 if (G_UNLIKELY (!src)) {
879 GST_WARNING_OBJECT (self, "Can't get src pad from renderer");
880 return FALSE;
881 }
882
883 sink = gst_element_get_static_pad (self->post_colorspace, "sink");
884 if (G_UNLIKELY (!sink)) {
885 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
886 gst_object_unref (src);
887 return FALSE;
888 }
889
890 if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) {
891 GST_WARNING_OBJECT (self, "Can't link renderer with " COLORSPACE);
892 gst_object_unref (src);
893 gst_object_unref (sink);
894 return FALSE;
895 }
896 gst_object_unref (src);
897 gst_object_unref (sink);
898
899 if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace,
900 COLORSPACE, NULL, "pre-colorspace", FALSE))) {
901 return FALSE;
902 }
903
904 sink = _get_video_pad (renderer);
905 if (G_UNLIKELY (!sink)) {
906 GST_WARNING_OBJECT (self, "Can't get video sink from renderer");
907 return FALSE;
908 }
909
910 src = gst_element_get_static_pad (self->pre_colorspace, "src");
911 if (G_UNLIKELY (!src)) {
912 GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE);
913 gst_object_unref (sink);
914 return FALSE;
915 }
916
917 if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) {
918 GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to renderer");
919 gst_object_unref (src);
920 gst_object_unref (sink);
921 return FALSE;
922 }
923 gst_object_unref (src);
924 gst_object_unref (sink);
925
926 /* Set src ghostpad target */
927 src = gst_element_get_static_pad (self->post_colorspace, "src");
928 if (G_UNLIKELY (!src)) {
929 GST_WARNING_OBJECT (self, "Can't get src pad from " COLORSPACE);
930 return FALSE;
931 }
932 } else {
933 /* Set src ghostpad target in the hardware accelerated case */
934
935 src = gst_element_get_static_pad (renderer, "src");
936 if (G_UNLIKELY (!src)) {
937 GST_WARNING_OBJECT (self, "Can't get src pad from renderer");
938 return FALSE;
939 }
940 }
941 } else { /* No video pad */
942 GstCaps *allowed_caps, *video_caps = NULL;
943 GstPad *video_peer;
944 gboolean is_subset = FALSE;
945
946 video_peer = gst_pad_get_peer (self->video_sinkpad);
947 if (video_peer) {
948 video_caps = gst_pad_get_current_caps (video_peer);
949 if (!video_caps) {
950 video_caps = gst_pad_query_caps (video_peer, NULL);
951 }
952 gst_object_unref (video_peer);
953 }
954
955 sink = _get_video_pad (renderer);
956 if (G_UNLIKELY (!sink)) {
957 GST_WARNING_OBJECT (self, "Can't get video sink from renderer");
958 if (video_caps)
959 gst_caps_unref (video_caps);
960 return FALSE;
961 }
962 allowed_caps = gst_pad_query_caps (sink, NULL);
963 gst_object_unref (sink);
964
965 if (allowed_caps && video_caps)
966 is_subset = gst_caps_is_subset (video_caps, allowed_caps);
967
968 if (allowed_caps)
969 gst_caps_unref (allowed_caps);
970
971 if (video_caps)
972 gst_caps_unref (video_caps);
973
974 if (G_UNLIKELY (!is_subset)) {
975 GST_WARNING_OBJECT (self, "Renderer with custom caps is not "
976 "compatible with video stream");
977 return FALSE;
978 }
979
980 src = gst_element_get_static_pad (renderer, "src");
981 if (G_UNLIKELY (!src)) {
982 GST_WARNING_OBJECT (self, "Can't get src pad from renderer");
983 return FALSE;
984 }
985 }
986
987 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
988 (self->srcpad), src))) {
989 GST_WARNING_OBJECT (self, "Can't set srcpad target");
990 gst_object_unref (src);
991 return FALSE;
992 }
993 gst_object_unref (src);
994
995 /* Set the sink ghostpad targets */
996 if (self->pre_colorspace) {
997 sink = gst_element_get_static_pad (self->pre_colorspace, "sink");
998 if (G_UNLIKELY (!sink)) {
999 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
1000 return FALSE;
1001 }
1002 } else {
1003 sink = _get_video_pad (renderer);
1004 if (G_UNLIKELY (!sink)) {
1005 GST_WARNING_OBJECT (self, "Can't get sink pad from %" GST_PTR_FORMAT,
1006 renderer);
1007 return FALSE;
1008 }
1009 }
1010
1011 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1012 (self->video_sinkpad), sink))) {
1013 GST_WARNING_OBJECT (self, "Can't set video sinkpad target");
1014 gst_object_unref (sink);
1015 return FALSE;
1016 }
1017 gst_object_unref (sink);
1018
1019 sink = _get_sub_pad (renderer);
1020 if (G_UNLIKELY (!sink)) {
1021 GST_WARNING_OBJECT (self, "Failed to get subpad");
1022 return FALSE;
1023 }
1024
1025 if (subtitle_src) {
1026 if (G_UNLIKELY (gst_pad_link (subtitle_src, sink) != GST_PAD_LINK_OK)) {
1027 GST_WARNING_OBJECT (self, "Failed to link subtitle srcpad with renderer");
1028 gst_object_unref (sink);
1029 return FALSE;
1030 }
1031 } else {
1032 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1033 (self->subtitle_sinkpad), sink))) {
1034 GST_WARNING_OBJECT (self, "Failed to set subtitle sink target");
1035 gst_object_unref (sink);
1036 return FALSE;
1037 }
1038 }
1039 gst_object_unref (sink);
1040
1041 return TRUE;
1042 }
1043
1044 static GstPadProbeReturn
_pad_blocked_cb(GstPad * pad,GstPadProbeInfo * info,gpointer user_data)1045 _pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
1046 {
1047 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (user_data);
1048 GstCaps *subcaps;
1049 GList *l, *factories = NULL;
1050
1051 if (GST_IS_EVENT (info->data)) {
1052 if (!GST_EVENT_IS_SERIALIZED (info->data)) {
1053 GST_DEBUG_OBJECT (pad, "Letting non-serialized event %s pass",
1054 GST_EVENT_TYPE_NAME (info->data));
1055 return GST_PAD_PROBE_PASS;
1056 }
1057 if (GST_EVENT_TYPE (info->data) == GST_EVENT_STREAM_START) {
1058 GST_DEBUG_OBJECT (pad, "Letting event %s pass",
1059 GST_EVENT_TYPE_NAME (info->data));
1060 return GST_PAD_PROBE_PASS;
1061 }
1062 }
1063
1064 GST_DEBUG_OBJECT (pad, "Pad blocked");
1065
1066 GST_SUBTITLE_OVERLAY_LOCK (self);
1067 if (pad == self->video_block_pad)
1068 self->video_sink_blocked = TRUE;
1069 else if (pad == self->subtitle_block_pad)
1070 self->subtitle_sink_blocked = TRUE;
1071
1072 /* Now either both or the video sink are blocked */
1073
1074 /* Get current subtitle caps */
1075 subcaps = self->subcaps;
1076 if (!subcaps) {
1077 GstPad *peer;
1078
1079 peer = gst_pad_get_peer (self->subtitle_sinkpad);
1080 if (peer) {
1081 subcaps = gst_pad_get_current_caps (peer);
1082 if (!subcaps) {
1083 subcaps = gst_pad_query_caps (peer, NULL);
1084 if (!gst_caps_is_fixed (subcaps)) {
1085 gst_caps_unref (subcaps);
1086 subcaps = NULL;
1087 }
1088 }
1089 gst_object_unref (peer);
1090 }
1091 gst_caps_replace (&self->subcaps, subcaps);
1092 if (subcaps)
1093 gst_caps_unref (subcaps);
1094 }
1095 GST_DEBUG_OBJECT (self, "Current subtitle caps: %" GST_PTR_FORMAT, subcaps);
1096
1097 /* If there are no subcaps but the subtitle sink is blocked upstream
1098 * must behave wrong as there are no fixed caps set for the first
1099 * buffer or in-order event after stream-start */
1100 if (G_UNLIKELY (!subcaps && self->subtitle_sink_blocked)) {
1101 GST_ELEMENT_WARNING (self, CORE, NEGOTIATION, (NULL),
1102 ("Subtitle sink is blocked but we have no subtitle caps"));
1103 subcaps = NULL;
1104 }
1105
1106 if (self->subtitle_error || (self->silent && !self->silent_property)) {
1107 _setup_passthrough (self);
1108 do_async_done (self);
1109 goto out;
1110 }
1111
1112 /* Now do something with the caps */
1113 if (subcaps && !self->subtitle_flush) {
1114 GstPad *target =
1115 gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad));
1116
1117 if (target && pad_supports_caps (target, subcaps)) {
1118 GST_DEBUG_OBJECT (pad, "Target accepts caps");
1119
1120 gst_object_unref (target);
1121
1122 /* Unblock pads */
1123 unblock_video (self);
1124 unblock_subtitle (self);
1125 goto out;
1126 } else if (target) {
1127 gst_object_unref (target);
1128 }
1129 }
1130
1131 if (self->subtitle_sink_blocked && !self->video_sink_blocked) {
1132 GST_DEBUG_OBJECT (self, "Subtitle sink blocked but video not blocked");
1133 block_video (self);
1134 goto out;
1135 }
1136
1137 self->subtitle_flush = FALSE;
1138
1139 /* Find our factories */
1140 g_mutex_lock (&self->factories_lock);
1141 gst_subtitle_overlay_update_factory_list (self);
1142 if (subcaps) {
1143 factories =
1144 gst_subtitle_overlay_get_factories_for_caps (self->factories, subcaps);
1145 if (!factories) {
1146 GstMessage *msg;
1147
1148 msg = gst_missing_decoder_message_new (GST_ELEMENT_CAST (self), subcaps);
1149 gst_element_post_message (GST_ELEMENT_CAST (self), msg);
1150 GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, (NULL),
1151 ("no suitable subtitle plugin found"));
1152 subcaps = NULL;
1153 self->subtitle_error = TRUE;
1154 }
1155 }
1156 g_mutex_unlock (&self->factories_lock);
1157
1158 if (!subcaps) {
1159 _setup_passthrough (self);
1160 do_async_done (self);
1161 goto out;
1162 }
1163
1164 /* Now the interesting parts are done: subtitle overlaying! */
1165
1166 /* Sort the factories by rank */
1167 factories = g_list_sort (factories, (GCompareFunc) _sort_by_ranks);
1168
1169 for (l = factories; l; l = l->next) {
1170 GstElementFactory *factory = l->data;
1171 gboolean is_renderer = _is_renderer (factory);
1172 GstPad *sink, *src;
1173
1174 /* Unlink & destroy everything */
1175
1176 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
1177 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL);
1178 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad),
1179 NULL);
1180 self->silent_property = NULL;
1181 _remove_element (self, &self->post_colorspace);
1182 _remove_element (self, &self->overlay);
1183 _remove_element (self, &self->parser);
1184 _remove_element (self, &self->renderer);
1185 _remove_element (self, &self->pre_colorspace);
1186 _remove_element (self, &self->passthrough_identity);
1187
1188 GST_DEBUG_OBJECT (self, "Trying factory '%s'",
1189 GST_STR_NULL (gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST
1190 (factory))));
1191
1192 if (G_UNLIKELY ((is_renderer
1193 && !_create_element (self, &self->renderer, NULL, factory,
1194 "renderer", FALSE)) || (!is_renderer
1195 && !_create_element (self, &self->parser, NULL, factory,
1196 "parser", FALSE))))
1197 continue;
1198
1199 if (!is_renderer) {
1200 GstCaps *parser_caps;
1201 GList *overlay_factories, *k;
1202
1203 if (!_setup_parser (self))
1204 continue;
1205
1206 /* Find our factories */
1207 src = gst_element_get_static_pad (self->parser, "src");
1208 parser_caps = gst_pad_query_caps (src, NULL);
1209 gst_object_unref (src);
1210
1211 g_assert (parser_caps != NULL);
1212
1213 g_mutex_lock (&self->factories_lock);
1214 gst_subtitle_overlay_update_factory_list (self);
1215 GST_DEBUG_OBJECT (self,
1216 "Searching overlay factories for caps %" GST_PTR_FORMAT, parser_caps);
1217 overlay_factories =
1218 gst_subtitle_overlay_get_factories_for_caps (self->factories,
1219 parser_caps);
1220 g_mutex_unlock (&self->factories_lock);
1221
1222 if (!overlay_factories) {
1223 GST_WARNING_OBJECT (self,
1224 "Found no suitable overlay factories for caps %" GST_PTR_FORMAT,
1225 parser_caps);
1226 gst_caps_unref (parser_caps);
1227 continue;
1228 }
1229 gst_caps_unref (parser_caps);
1230
1231 /* Sort the factories by rank */
1232 overlay_factories =
1233 g_list_sort (overlay_factories, (GCompareFunc) _sort_by_ranks);
1234
1235 for (k = overlay_factories; k; k = k->next) {
1236 GstElementFactory *overlay_factory = k->data;
1237
1238 GST_DEBUG_OBJECT (self, "Trying overlay factory '%s'",
1239 GST_STR_NULL (gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST
1240 (overlay_factory))));
1241
1242 /* Try this factory and link it, otherwise unlink everything
1243 * again and remove the overlay. Up to this point only the
1244 * parser was instantiated and setup, nothing was linked
1245 */
1246
1247 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
1248 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad),
1249 NULL);
1250 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad),
1251 NULL);
1252 self->silent_property = NULL;
1253 _remove_element (self, &self->post_colorspace);
1254 _remove_element (self, &self->overlay);
1255 _remove_element (self, &self->pre_colorspace);
1256
1257 if (!_create_element (self, &self->overlay, NULL, overlay_factory,
1258 "overlay", FALSE)) {
1259 GST_DEBUG_OBJECT (self, "Could not create element");
1260 continue;
1261 }
1262
1263 if (!_setup_renderer (self, self->overlay)) {
1264 GST_DEBUG_OBJECT (self, "Could not setup element");
1265 continue;
1266 }
1267
1268 src = gst_element_get_static_pad (self->parser, "src");
1269 if (!_link_renderer (self, self->overlay, src)) {
1270 GST_DEBUG_OBJECT (self, "Could not link element");
1271 gst_object_unref (src);
1272 continue;
1273 }
1274 gst_object_unref (src);
1275
1276 /* Everything done here, go out of loop */
1277 GST_DEBUG_OBJECT (self, "%s is a suitable element",
1278 GST_STR_NULL (gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST
1279 (overlay_factory))));
1280 break;
1281 }
1282
1283 if (overlay_factories)
1284 gst_plugin_feature_list_free (overlay_factories);
1285
1286 if (G_UNLIKELY (k == NULL)) {
1287 GST_WARNING_OBJECT (self, "Failed to find usable overlay factory");
1288 continue;
1289 }
1290
1291 /* Now link subtitle sinkpad of the bin and the parser */
1292 sink = gst_element_get_static_pad (self->parser, "sink");
1293 if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1294 (self->subtitle_sinkpad), sink)) {
1295 gst_object_unref (sink);
1296 continue;
1297 }
1298 gst_object_unref (sink);
1299
1300 /* Everything done here, go out of loop */
1301 break;
1302 } else {
1303 /* Is renderer factory */
1304
1305 if (!_setup_renderer (self, self->renderer))
1306 continue;
1307
1308 /* subtitle_src==NULL means: use subtitle_sink ghostpad */
1309 if (!_link_renderer (self, self->renderer, NULL))
1310 continue;
1311
1312 /* Everything done here, go out of loop */
1313 break;
1314 }
1315 }
1316
1317 if (G_UNLIKELY (l == NULL)) {
1318 GST_ELEMENT_WARNING (self, CORE, FAILED, (NULL),
1319 ("Failed to find any usable factories"));
1320 self->subtitle_error = TRUE;
1321 _setup_passthrough (self);
1322 do_async_done (self);
1323 goto out;
1324 }
1325
1326 GST_DEBUG_OBJECT (self, "Everything worked, unblocking pads");
1327 unblock_video (self);
1328 unblock_subtitle (self);
1329 _update_subtitle_offset (self);
1330 do_async_done (self);
1331
1332 out:
1333 if (factories)
1334 gst_plugin_feature_list_free (factories);
1335 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1336
1337 return GST_PAD_PROBE_OK;
1338 }
1339
1340 static GstStateChangeReturn
gst_subtitle_overlay_change_state(GstElement * element,GstStateChange transition)1341 gst_subtitle_overlay_change_state (GstElement * element,
1342 GstStateChange transition)
1343 {
1344 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (element);
1345 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1346
1347 switch (transition) {
1348 case GST_STATE_CHANGE_NULL_TO_READY:
1349 GST_DEBUG_OBJECT (self, "State change NULL->READY");
1350 g_mutex_lock (&self->factories_lock);
1351 if (G_UNLIKELY (!gst_subtitle_overlay_update_factory_list (self))) {
1352 g_mutex_unlock (&self->factories_lock);
1353 return GST_STATE_CHANGE_FAILURE;
1354 }
1355 g_mutex_unlock (&self->factories_lock);
1356
1357 GST_SUBTITLE_OVERLAY_LOCK (self);
1358 /* Set the internal pads to blocking */
1359 block_video (self);
1360 block_subtitle (self);
1361 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1362 break;
1363 case GST_STATE_CHANGE_READY_TO_PAUSED:
1364 GST_DEBUG_OBJECT (self, "State change READY->PAUSED");
1365
1366 self->fps_n = self->fps_d = 0;
1367
1368 self->subtitle_flush = FALSE;
1369 self->subtitle_error = FALSE;
1370
1371 self->downstream_chain_error = FALSE;
1372
1373 do_async_start (self);
1374 ret = GST_STATE_CHANGE_ASYNC;
1375
1376 break;
1377 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1378 GST_DEBUG_OBJECT (self, "State change PAUSED->PLAYING");
1379 default:
1380 break;
1381 }
1382
1383 {
1384 GstStateChangeReturn bret;
1385
1386 bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1387 GST_DEBUG_OBJECT (self, "Base class state changed returned: %d", bret);
1388 if (G_UNLIKELY (bret == GST_STATE_CHANGE_FAILURE)) {
1389 do_async_done (self);
1390 return ret;
1391 }
1392
1393 else if (bret == GST_STATE_CHANGE_ASYNC)
1394 ret = bret;
1395 else if (G_UNLIKELY (bret == GST_STATE_CHANGE_NO_PREROLL)) {
1396 do_async_done (self);
1397 ret = bret;
1398 }
1399 }
1400
1401 switch (transition) {
1402 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1403 GST_DEBUG_OBJECT (self, "State change PLAYING->PAUSED");
1404 break;
1405 case GST_STATE_CHANGE_PAUSED_TO_READY:
1406 GST_DEBUG_OBJECT (self, "State change PAUSED->READY");
1407
1408 /* Set the pads back to blocking state */
1409 GST_SUBTITLE_OVERLAY_LOCK (self);
1410 block_video (self);
1411 block_subtitle (self);
1412 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1413
1414 do_async_done (self);
1415
1416 break;
1417 case GST_STATE_CHANGE_READY_TO_NULL:
1418 GST_DEBUG_OBJECT (self, "State change READY->NULL");
1419
1420 GST_SUBTITLE_OVERLAY_LOCK (self);
1421 gst_caps_replace (&self->subcaps, NULL);
1422
1423 /* Unlink ghost pads */
1424 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
1425 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL);
1426 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad),
1427 NULL);
1428
1429 /* Unblock pads */
1430 unblock_video (self);
1431 unblock_subtitle (self);
1432
1433 /* Remove elements */
1434 self->silent_property = NULL;
1435 _remove_element (self, &self->post_colorspace);
1436 _remove_element (self, &self->overlay);
1437 _remove_element (self, &self->parser);
1438 _remove_element (self, &self->renderer);
1439 _remove_element (self, &self->pre_colorspace);
1440 _remove_element (self, &self->passthrough_identity);
1441 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1442
1443 break;
1444 default:
1445 break;
1446 }
1447
1448 return ret;
1449 }
1450
1451 static void
gst_subtitle_overlay_handle_message(GstBin * bin,GstMessage * message)1452 gst_subtitle_overlay_handle_message (GstBin * bin, GstMessage * message)
1453 {
1454 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (bin);
1455
1456 if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
1457 GstObject *src = GST_MESSAGE_SRC (message);
1458
1459 /* Convert error messages from the subtitle pipeline to
1460 * warnings and switch to passthrough mode */
1461 if (src && (
1462 (self->overlay
1463 && gst_object_has_as_ancestor (src,
1464 GST_OBJECT_CAST (self->overlay))) || (self->parser
1465 && gst_object_has_as_ancestor (src,
1466 GST_OBJECT_CAST (self->parser))) || (self->renderer
1467 && gst_object_has_as_ancestor (src,
1468 GST_OBJECT_CAST (self->renderer))))) {
1469 GError *err = NULL;
1470 gchar *debug = NULL;
1471 GstMessage *wmsg;
1472
1473 gst_message_parse_error (message, &err, &debug);
1474 GST_DEBUG_OBJECT (self,
1475 "Got error message from subtitle element %s: %s (%s)",
1476 GST_MESSAGE_SRC_NAME (message), GST_STR_NULL (err->message),
1477 GST_STR_NULL (debug));
1478
1479 wmsg = gst_message_new_warning (src, err, debug);
1480 gst_message_unref (message);
1481 g_error_free (err);
1482 g_free (debug);
1483 message = wmsg;
1484
1485 GST_SUBTITLE_OVERLAY_LOCK (self);
1486 self->subtitle_error = TRUE;
1487
1488 block_subtitle (self);
1489 block_video (self);
1490 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1491 }
1492 }
1493
1494 GST_BIN_CLASS (parent_class)->handle_message (bin, message);
1495 }
1496
1497 static void
gst_subtitle_overlay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1498 gst_subtitle_overlay_get_property (GObject * object, guint prop_id,
1499 GValue * value, GParamSpec * pspec)
1500 {
1501 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (object);
1502
1503 switch (prop_id) {
1504 case PROP_SILENT:
1505 g_value_set_boolean (value, self->silent);
1506 break;
1507 case PROP_FONT_DESC:
1508 GST_SUBTITLE_OVERLAY_LOCK (self);
1509 g_value_set_string (value, self->font_desc);
1510 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1511 break;
1512 case PROP_SUBTITLE_ENCODING:
1513 GST_SUBTITLE_OVERLAY_LOCK (self);
1514 g_value_set_string (value, self->encoding);
1515 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1516 break;
1517 case PROP_SUBTITLE_TS_OFFSET:
1518 GST_SUBTITLE_OVERLAY_LOCK (self);
1519 g_value_set_int64 (value, self->subtitle_ts_offset);
1520 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1521 break;
1522
1523 default:
1524 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1525 break;
1526 }
1527 }
1528
1529 static void
gst_subtitle_overlay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1530 gst_subtitle_overlay_set_property (GObject * object, guint prop_id,
1531 const GValue * value, GParamSpec * pspec)
1532 {
1533 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (object);
1534
1535 switch (prop_id) {
1536 case PROP_SILENT:
1537 GST_SUBTITLE_OVERLAY_LOCK (self);
1538 self->silent = g_value_get_boolean (value);
1539 if (self->silent_property) {
1540 gboolean silent = self->silent;
1541
1542 if (self->silent_property_invert)
1543 silent = !silent;
1544
1545 if (self->overlay)
1546 g_object_set (self->overlay, self->silent_property, silent, NULL);
1547 else if (self->renderer)
1548 g_object_set (self->renderer, self->silent_property, silent, NULL);
1549 } else {
1550 block_subtitle (self);
1551 block_video (self);
1552 }
1553 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1554 break;
1555 case PROP_FONT_DESC:
1556 GST_SUBTITLE_OVERLAY_LOCK (self);
1557 g_free (self->font_desc);
1558 self->font_desc = g_value_dup_string (value);
1559 if (self->overlay
1560 && _has_property_with_type (G_OBJECT (self->overlay), "font-desc",
1561 G_TYPE_STRING))
1562 g_object_set (self->overlay, "font-desc", self->font_desc, NULL);
1563 else if (self->renderer
1564 && _has_property_with_type (G_OBJECT (self->renderer), "font-desc",
1565 G_TYPE_STRING))
1566 g_object_set (self->renderer, "font-desc", self->font_desc, NULL);
1567 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1568 break;
1569 case PROP_SUBTITLE_ENCODING:
1570 GST_SUBTITLE_OVERLAY_LOCK (self);
1571 g_free (self->encoding);
1572 self->encoding = g_value_dup_string (value);
1573 if (self->renderer
1574 && _has_property_with_type (G_OBJECT (self->renderer),
1575 "subtitle-encoding", G_TYPE_STRING))
1576 g_object_set (self->renderer, "subtitle-encoding", self->encoding,
1577 NULL);
1578 if (self->parser
1579 && _has_property_with_type (G_OBJECT (self->parser),
1580 "subtitle-encoding", G_TYPE_STRING))
1581 g_object_set (self->parser, "subtitle-encoding", self->encoding, NULL);
1582 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1583 break;
1584 case PROP_SUBTITLE_TS_OFFSET:
1585 GST_SUBTITLE_OVERLAY_LOCK (self);
1586 self->subtitle_ts_offset = g_value_get_int64 (value);
1587 _update_subtitle_offset (self);
1588 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1589 break;
1590
1591 default:
1592 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1593 break;
1594 }
1595 }
1596
1597 static void
gst_subtitle_overlay_class_init(GstSubtitleOverlayClass * klass)1598 gst_subtitle_overlay_class_init (GstSubtitleOverlayClass * klass)
1599 {
1600 GObjectClass *gobject_class = (GObjectClass *) klass;
1601 GstElementClass *element_class = (GstElementClass *) klass;
1602 GstBinClass *bin_class = (GstBinClass *) klass;
1603
1604 gobject_class->finalize = gst_subtitle_overlay_finalize;
1605 gobject_class->set_property = gst_subtitle_overlay_set_property;
1606 gobject_class->get_property = gst_subtitle_overlay_get_property;
1607
1608 g_object_class_install_property (gobject_class, PROP_SILENT,
1609 g_param_spec_boolean ("silent",
1610 "Silent",
1611 "Whether to show subtitles", FALSE,
1612 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1613
1614 g_object_class_install_property (gobject_class, PROP_FONT_DESC,
1615 g_param_spec_string ("font-desc",
1616 "Subtitle font description",
1617 "Pango font description of font "
1618 "to be used for subtitle rendering", NULL,
1619 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1620
1621 g_object_class_install_property (gobject_class, PROP_SUBTITLE_ENCODING,
1622 g_param_spec_string ("subtitle-encoding", "subtitle encoding",
1623 "Encoding to assume if input subtitles are not in UTF-8 encoding. "
1624 "If not set, the GST_SUBTITLE_ENCODING environment variable will "
1625 "be checked for an encoding to use. If that is not set either, "
1626 "ISO-8859-15 will be assumed.", NULL,
1627 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1628
1629 g_object_class_install_property (gobject_class, PROP_SUBTITLE_TS_OFFSET,
1630 g_param_spec_int64 ("subtitle-ts-offset", "Subtitle Timestamp Offset",
1631 "The synchronisation offset between text and video in nanoseconds",
1632 G_MININT64, G_MAXINT64, 0,
1633 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1634
1635 gst_element_class_add_static_pad_template (element_class, &srctemplate);
1636
1637 gst_element_class_add_static_pad_template (element_class,
1638 &video_sinktemplate);
1639 gst_element_class_add_static_pad_template (element_class,
1640 &subtitle_sinktemplate);
1641
1642 gst_element_class_set_static_metadata (element_class, "Subtitle Overlay",
1643 "Video/Overlay/Subtitle",
1644 "Overlays a video stream with subtitles",
1645 "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
1646
1647 element_class->change_state =
1648 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_change_state);
1649
1650 bin_class->handle_message =
1651 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_handle_message);
1652 }
1653
1654 static GstFlowReturn
gst_subtitle_overlay_src_proxy_chain(GstPad * proxypad,GstObject * parent,GstBuffer * buffer)1655 gst_subtitle_overlay_src_proxy_chain (GstPad * proxypad, GstObject * parent,
1656 GstBuffer * buffer)
1657 {
1658 GstPad *ghostpad;
1659 GstSubtitleOverlay *self;
1660 GstFlowReturn ret;
1661
1662 ghostpad = GST_PAD_CAST (parent);
1663 if (G_UNLIKELY (!ghostpad)) {
1664 gst_buffer_unref (buffer);
1665 return GST_FLOW_ERROR;
1666 }
1667 self = GST_SUBTITLE_OVERLAY_CAST (gst_pad_get_parent (ghostpad));
1668 if (G_UNLIKELY (!self || self->srcpad != ghostpad)) {
1669 gst_buffer_unref (buffer);
1670 gst_object_unref (ghostpad);
1671 return GST_FLOW_ERROR;
1672 }
1673
1674 ret = gst_proxy_pad_chain_default (proxypad, parent, buffer);
1675
1676 if (IS_VIDEO_CHAIN_IGNORE_ERROR (ret)) {
1677 GST_ERROR_OBJECT (self, "Downstream chain error: %s",
1678 gst_flow_get_name (ret));
1679 self->downstream_chain_error = TRUE;
1680 }
1681
1682 gst_object_unref (self);
1683
1684 return ret;
1685 }
1686
1687 static gboolean
gst_subtitle_overlay_src_proxy_event(GstPad * proxypad,GstObject * parent,GstEvent * event)1688 gst_subtitle_overlay_src_proxy_event (GstPad * proxypad, GstObject * parent,
1689 GstEvent * event)
1690 {
1691 GstPad *ghostpad = NULL;
1692 GstSubtitleOverlay *self = NULL;
1693 gboolean ret = FALSE;
1694 const GstStructure *s;
1695
1696 ghostpad = GST_PAD_CAST (parent);
1697 if (G_UNLIKELY (!ghostpad))
1698 goto out;
1699 self = GST_SUBTITLE_OVERLAY_CAST (gst_pad_get_parent (ghostpad));
1700 if (G_UNLIKELY (!self || self->srcpad != ghostpad))
1701 goto out;
1702
1703 s = gst_event_get_structure (event);
1704 if (s && gst_structure_id_has_field (s, _subtitle_overlay_event_marker_id)) {
1705 GST_DEBUG_OBJECT (ghostpad,
1706 "Dropping event with marker: %" GST_PTR_FORMAT,
1707 gst_event_get_structure (event));
1708 gst_event_unref (event);
1709 event = NULL;
1710 ret = TRUE;
1711 } else {
1712 ret = gst_pad_event_default (proxypad, parent, event);
1713 event = NULL;
1714 }
1715
1716 out:
1717 if (event)
1718 gst_event_unref (event);
1719 if (self)
1720 gst_object_unref (self);
1721
1722 return ret;
1723 }
1724
1725 static gboolean
gst_subtitle_overlay_video_sink_setcaps(GstSubtitleOverlay * self,GstCaps * caps)1726 gst_subtitle_overlay_video_sink_setcaps (GstSubtitleOverlay * self,
1727 GstCaps * caps)
1728 {
1729 GstPad *target;
1730 gboolean ret = TRUE;
1731 GstVideoInfo info;
1732
1733 GST_DEBUG_OBJECT (self, "Setting caps: %" GST_PTR_FORMAT, caps);
1734
1735 if (!gst_video_info_from_caps (&info, caps)) {
1736 GST_ERROR_OBJECT (self, "Failed to parse caps");
1737 ret = FALSE;
1738 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1739 goto out;
1740 }
1741
1742 target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->video_sinkpad));
1743
1744 GST_SUBTITLE_OVERLAY_LOCK (self);
1745
1746 if (!target || !pad_supports_caps (target, caps)) {
1747 GST_DEBUG_OBJECT (target, "Target did not accept caps -- reconfiguring");
1748
1749 block_subtitle (self);
1750 block_video (self);
1751 }
1752
1753 if (self->fps_n != info.fps_n || self->fps_d != info.fps_d) {
1754 GST_DEBUG_OBJECT (self, "New video fps: %d/%d", info.fps_n, info.fps_d);
1755 self->fps_n = info.fps_n;
1756 self->fps_d = info.fps_d;
1757 gst_subtitle_overlay_set_fps (self);
1758 }
1759 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1760
1761 if (target)
1762 gst_object_unref (target);
1763
1764 out:
1765
1766 return ret;
1767 }
1768
1769 static gboolean
gst_subtitle_overlay_video_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)1770 gst_subtitle_overlay_video_sink_event (GstPad * pad, GstObject * parent,
1771 GstEvent * event)
1772 {
1773 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent);
1774 gboolean ret;
1775
1776 switch (GST_EVENT_TYPE (event)) {
1777 case GST_EVENT_CAPS:
1778 {
1779 GstCaps *caps;
1780
1781 gst_event_parse_caps (event, &caps);
1782 ret = gst_subtitle_overlay_video_sink_setcaps (self, caps);
1783 if (!ret)
1784 goto done;
1785 break;
1786 }
1787 default:
1788 break;
1789 }
1790
1791 ret = gst_pad_event_default (pad, parent, gst_event_ref (event));
1792
1793 done:
1794 gst_event_unref (event);
1795
1796 return ret;
1797 }
1798
1799 static GstFlowReturn
gst_subtitle_overlay_video_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)1800 gst_subtitle_overlay_video_sink_chain (GstPad * pad, GstObject * parent,
1801 GstBuffer * buffer)
1802 {
1803 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent);
1804 GstFlowReturn ret = gst_proxy_pad_chain_default (pad, parent, buffer);
1805
1806 if (G_UNLIKELY (self->downstream_chain_error) || self->passthrough_identity) {
1807 return ret;
1808 } else if (IS_VIDEO_CHAIN_IGNORE_ERROR (ret)) {
1809 GST_DEBUG_OBJECT (self, "Subtitle renderer produced chain error: %s",
1810 gst_flow_get_name (ret));
1811 GST_SUBTITLE_OVERLAY_LOCK (self);
1812 self->subtitle_error = TRUE;
1813 block_subtitle (self);
1814 block_video (self);
1815 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1816
1817 return GST_FLOW_OK;
1818 }
1819
1820 return ret;
1821 }
1822
1823 static GstFlowReturn
gst_subtitle_overlay_subtitle_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)1824 gst_subtitle_overlay_subtitle_sink_chain (GstPad * pad, GstObject * parent,
1825 GstBuffer * buffer)
1826 {
1827 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent);
1828
1829 if (self->subtitle_error) {
1830 gst_buffer_unref (buffer);
1831 return GST_FLOW_OK;
1832 } else {
1833 GstFlowReturn ret = gst_proxy_pad_chain_default (pad, parent, buffer);
1834
1835 if (IS_SUBTITLE_CHAIN_IGNORE_ERROR (ret)) {
1836 GST_DEBUG_OBJECT (self, "Subtitle chain error: %s",
1837 gst_flow_get_name (ret));
1838 GST_SUBTITLE_OVERLAY_LOCK (self);
1839 self->subtitle_error = TRUE;
1840 block_subtitle (self);
1841 block_video (self);
1842 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1843
1844 return GST_FLOW_OK;
1845 }
1846
1847 return ret;
1848 }
1849 }
1850
1851 static GstCaps *
gst_subtitle_overlay_subtitle_sink_getcaps(GstPad * pad,GstCaps * filter)1852 gst_subtitle_overlay_subtitle_sink_getcaps (GstPad * pad, GstCaps * filter)
1853 {
1854 GstCaps *ret, *subcaps;
1855
1856 subcaps = gst_subtitle_overlay_create_factory_caps ();
1857 if (filter) {
1858 ret = gst_caps_intersect_full (filter, subcaps, GST_CAPS_INTERSECT_FIRST);
1859 gst_caps_unref (subcaps);
1860 } else {
1861 ret = subcaps;
1862 }
1863
1864 return ret;
1865 }
1866
1867 static gboolean
gst_subtitle_overlay_subtitle_sink_setcaps(GstSubtitleOverlay * self,GstCaps * caps)1868 gst_subtitle_overlay_subtitle_sink_setcaps (GstSubtitleOverlay * self,
1869 GstCaps * caps)
1870 {
1871 gboolean ret = TRUE;
1872 GstPad *target = NULL;
1873
1874 GST_DEBUG_OBJECT (self, "Setting caps: %" GST_PTR_FORMAT, caps);
1875
1876 target =
1877 gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad));
1878
1879 GST_SUBTITLE_OVERLAY_LOCK (self);
1880 gst_caps_replace (&self->subcaps, caps);
1881
1882 if (target && pad_supports_caps (target, caps)) {
1883 GST_DEBUG_OBJECT (self, "Target accepts caps");
1884 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1885 goto out;
1886 }
1887
1888 GST_DEBUG_OBJECT (self, "Target did not accept caps");
1889
1890 self->subtitle_error = FALSE;
1891 block_subtitle (self);
1892 block_video (self);
1893 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1894
1895 out:
1896 if (target)
1897 gst_object_unref (target);
1898
1899 return ret;
1900 }
1901
1902 static GstPadLinkReturn
gst_subtitle_overlay_subtitle_sink_link(GstPad * pad,GstObject * parent,GstPad * peer)1903 gst_subtitle_overlay_subtitle_sink_link (GstPad * pad, GstObject * parent,
1904 GstPad * peer)
1905 {
1906 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent);
1907 GstCaps *caps;
1908
1909 GST_DEBUG_OBJECT (pad, "Linking pad to peer %" GST_PTR_FORMAT, peer);
1910
1911 caps = gst_pad_get_current_caps (peer);
1912 if (!caps) {
1913 caps = gst_pad_query_caps (peer, NULL);
1914 if (!gst_caps_is_fixed (caps)) {
1915 gst_caps_unref (caps);
1916 caps = NULL;
1917 }
1918 }
1919
1920 if (caps) {
1921 GST_SUBTITLE_OVERLAY_LOCK (self);
1922 GST_DEBUG_OBJECT (pad, "Have fixed peer caps: %" GST_PTR_FORMAT, caps);
1923 gst_caps_replace (&self->subcaps, caps);
1924
1925 self->subtitle_error = FALSE;
1926
1927 block_subtitle (self);
1928 block_video (self);
1929 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1930 gst_caps_unref (caps);
1931 }
1932
1933 return GST_PAD_LINK_OK;
1934 }
1935
1936 static void
gst_subtitle_overlay_subtitle_sink_unlink(GstPad * pad,GstObject * parent)1937 gst_subtitle_overlay_subtitle_sink_unlink (GstPad * pad, GstObject * parent)
1938 {
1939 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent);
1940
1941 /* FIXME: Can't use gst_pad_get_parent() here because this is called with
1942 * the object lock from state changes
1943 */
1944
1945 GST_DEBUG_OBJECT (pad, "Pad unlinking");
1946 gst_caps_replace (&self->subcaps, NULL);
1947
1948 GST_SUBTITLE_OVERLAY_LOCK (self);
1949 self->subtitle_error = FALSE;
1950
1951 block_subtitle (self);
1952 block_video (self);
1953 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1954 }
1955
1956 static gboolean
gst_subtitle_overlay_subtitle_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)1957 gst_subtitle_overlay_subtitle_sink_event (GstPad * pad, GstObject * parent,
1958 GstEvent * event)
1959 {
1960 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent);
1961 gboolean ret;
1962
1963 GST_DEBUG_OBJECT (pad, "Got event %" GST_PTR_FORMAT, event);
1964
1965 if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB &&
1966 gst_event_has_name (event, "playsink-custom-subtitle-flush")) {
1967 GST_DEBUG_OBJECT (pad, "Custom subtitle flush event");
1968 GST_SUBTITLE_OVERLAY_LOCK (self);
1969 self->subtitle_flush = TRUE;
1970 self->subtitle_error = FALSE;
1971 block_subtitle (self);
1972 block_video (self);
1973 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1974
1975 gst_event_unref (event);
1976 event = NULL;
1977 ret = TRUE;
1978 goto out;
1979 }
1980
1981 switch (GST_EVENT_TYPE (event)) {
1982 case GST_EVENT_CAPS:
1983 {
1984 GstCaps *caps;
1985
1986 gst_event_parse_caps (event, &caps);
1987 ret = gst_subtitle_overlay_subtitle_sink_setcaps (self, caps);
1988 if (!ret)
1989 goto out;
1990 break;
1991 }
1992 case GST_EVENT_FLUSH_STOP:
1993 case GST_EVENT_FLUSH_START:
1994 case GST_EVENT_SEGMENT:
1995 case GST_EVENT_EOS:
1996 {
1997 GstStructure *structure;
1998
1999 /* Add our event marker to make sure no events from here go ever outside
2000 * the element, they're only interesting for our internal elements */
2001 event = GST_EVENT_CAST (gst_event_make_writable (event));
2002 structure = gst_event_writable_structure (event);
2003
2004 gst_structure_id_set (structure, _subtitle_overlay_event_marker_id,
2005 G_TYPE_BOOLEAN, TRUE, NULL);
2006 break;
2007 }
2008 default:
2009 break;
2010 }
2011
2012 ret = gst_pad_event_default (pad, parent, gst_event_ref (event));
2013
2014 gst_event_unref (event);
2015
2016 out:
2017 return ret;
2018 }
2019
2020 static gboolean
gst_subtitle_overlay_subtitle_sink_query(GstPad * pad,GstObject * parent,GstQuery * query)2021 gst_subtitle_overlay_subtitle_sink_query (GstPad * pad, GstObject * parent,
2022 GstQuery * query)
2023 {
2024 gboolean ret;
2025
2026 switch (GST_QUERY_TYPE (query)) {
2027 case GST_QUERY_ACCEPT_CAPS:
2028 {
2029 gst_query_set_accept_caps_result (query, TRUE);
2030 ret = TRUE;
2031 break;
2032 }
2033 case GST_QUERY_CAPS:
2034 {
2035 GstCaps *filter, *caps;
2036
2037 gst_query_parse_caps (query, &filter);
2038 caps = gst_subtitle_overlay_subtitle_sink_getcaps (pad, filter);
2039 gst_query_set_caps_result (query, caps);
2040 gst_caps_unref (caps);
2041 ret = TRUE;
2042 break;
2043 }
2044 default:
2045 ret = gst_pad_query_default (pad, parent, query);
2046 break;
2047 }
2048
2049 return ret;
2050 }
2051
2052 static void
gst_subtitle_overlay_init(GstSubtitleOverlay * self)2053 gst_subtitle_overlay_init (GstSubtitleOverlay * self)
2054 {
2055 GstPadTemplate *templ;
2056 GstPad *proxypad = NULL;
2057
2058 g_mutex_init (&self->lock);
2059 g_mutex_init (&self->factories_lock);
2060
2061 templ = gst_static_pad_template_get (&srctemplate);
2062 self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ);
2063 gst_object_unref (templ);
2064
2065 proxypad =
2066 GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (self->srcpad)));
2067 gst_pad_set_event_function (proxypad,
2068 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_src_proxy_event));
2069 gst_pad_set_chain_function (proxypad,
2070 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_src_proxy_chain));
2071 gst_object_unref (proxypad);
2072
2073 gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
2074
2075 templ = gst_static_pad_template_get (&video_sinktemplate);
2076 self->video_sinkpad =
2077 gst_ghost_pad_new_no_target_from_template ("video_sink", templ);
2078 gst_object_unref (templ);
2079 gst_pad_set_event_function (self->video_sinkpad,
2080 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_event));
2081 gst_pad_set_chain_function (self->video_sinkpad,
2082 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_chain));
2083
2084 proxypad =
2085 GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
2086 (self->video_sinkpad)));
2087 self->video_block_pad = proxypad;
2088 gst_object_unref (proxypad);
2089 gst_element_add_pad (GST_ELEMENT_CAST (self), self->video_sinkpad);
2090
2091 templ = gst_static_pad_template_get (&subtitle_sinktemplate);
2092 self->subtitle_sinkpad =
2093 gst_ghost_pad_new_no_target_from_template ("subtitle_sink", templ);
2094 gst_object_unref (templ);
2095 gst_pad_set_link_function (self->subtitle_sinkpad,
2096 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_link));
2097 gst_pad_set_unlink_function (self->subtitle_sinkpad,
2098 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_unlink));
2099 gst_pad_set_event_function (self->subtitle_sinkpad,
2100 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_event));
2101 gst_pad_set_query_function (self->subtitle_sinkpad,
2102 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_query));
2103 gst_pad_set_chain_function (self->subtitle_sinkpad,
2104 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_chain));
2105
2106 proxypad =
2107 GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
2108 (self->subtitle_sinkpad)));
2109 self->subtitle_block_pad = proxypad;
2110 gst_object_unref (proxypad);
2111
2112 gst_element_add_pad (GST_ELEMENT_CAST (self), self->subtitle_sinkpad);
2113
2114 self->fps_n = 0;
2115 self->fps_d = 0;
2116 }
2117