2 * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
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.
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.
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., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 * SECTION:element-subtitleoverlay
23 * #GstBin that auto-magically overlays a video stream with subtitles by
24 * autoplugging the required elements.
26 * It supports raw, timestamped text, different textual subtitle formats and
27 * DVD subpicture subtitles.
30 * <title>Examples</title>
32 * gst-launch -v filesrc location=test.mkv ! matroskademux name=demux ! "video/x-h264" ! queue2 ! decodebin ! subtitleoverlay name=overlay ! videoconvert ! autovideosink demux. ! "video/x-dvd-subpicture" ! queue2 ! overlay.
33 * ]| This will play back the given Matroska file with h264 video and subpicture subtitles.
41 #include "gstsubtitleoverlay.h"
43 #include <gst/pbutils/missing-plugins.h>
44 #include <gst/video/video.h>
47 GST_DEBUG_CATEGORY_STATIC (subtitle_overlay_debug);
48 #define GST_CAT_DEFAULT subtitle_overlay_debug
50 #define IS_SUBTITLE_CHAIN_IGNORE_ERROR(flow) \
51 G_UNLIKELY (flow == GST_FLOW_ERROR || flow == GST_FLOW_NOT_NEGOTIATED)
53 #define IS_VIDEO_CHAIN_IGNORE_ERROR(flow) \
54 G_UNLIKELY (flow == GST_FLOW_ERROR)
56 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
61 static GstStaticPadTemplate video_sinktemplate =
62 GST_STATIC_PAD_TEMPLATE ("video_sink",
67 static GstStaticPadTemplate subtitle_sinktemplate =
68 GST_STATIC_PAD_TEMPLATE ("subtitle_sink",
78 PROP_SUBTITLE_ENCODING
81 #define gst_subtitle_overlay_parent_class parent_class
82 G_DEFINE_TYPE (GstSubtitleOverlay, gst_subtitle_overlay, GST_TYPE_BIN);
84 static GQuark _subtitle_overlay_event_marker_id = 0;
87 do_async_start (GstSubtitleOverlay * self)
89 if (!self->do_async) {
90 GstMessage *msg = gst_message_new_async_start (GST_OBJECT_CAST (self));
92 GST_DEBUG_OBJECT (self, "Posting async-start");
93 GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (self), msg);
94 self->do_async = TRUE;
99 do_async_done (GstSubtitleOverlay * self)
101 if (self->do_async) {
103 gst_message_new_async_done (GST_OBJECT_CAST (self), FALSE);
105 GST_DEBUG_OBJECT (self, "Posting async-done");
106 GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (self), msg);
107 self->do_async = FALSE;
111 static GstPadProbeReturn
112 _pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data);
115 block_video (GstSubtitleOverlay * self)
117 if (self->video_block_id != 0)
120 if (self->video_block_pad) {
121 self->video_block_id =
122 gst_pad_add_probe (self->video_block_pad,
123 GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, _pad_blocked_cb, self, NULL);
128 unblock_video (GstSubtitleOverlay * self)
130 if (self->video_block_id) {
131 gst_pad_remove_probe (self->video_block_pad, self->video_block_id);
132 self->video_sink_blocked = FALSE;
133 self->video_block_id = 0;
138 block_subtitle (GstSubtitleOverlay * self)
140 if (self->subtitle_block_id != 0)
143 if (self->subtitle_block_pad) {
144 self->subtitle_block_id =
145 gst_pad_add_probe (self->subtitle_block_pad,
146 GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, _pad_blocked_cb, self, NULL);
151 unblock_subtitle (GstSubtitleOverlay * self)
153 if (self->subtitle_block_id) {
154 gst_pad_remove_probe (self->subtitle_block_pad, self->subtitle_block_id);
155 self->subtitle_sink_blocked = FALSE;
156 self->subtitle_block_id = 0;
161 gst_subtitle_overlay_finalize (GObject * object)
163 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (object);
166 g_mutex_free (self->lock);
170 if (self->factories_lock) {
171 g_mutex_free (self->factories_lock);
172 self->factories_lock = NULL;
176 gst_plugin_feature_list_free (self->factories);
177 self->factories = NULL;
178 gst_caps_replace (&self->factory_caps, NULL);
180 if (self->font_desc) {
181 g_free (self->font_desc);
182 self->font_desc = NULL;
185 if (self->encoding) {
186 g_free (self->encoding);
187 self->encoding = NULL;
190 G_OBJECT_CLASS (parent_class)->finalize (object);
194 _is_renderer (GstElementFactory * factory)
196 const gchar *klass, *name;
198 klass = gst_element_factory_get_klass (factory);
199 name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory));
201 if (strstr (klass, "Overlay/Subtitle") != NULL ||
202 strstr (klass, "Overlay/SubPicture") != NULL)
204 if (strcmp (name, "textoverlay") == 0)
210 _is_parser (GstElementFactory * factory)
214 klass = gst_element_factory_get_klass (factory);
216 if (strstr (klass, "Parser/Subtitle") != NULL)
221 static const gchar *_sub_pad_names[] = { "subpicture", "subpicture_sink",
223 "subtitle_sink", "subtitle"
226 static inline gboolean
227 _is_raw_video (GstStructure * s)
231 name = gst_structure_get_name (s);
233 if (g_str_has_prefix (name, "video/x-raw"))
239 _is_raw_video_pad (GstPad * pad)
241 GstCaps *caps = gst_pad_get_current_caps (pad);
244 raw = _is_raw_video (gst_caps_get_structure (caps, 0));
246 gst_caps_unref (caps);
252 _get_sub_caps (GstElementFactory * factory)
254 const GList *templates;
256 gboolean is_parser = _is_parser (factory);
258 templates = gst_element_factory_get_static_pad_templates (factory);
259 for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
260 GstStaticPadTemplate *templ = walk->data;
262 if (templ->direction == GST_PAD_SINK && templ->presence == GST_PAD_ALWAYS) {
263 gboolean found = FALSE;
270 for (i = 0; i < G_N_ELEMENTS (_sub_pad_names); i++) {
271 if (strcmp (templ->name_template, _sub_pad_names[i]) == 0) {
278 return gst_static_caps_get (&templ->static_caps);
285 _factory_filter (GstPluginFeature * feature, GstCaps ** subcaps)
287 GstElementFactory *factory;
290 const GList *templates;
292 gboolean is_renderer;
293 GstCaps *templ_caps = NULL;
294 gboolean have_video_sink = FALSE;
296 /* we only care about element factories */
297 if (!GST_IS_ELEMENT_FACTORY (feature))
300 factory = GST_ELEMENT_FACTORY_CAST (feature);
302 /* only select elements with autoplugging rank or textoverlay */
303 name = gst_plugin_feature_get_name (feature);
304 rank = gst_plugin_feature_get_rank (feature);
305 if (strcmp ("textoverlay", name) != 0 && rank < GST_RANK_MARGINAL)
308 /* Check if it's a renderer or a parser */
309 if (_is_renderer (factory)) {
311 } else if (_is_parser (factory)) {
317 /* Check if there's a video sink in case of a renderer */
319 templates = gst_element_factory_get_static_pad_templates (factory);
320 for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
321 GstStaticPadTemplate *templ = walk->data;
323 /* we only care about the always-sink templates */
324 if (templ->direction == GST_PAD_SINK && templ->presence == GST_PAD_ALWAYS) {
325 if (strcmp (templ->name_template, "video") == 0 ||
326 strcmp (templ->name_template, "video_sink") == 0) {
327 have_video_sink = TRUE;
332 templ_caps = _get_sub_caps (factory);
334 if (is_renderer && have_video_sink && templ_caps) {
335 GST_DEBUG ("Found renderer element %s (%s) with caps %" GST_PTR_FORMAT,
336 gst_element_factory_get_longname (factory),
337 gst_plugin_feature_get_name (feature), templ_caps);
338 gst_caps_merge (*subcaps, templ_caps);
340 } else if (!is_renderer && !have_video_sink && templ_caps) {
341 GST_DEBUG ("Found parser element %s (%s) with caps %" GST_PTR_FORMAT,
342 gst_element_factory_get_longname (factory),
343 gst_plugin_feature_get_name (feature), templ_caps);
344 gst_caps_merge (*subcaps, templ_caps);
348 gst_caps_unref (templ_caps);
353 /* Call with factories_lock! */
355 gst_subtitle_overlay_update_factory_list (GstSubtitleOverlay * self)
358 || self->factories_cookie !=
359 gst_default_registry_get_feature_list_cookie ()) {
363 subcaps = gst_caps_new_empty ();
365 factories = gst_default_registry_feature_filter (
366 (GstPluginFeatureFilter) _factory_filter, FALSE, &subcaps);
367 GST_DEBUG_OBJECT (self, "Created factory caps: %" GST_PTR_FORMAT, subcaps);
368 gst_caps_replace (&self->factory_caps, subcaps);
369 gst_caps_unref (subcaps);
371 gst_plugin_feature_list_free (self->factories);
372 self->factories = factories;
373 self->factories_cookie = gst_default_registry_get_feature_list_cookie ();
376 return (self->factories != NULL);
379 G_LOCK_DEFINE_STATIC (_factory_caps);
380 static GstCaps *_factory_caps = NULL;
381 static guint32 _factory_caps_cookie = 0;
384 gst_subtitle_overlay_create_factory_caps (void)
387 GstCaps *subcaps = NULL;
389 G_LOCK (_factory_caps);
391 || _factory_caps_cookie !=
392 gst_default_registry_get_feature_list_cookie ()) {
394 gst_caps_unref (_factory_caps);
395 _factory_caps = gst_caps_new_empty ();
397 factories = gst_default_registry_feature_filter (
398 (GstPluginFeatureFilter) _factory_filter, FALSE, &_factory_caps);
399 GST_DEBUG ("Created factory caps: %" GST_PTR_FORMAT, _factory_caps);
400 gst_plugin_feature_list_free (factories);
401 _factory_caps_cookie = gst_default_registry_get_feature_list_cookie ();
403 subcaps = gst_caps_ref (_factory_caps);
404 G_UNLOCK (_factory_caps);
410 check_factory_for_caps (GstElementFactory * factory, const GstCaps * caps)
412 GstCaps *fcaps = _get_sub_caps (factory);
413 gboolean ret = (fcaps) ? gst_caps_can_intersect (fcaps, caps) : FALSE;
416 gst_caps_unref (fcaps);
419 gst_object_ref (factory);
424 gst_subtitle_overlay_get_factories_for_caps (const GList * list,
425 const GstCaps * caps)
427 const GList *walk = list;
428 GList *result = NULL;
431 GstElementFactory *factory = walk->data;
433 walk = g_list_next (walk);
435 if (check_factory_for_caps (factory, caps)) {
436 result = g_list_prepend (result, factory);
444 _sort_by_ranks (GstPluginFeature * f1, GstPluginFeature * f2)
447 const gchar *rname1, *rname2;
449 diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1);
453 /* If the ranks are the same sort by name to get deterministic results */
454 rname1 = gst_plugin_feature_get_name (f1);
455 rname2 = gst_plugin_feature_get_name (f2);
457 diff = strcmp (rname1, rname2);
463 _get_sub_pad (GstElement * element)
468 for (i = 0; i < G_N_ELEMENTS (_sub_pad_names); i++) {
469 pad = gst_element_get_static_pad (element, _sub_pad_names[i]);
477 _get_video_pad (GstElement * element)
479 static const gchar *pad_names[] = { "video", "video_sink" };
483 for (i = 0; i < G_N_ELEMENTS (pad_names); i++) {
484 pad = gst_element_get_static_pad (element, pad_names[i]);
492 _create_element (GstSubtitleOverlay * self, GstElement ** element,
493 const gchar * factory_name, GstElementFactory * factory,
494 const gchar * element_name, gboolean mandatory)
498 g_assert (!factory || !factory_name);
501 elt = gst_element_factory_make (factory_name, element_name);
504 gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory));
505 elt = gst_element_factory_create (factory, element_name);
508 if (G_UNLIKELY (!elt)) {
513 gst_missing_element_message_new (GST_ELEMENT_CAST (self),
515 gst_element_post_message (GST_ELEMENT_CAST (self), msg);
518 GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL),
519 ("no '%s' plugin found", factory_name));
521 GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, (NULL),
522 ("no '%s' plugin found", factory_name));
525 GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL),
526 ("can't instantiate '%s'", factory_name));
528 GST_ELEMENT_WARNING (self, CORE, FAILED, (NULL),
529 ("can't instantiate '%s'", factory_name));
536 if (G_UNLIKELY (gst_element_set_state (elt,
537 GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS)) {
538 gst_object_unref (elt);
540 GST_ELEMENT_ERROR (self, CORE, STATE_CHANGE, (NULL),
541 ("failed to set '%s' to READY", factory_name));
543 GST_WARNING_OBJECT (self, "Failed to set '%s' to READY", factory_name);
548 if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (self), gst_object_ref (elt)))) {
549 gst_element_set_state (elt, GST_STATE_NULL);
550 gst_object_unref (elt);
552 GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL),
553 ("failed to add '%s' to subtitleoverlay", factory_name));
555 GST_WARNING_OBJECT (self, "Failed to add '%s' to subtitleoverlay",
561 gst_element_sync_state_with_parent (elt);
567 _remove_element (GstSubtitleOverlay * self, GstElement ** element)
570 gst_bin_remove (GST_BIN_CAST (self), *element);
571 gst_element_set_state (*element, GST_STATE_NULL);
572 gst_object_unref (*element);
578 _generate_update_segment_event (GstSegment * segment, GstEvent ** event1)
581 GstStructure *structure;
583 event = gst_event_new_segment (segment);
584 structure = gst_event_writable_structure (event);
585 gst_structure_id_set (structure, _subtitle_overlay_event_marker_id,
586 G_TYPE_BOOLEAN, TRUE, NULL);
591 _setup_passthrough (GstSubtitleOverlay * self)
594 GstElement *identity;
596 GST_DEBUG_OBJECT (self, "Doing video passthrough");
598 if (self->passthrough_identity) {
599 GST_DEBUG_OBJECT (self, "Already in passthrough mode");
603 /* Unlink & destroy everything */
604 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
605 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL);
606 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad), NULL);
607 self->silent_property = NULL;
608 _remove_element (self, &self->post_colorspace);
609 _remove_element (self, &self->overlay);
610 _remove_element (self, &self->parser);
611 _remove_element (self, &self->renderer);
612 _remove_element (self, &self->pre_colorspace);
613 _remove_element (self, &self->passthrough_identity);
615 if (G_UNLIKELY (!_create_element (self, &self->passthrough_identity,
616 "identity", NULL, "passthrough-identity", TRUE))) {
620 identity = self->passthrough_identity;
621 g_object_set (G_OBJECT (identity), "silent", TRUE, "signal-handoffs", FALSE,
624 /* Set src ghostpad target */
625 src = gst_element_get_static_pad (self->passthrough_identity, "src");
626 if (G_UNLIKELY (!src)) {
627 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
628 ("Failed to get srcpad from identity"));
632 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
634 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
635 ("Failed to set srcpad target"));
636 gst_object_unref (src);
639 gst_object_unref (src);
641 sink = gst_element_get_static_pad (self->passthrough_identity, "sink");
642 if (G_UNLIKELY (!sink)) {
643 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
644 ("Failed to get sinkpad from identity"));
648 /* Send segment to the identity. This is dropped because identity
649 * is not linked downstream yet */
650 if (self->video_segment.format != GST_FORMAT_UNDEFINED) {
653 _generate_update_segment_event (&self->video_segment, &event1);
654 GST_DEBUG_OBJECT (self,
655 "Pushing video segment event: %" GST_PTR_FORMAT, event1);
656 gst_pad_send_event (sink, event1);
659 /* Link sink ghostpads to identity */
660 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
661 (self->video_sinkpad), sink))) {
662 GST_ELEMENT_ERROR (self, CORE, PAD, (NULL),
663 ("Failed to set video sinkpad target"));
664 gst_object_unref (sink);
667 gst_object_unref (sink);
669 GST_DEBUG_OBJECT (self, "Video passthrough setup successfully");
673 unblock_video (self);
674 unblock_subtitle (self);
679 /* Must be called with subtitleoverlay lock! */
681 gst_subtitle_overlay_set_fps (GstSubtitleOverlay * self)
683 GObjectClass *gobject_class;
686 if (!self->parser || self->fps_d == 0)
689 gobject_class = G_OBJECT_GET_CLASS (self->parser);
690 pspec = g_object_class_find_property (gobject_class, "video-fps");
691 if (!pspec || pspec->value_type != GST_TYPE_FRACTION)
694 GST_DEBUG_OBJECT (self, "Updating video-fps property in parser");
695 g_object_set (self->parser, "video-fps", self->fps_n, self->fps_d, NULL);
699 _get_silent_property (GstElement * element, gboolean * invert)
708 GObjectClass *gobject_class;
712 gobject_class = G_OBJECT_GET_CLASS (element);
714 for (i = 0; i < G_N_ELEMENTS (properties); i++) {
715 pspec = g_object_class_find_property (gobject_class, properties[i].name);
716 if (pspec && pspec->value_type == G_TYPE_BOOLEAN) {
717 *invert = properties[i].invert;
718 return properties[i].name;
725 _has_subtitle_encoding_property (GstElement * element)
730 g_object_class_find_property (G_OBJECT_GET_CLASS (element),
731 "subtitle-encoding");
732 return (pspec && pspec->value_type == G_TYPE_STRING);
736 _has_font_desc_property (GstElement * element)
741 g_object_class_find_property (G_OBJECT_GET_CLASS (element), "font-desc");
742 return (pspec && pspec->value_type == G_TYPE_STRING);
745 static GstPadProbeReturn
746 _pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
748 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (user_data);
750 GList *l, *factories = NULL;
752 GST_DEBUG_OBJECT (pad, "Pad blocked");
754 GST_SUBTITLE_OVERLAY_LOCK (self);
755 if (pad == self->video_block_pad)
756 self->video_sink_blocked = TRUE;
757 else if (pad == self->subtitle_block_pad)
758 self->subtitle_sink_blocked = TRUE;
760 /* Now either both or the video sink are blocked */
762 /* Get current subtitle caps */
763 subcaps = self->subcaps;
767 peer = gst_pad_get_peer (self->subtitle_sinkpad);
769 subcaps = gst_pad_get_current_caps (peer);
771 subcaps = gst_pad_query_caps (peer, NULL);
772 if (!gst_caps_is_fixed (subcaps)) {
773 gst_caps_unref (subcaps);
777 gst_object_unref (peer);
779 gst_caps_replace (&self->subcaps, subcaps);
781 gst_caps_unref (subcaps);
783 GST_DEBUG_OBJECT (self, "Current subtitle caps: %" GST_PTR_FORMAT, subcaps);
785 /* If there are no subcaps but the subtitle sink is blocked upstream
786 * must behave wrong as there are no fixed caps set for the first
787 * buffer or in-order event */
788 if (G_UNLIKELY (!subcaps && self->subtitle_sink_blocked)) {
789 GST_ELEMENT_WARNING (self, CORE, NEGOTIATION, (NULL),
790 ("Subtitle sink is blocked but we have no subtitle caps"));
794 if (self->subtitle_error || (self->silent && !self->silent_property)) {
795 _setup_passthrough (self);
796 do_async_done (self);
800 /* Now do something with the caps */
801 if (subcaps && !self->subtitle_flush) {
803 gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad));
805 if (target && gst_pad_accept_caps (target, subcaps)) {
806 GST_DEBUG_OBJECT (pad, "Target accepts caps");
808 gst_object_unref (target);
811 unblock_video (self);
812 unblock_subtitle (self);
815 gst_object_unref (target);
819 if (self->subtitle_sink_blocked && !self->video_sink_blocked) {
820 GST_DEBUG_OBJECT (self, "Subtitle sink blocked but video not blocked");
825 self->subtitle_flush = FALSE;
827 /* Find our factories */
828 g_mutex_lock (self->factories_lock);
829 gst_subtitle_overlay_update_factory_list (self);
832 gst_subtitle_overlay_get_factories_for_caps (self->factories, subcaps);
836 msg = gst_missing_decoder_message_new (GST_ELEMENT_CAST (self), subcaps);
837 gst_element_post_message (GST_ELEMENT_CAST (self), msg);
838 GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, (NULL),
839 ("no suitable subtitle plugin found"));
841 self->subtitle_error = TRUE;
844 g_mutex_unlock (self->factories_lock);
847 _setup_passthrough (self);
848 do_async_done (self);
852 /* Now the interesting parts are done: subtitle overlaying! */
854 /* Sort the factories by rank */
855 factories = g_list_sort (factories, (GCompareFunc) _sort_by_ranks);
857 for (l = factories; l; l = l->next) {
858 GstElementFactory *factory = l->data;
859 gboolean is_renderer = _is_renderer (factory);
863 /* Unlink & destroy everything */
865 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
866 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL);
867 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad),
869 self->silent_property = NULL;
870 _remove_element (self, &self->post_colorspace);
871 _remove_element (self, &self->overlay);
872 _remove_element (self, &self->parser);
873 _remove_element (self, &self->renderer);
874 _remove_element (self, &self->pre_colorspace);
875 _remove_element (self, &self->passthrough_identity);
877 GST_DEBUG_OBJECT (self, "Trying factory '%s'",
878 GST_STR_NULL (gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST
881 if (G_UNLIKELY ((is_renderer
882 && !_create_element (self, &self->renderer, NULL, factory,
883 "renderer", FALSE)) || (!is_renderer
884 && !_create_element (self, &self->parser, NULL, factory,
888 element = is_renderer ? self->renderer : self->parser;
890 /* If this is a parser, create textoverlay and link video and the parser to it
891 * Else link the renderer to the output colorspace */
896 /* Try to get the latest video framerate */
897 video_peer = gst_pad_get_peer (self->video_sinkpad);
901 video_caps = gst_pad_get_current_caps (video_peer);
903 video_caps = gst_pad_query_caps (video_peer, NULL);
904 if (!gst_caps_is_fixed (video_caps)) {
905 gst_caps_unref (video_caps);
913 if (gst_video_info_from_caps (&info, video_caps)) {
914 if (self->fps_n != info.fps_n || self->fps_d != info.fps_d) {
915 GST_DEBUG_OBJECT (self, "New video fps: %d/%d", info.fps_n,
917 self->fps_n = info.fps_n;
918 self->fps_d = info.fps_d;
924 gst_caps_unref (video_caps);
925 gst_object_unref (video_peer);
928 if (_has_subtitle_encoding_property (self->parser))
929 g_object_set (self->parser, "subtitle-encoding", self->encoding, NULL);
931 /* Try to set video fps on the parser */
932 gst_subtitle_overlay_set_fps (self);
934 /* First link everything internally */
935 if (G_UNLIKELY (!_create_element (self, &self->overlay, "textoverlay",
936 NULL, "overlay", FALSE))) {
939 overlay = self->overlay;
940 self->silent_property = "silent";
941 self->silent_property_invert = FALSE;
943 /* Set some properties */
944 g_object_set (G_OBJECT (overlay),
945 "halign", "center", "valign", "bottom", "wait-text", FALSE, NULL);
947 g_object_set (G_OBJECT (overlay), "font-desc", self->font_desc, NULL);
949 src = gst_element_get_static_pad (element, "src");
950 if (G_UNLIKELY (!src)) {
954 sink = gst_element_get_static_pad (overlay, "text_sink");
955 if (G_UNLIKELY (!sink)) {
956 GST_WARNING_OBJECT (self, "Can't get text sink from textoverlay");
957 gst_object_unref (src);
961 if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) {
962 GST_WARNING_OBJECT (self, "Can't link parser to textoverlay");
963 gst_object_unref (sink);
964 gst_object_unref (src);
967 gst_object_unref (sink);
968 gst_object_unref (src);
970 if (G_UNLIKELY (!_create_element (self, &self->post_colorspace,
971 COLORSPACE, NULL, "post-colorspace", FALSE))) {
975 src = gst_element_get_static_pad (overlay, "src");
976 if (G_UNLIKELY (!src)) {
977 GST_WARNING_OBJECT (self, "Can't get src pad from overlay");
981 sink = gst_element_get_static_pad (self->post_colorspace, "sink");
982 if (G_UNLIKELY (!sink)) {
983 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
984 gst_object_unref (src);
988 if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) {
989 GST_WARNING_OBJECT (self, "Can't link overlay with " COLORSPACE);
990 gst_object_unref (src);
991 gst_object_unref (sink);
994 gst_object_unref (src);
995 gst_object_unref (sink);
997 if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace,
998 COLORSPACE, NULL, "pre-colorspace", FALSE))) {
1002 sink = gst_element_get_static_pad (overlay, "video_sink");
1003 if (G_UNLIKELY (!sink)) {
1004 GST_WARNING_OBJECT (self, "Can't get video sink from textoverlay");
1008 src = gst_element_get_static_pad (self->pre_colorspace, "src");
1009 if (G_UNLIKELY (!src)) {
1010 GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE);
1011 gst_object_unref (sink);
1015 if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) {
1016 GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to textoverlay");
1017 gst_object_unref (src);
1018 gst_object_unref (sink);
1021 gst_object_unref (src);
1022 gst_object_unref (sink);
1024 /* Set src ghostpad target */
1025 src = gst_element_get_static_pad (self->post_colorspace, "src");
1026 if (G_UNLIKELY (!src)) {
1027 GST_WARNING_OBJECT (self, "Can't get src pad from " COLORSPACE);
1031 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1032 (self->srcpad), src))) {
1033 GST_WARNING_OBJECT (self, "Can't set srcpad target");
1034 gst_object_unref (src);
1037 gst_object_unref (src);
1039 /* Send segments to the parser/overlay if necessary. These are not sent
1040 * outside this element because of the proxy pad event function */
1041 if (self->video_segment.format != GST_FORMAT_UNDEFINED) {
1044 sink = gst_element_get_static_pad (self->pre_colorspace, "sink");
1045 if (G_UNLIKELY (!sink)) {
1046 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
1050 _generate_update_segment_event (&self->video_segment, &event1);
1051 GST_DEBUG_OBJECT (self,
1052 "Pushing video segment event: %" GST_PTR_FORMAT, event1);
1053 gst_pad_send_event (sink, event1);
1055 gst_object_unref (sink);
1058 if (self->subtitle_segment.format != GST_FORMAT_UNDEFINED) {
1061 sink = gst_element_get_static_pad (element, "sink");
1062 if (G_UNLIKELY (!sink)) {
1063 GST_WARNING_OBJECT (self, "Failed to get subpad");
1067 _generate_update_segment_event (&self->subtitle_segment, &event1);
1068 GST_DEBUG_OBJECT (self,
1069 "Pushing subtitle segment event: %" GST_PTR_FORMAT, event1);
1070 gst_pad_send_event (sink, event1);
1072 gst_object_unref (sink);
1075 /* Set the sink ghostpad targets */
1076 sink = gst_element_get_static_pad (self->pre_colorspace, "sink");
1077 if (G_UNLIKELY (!sink)) {
1078 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
1082 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1083 (self->video_sinkpad), sink))) {
1084 GST_WARNING_OBJECT (self, "Can't set video sinkpad target");
1085 gst_object_unref (sink);
1088 gst_object_unref (sink);
1090 /* Link subtitle identity to subtitle pad of our element */
1091 sink = gst_element_get_static_pad (element, "sink");
1092 if (G_UNLIKELY (!sink)) {
1093 GST_WARNING_OBJECT (self, "Failed to get subpad");
1097 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1098 (self->subtitle_sinkpad), sink))) {
1099 GST_WARNING_OBJECT (self, "Failed to set subtitle sink target");
1100 gst_object_unref (sink);
1103 gst_object_unref (sink);
1106 gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory));
1107 gboolean is_raw_video = _is_raw_video_pad (self->video_sinkpad);
1109 if (strcmp (name, "textoverlay") == 0) {
1110 /* Set some textoverlay specific properties */
1111 g_object_set (G_OBJECT (element),
1112 "halign", "center", "valign", "bottom", "wait-text", FALSE, NULL);
1113 if (self->font_desc)
1114 g_object_set (G_OBJECT (element), "font-desc", self->font_desc, NULL);
1115 self->silent_property = "silent";
1116 self->silent_property_invert = FALSE;
1118 self->silent_property =
1119 _get_silent_property (element, &self->silent_property_invert);
1120 if (_has_subtitle_encoding_property (self->renderer))
1121 g_object_set (self->renderer, "subtitle-encoding", self->encoding,
1123 if (_has_font_desc_property (self->renderer))
1124 g_object_set (self->renderer, "font-desc", self->font_desc, NULL);
1128 /* First check that renderer also supports raw video */
1129 sink = _get_video_pad (element);
1130 if (G_UNLIKELY (!sink)) {
1131 GST_WARNING_OBJECT (self, "Can't get video sink from renderer");
1135 if (G_UNLIKELY (!_is_raw_video_pad (sink))) {
1136 GST_DEBUG_OBJECT (self, "Renderer doesn't support raw video");
1137 gst_object_unref (sink);
1140 gst_object_unref (sink);
1142 /* First link everything internally */
1143 if (G_UNLIKELY (!_create_element (self, &self->post_colorspace,
1144 COLORSPACE, NULL, "post-colorspace", FALSE))) {
1147 src = gst_element_get_static_pad (element, "src");
1148 if (G_UNLIKELY (!src)) {
1149 GST_WARNING_OBJECT (self, "Can't get src pad from renderer");
1153 sink = gst_element_get_static_pad (self->post_colorspace, "sink");
1154 if (G_UNLIKELY (!sink)) {
1155 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
1156 gst_object_unref (src);
1160 if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) {
1161 GST_WARNING_OBJECT (self, "Can't link renderer with " COLORSPACE);
1162 gst_object_unref (src);
1163 gst_object_unref (sink);
1166 gst_object_unref (src);
1167 gst_object_unref (sink);
1169 if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace,
1170 COLORSPACE, NULL, "pre-colorspace", FALSE))) {
1174 sink = _get_video_pad (element);
1175 if (G_UNLIKELY (!sink)) {
1176 GST_WARNING_OBJECT (self, "Can't get video sink from renderer");
1180 src = gst_element_get_static_pad (self->pre_colorspace, "src");
1181 if (G_UNLIKELY (!src)) {
1182 GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE);
1183 gst_object_unref (sink);
1187 if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) {
1188 GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to renderer");
1189 gst_object_unref (src);
1190 gst_object_unref (sink);
1193 gst_object_unref (src);
1194 gst_object_unref (sink);
1196 /* Set src ghostpad target */
1197 src = gst_element_get_static_pad (self->post_colorspace, "src");
1198 if (G_UNLIKELY (!src)) {
1199 GST_WARNING_OBJECT (self, "Can't get src pad from " COLORSPACE);
1202 } else { /* No raw video pad */
1203 GstCaps *allowed_caps, *video_caps = NULL;
1205 gboolean can_intersect = FALSE;
1207 video_peer = gst_pad_get_peer (self->video_sinkpad);
1209 video_caps = gst_pad_get_current_caps (video_peer);
1211 video_caps = gst_pad_query_caps (video_peer, NULL);
1213 gst_object_unref (video_peer);
1216 sink = _get_video_pad (element);
1217 if (G_UNLIKELY (!sink)) {
1218 GST_WARNING_OBJECT (self, "Can't get video sink from renderer");
1221 allowed_caps = gst_pad_query_caps (sink, NULL);
1222 gst_object_unref (sink);
1224 if (allowed_caps && video_caps)
1225 can_intersect = gst_caps_can_intersect (allowed_caps, video_caps);
1228 gst_caps_unref (allowed_caps);
1231 gst_caps_unref (video_caps);
1233 if (G_UNLIKELY (!can_intersect)) {
1234 GST_WARNING_OBJECT (self, "Renderer with custom caps is not "
1235 "compatible with video stream");
1239 src = gst_element_get_static_pad (element, "src");
1240 if (G_UNLIKELY (!src)) {
1241 GST_WARNING_OBJECT (self, "Can't get src pad from renderer");
1246 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1247 (self->srcpad), src))) {
1248 GST_WARNING_OBJECT (self, "Can't set srcpad target");
1249 gst_object_unref (src);
1252 gst_object_unref (src);
1254 /* Send segments to the renderer if necessary. These are not sent
1255 * outside this element because of the proxy pad event handler */
1256 if (self->video_segment.format != GST_FORMAT_UNDEFINED) {
1259 sink = gst_element_get_static_pad (self->pre_colorspace, "sink");
1260 if (G_UNLIKELY (!sink)) {
1261 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
1265 _generate_update_segment_event (&self->video_segment, &event1);
1266 GST_DEBUG_OBJECT (self,
1267 "Pushing video segment event: %" GST_PTR_FORMAT, event1);
1268 gst_pad_send_event (sink, event1);
1269 gst_object_unref (sink);
1272 if (self->subtitle_segment.format != GST_FORMAT_UNDEFINED) {
1275 sink = _get_sub_pad (element);
1276 if (G_UNLIKELY (!sink)) {
1277 GST_WARNING_OBJECT (self, "Failed to get subpad");
1281 _generate_update_segment_event (&self->subtitle_segment, &event1);
1282 GST_DEBUG_OBJECT (self,
1283 "Pushing subtitle segment event: %" GST_PTR_FORMAT, event1);
1284 gst_pad_send_event (sink, event1);
1285 gst_object_unref (sink);
1288 /* Set the sink ghostpad targets */
1289 if (self->pre_colorspace) {
1290 sink = gst_element_get_static_pad (self->pre_colorspace, "sink");
1291 if (G_UNLIKELY (!sink)) {
1292 GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE);
1296 sink = _get_video_pad (element);
1297 if (G_UNLIKELY (!sink)) {
1298 GST_WARNING_OBJECT (self, "Can't get sink pad from %" GST_PTR_FORMAT,
1304 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1305 (self->video_sinkpad), sink))) {
1306 GST_WARNING_OBJECT (self, "Can't set video sinkpad target");
1307 gst_object_unref (sink);
1310 gst_object_unref (sink);
1312 sink = _get_sub_pad (element);
1313 if (G_UNLIKELY (!sink)) {
1314 GST_WARNING_OBJECT (self, "Failed to get subpad");
1318 if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST
1319 (self->subtitle_sinkpad), sink))) {
1320 GST_WARNING_OBJECT (self, "Failed to set subtitle sink target");
1321 gst_object_unref (sink);
1324 gst_object_unref (sink);
1330 if (G_UNLIKELY (l == NULL)) {
1331 GST_ELEMENT_WARNING (self, CORE, FAILED, (NULL),
1332 ("Failed to find any usable factories"));
1333 self->subtitle_error = TRUE;
1334 _setup_passthrough (self);
1335 do_async_done (self);
1337 GST_DEBUG_OBJECT (self, "Everything worked, unblocking pads");
1338 unblock_video (self);
1339 unblock_subtitle (self);
1340 do_async_done (self);
1345 gst_plugin_feature_list_free (factories);
1346 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1348 return GST_PAD_PROBE_OK;
1351 static GstStateChangeReturn
1352 gst_subtitle_overlay_change_state (GstElement * element,
1353 GstStateChange transition)
1355 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (element);
1356 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1358 switch (transition) {
1359 case GST_STATE_CHANGE_NULL_TO_READY:
1360 GST_DEBUG_OBJECT (self, "State change NULL->READY");
1361 g_mutex_lock (self->factories_lock);
1362 if (G_UNLIKELY (!gst_subtitle_overlay_update_factory_list (self))) {
1363 g_mutex_unlock (self->factories_lock);
1364 return GST_STATE_CHANGE_FAILURE;
1366 g_mutex_unlock (self->factories_lock);
1368 GST_SUBTITLE_OVERLAY_LOCK (self);
1369 /* Set the internal pads to blocking */
1371 block_subtitle (self);
1372 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1374 case GST_STATE_CHANGE_READY_TO_PAUSED:
1375 GST_DEBUG_OBJECT (self, "State change READY->PAUSED");
1376 gst_segment_init (&self->video_segment, GST_FORMAT_UNDEFINED);
1377 gst_segment_init (&self->subtitle_segment, GST_FORMAT_UNDEFINED);
1379 self->fps_n = self->fps_d = 0;
1381 self->subtitle_flush = FALSE;
1382 self->subtitle_error = FALSE;
1384 self->downstream_chain_error = FALSE;
1386 do_async_start (self);
1387 ret = GST_STATE_CHANGE_ASYNC;
1390 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1391 GST_DEBUG_OBJECT (self, "State change PAUSED->PLAYING");
1397 GstStateChangeReturn bret;
1399 bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1400 GST_DEBUG_OBJECT (self, "Base class state changed returned: %d", bret);
1401 if (G_UNLIKELY (bret == GST_STATE_CHANGE_FAILURE))
1403 else if (bret == GST_STATE_CHANGE_ASYNC)
1405 else if (G_UNLIKELY (bret == GST_STATE_CHANGE_NO_PREROLL)) {
1406 do_async_done (self);
1411 switch (transition) {
1412 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1413 GST_DEBUG_OBJECT (self, "State change PLAYING->PAUSED");
1415 case GST_STATE_CHANGE_PAUSED_TO_READY:
1416 GST_DEBUG_OBJECT (self, "State change PAUSED->READY");
1417 do_async_done (self);
1420 case GST_STATE_CHANGE_READY_TO_NULL:
1421 GST_DEBUG_OBJECT (self, "State change READY->NULL");
1423 GST_SUBTITLE_OVERLAY_LOCK (self);
1424 gst_caps_replace (&self->subcaps, NULL);
1426 /* Unlink ghost pads */
1427 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
1428 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL);
1429 gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad),
1433 unblock_video (self);
1434 unblock_subtitle (self);
1436 /* Remove elements */
1437 self->silent_property = NULL;
1438 _remove_element (self, &self->post_colorspace);
1439 _remove_element (self, &self->overlay);
1440 _remove_element (self, &self->parser);
1441 _remove_element (self, &self->renderer);
1442 _remove_element (self, &self->pre_colorspace);
1443 _remove_element (self, &self->passthrough_identity);
1444 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1455 gst_subtitle_overlay_handle_message (GstBin * bin, GstMessage * message)
1457 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (bin);
1459 if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
1460 GstObject *src = GST_MESSAGE_SRC (message);
1462 /* Convert error messages from the subtitle pipeline to
1463 * warnings and switch to passthrough mode */
1466 && gst_object_has_ancestor (src,
1467 GST_OBJECT_CAST (self->overlay))) || (self->parser
1468 && gst_object_has_ancestor (src,
1469 GST_OBJECT_CAST (self->parser))) || (self->renderer
1470 && gst_object_has_ancestor (src,
1471 GST_OBJECT_CAST (self->renderer))))) {
1473 gchar *debug = NULL;
1476 gst_message_parse_error (message, &err, &debug);
1477 GST_DEBUG_OBJECT (self,
1478 "Got error message from subtitle element %s: %s (%s)",
1479 GST_MESSAGE_SRC_NAME (message), GST_STR_NULL (err->message),
1480 GST_STR_NULL (debug));
1482 wmsg = gst_message_new_warning (src, err, debug);
1483 gst_message_unref (message);
1488 GST_SUBTITLE_OVERLAY_LOCK (self);
1489 self->subtitle_error = TRUE;
1491 block_subtitle (self);
1493 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1497 GST_BIN_CLASS (parent_class)->handle_message (bin, message);
1501 gst_subtitle_overlay_get_property (GObject * object, guint prop_id,
1502 GValue * value, GParamSpec * pspec)
1504 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (object);
1508 g_value_set_boolean (value, self->silent);
1510 case PROP_FONT_DESC:
1511 GST_SUBTITLE_OVERLAY_LOCK (self);
1512 g_value_set_string (value, self->font_desc);
1513 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1515 case PROP_SUBTITLE_ENCODING:
1516 GST_SUBTITLE_OVERLAY_LOCK (self);
1517 g_value_set_string (value, self->encoding);
1518 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1521 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1527 gst_subtitle_overlay_set_property (GObject * object, guint prop_id,
1528 const GValue * value, GParamSpec * pspec)
1530 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (object);
1534 GST_SUBTITLE_OVERLAY_LOCK (self);
1535 self->silent = g_value_get_boolean (value);
1536 if (self->silent_property) {
1537 gboolean silent = self->silent;
1539 if (self->silent_property_invert)
1543 g_object_set (self->overlay, self->silent_property, silent, NULL);
1544 else if (self->renderer)
1545 g_object_set (self->renderer, self->silent_property, silent, NULL);
1547 block_subtitle (self);
1550 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1552 case PROP_FONT_DESC:
1553 GST_SUBTITLE_OVERLAY_LOCK (self);
1554 g_free (self->font_desc);
1555 self->font_desc = g_value_dup_string (value);
1557 g_object_set (self->overlay, "font-desc", self->font_desc, NULL);
1558 else if (self->renderer && _has_font_desc_property (self->renderer))
1559 g_object_set (self->renderer, "font-desc", self->font_desc, NULL);
1560 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1562 case PROP_SUBTITLE_ENCODING:
1563 GST_SUBTITLE_OVERLAY_LOCK (self);
1564 g_free (self->encoding);
1565 self->encoding = g_value_dup_string (value);
1566 if (self->renderer && _has_subtitle_encoding_property (self->renderer))
1567 g_object_set (self->renderer, "subtitle-encoding", self->encoding,
1569 if (self->parser && _has_subtitle_encoding_property (self->parser))
1570 g_object_set (self->parser, "subtitle-encoding", self->encoding, NULL);
1571 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1574 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1580 gst_subtitle_overlay_class_init (GstSubtitleOverlayClass * klass)
1582 GObjectClass *gobject_class = (GObjectClass *) klass;
1583 GstElementClass *element_class = (GstElementClass *) klass;
1584 GstBinClass *bin_class = (GstBinClass *) klass;
1586 gobject_class->finalize = gst_subtitle_overlay_finalize;
1587 gobject_class->set_property = gst_subtitle_overlay_set_property;
1588 gobject_class->get_property = gst_subtitle_overlay_get_property;
1590 g_object_class_install_property (gobject_class, PROP_SILENT,
1591 g_param_spec_boolean ("silent",
1593 "Whether to show subtitles", FALSE,
1594 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1596 g_object_class_install_property (gobject_class, PROP_FONT_DESC,
1597 g_param_spec_string ("font-desc",
1598 "Subtitle font description",
1599 "Pango font description of font "
1600 "to be used for subtitle rendering", NULL,
1601 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1603 g_object_class_install_property (gobject_class, PROP_SUBTITLE_ENCODING,
1604 g_param_spec_string ("subtitle-encoding", "subtitle encoding",
1605 "Encoding to assume if input subtitles are not in UTF-8 encoding. "
1606 "If not set, the GST_SUBTITLE_ENCODING environment variable will "
1607 "be checked for an encoding to use. If that is not set either, "
1608 "ISO-8859-15 will be assumed.", NULL,
1609 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1611 gst_element_class_add_pad_template (element_class,
1612 gst_static_pad_template_get (&srctemplate));
1614 gst_element_class_add_pad_template (element_class,
1615 gst_static_pad_template_get (&video_sinktemplate));
1616 gst_element_class_add_pad_template (element_class,
1617 gst_static_pad_template_get (&subtitle_sinktemplate));
1619 gst_element_class_set_details_simple (element_class, "Subtitle Overlay",
1620 "Video/Overlay/Subtitle",
1621 "Overlays a video stream with subtitles",
1622 "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
1624 element_class->change_state =
1625 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_change_state);
1627 bin_class->handle_message =
1628 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_handle_message);
1631 static GstFlowReturn
1632 gst_subtitle_overlay_src_proxy_chain (GstPad * proxypad, GstBuffer * buffer)
1635 GstSubtitleOverlay *self;
1638 ghostpad = GST_PAD_CAST (gst_pad_get_parent (proxypad));
1639 if (G_UNLIKELY (!ghostpad)) {
1640 gst_buffer_unref (buffer);
1641 return GST_FLOW_ERROR;
1643 self = GST_SUBTITLE_OVERLAY_CAST (gst_pad_get_parent (ghostpad));
1644 if (G_UNLIKELY (!self || self->srcpad != ghostpad)) {
1645 gst_buffer_unref (buffer);
1646 gst_object_unref (ghostpad);
1647 return GST_FLOW_ERROR;
1650 ret = gst_proxy_pad_chain_default (proxypad, buffer);
1652 if (IS_VIDEO_CHAIN_IGNORE_ERROR (ret)) {
1653 GST_ERROR_OBJECT (self, "Downstream chain error: %s",
1654 gst_flow_get_name (ret));
1655 self->downstream_chain_error = TRUE;
1658 gst_object_unref (self);
1659 gst_object_unref (ghostpad);
1665 gst_subtitle_overlay_src_proxy_event (GstPad * proxypad, GstEvent * event)
1667 GstPad *ghostpad = NULL;
1668 GstSubtitleOverlay *self = NULL;
1669 gboolean ret = FALSE;
1670 const GstStructure *s;
1672 ghostpad = GST_PAD_CAST (gst_pad_get_parent (proxypad));
1673 if (G_UNLIKELY (!ghostpad))
1675 self = GST_SUBTITLE_OVERLAY_CAST (gst_pad_get_parent (ghostpad));
1676 if (G_UNLIKELY (!self || self->srcpad != ghostpad))
1679 s = gst_event_get_structure (event);
1680 if (s && gst_structure_id_has_field (s, _subtitle_overlay_event_marker_id)) {
1681 GST_DEBUG_OBJECT (ghostpad, "Dropping event with marker: %" GST_PTR_FORMAT,
1683 gst_event_unref (event);
1687 ret = gst_proxy_pad_event_default (proxypad, event);
1693 gst_event_unref (event);
1695 gst_object_unref (self);
1697 gst_object_unref (ghostpad);
1702 gst_subtitle_overlay_video_sink_setcaps (GstSubtitleOverlay * self,
1706 gboolean ret = TRUE;
1709 GST_DEBUG_OBJECT (self, "Setting caps: %" GST_PTR_FORMAT, caps);
1711 if (!gst_video_info_from_caps (&info, caps)) {
1712 GST_ERROR_OBJECT (self, "Failed to parse caps");
1714 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1718 target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->video_sinkpad));
1720 GST_SUBTITLE_OVERLAY_LOCK (self);
1722 if (!target || !gst_pad_accept_caps (target, caps)) {
1723 GST_DEBUG_OBJECT (target, "Target did not accept caps -- reconfiguring");
1725 block_subtitle (self);
1729 GST_SUBTITLE_OVERLAY_LOCK (self);
1730 if (self->fps_n != info.fps_n || self->fps_d != info.fps_d) {
1731 GST_DEBUG_OBJECT (self, "New video fps: %d/%d", info.fps_n, info.fps_d);
1732 self->fps_n = info.fps_n;
1733 self->fps_d = info.fps_d;
1734 gst_subtitle_overlay_set_fps (self);
1736 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1739 gst_object_unref (target);
1747 gst_subtitle_overlay_video_sink_event (GstPad * pad, GstEvent * event)
1749 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad));
1752 switch (GST_EVENT_TYPE (event)) {
1753 case GST_EVENT_FLUSH_STOP:
1755 GST_DEBUG_OBJECT (pad,
1756 "Resetting video segment because of flush-stop event");
1757 gst_segment_init (&self->video_segment, GST_FORMAT_UNDEFINED);
1758 self->fps_n = self->fps_d = 0;
1761 case GST_EVENT_CAPS:
1765 gst_event_parse_caps (event, &caps);
1766 ret = gst_subtitle_overlay_video_sink_setcaps (self, caps);
1775 ret = gst_proxy_pad_event_default (pad, gst_event_ref (event));
1777 if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
1778 GST_DEBUG_OBJECT (pad, "segment event: %" GST_PTR_FORMAT, event);
1779 gst_event_copy_segment (event, &self->video_segment);
1781 if (self->video_segment.format != GST_FORMAT_TIME)
1782 goto invalid_format;
1786 gst_event_unref (event);
1787 gst_object_unref (self);
1794 GST_ERROR_OBJECT (pad, "Newsegment event in non-time format: %s",
1795 gst_format_get_name (self->video_segment.format));
1801 static GstFlowReturn
1802 gst_subtitle_overlay_video_sink_chain (GstPad * pad, GstBuffer * buffer)
1804 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (GST_PAD_PARENT (pad));
1805 GstFlowReturn ret = gst_proxy_pad_chain_default (pad, buffer);
1807 if (G_UNLIKELY (self->downstream_chain_error) || self->passthrough_identity) {
1809 } else if (IS_VIDEO_CHAIN_IGNORE_ERROR (ret)) {
1810 GST_DEBUG_OBJECT (self, "Subtitle renderer produced chain error: %s",
1811 gst_flow_get_name (ret));
1812 GST_SUBTITLE_OVERLAY_LOCK (self);
1813 self->subtitle_error = TRUE;
1814 block_subtitle (self);
1816 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1824 static GstFlowReturn
1825 gst_subtitle_overlay_subtitle_sink_chain (GstPad * pad, GstBuffer * buffer)
1827 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (GST_PAD_PARENT (pad));
1829 if (self->subtitle_error) {
1830 gst_buffer_unref (buffer);
1833 GstFlowReturn ret = gst_proxy_pad_chain_default (pad, buffer);
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);
1842 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1852 gst_subtitle_overlay_subtitle_sink_getcaps (GstPad * pad, GstCaps * filter)
1854 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad));
1857 g_mutex_lock (self->factories_lock);
1858 if (G_UNLIKELY (!gst_subtitle_overlay_update_factory_list (self)))
1859 ret = GST_CAPS_NONE;
1862 gst_caps_intersect_full (filter, self->factory_caps,
1863 GST_CAPS_INTERSECT_FIRST);
1865 ret = gst_caps_ref (self->factory_caps);
1866 g_mutex_unlock (self->factories_lock);
1868 GST_DEBUG_OBJECT (pad, "Returning subtitle caps %" GST_PTR_FORMAT, ret);
1870 gst_object_unref (self);
1876 gst_subtitle_overlay_subtitle_sink_setcaps (GstSubtitleOverlay * self,
1879 gboolean ret = TRUE;
1880 GstPad *target = NULL;;
1882 GST_DEBUG_OBJECT (self, "Setting caps: %" GST_PTR_FORMAT, caps);
1885 gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad));
1887 GST_SUBTITLE_OVERLAY_LOCK (self);
1888 gst_caps_replace (&self->subcaps, caps);
1890 if (target && gst_pad_accept_caps (target, caps)) {
1891 GST_DEBUG_OBJECT (self, "Target accepts caps");
1892 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1896 GST_DEBUG_OBJECT (self, "Target did not accept caps");
1898 self->subtitle_error = FALSE;
1899 block_subtitle (self);
1901 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1905 gst_object_unref (target);
1910 static GstPadLinkReturn
1911 gst_subtitle_overlay_subtitle_sink_link (GstPad * pad, GstPad * peer)
1913 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad));
1914 GstPadLinkReturn ret;
1917 GST_DEBUG_OBJECT (pad, "Linking pad to peer %" GST_PTR_FORMAT, peer);
1919 caps = gst_pad_get_current_caps (peer);
1921 caps = gst_pad_query_caps (peer, NULL);
1922 if (!gst_caps_is_fixed (caps)) {
1923 gst_caps_unref (caps);
1929 GST_SUBTITLE_OVERLAY_LOCK (self);
1930 GST_DEBUG_OBJECT (pad, "Have fixed peer caps: %" GST_PTR_FORMAT, caps);
1931 gst_caps_replace (&self->subcaps, caps);
1933 self->subtitle_error = FALSE;
1935 block_subtitle (self);
1937 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1938 gst_caps_unref (caps);
1941 ret = gst_ghost_pad_link_default (pad, peer);
1943 gst_object_unref (self);
1948 gst_subtitle_overlay_subtitle_sink_unlink (GstPad * pad)
1950 GstSubtitleOverlay *self =
1951 GST_SUBTITLE_OVERLAY (gst_object_ref (GST_PAD_PARENT (pad)));
1953 /* FIXME: Can't use gst_pad_get_parent() here because this is called with
1954 * the object lock from state changes
1957 GST_DEBUG_OBJECT (pad, "Pad unlinking");
1958 gst_caps_replace (&self->subcaps, NULL);
1960 gst_ghost_pad_unlink_default (pad);
1962 GST_SUBTITLE_OVERLAY_LOCK (self);
1963 self->subtitle_error = FALSE;
1965 block_subtitle (self);
1967 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1969 gst_object_unref (self);
1973 gst_subtitle_overlay_subtitle_sink_event (GstPad * pad, GstEvent * event)
1975 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad));
1978 if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB &&
1979 gst_event_has_name (event, "subtitleoverlay-flush-subtitle")) {
1980 GST_DEBUG_OBJECT (pad, "Custom subtitle flush event");
1981 GST_SUBTITLE_OVERLAY_LOCK (self);
1982 self->subtitle_flush = TRUE;
1983 self->subtitle_error = FALSE;
1984 block_subtitle (self);
1986 GST_SUBTITLE_OVERLAY_UNLOCK (self);
1988 gst_event_unref (event);
1994 switch (GST_EVENT_TYPE (event)) {
1995 case GST_EVENT_CAPS:
1999 gst_event_parse_caps (event, &caps);
2000 ret = gst_subtitle_overlay_subtitle_sink_setcaps (self, caps);
2005 case GST_EVENT_FLUSH_STOP:
2006 GST_DEBUG_OBJECT (pad,
2007 "Resetting subtitle segment because of flush-stop");
2008 gst_segment_init (&self->subtitle_segment, GST_FORMAT_UNDEFINED);
2010 case GST_EVENT_FLUSH_START:
2011 case GST_EVENT_SEGMENT:
2014 GstStructure *structure;
2016 /* Add our event marker to make sure no events from here go ever outside
2017 * the element, they're only interesting for our internal elements */
2018 event = GST_EVENT_CAST (gst_event_make_writable (event));
2019 structure = gst_event_writable_structure (event);
2021 gst_structure_id_set (structure, _subtitle_overlay_event_marker_id,
2022 G_TYPE_BOOLEAN, TRUE, NULL);
2029 ret = gst_proxy_pad_event_default (pad, gst_event_ref (event));
2031 if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
2032 GST_DEBUG_OBJECT (pad, "segment event: %" GST_PTR_FORMAT, event);
2033 gst_event_copy_segment (event, &self->subtitle_segment);
2034 GST_DEBUG_OBJECT (pad, "New subtitle segment: %" GST_SEGMENT_FORMAT,
2035 &self->subtitle_segment);
2037 gst_event_unref (event);
2040 gst_object_unref (self);
2045 gst_subtitle_overlay_subtitle_sink_query (GstPad * pad, GstQuery * query)
2047 GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad));
2050 switch (GST_QUERY_TYPE (query)) {
2051 case GST_QUERY_ACCEPT_CAPS:
2053 GstCaps *caps, *othercaps;
2055 gst_query_parse_accept_caps (query, &caps);
2056 othercaps = gst_subtitle_overlay_subtitle_sink_getcaps (pad, NULL);
2057 ret = gst_caps_is_subset (caps, othercaps);
2058 gst_caps_unref (othercaps);
2059 gst_query_set_accept_caps_result (query, ret);
2063 case GST_QUERY_CAPS:
2065 GstCaps *filter, *caps;
2067 gst_query_parse_caps (query, &filter);
2068 caps = gst_subtitle_overlay_subtitle_sink_getcaps (pad, filter);
2069 gst_query_set_caps_result (query, caps);
2070 gst_caps_unref (caps);
2075 ret = gst_pad_query_default (pad, query);
2078 gst_object_unref (self);
2084 gst_subtitle_overlay_init (GstSubtitleOverlay * self)
2086 GstPadTemplate *templ;
2087 GstPad *proxypad = NULL;
2089 self->lock = g_mutex_new ();
2090 self->factories_lock = g_mutex_new ();
2092 templ = gst_static_pad_template_get (&srctemplate);
2093 self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ);
2094 gst_object_unref (templ);
2097 GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (self->srcpad)));
2098 gst_pad_set_event_function (proxypad,
2099 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_src_proxy_event));
2100 gst_pad_set_chain_function (proxypad,
2101 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_src_proxy_chain));
2102 gst_object_unref (proxypad);
2104 gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
2106 templ = gst_static_pad_template_get (&video_sinktemplate);
2107 self->video_sinkpad =
2108 gst_ghost_pad_new_no_target_from_template ("video_sink", templ);
2109 gst_object_unref (templ);
2110 gst_pad_set_event_function (self->video_sinkpad,
2111 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_event));
2112 gst_pad_set_chain_function (self->video_sinkpad,
2113 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_chain));
2116 GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
2117 (self->video_sinkpad)));
2118 self->video_block_pad = proxypad;
2119 gst_object_unref (proxypad);
2120 gst_element_add_pad (GST_ELEMENT_CAST (self), self->video_sinkpad);
2122 templ = gst_static_pad_template_get (&subtitle_sinktemplate);
2123 self->subtitle_sinkpad =
2124 gst_ghost_pad_new_no_target_from_template ("subtitle_sink", templ);
2125 gst_object_unref (templ);
2126 gst_pad_set_link_function (self->subtitle_sinkpad,
2127 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_link));
2128 gst_pad_set_unlink_function (self->subtitle_sinkpad,
2129 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_unlink));
2130 gst_pad_set_event_function (self->subtitle_sinkpad,
2131 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_event));
2132 gst_pad_set_query_function (self->subtitle_sinkpad,
2133 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_query));
2134 gst_pad_set_chain_function (self->subtitle_sinkpad,
2135 GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_chain));
2138 GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
2139 (self->subtitle_sinkpad)));
2140 self->subtitle_block_pad = proxypad;
2141 gst_object_unref (proxypad);
2143 gst_element_add_pad (GST_ELEMENT_CAST (self), self->subtitle_sinkpad);
2150 gst_subtitle_overlay_plugin_init (GstPlugin * plugin)
2152 GST_DEBUG_CATEGORY_INIT (subtitle_overlay_debug, "subtitleoverlay", 0,
2153 "Subtitle Overlay");
2155 _subtitle_overlay_event_marker_id =
2156 g_quark_from_static_string ("gst-subtitle-overlay-event-marker");
2158 return gst_element_register (plugin, "subtitleoverlay", GST_RANK_NONE,
2159 GST_TYPE_SUBTITLE_OVERLAY);