• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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