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