From 92ccb878506aa742f6339c1ec4805ee840014ff4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 13 Oct 2009 16:48:34 +0200 Subject: [PATCH] subtitleoverlay: Add new element for generic subtitle overlaying This autopluggs the required elements for parsing and rendering different subtitle formats on a video stream. Fixes bug #600370. --- gst/playback/Makefile.am | 7 +- gst/playback/gstplayback.c | 2 + gst/playback/gstsubtitleoverlay.c | 1671 +++++++++++++++++++++++++++++++++++++ gst/playback/gstsubtitleoverlay.h | 116 +++ 4 files changed, 1794 insertions(+), 2 deletions(-) create mode 100644 gst/playback/gstsubtitleoverlay.c create mode 100644 gst/playback/gstsubtitleoverlay.h diff --git a/gst/playback/Makefile.am b/gst/playback/Makefile.am index 0647a72..db5d3f4 100644 --- a/gst/playback/Makefile.am +++ b/gst/playback/Makefile.am @@ -21,7 +21,8 @@ libgstplaybin_la_SOURCES = \ gstinputselector.c \ gstscreenshot.c \ gststreaminfo.c \ - gststreamselector.c + gststreamselector.c \ + gstsubtitleoverlay.c nodist_libgstplaybin_la_SOURCES = $(built_sources) libgstplaybin_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) @@ -29,6 +30,7 @@ libgstplaybin_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstplaybin_la_LIBADD = \ $(top_builddir)/gst-libs/gst/pbutils/libgstpbutils-@GST_MAJORMINOR@.la \ $(top_builddir)/gst-libs/gst/interfaces/libgstinterfaces-@GST_MAJORMINOR@.la \ + $(top_builddir)/gst-libs/gst/video/libgstvideo-@GST_MAJORMINOR@.la \ $(GST_LIBS) libgstplaybin_la_LIBTOOLFLAGS = --tag=disable-static @@ -59,7 +61,8 @@ noinst_HEADERS = \ gstplay-enum.h \ gstscreenshot.h \ gststreamselector.h \ - gstrawcaps.h + gstrawcaps.h \ + gstsubtitleoverlay.h noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7 diff --git a/gst/playback/gstplayback.c b/gst/playback/gstplayback.c index 3a445ae..a4db4241 100644 --- a/gst/playback/gstplayback.c +++ b/gst/playback/gstplayback.c @@ -30,6 +30,7 @@ #include "gststreamselector.h" #include "gststreaminfo.h" #include "gstplaysink.h" +#include "gstsubtitleoverlay.h" gboolean gst_play_bin_plugin_init (GstPlugin * plugin); gboolean gst_play_bin2_plugin_init (GstPlugin * plugin); @@ -56,6 +57,7 @@ plugin_init (GstPlugin * plugin) res = gst_play_bin_plugin_init (plugin); res &= gst_play_bin2_plugin_init (plugin); res &= gst_play_sink_plugin_init (plugin); + res &= gst_subtitle_overlay_plugin_init (plugin); return res; } diff --git a/gst/playback/gstsubtitleoverlay.c b/gst/playback/gstsubtitleoverlay.c new file mode 100644 index 0000000..f04ad41 --- /dev/null +++ b/gst/playback/gstsubtitleoverlay.c @@ -0,0 +1,1671 @@ +/* + * Copyright (C) 2009 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-subtitleoverlay + * + * #GstBin that auto-magically overlays a video stream with subtitles by + * autoplugging the required elements. + * + * It supports raw, timestamped text, different textual subtitle formats and + * DVD subpicture subtitles. + * + * + * Examples + * |[ + * gst-launch -v filesrc location=test.mkv ! matroskademux name=demux ! "video/x-h264" ! queue2 ! decodebin2 ! subtitleoverlay name=overlay ! ffmpegcolorspace ! autovideosink demux. ! "video/x-dvd-subpicture" ! queue2 ! overlay. + * ]| This will play back the given Matroska file with h264 video and subpicture subtitles. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstsubtitleoverlay.h" + +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (subtitle_overlay_debug); +#define GST_CAT_DEFAULT subtitle_overlay_debug + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-rgb; video/x-raw-yuv")); + +static GstStaticPadTemplate video_sinktemplate = + GST_STATIC_PAD_TEMPLATE ("video_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-rgb; video/x-raw-yuv")); + +static GstStaticPadTemplate subtitle_sinktemplate = +GST_STATIC_PAD_TEMPLATE ("subtitle_sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +enum +{ + PROP_0, + PROP_FONT_DESC +}; + +GST_BOILERPLATE (GstSubtitleOverlay, gst_subtitle_overlay, GstBin, + GST_TYPE_BIN); + +static void _pad_blocked_cb (GstPad * pad, gboolean blocked, + gpointer user_data); + +static GQuark _subtitle_overlay_event_marker_id = 0; + +static void +do_async_start (GstSubtitleOverlay * self) +{ + if (!self->do_async) { + GstMessage *msg = + gst_message_new_async_start (GST_OBJECT_CAST (self), FALSE); + + GST_DEBUG_OBJECT (self, "Posting async-start"); + parent_class->handle_message (GST_BIN_CAST (self), msg); + self->do_async = TRUE; + } +} + +static void +do_async_done (GstSubtitleOverlay * self) +{ + if (self->do_async) { + GstMessage *msg = gst_message_new_async_done (GST_OBJECT_CAST (self)); + + GST_DEBUG_OBJECT (self, "Posting async-done"); + parent_class->handle_message (GST_BIN_CAST (self), msg); + self->do_async = FALSE; + } +} + +static void +gst_subtitle_overlay_finalize (GObject * object) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (object); + + if (self->lock) { + g_mutex_free (self->lock); + self->lock = NULL; + } + + if (self->font_desc) { + g_free (self->font_desc); + self->font_desc = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +_is_renderer (GstElementFactory * factory) +{ + const gchar *klass, *name; + + klass = gst_element_factory_get_klass (factory); + name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)); + + if (strstr (klass, "Overlay/Subtitle") != NULL || + strstr (klass, "Overlay/SubPicture") != NULL) + return TRUE; + if (strcmp (name, "textoverlay") == 0) + return TRUE; + return FALSE; +} + +static gboolean +_is_parser (GstElementFactory * factory) +{ + const gchar *klass; + + klass = gst_element_factory_get_klass (factory); + + if (strstr (klass, "Parser/Subtitle") != NULL) + return TRUE; + return FALSE; +} + +static const gchar *_sub_pad_names[] = { "subpicture", "subpicture_sink", + "text", "text_sink", + "subtitle_sink", "subtitle" +}; + +static GstCaps * +_get_sub_caps (GstElementFactory * factory) +{ + const GList *templates; + GList *walk; + gboolean is_parser = _is_parser (factory); + + templates = gst_element_factory_get_static_pad_templates (factory); + for (walk = (GList *) templates; walk; walk = g_list_next (walk)) { + GstStaticPadTemplate *templ = walk->data; + + if (templ->direction == GST_PAD_SINK && templ->presence == GST_PAD_ALWAYS) { + gboolean found = FALSE; + + if (is_parser) { + found = TRUE; + } else { + guint i; + + for (i = 0; i < G_N_ELEMENTS (_sub_pad_names); i++) { + if (strcmp (templ->name_template, _sub_pad_names[i]) == 0) { + found = TRUE; + break; + } + } + } + if (found) + return gst_static_caps_get (&templ->static_caps); + } + } + return NULL; +} + +static gboolean +_factory_filter (GstPluginFeature * feature, GstCaps ** subcaps) +{ + GstElementFactory *factory; + guint rank; + const gchar *name; + const GList *templates; + GList *walk; + gboolean is_renderer; + GstCaps *templ_caps = NULL; + gboolean have_video_sink = FALSE; + + /* we only care about element factories */ + if (!GST_IS_ELEMENT_FACTORY (feature)) + return FALSE; + + factory = GST_ELEMENT_FACTORY_CAST (feature); + + /* only select elements with autoplugging rank or textoverlay */ + name = gst_plugin_feature_get_name (feature); + rank = gst_plugin_feature_get_rank (feature); + if (strcmp ("textoverlay", name) != 0 && rank < GST_RANK_MARGINAL) + return FALSE; + + /* Check if it's a renderer or a parser */ + if (_is_renderer (factory)) { + is_renderer = TRUE; + } else if (_is_parser (factory)) { + is_renderer = FALSE; + } else { + return FALSE; + } + + /* Check if there's a video sink in case of a renderer */ + if (is_renderer) { + templates = gst_element_factory_get_static_pad_templates (factory); + for (walk = (GList *) templates; walk; walk = g_list_next (walk)) { + GstStaticPadTemplate *templ = walk->data; + + /* we only care about the always-sink templates */ + if (templ->direction == GST_PAD_SINK && templ->presence == GST_PAD_ALWAYS) { + if (strcmp (templ->name_template, "video") == 0 || + strcmp (templ->name_template, "video_sink") == 0) { + have_video_sink = TRUE; + } + } + } + } + templ_caps = _get_sub_caps (factory); + + if (is_renderer && have_video_sink && templ_caps) { + GstCaps *tmp; + + GST_DEBUG ("Found renderer element %s (%s) with caps %" GST_PTR_FORMAT, + gst_element_factory_get_longname (factory), + gst_plugin_feature_get_name (feature), templ_caps); + tmp = gst_caps_union (*subcaps, templ_caps); + gst_caps_unref (templ_caps); + gst_caps_replace (subcaps, tmp); + gst_caps_unref (tmp); + return TRUE; + } else if (!is_renderer && !have_video_sink && templ_caps) { + GstCaps *tmp; + + GST_DEBUG ("Found parser element %s (%s) with caps %" GST_PTR_FORMAT, + gst_element_factory_get_longname (factory), + gst_plugin_feature_get_name (feature), templ_caps); + tmp = gst_caps_union (*subcaps, templ_caps); + gst_caps_unref (templ_caps); + gst_caps_replace (subcaps, tmp); + gst_caps_unref (tmp); + return TRUE; + } else { + if (templ_caps) + gst_caps_unref (templ_caps); + return FALSE; + } +} + +static gboolean +gst_subtitle_overlay_create_factory_list (GstSubtitleOverlay * self) +{ + if (g_once_init_enter ((gsize *) & self->factories)) { + GstCaps *subcaps; + GList *factories; + + subcaps = gst_caps_new_empty (); + + factories = gst_default_registry_feature_filter ( + (GstPluginFeatureFilter) _factory_filter, FALSE, &subcaps); + GST_DEBUG_OBJECT (self, "Created factory caps: %" GST_PTR_FORMAT, subcaps); + gst_caps_replace (&self->factory_caps, subcaps); + gst_caps_unref (subcaps); + g_once_init_leave ((gsize *) & self->factories, (gsize) factories); + } + + return (self->factories != NULL); +} + +GstCaps * +gst_subtitle_overlay_create_factory_caps (void) +{ + GList *factories; + GstCaps *subcaps; + + subcaps = gst_caps_new_empty (); + + factories = gst_default_registry_feature_filter ( + (GstPluginFeatureFilter) _factory_filter, FALSE, &subcaps); + GST_DEBUG ("Created factory caps: %" GST_PTR_FORMAT, subcaps); + gst_plugin_feature_list_free (factories); + + return subcaps; +} + +static gboolean +_filter_factories_for_caps (GstElementFactory * factory, const GstCaps * caps) +{ + GstCaps *fcaps = _get_sub_caps (factory); + gboolean ret = (fcaps) ? gst_caps_can_intersect (fcaps, caps) : FALSE; + + if (fcaps) + gst_caps_unref (fcaps); + return ret; +} + +static gint +_sort_by_ranks (GstPluginFeature * f1, GstPluginFeature * f2) +{ + gint diff; + const gchar *rname1, *rname2; + + diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1); + if (diff != 0) + return diff; + + /* If the ranks are the same sort by name to get deterministic results */ + rname1 = gst_plugin_feature_get_name (f1); + rname2 = gst_plugin_feature_get_name (f2); + + diff = strcmp (rname1, rname2); + + return diff; +} + +static GstPad * +_get_sub_pad (GstElement * element) +{ + GstPad *pad; + guint i; + + for (i = 0; i < G_N_ELEMENTS (_sub_pad_names); i++) { + pad = gst_element_get_static_pad (element, _sub_pad_names[i]); + if (pad) + return pad; + } + return NULL; +} + +static GstPad * +_get_video_pad (GstElement * element) +{ + static const gchar *pad_names[] = { "video", "video_sink" }; + GstPad *pad; + guint i; + + for (i = 0; i < G_N_ELEMENTS (pad_names); i++) { + pad = gst_element_get_static_pad (element, pad_names[i]); + if (pad) + return pad; + } + return NULL; +} + +static gboolean +_create_element (GstSubtitleOverlay * self, GstElement ** element, + const gchar * factory_name, GstElementFactory * factory, + const gchar * element_name, gboolean mandatory) +{ + GstElement *elt; + + g_assert (!factory || !factory_name); + + if (factory_name) { + elt = gst_element_factory_make (factory_name, element_name); + } else { + factory_name = + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)); + elt = gst_element_factory_create (factory, element_name); + } + + if (G_UNLIKELY (!elt)) { + if (!factory) { + GstMessage *msg; + + msg = + gst_missing_element_message_new (GST_ELEMENT_CAST (self), + factory_name); + gst_element_post_message (GST_ELEMENT_CAST (self), msg); + + if (mandatory) + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("no '%s' plugin found", factory_name)); + else + GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, (NULL), + ("no '%s' plugin found", factory_name)); + } else { + if (mandatory) { + GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL), + ("can't instantiate '%s'", factory_name)); + } else { + GST_ELEMENT_WARNING (self, CORE, FAILED, (NULL), + ("can't instantiate '%s'", factory_name)); + } + } + + return FALSE; + } + + if (G_UNLIKELY (gst_element_set_state (elt, + GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS)) { + gst_object_unref (elt); + if (mandatory) { + GST_ELEMENT_ERROR (self, CORE, STATE_CHANGE, (NULL), + ("failed to set '%s' to READY", factory_name)); + } else { + GST_WARNING_OBJECT (self, "Failed to set '%s' to READY", factory_name); + } + return FALSE; + } + + if (G_UNLIKELY (!gst_bin_add (GST_BIN_CAST (self), gst_object_ref (elt)))) { + gst_element_set_state (elt, GST_STATE_NULL); + gst_object_unref (elt); + if (mandatory) { + GST_ELEMENT_ERROR (self, CORE, FAILED, (NULL), + ("failed to add '%s' to subtitleoverlay", factory_name)); + } else { + GST_WARNING_OBJECT (self, "Failed to add '%s' to subtitleoverlay", + factory_name); + } + return FALSE; + } + + gst_element_sync_state_with_parent (elt); + *element = elt; + return TRUE; +} + +static void +_remove_element (GstSubtitleOverlay * self, GstElement ** element) +{ + if (*element) { + gst_bin_remove (GST_BIN_CAST (self), *element); + gst_element_set_state (*element, GST_STATE_NULL); + gst_object_unref (*element); + *element = NULL; + } +} + +static void +_generate_update_newsegment_event (GstSegment * segment, GstEvent ** event1, + GstEvent ** event2) +{ + GstEvent *event; + + *event1 = NULL; + *event2 = NULL; + + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, 0, segment->accum, 0); + gst_structure_id_set (event->structure, _subtitle_overlay_event_marker_id, + G_TYPE_BOOLEAN, TRUE, NULL); + *event1 = event; + + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, + segment->start, segment->stop, segment->time); + gst_structure_id_set (event->structure, _subtitle_overlay_event_marker_id, + G_TYPE_BOOLEAN, TRUE, NULL); + *event2 = event; +} + +static gboolean +_setup_passthrough (GstSubtitleOverlay * self) +{ + GstPad *src, *sink; + GstElement *identity; + + GST_DEBUG_OBJECT (self, "Doing video passthrough"); + + if (self->passthrough_identity) { + GST_DEBUG_OBJECT (self, "Already in passthrough mode"); + goto out; + } + + /* Unlink & destroy everything */ + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad), NULL); + _remove_element (self, &self->post_colorspace); + _remove_element (self, &self->overlay); + _remove_element (self, &self->parser); + _remove_element (self, &self->renderer); + _remove_element (self, &self->pre_colorspace); + _remove_element (self, &self->passthrough_identity); + + if (G_UNLIKELY (!_create_element (self, &self->passthrough_identity, + "identity", NULL, "passthrough-identity", TRUE))) { + return FALSE; + } + + identity = self->passthrough_identity; + g_object_set (G_OBJECT (identity), "silent", TRUE, "signal-handoffs", FALSE, + NULL); + + /* Set src ghostpad target */ + src = gst_element_get_static_pad (self->passthrough_identity, "src"); + if (G_UNLIKELY (!src)) { + GST_ELEMENT_ERROR (self, CORE, PAD, (NULL), + ("Failed to get srcpad from identity")); + return FALSE; + } + + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + src))) { + GST_ELEMENT_ERROR (self, CORE, PAD, (NULL), + ("Failed to set srcpad target")); + gst_object_unref (src); + return FALSE; + } + gst_object_unref (src); + + sink = gst_element_get_static_pad (self->passthrough_identity, "sink"); + if (G_UNLIKELY (!sink)) { + GST_ELEMENT_ERROR (self, CORE, PAD, (NULL), + ("Failed to get sinkpad from identity")); + return FALSE; + } + + /* Send segment to the identity. This is dropped because identity + * is not linked downstream yet */ + if (!self->video_segment_pending) { + GstEvent *event1, *event2; + + _generate_update_newsegment_event (&self->video_segment, &event1, &event2); + GST_DEBUG_OBJECT (self, + "Pushing video accumulate newsegment event: %" GST_PTR_FORMAT, + event1->structure); + GST_DEBUG_OBJECT (self, + "Pushing video update newsegment event: %" GST_PTR_FORMAT, + event2->structure); + gst_pad_send_event (sink, event1); + gst_pad_send_event (sink, event2); + } + + /* Link sink ghostpads to identity */ + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST + (self->video_sinkpad), sink))) { + GST_ELEMENT_ERROR (self, CORE, PAD, (NULL), + ("Failed to set video sinkpad target")); + gst_object_unref (sink); + return FALSE; + } + gst_object_unref (sink); + + GST_DEBUG_OBJECT (self, "Video passthrough setup successfully"); + +out: + /* Unblock pads */ + gst_pad_set_blocked_async_full (self->video_block_pad, FALSE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + + if (self->subtitle_sink_blocked) + gst_pad_set_blocked_async_full (self->subtitle_block_pad, FALSE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + + return TRUE; +} + +static void +_pad_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (user_data); + GstCaps *subcaps; + GList *l, *factories = NULL; + + GST_DEBUG_OBJECT (pad, "Pad blocked: %d", blocked); + + GST_SUBTITLE_OVERLAY_LOCK (self); + if (pad == self->video_block_pad) + self->video_sink_blocked = blocked; + else if (pad == self->subtitle_block_pad) + self->subtitle_sink_blocked = blocked; + + if (!blocked) { + GST_SUBTITLE_OVERLAY_UNLOCK (self); + return; + } + + /* Now either both or the video sink are blocked */ + + /* Get current subtitle caps */ + subcaps = self->subcaps; + if (!subcaps) { + GstPad *peer; + + peer = gst_pad_get_peer (self->subtitle_sinkpad); + if (peer) { + subcaps = gst_pad_get_negotiated_caps (peer); + if (!subcaps) { + subcaps = gst_pad_get_caps (peer); + if (!gst_caps_is_fixed (subcaps)) { + gst_caps_unref (subcaps); + subcaps = NULL; + } + } + gst_object_unref (peer); + } + gst_caps_replace (&self->subcaps, subcaps); + if (subcaps) + gst_caps_unref (subcaps); + } + GST_DEBUG_OBJECT (self, "Current subtitle caps: %" GST_PTR_FORMAT, subcaps); + + /* If there are no subcaps but the subtitle sink is blocked upstream + * must behave wrong as there are no fixed caps set for the first + * buffer or in-order event */ + if (G_UNLIKELY (!subcaps && self->subtitle_sink_blocked)) { + GST_ELEMENT_WARNING (self, CORE, NEGOTIATION, (NULL), + ("Subtitle sink is blocked but we have no subtitle caps")); + subcaps = NULL; + } + + /* Now do something with the caps */ + if (subcaps && !self->subtitle_flush) { + GstPad *target = + gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad)); + + if (target && gst_pad_accept_caps (target, subcaps)) { + GST_DEBUG_OBJECT (pad, "Target accepts caps"); + + gst_object_unref (target); + + /* Unblock pads */ + gst_pad_set_blocked_async_full (self->video_block_pad, FALSE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + + if (self->subtitle_sink_blocked) + gst_pad_set_blocked_async_full (self->subtitle_block_pad, FALSE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + goto out; + } else if (target) { + gst_object_unref (target); + } + } + + if (self->subtitle_sink_blocked && !self->video_sink_blocked) { + GST_DEBUG_OBJECT (self, "Subtitle sink blocked but video not blocked"); + gst_pad_set_blocked_async_full (self->video_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + goto out; + } + + self->subtitle_flush = FALSE; + + /* Find our factories */ + if (subcaps) { + factories = gst_filter_run (self->factories, + (GstFilterFunc) _filter_factories_for_caps, FALSE, subcaps); + if (!factories) { + GstMessage *msg; + + msg = gst_missing_decoder_message_new (GST_ELEMENT_CAST (self), subcaps); + gst_element_post_message (GST_ELEMENT_CAST (self), msg); + GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, (NULL), + ("no suitable subtitle plugin found")); + subcaps = NULL; + } + } + + if (!subcaps) { + _setup_passthrough (self); + do_async_done (self); + goto out; + } + + /* Now the interesting parts are done: subtitle overlaying! */ + + /* Sort the factories by rank */ + factories = g_list_sort (factories, (GCompareFunc) _sort_by_ranks); + + for (l = factories; l; l = l->next) { + GstElementFactory *factory = l->data; + gboolean is_renderer = _is_renderer (factory); + GstElement *element; + GstPad *sink, *src; + + /* Unlink & destroy everything */ + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad), + NULL); + _remove_element (self, &self->post_colorspace); + _remove_element (self, &self->overlay); + _remove_element (self, &self->parser); + _remove_element (self, &self->renderer); + _remove_element (self, &self->pre_colorspace); + _remove_element (self, &self->passthrough_identity); + + GST_DEBUG_OBJECT (self, "Trying factory '%s'", + GST_STR_NULL (gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST + (factory)))); + + if (G_UNLIKELY ((is_renderer + && !_create_element (self, &self->renderer, NULL, factory, + "renderer", FALSE)) || (!is_renderer + && !_create_element (self, &self->parser, NULL, factory, + "parser", FALSE)))) + continue; + + element = is_renderer ? self->renderer : self->parser; + + /* If this is a parser, create textoverlay and link video and the parser to it + * Else link the renderer to the output colorspace */ + if (!is_renderer) { + GstElement *overlay; + + /* First link everything internally */ + if (G_UNLIKELY (!_create_element (self, &self->overlay, "textoverlay", + NULL, "overlay", FALSE))) { + continue; + } + overlay = self->overlay; + + /* Set some properties */ + g_object_set (G_OBJECT (overlay), + "halign", "center", "valign", "bottom", "wait-text", FALSE, NULL); + if (self->font_desc) + g_object_set (G_OBJECT (overlay), "font-desc", self->font_desc, NULL); + + src = gst_element_get_static_pad (element, "src"); + if (G_UNLIKELY (!src)) { + continue; + } + + sink = gst_element_get_static_pad (overlay, "text_sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get text sink from textoverlay"); + gst_object_unref (src); + } + + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link parser to textoverlay"); + gst_object_unref (sink); + gst_object_unref (src); + } + gst_object_unref (sink); + gst_object_unref (src); + + if (G_UNLIKELY (!_create_element (self, &self->post_colorspace, + "ffmpegcolorspace", NULL, "post-colorspace", FALSE))) { + continue; + } + + src = gst_element_get_static_pad (overlay, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from overlay"); + continue; + } + + sink = gst_element_get_static_pad (self->post_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from ffmpegcolorspace"); + gst_object_unref (src); + continue; + } + + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link overlay with ffmpegcolorspace"); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } + gst_object_unref (src); + gst_object_unref (sink); + + if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace, + "ffmpegcolorspace", NULL, "pre-colorspace", FALSE))) { + continue; + } + + sink = gst_element_get_static_pad (overlay, "video_sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from textoverlay"); + continue; + } + + src = gst_element_get_static_pad (self->pre_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get srcpad from ffmpegcolorspace"); + gst_object_unref (sink); + continue; + } + + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link ffmpegcolorspace to textoverlay"); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } + gst_object_unref (src); + gst_object_unref (sink); + + /* Set src ghostpad target */ + src = gst_element_get_static_pad (self->post_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from ffmpegcolorspace"); + continue; + } + + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST + (self->srcpad), src))) { + GST_WARNING_OBJECT (self, "Can't set srcpad target"); + gst_object_unref (src); + continue; + } + gst_object_unref (src); + + /* Send segments to the parser/overlay if necessary. These are not sent + * outside this element because of the proxy pad event function */ + if (!self->video_segment_pending) { + GstEvent *event1, *event2; + + sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from ffmpegcolorspace"); + continue; + } + + _generate_update_newsegment_event (&self->video_segment, &event1, + &event2); + GST_DEBUG_OBJECT (self, + "Pushing video accumulate newsegment event: %" GST_PTR_FORMAT, + event1->structure); + GST_DEBUG_OBJECT (self, + "Pushing video update newsegment event: %" GST_PTR_FORMAT, + event2->structure); + gst_pad_send_event (sink, event1); + gst_pad_send_event (sink, event2); + + gst_object_unref (sink); + } + + if (!self->subtitle_segment_pending) { + GstEvent *event1, *event2; + + sink = gst_element_get_static_pad (element, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Failed to get subpad"); + continue; + } + + _generate_update_newsegment_event (&self->subtitle_segment, &event1, + &event2); + GST_DEBUG_OBJECT (self, + "Pushing subtitle accumulate newsegment event: %" GST_PTR_FORMAT, + event1->structure); + GST_DEBUG_OBJECT (self, + "Pushing subtitle update newsegment event: %" GST_PTR_FORMAT, + event2->structure); + gst_pad_send_event (sink, event1); + gst_pad_send_event (sink, event2); + + gst_object_unref (sink); + } + + /* Set the sink ghostpad targets */ + sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from ffmpegcolorspace"); + continue; + } + + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST + (self->video_sinkpad), sink))) { + GST_WARNING_OBJECT (self, "Can't set video sinkpad target"); + gst_object_unref (sink); + continue; + } + gst_object_unref (sink); + + /* Link subtitle identity to subtitle pad of our element */ + sink = gst_element_get_static_pad (element, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Failed to get subpad"); + continue; + } + + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST + (self->subtitle_sinkpad), sink))) { + GST_WARNING_OBJECT (self, "Failed to set subtitle sink target"); + gst_object_unref (sink); + continue; + } + gst_object_unref (sink); + } else { + const gchar *name = + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)); + + if (strcmp (name, "textoverlay") == 0) { + /* Set some textoverlay specific properties */ + g_object_set (G_OBJECT (element), + "halign", "center", "valign", "bottom", "wait-text", FALSE, NULL); + if (self->font_desc) + g_object_set (G_OBJECT (element), "font-desc", self->font_desc, NULL); + } + + /* First link everything internally */ + if (G_UNLIKELY (!_create_element (self, &self->post_colorspace, + "ffmpegcolorspace", NULL, "post-colorspace", FALSE))) { + continue; + } + + src = gst_element_get_static_pad (element, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); + continue; + } + + sink = gst_element_get_static_pad (self->post_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from ffmpegcolorspace"); + gst_object_unref (src); + continue; + } + + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link renderer with ffmpegcolorspace"); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } + gst_object_unref (src); + gst_object_unref (sink); + + if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace, + "ffmpegcolorspace", NULL, "pre-colorspace", FALSE))) { + continue; + } + + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); + continue; + } + + src = gst_element_get_static_pad (self->pre_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get srcpad from ffmpegcolorspace"); + gst_object_unref (sink); + continue; + } + + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link ffmpegcolorspace to renderer"); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } + gst_object_unref (src); + gst_object_unref (sink); + + /* Set src ghostpad target */ + src = gst_element_get_static_pad (self->post_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from ffmpegcolorspace"); + continue; + } + + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST + (self->srcpad), src))) { + GST_WARNING_OBJECT (self, "Can't set srcpad target"); + gst_object_unref (src); + continue; + } + gst_object_unref (src); + + /* Send segments to the renderer if necessary. These are not sent + * outside this element because of the proxy pad event handler */ + if (!self->video_segment_pending) { + GstEvent *event1, *event2; + + sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from ffmpegcolorspace"); + continue; + } + + _generate_update_newsegment_event (&self->video_segment, &event1, + &event2); + GST_DEBUG_OBJECT (self, + "Pushing video accumulate newsegment event: %" GST_PTR_FORMAT, + event1->structure); + GST_DEBUG_OBJECT (self, + "Pushing video update newsegment event: %" GST_PTR_FORMAT, + event2->structure); + gst_pad_send_event (sink, event1); + gst_pad_send_event (sink, event2); + gst_object_unref (sink); + } + + if (!self->subtitle_segment_pending) { + GstEvent *event1, *event2; + + sink = _get_sub_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Failed to get subpad"); + continue; + } + + _generate_update_newsegment_event (&self->subtitle_segment, &event1, + &event2); + GST_DEBUG_OBJECT (self, + "Pushing subtitle accumulate newsegment event: %" GST_PTR_FORMAT, + event1->structure); + GST_DEBUG_OBJECT (self, + "Pushing subtitle update newsegment event: %" GST_PTR_FORMAT, + event2->structure); + gst_pad_send_event (sink, event1); + gst_pad_send_event (sink, event2); + gst_object_unref (sink); + } + + /* Set the sink ghostpad targets */ + sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from ffmpegcolorspace"); + continue; + } + + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST + (self->video_sinkpad), sink))) { + GST_WARNING_OBJECT (self, "Can't set video sinkpad target"); + gst_object_unref (sink); + continue; + } + + sink = _get_sub_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Failed to get subpad"); + continue; + } + + if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST + (self->subtitle_sinkpad), sink))) { + GST_WARNING_OBJECT (self, "Failed to set subtitle sink target"); + gst_object_unref (sink); + continue; + } + gst_object_unref (sink); + } + + break; + } + + if (G_UNLIKELY (l == NULL)) { + GST_ELEMENT_WARNING (self, CORE, FAILED, (NULL), + ("Failed to find any usable factories")); + _setup_passthrough (self); + do_async_done (self); + } else { + GST_DEBUG_OBJECT (self, "Everything worked, unblocking pads"); + gst_pad_set_blocked_async_full (self->video_block_pad, FALSE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + gst_pad_set_blocked_async_full (self->subtitle_block_pad, FALSE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + do_async_done (self); + } + +out: + if (factories) + g_list_free (factories); + GST_SUBTITLE_OVERLAY_UNLOCK (self); +} + +static GstStateChangeReturn +gst_subtitle_overlay_change_state (GstElement * element, + GstStateChange transition) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + GST_DEBUG_OBJECT (self, "State change NULL->READY"); + if (G_UNLIKELY (!gst_subtitle_overlay_create_factory_list (self))) + return GST_STATE_CHANGE_FAILURE; + + GST_SUBTITLE_OVERLAY_LOCK (self); + /* Set the internal pads to blocking */ + gst_pad_set_blocked_async_full (self->video_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + gst_pad_set_blocked_async_full (self->subtitle_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG_OBJECT (self, "State change READY->PAUSED"); + gst_segment_init (&self->video_segment, GST_FORMAT_UNDEFINED); + gst_segment_init (&self->subtitle_segment, GST_FORMAT_UNDEFINED); + + self->video_segment_pending = FALSE; + self->subtitle_segment_pending = FALSE; + self->subtitle_flush = FALSE; + + do_async_start (self); + ret = GST_STATE_CHANGE_ASYNC; + + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + GST_DEBUG_OBJECT (self, "State change PAUSED->PLAYING"); + default: + break; + } + + { + GstStateChangeReturn bret; + + bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + GST_DEBUG_OBJECT (self, "Base class state changed returned: %d", bret); + if (G_UNLIKELY (bret == GST_STATE_CHANGE_FAILURE)) + return ret; + else if (bret == GST_STATE_CHANGE_ASYNC) + ret = bret; + else if (G_UNLIKELY (bret == GST_STATE_CHANGE_NO_PREROLL)) { + do_async_done (self); + ret = bret; + } + } + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + GST_DEBUG_OBJECT (self, "State change PLAYING->PAUSED"); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT (self, "State change PAUSED->READY"); + do_async_done (self); + + break; + case GST_STATE_CHANGE_READY_TO_NULL:{ + GstPad *pad; + + GST_DEBUG_OBJECT (self, "State change READY->NULL"); + + if (self->factories) + gst_plugin_feature_list_free (self->factories); + self->factories = NULL; + gst_caps_replace (&self->factory_caps, NULL); + + GST_SUBTITLE_OVERLAY_LOCK (self); + gst_caps_replace (&self->subcaps, NULL); + + /* Unlink ghost pads */ + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->video_sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad), + NULL); + + /* Unblock pads */ + if (self->video_block_pad) { + pad = self->video_block_pad; + gst_pad_set_blocked_async_full (pad, FALSE, _pad_blocked_cb, + gst_object_ref (self), (GDestroyNotify) gst_object_unref); + } + + if (self->subtitle_block_pad) { + pad = self->subtitle_block_pad; + gst_pad_set_blocked_async_full (pad, FALSE, _pad_blocked_cb, + gst_object_ref (self), (GDestroyNotify) gst_object_unref); + } + + /* Remove elements */ + _remove_element (self, &self->post_colorspace); + _remove_element (self, &self->overlay); + _remove_element (self, &self->parser); + _remove_element (self, &self->renderer); + _remove_element (self, &self->pre_colorspace); + _remove_element (self, &self->passthrough_identity); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + + break; + } + default: + break; + } + + return ret; +} + +static void +gst_subtitle_overlay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (object); + + switch (prop_id) { + case PROP_FONT_DESC: + GST_SUBTITLE_OVERLAY_LOCK (self); + g_free (self->font_desc); + self->font_desc = g_value_dup_string (value); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_subtitle_overlay_base_init (gpointer g_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&srctemplate)); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&video_sinktemplate)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&subtitle_sinktemplate)); + + gst_element_class_set_details_simple (gstelement_class, "Subtitle Overlay", + "Video/Overlay/Subtitle", + "Overlays a video stream with subtitles", + "Sebastian Dröge "); +} + +static void +gst_subtitle_overlay_class_init (GstSubtitleOverlayClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + + gobject_class->finalize = gst_subtitle_overlay_finalize; + gobject_class->set_property = gst_subtitle_overlay_set_property; + + g_object_class_install_property (gobject_class, PROP_FONT_DESC, + g_param_spec_string ("font-desc", + "Subtitle font description", + "Pango font description of font " + "to be used for subtitle rendering", NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_change_state); +} + +static gboolean +gst_subtitle_overlay_src_proxy_event (GstPad * proxypad, GstEvent * event) +{ + GstPad *ghostpad = NULL; + GstSubtitleOverlay *self = NULL; + gboolean ret = FALSE; + const GstStructure *s; + + ghostpad = GST_PAD_CAST (gst_pad_get_parent (proxypad)); + if (G_UNLIKELY (!ghostpad)) + goto out; + self = GST_SUBTITLE_OVERLAY_CAST (gst_pad_get_parent (ghostpad)); + if (G_UNLIKELY (!self || self->srcpad != ghostpad)) + goto out; + + s = gst_event_get_structure (event); + if (s && gst_structure_id_has_field (s, _subtitle_overlay_event_marker_id)) { + GST_DEBUG_OBJECT (ghostpad, "Dropping event with marker: %" GST_PTR_FORMAT, + event->structure); + gst_event_unref (event); + event = NULL; + ret = TRUE; + } else { + ret = self->src_proxy_event (proxypad, event); + event = NULL; + } + +out: + if (event) + gst_event_unref (event); + if (self) + gst_object_unref (self); + if (ghostpad) + gst_object_unref (ghostpad); + return ret; +} + +static gboolean +gst_subtitle_overlay_video_sink_event (GstPad * pad, GstEvent * event) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad)); + gboolean ret; + gboolean is_newsegment_event = FALSE; + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + gboolean update; + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, position; + + GST_DEBUG_OBJECT (pad, "Newsegment event: %" GST_PTR_FORMAT, + event->structure); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + if (format != GST_FORMAT_TIME) { + GST_ERROR_OBJECT (pad, "Newsegment event in non-time format: %s", + gst_format_get_name (format)); + gst_object_unref (event); + gst_object_unref (self); + return FALSE; + } + + GST_DEBUG_OBJECT (pad, "Old video segment: %" GST_SEGMENT_FORMAT, + &self->video_segment); + gst_segment_set_newsegment_full (&self->video_segment, update, rate, + applied_rate, format, start, stop, position); + GST_DEBUG_OBJECT (pad, "New video segment: %" GST_SEGMENT_FORMAT, + &self->video_segment); + is_newsegment_event = TRUE; + } else if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) { + GST_DEBUG_OBJECT (pad, + "Resetting video segment because of flush-stop event"); + gst_segment_init (&self->video_segment, GST_FORMAT_UNDEFINED); + } + + if (is_newsegment_event) + self->video_segment_pending = TRUE; + ret = self->video_sink_event (pad, event); + self->video_segment_pending = FALSE; + + gst_object_unref (self); + return ret; +} + +static GstCaps * +gst_subtitle_overlay_subtitle_sink_getcaps (GstPad * pad) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad)); + GstCaps *ret; + + if (G_UNLIKELY (!gst_subtitle_overlay_create_factory_list (self))) + ret = GST_CAPS_NONE; + else + ret = gst_caps_ref (self->factory_caps); + + GST_DEBUG_OBJECT (pad, "Returning subtitle caps %" GST_PTR_FORMAT, ret); + + gst_object_unref (self); + + return ret; +} + +static gboolean +gst_subtitle_overlay_subtitle_sink_acceptcaps (GstPad * pad, GstCaps * caps) +{ + GstCaps *othercaps = gst_subtitle_overlay_subtitle_sink_getcaps (pad); + gboolean ret = gst_caps_can_intersect (caps, othercaps); + + gst_caps_unref (othercaps); + + return ret; +} + +static gboolean +gst_subtitle_overlay_subtitle_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad)); + gboolean ret = TRUE; + GstPad *target = NULL;; + + GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps); + + target = + gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad)); + + GST_SUBTITLE_OVERLAY_LOCK (self); + gst_caps_replace (&self->subcaps, caps); + + if (target && gst_pad_accept_caps (target, caps)) { + GST_DEBUG_OBJECT (pad, "Target accepts caps"); + ret = self->subtitle_sink_setcaps (pad, caps); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + goto out; + } + + GST_DEBUG_OBJECT (pad, "Target did not accept caps"); + + gst_pad_set_blocked_async_full (self->subtitle_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + + gst_pad_set_blocked_async_full (self->video_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + +out: + if (target) + gst_object_unref (target); + gst_object_unref (self); + return ret; +} + +static GstPadLinkReturn +gst_subtitle_overlay_subtitle_sink_link (GstPad * pad, GstPad * peer) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad)); + GstPadLinkReturn ret; + GstCaps *caps; + + GST_DEBUG_OBJECT (pad, "Linking pad to peer %" GST_PTR_FORMAT, peer); + + caps = gst_pad_get_negotiated_caps (peer); + if (!caps) { + caps = gst_pad_get_caps (peer); + if (!gst_caps_is_fixed (caps)) { + gst_caps_unref (caps); + caps = NULL; + } + } + + if (caps) { + GST_SUBTITLE_OVERLAY_LOCK (self); + GST_DEBUG_OBJECT (pad, "Have fixed peer caps: %" GST_PTR_FORMAT, caps); + gst_caps_replace (&self->subcaps, caps); + + gst_pad_set_blocked_async_full (self->subtitle_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + + gst_pad_set_blocked_async_full (self->video_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + gst_caps_unref (caps); + } + + ret = self->subtitle_sink_link (pad, peer); + + gst_object_unref (self); + return ret; +} + +static void +gst_subtitle_overlay_subtitle_sink_unlink (GstPad * pad) +{ + GstSubtitleOverlay *self = + GST_SUBTITLE_OVERLAY (gst_object_ref (GST_PAD_PARENT (pad))); + + /* FIXME: Can't use gst_pad_get_parent() here because this is called with + * the object lock from state changes + */ + + GST_DEBUG_OBJECT (pad, "Pad unlinking"); + gst_caps_replace (&self->subcaps, NULL); + + self->subtitle_sink_unlink (pad); + + GST_SUBTITLE_OVERLAY_LOCK (self); + if (self->subtitle_block_pad) + gst_pad_set_blocked_async_full (self->subtitle_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + + if (self->video_block_pad) + gst_pad_set_blocked_async_full (self->video_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + + gst_object_unref (self); +} + +static gboolean +gst_subtitle_overlay_subtitle_sink_event (GstPad * pad, GstEvent * event) +{ + GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad)); + gboolean ret; + gboolean is_newsegment = FALSE; + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + gboolean update; + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, position; + + GST_DEBUG_OBJECT (pad, "Newsegment event: %" GST_PTR_FORMAT, + event->structure); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + GST_DEBUG_OBJECT (pad, "Old subtitle segment: %" GST_SEGMENT_FORMAT, + &self->subtitle_segment); + if (self->subtitle_segment.format != format) { + GST_DEBUG_OBJECT (pad, "Subtitle segment format changed: %s -> %s", + gst_format_get_name (self->subtitle_segment.format), + gst_format_get_name (format)); + gst_segment_init (&self->subtitle_segment, format); + } + + gst_segment_set_newsegment_full (&self->subtitle_segment, update, rate, + applied_rate, format, start, stop, position); + GST_DEBUG_OBJECT (pad, "New subtitle segment: %" GST_SEGMENT_FORMAT, + &self->subtitle_segment); + + is_newsegment = TRUE; + } else if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB && + event->structure + && strcmp (gst_structure_get_name (event->structure), + "subtitleoverlay-flush-subtitle") == 0) { + GST_DEBUG_OBJECT (pad, "Custom subtitle flush event"); + GST_SUBTITLE_OVERLAY_LOCK (self); + self->subtitle_flush = TRUE; + if (self->subtitle_block_pad) + gst_pad_set_blocked_async_full (self->subtitle_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + if (self->video_block_pad) + gst_pad_set_blocked_async_full (self->video_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_SUBTITLE_OVERLAY_UNLOCK (self); + + gst_event_unref (event); + event = NULL; + ret = TRUE; + goto out; + } + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + GST_DEBUG_OBJECT (pad, + "Resetting subtitle segment because of flush-stop"); + gst_segment_init (&self->subtitle_segment, GST_FORMAT_UNDEFINED); + /* fall through */ + case GST_EVENT_FLUSH_START: + case GST_EVENT_NEWSEGMENT: + case GST_EVENT_EOS: + /* Add our event marker to make sure no events from here go ever outside + * the element, they're only interesting for our internal elements */ + event = + GST_EVENT_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST + (event))); + if (!event->structure) { + event->structure = + gst_structure_id_empty_new (_subtitle_overlay_event_marker_id); + gst_structure_set_parent_refcount (event->structure, + &event->mini_object.refcount); + } + gst_structure_id_set (event->structure, _subtitle_overlay_event_marker_id, + G_TYPE_BOOLEAN, TRUE, NULL); + break; + default: + break; + } + + if (is_newsegment) + self->subtitle_segment_pending = TRUE; + ret = self->subtitle_sink_event (pad, event); + self->subtitle_segment_pending = FALSE; + +out: + gst_object_unref (self); + return ret; +} + +static void +gst_subtitle_overlay_init (GstSubtitleOverlay * self, + GstSubtitleOverlayClass * klass) +{ + GstPadTemplate *templ; + GstIterator *it; + GstPad *proxypad = NULL; + + self->lock = g_mutex_new (); + + templ = gst_static_pad_template_get (&srctemplate); + self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ); + it = gst_pad_iterate_internal_links (self->srcpad); + if (G_UNLIKELY (!it + || gst_iterator_next (it, (gpointer) & proxypad) != GST_ITERATOR_OK + || proxypad == NULL)) { + GST_ERROR_OBJECT (self, "Failed to get proxypad of srcpad"); + } else { + self->src_proxy_event = GST_PAD_EVENTFUNC (proxypad); + gst_pad_set_event_function (proxypad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_src_proxy_event)); + gst_object_unref (proxypad); + } + if (it) + gst_iterator_free (it); + + gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad); + + templ = gst_static_pad_template_get (&video_sinktemplate); + self->video_sinkpad = + gst_ghost_pad_new_no_target_from_template ("video_sink", templ); + self->video_sink_event = GST_PAD_EVENTFUNC (self->video_sinkpad); + gst_pad_set_event_function (self->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_event)); + + proxypad = NULL; + it = gst_pad_iterate_internal_links (self->video_sinkpad); + if (G_UNLIKELY (!it + || gst_iterator_next (it, (gpointer) & proxypad) != GST_ITERATOR_OK + || proxypad == NULL)) { + GST_ERROR_OBJECT (self, + "Failed to get internally linked pad from video sinkpad"); + } + if (it) + gst_iterator_free (it); + self->video_block_pad = proxypad; + gst_element_add_pad (GST_ELEMENT_CAST (self), self->video_sinkpad); + + templ = gst_static_pad_template_get (&subtitle_sinktemplate); + self->subtitle_sinkpad = + gst_ghost_pad_new_no_target_from_template ("subtitle_sink", templ); + self->subtitle_sink_link = GST_PAD_LINKFUNC (self->subtitle_sinkpad); + gst_pad_set_link_function (self->subtitle_sinkpad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_link)); + self->subtitle_sink_unlink = GST_PAD_UNLINKFUNC (self->subtitle_sinkpad); + gst_pad_set_unlink_function (self->subtitle_sinkpad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_unlink)); + self->subtitle_sink_event = GST_PAD_EVENTFUNC (self->subtitle_sinkpad); + gst_pad_set_event_function (self->subtitle_sinkpad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_event)); + self->subtitle_sink_setcaps = GST_PAD_SETCAPSFUNC (self->subtitle_sinkpad); + gst_pad_set_setcaps_function (self->subtitle_sinkpad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_setcaps)); + gst_pad_set_getcaps_function (self->subtitle_sinkpad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_getcaps)); + gst_pad_set_acceptcaps_function (self->subtitle_sinkpad, + GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_acceptcaps)); + gst_pad_set_bufferalloc_function (self->subtitle_sinkpad, NULL); + + proxypad = NULL; + it = gst_pad_iterate_internal_links (self->subtitle_sinkpad); + if (G_UNLIKELY (!it + || gst_iterator_next (it, (gpointer) & proxypad) != GST_ITERATOR_OK + || proxypad == NULL)) { + GST_ERROR_OBJECT (self, + "Failed to get internally linked pad from subtitle sinkpad"); + } + if (it) + gst_iterator_free (it); + self->subtitle_block_pad = proxypad; + + gst_element_add_pad (GST_ELEMENT_CAST (self), self->subtitle_sinkpad); +} + +gboolean +gst_subtitle_overlay_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (subtitle_overlay_debug, "subtitleoverlay", 0, + "Subtitle Overlay"); + + _subtitle_overlay_event_marker_id = + g_quark_from_static_string ("gst-subtitle-overlay-event-marker"); + + return gst_element_register (plugin, "subtitleoverlay", GST_RANK_NONE, + GST_TYPE_SUBTITLE_OVERLAY); +} diff --git a/gst/playback/gstsubtitleoverlay.h b/gst/playback/gstsubtitleoverlay.h new file mode 100644 index 0000000..8d1fe05 --- /dev/null +++ b/gst/playback/gstsubtitleoverlay.h @@ -0,0 +1,116 @@ +/* GStreamer + * Copyright (C) 2009 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_SUBTITLE_OVERLAY_H__ +#define __GST_SUBTITLE_OVERLAY_H__ + +#include + +G_BEGIN_DECLS +#define GST_TYPE_SUBTITLE_OVERLAY \ + (gst_subtitle_overlay_get_type()) +#define GST_SUBTITLE_OVERLAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_SUBTITLE_OVERLAY, GstSubtitleOverlay)) +#define GST_SUBTITLE_OVERLAY_CAST(obj) \ + ((GstSubtitleOverlay *) obj) +#define GST_SUBTITLE_OVERLAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_SUBTITLE_OVERLAY, GstSubtitleOverlayClass)) +#define GST_IS_SUBTITLE_OVERLAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_SUBTITLE_OVERLAY)) +#define GST_IS_SUBTITLE_OVERLAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_SUBTITLE_OVERLAY)) + +#define GST_SUBTITLE_OVERLAY_LOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "locking from thread %p", \ + g_thread_self ()); \ + g_mutex_lock (GST_SUBTITLE_OVERLAY_CAST(obj)->lock); \ + GST_LOG_OBJECT (obj, \ + "locked from thread %p", \ + g_thread_self ()); \ +} G_STMT_END + +#define GST_SUBTITLE_OVERLAY_UNLOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "unlocking from thread %p", \ + g_thread_self ()); \ + g_mutex_unlock (GST_SUBTITLE_OVERLAY_CAST(obj)->lock); \ +} G_STMT_END + +typedef struct _GstSubtitleOverlay GstSubtitleOverlay; +typedef struct _GstSubtitleOverlayClass GstSubtitleOverlayClass; + +struct _GstSubtitleOverlay +{ + GstBin parent; + + gchar *font_desc; + + gboolean do_async; + + GstPad *srcpad; + GstPadEventFunction src_proxy_event; + + GstPad *video_sinkpad; + GstPad *video_block_pad; + GstPadEventFunction video_sink_event; + gboolean video_sink_blocked; + GstSegment video_segment; + gboolean video_segment_pending; + + GstPad *subtitle_sinkpad; + GstPad *subtitle_block_pad; + GstPadLinkFunction subtitle_sink_link; + GstPadUnlinkFunction subtitle_sink_unlink; + GstPadEventFunction subtitle_sink_event; + GstPadSetCapsFunction subtitle_sink_setcaps; + gboolean subtitle_sink_blocked; + GstSegment subtitle_segment; + gboolean subtitle_segment_pending; + gboolean subtitle_flush; + + GList *factories; + GstCaps *factory_caps; + + GMutex *lock; + GstCaps *subcaps; + + GstElement *passthrough_identity; + + GstElement *pre_colorspace; + GstElement *post_colorspace; + + GstElement *parser; + GstElement *overlay; + + GstElement *renderer; +}; + +struct _GstSubtitleOverlayClass +{ + GstBinClass parent; +}; + +GType gst_subtitle_overlay_get_type (void); +gboolean gst_subtitle_overlay_plugin_init (GstPlugin * plugin); + +GstCaps *gst_subtitle_overlay_create_factory_caps (void); + +G_END_DECLS +#endif /* __GST_SUBTITLE_OVERLAY_H__ */ -- 2.7.4