| /* |
| * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:element-subtitleoverlay |
| * @title: 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-1.0 -v filesrc location=test.mkv ! matroskademux name=demux ! video/x-h264 ! queue ! decodebin ! subtitleoverlay name=overlay ! videoconvert ! autovideosink demux. ! subpicture/x-dvd ! queue ! overlay. |
| * ]| |
| * This will play back the given Matroska file with h264 video and dvd subpicture style subtitles. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstsubtitleoverlay.h" |
| |
| #include <gst/pbutils/missing-plugins.h> |
| #include <gst/video/video.h> |
| #include <string.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (subtitle_overlay_debug); |
| #define GST_CAT_DEFAULT subtitle_overlay_debug |
| |
| #define IS_SUBTITLE_CHAIN_IGNORE_ERROR(flow) \ |
| G_UNLIKELY (flow == GST_FLOW_ERROR || flow == GST_FLOW_NOT_NEGOTIATED) |
| |
| #define IS_VIDEO_CHAIN_IGNORE_ERROR(flow) \ |
| G_UNLIKELY (flow == GST_FLOW_ERROR) |
| |
| static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| static GstStaticPadTemplate video_sinktemplate = |
| GST_STATIC_PAD_TEMPLATE ("video_sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| static GstStaticPadTemplate subtitle_sinktemplate = |
| GST_STATIC_PAD_TEMPLATE ("subtitle_sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| enum |
| { |
| PROP_0, |
| PROP_SILENT, |
| PROP_FONT_DESC, |
| PROP_SUBTITLE_ENCODING |
| }; |
| |
| #define gst_subtitle_overlay_parent_class parent_class |
| G_DEFINE_TYPE (GstSubtitleOverlay, gst_subtitle_overlay, GST_TYPE_BIN); |
| |
| 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)); |
| |
| GST_DEBUG_OBJECT (self, "Posting async-start"); |
| GST_BIN_CLASS (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_CLOCK_TIME_NONE); |
| |
| GST_DEBUG_OBJECT (self, "Posting async-done"); |
| GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (self), msg); |
| self->do_async = FALSE; |
| } |
| } |
| |
| static GstPadProbeReturn |
| _pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data); |
| |
| static void |
| block_video (GstSubtitleOverlay * self) |
| { |
| if (self->video_block_id != 0) |
| return; |
| |
| if (self->video_block_pad) { |
| self->video_block_id = |
| gst_pad_add_probe (self->video_block_pad, |
| GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, _pad_blocked_cb, self, NULL); |
| } |
| } |
| |
| static void |
| unblock_video (GstSubtitleOverlay * self) |
| { |
| if (self->video_block_id) { |
| gst_pad_remove_probe (self->video_block_pad, self->video_block_id); |
| self->video_sink_blocked = FALSE; |
| self->video_block_id = 0; |
| } |
| } |
| |
| static void |
| block_subtitle (GstSubtitleOverlay * self) |
| { |
| if (self->subtitle_block_id != 0) |
| return; |
| |
| if (self->subtitle_block_pad) { |
| self->subtitle_block_id = |
| gst_pad_add_probe (self->subtitle_block_pad, |
| GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, _pad_blocked_cb, self, NULL); |
| } |
| } |
| |
| static void |
| unblock_subtitle (GstSubtitleOverlay * self) |
| { |
| if (self->subtitle_block_id) { |
| gst_pad_remove_probe (self->subtitle_block_pad, self->subtitle_block_id); |
| self->subtitle_sink_blocked = FALSE; |
| self->subtitle_block_id = 0; |
| } |
| } |
| |
| static gboolean |
| pad_supports_caps (GstPad * pad, GstCaps * caps) |
| { |
| GstCaps *pad_caps; |
| gboolean ret = FALSE; |
| |
| pad_caps = gst_pad_query_caps (pad, NULL); |
| if (gst_caps_is_subset (caps, pad_caps)) |
| ret = TRUE; |
| gst_caps_unref (pad_caps); |
| |
| return ret; |
| } |
| |
| static void |
| gst_subtitle_overlay_finalize (GObject * object) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (object); |
| |
| g_mutex_clear (&self->lock); |
| g_mutex_clear (&self->factories_lock); |
| |
| if (self->factories) |
| gst_plugin_feature_list_free (self->factories); |
| self->factories = NULL; |
| gst_caps_replace (&self->factory_caps, NULL); |
| |
| if (self->font_desc) { |
| g_free (self->font_desc); |
| self->font_desc = NULL; |
| } |
| |
| if (self->encoding) { |
| g_free (self->encoding); |
| self->encoding = NULL; |
| } |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static gboolean |
| _is_renderer (GstElementFactory * factory) |
| { |
| const gchar *klass, *name; |
| |
| klass = |
| gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS); |
| name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)); |
| |
| if (klass != NULL) { |
| 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_metadata (factory, GST_ELEMENT_METADATA_KLASS); |
| |
| if (klass != NULL && strstr (klass, "Parser/Subtitle") != NULL) |
| return TRUE; |
| return FALSE; |
| } |
| |
| static const gchar *const _sub_pad_names[] = { "subpicture", "subpicture_sink", |
| "text", "text_sink", |
| "subtitle_sink", "subtitle" |
| }; |
| |
| static gboolean |
| _is_video_pad (GstPad * pad, gboolean * hw_accelerated) |
| { |
| GstPad *peer = gst_pad_get_peer (pad); |
| GstCaps *caps; |
| gboolean ret = FALSE; |
| const gchar *name; |
| guint i; |
| |
| if (peer) { |
| caps = gst_pad_get_current_caps (peer); |
| if (!caps) { |
| caps = gst_pad_query_caps (peer, NULL); |
| } |
| gst_object_unref (peer); |
| } else { |
| caps = gst_pad_query_caps (pad, NULL); |
| } |
| |
| for (i = 0; i < gst_caps_get_size (caps) && !ret; i++) { |
| name = gst_structure_get_name (gst_caps_get_structure (caps, i)); |
| if (g_str_equal (name, "video/x-raw")) { |
| ret = TRUE; |
| if (hw_accelerated) |
| *hw_accelerated = FALSE; |
| |
| } else if (g_str_has_prefix (name, "video/x-surface")) { |
| ret = TRUE; |
| if (hw_accelerated) |
| *hw_accelerated = TRUE; |
| } else { |
| |
| ret = FALSE; |
| if (hw_accelerated) |
| *hw_accelerated = FALSE; |
| } |
| } |
| |
| gst_caps_unref (caps); |
| |
| return ret; |
| } |
| |
| 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) { |
| GST_DEBUG ("Found renderer element %s (%s) with caps %" GST_PTR_FORMAT, |
| gst_element_factory_get_metadata (factory, |
| GST_ELEMENT_METADATA_LONGNAME), |
| gst_plugin_feature_get_name (feature), templ_caps); |
| *subcaps = gst_caps_merge (*subcaps, templ_caps); |
| return TRUE; |
| } else if (!is_renderer && !have_video_sink && templ_caps) { |
| GST_DEBUG ("Found parser element %s (%s) with caps %" GST_PTR_FORMAT, |
| gst_element_factory_get_metadata (factory, |
| GST_ELEMENT_METADATA_LONGNAME), |
| gst_plugin_feature_get_name (feature), templ_caps); |
| *subcaps = gst_caps_merge (*subcaps, templ_caps); |
| return TRUE; |
| } else { |
| if (templ_caps) |
| gst_caps_unref (templ_caps); |
| return FALSE; |
| } |
| } |
| |
| /* Call with factories_lock! */ |
| static gboolean |
| gst_subtitle_overlay_update_factory_list (GstSubtitleOverlay * self) |
| { |
| GstRegistry *registry; |
| guint cookie; |
| |
| registry = gst_registry_get (); |
| cookie = gst_registry_get_feature_list_cookie (registry); |
| if (!self->factories || self->factories_cookie != cookie) { |
| GstCaps *subcaps; |
| GList *factories; |
| |
| subcaps = gst_caps_new_empty (); |
| |
| factories = gst_registry_feature_filter (registry, |
| (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); |
| if (self->factories) |
| gst_plugin_feature_list_free (self->factories); |
| self->factories = factories; |
| self->factories_cookie = cookie; |
| } |
| |
| return (self->factories != NULL); |
| } |
| |
| G_LOCK_DEFINE_STATIC (_factory_caps); |
| static GstCaps *_factory_caps = NULL; |
| static guint32 _factory_caps_cookie = 0; |
| |
| GstCaps * |
| gst_subtitle_overlay_create_factory_caps (void) |
| { |
| GstRegistry *registry; |
| GList *factories; |
| GstCaps *subcaps = NULL; |
| guint cookie; |
| |
| registry = gst_registry_get (); |
| cookie = gst_registry_get_feature_list_cookie (registry); |
| G_LOCK (_factory_caps); |
| if (!_factory_caps || _factory_caps_cookie != cookie) { |
| if (_factory_caps) |
| gst_caps_unref (_factory_caps); |
| _factory_caps = gst_caps_new_empty (); |
| |
| /* The caps is cached */ |
| GST_MINI_OBJECT_FLAG_SET (_factory_caps, |
| GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); |
| |
| factories = gst_registry_feature_filter (registry, |
| (GstPluginFeatureFilter) _factory_filter, FALSE, &_factory_caps); |
| GST_DEBUG ("Created factory caps: %" GST_PTR_FORMAT, _factory_caps); |
| gst_plugin_feature_list_free (factories); |
| _factory_caps_cookie = cookie; |
| } |
| subcaps = gst_caps_ref (_factory_caps); |
| G_UNLOCK (_factory_caps); |
| |
| return subcaps; |
| } |
| |
| static gboolean |
| check_factory_for_caps (GstElementFactory * factory, const GstCaps * caps) |
| { |
| GstCaps *fcaps = _get_sub_caps (factory); |
| gboolean ret = (fcaps) ? gst_caps_is_subset (caps, fcaps) : FALSE; |
| |
| if (fcaps) |
| gst_caps_unref (fcaps); |
| |
| if (ret) |
| gst_object_ref (factory); |
| return ret; |
| } |
| |
| static GList * |
| gst_subtitle_overlay_get_factories_for_caps (const GList * list, |
| const GstCaps * caps) |
| { |
| const GList *walk = list; |
| GList *result = NULL; |
| |
| while (walk) { |
| GstElementFactory *factory = walk->data; |
| |
| walk = g_list_next (walk); |
| |
| if (check_factory_for_caps (factory, caps)) { |
| result = g_list_prepend (result, factory); |
| } |
| } |
| |
| return result; |
| } |
| |
| 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 *const 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 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); |
| self->silent_property = 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; |
| } |
| |
| /* 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 */ |
| unblock_video (self); |
| unblock_subtitle (self); |
| |
| return TRUE; |
| } |
| |
| /* Must be called with subtitleoverlay lock! */ |
| static gboolean |
| _has_property_with_type (GObject * object, const gchar * property, GType type) |
| { |
| GObjectClass *gobject_class; |
| GParamSpec *pspec; |
| |
| gobject_class = G_OBJECT_GET_CLASS (object); |
| pspec = g_object_class_find_property (gobject_class, property); |
| return (pspec && pspec->value_type == type); |
| } |
| |
| static void |
| gst_subtitle_overlay_set_fps (GstSubtitleOverlay * self) |
| { |
| if (!self->parser || self->fps_d == 0) |
| return; |
| |
| if (!_has_property_with_type (G_OBJECT (self->parser), "video-fps", |
| GST_TYPE_FRACTION)) |
| return; |
| |
| GST_DEBUG_OBJECT (self, "Updating video-fps property in parser"); |
| g_object_set (self->parser, "video-fps", self->fps_n, self->fps_d, NULL); |
| } |
| |
| static const gchar * |
| _get_silent_property (GstElement * element, gboolean * invert) |
| { |
| static const struct |
| { |
| const gchar *name; |
| gboolean invert; |
| } properties[] = { { |
| "silent", FALSE}, { |
| "enable", TRUE}}; |
| guint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (properties); i++) { |
| if (_has_property_with_type (G_OBJECT (element), properties[i].name, |
| G_TYPE_BOOLEAN)) { |
| *invert = properties[i].invert; |
| return properties[i].name; |
| } |
| } |
| return NULL; |
| } |
| |
| static gboolean |
| _setup_parser (GstSubtitleOverlay * self) |
| { |
| GstPad *video_peer; |
| |
| /* Try to get the latest video framerate */ |
| video_peer = gst_pad_get_peer (self->video_sinkpad); |
| if (video_peer) { |
| GstCaps *video_caps; |
| gint fps_n, fps_d; |
| |
| video_caps = gst_pad_get_current_caps (video_peer); |
| if (!video_caps) { |
| video_caps = gst_pad_query_caps (video_peer, NULL); |
| if (!gst_caps_is_fixed (video_caps)) { |
| gst_caps_unref (video_caps); |
| video_caps = NULL; |
| } |
| } |
| |
| if (video_caps) { |
| GstStructure *st = gst_caps_get_structure (video_caps, 0); |
| if (gst_structure_get_fraction (st, "framerate", &fps_n, &fps_d)) { |
| GST_DEBUG_OBJECT (self, "New video fps: %d/%d", fps_n, fps_d); |
| self->fps_n = fps_n; |
| self->fps_d = fps_d; |
| } |
| } |
| |
| if (video_caps) |
| gst_caps_unref (video_caps); |
| gst_object_unref (video_peer); |
| } |
| |
| if (_has_property_with_type (G_OBJECT (self->parser), "subtitle-encoding", |
| G_TYPE_STRING)) |
| g_object_set (self->parser, "subtitle-encoding", self->encoding, NULL); |
| |
| /* Try to set video fps on the parser */ |
| gst_subtitle_overlay_set_fps (self); |
| |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| _setup_renderer (GstSubtitleOverlay * self, GstElement * renderer) |
| { |
| GstElementFactory *factory = gst_element_get_factory (renderer); |
| const gchar *name = |
| gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)); |
| |
| if (strcmp (name, "textoverlay") == 0) { |
| /* Set some textoverlay specific properties */ |
| gst_util_set_object_arg (G_OBJECT (renderer), "halignment", "center"); |
| gst_util_set_object_arg (G_OBJECT (renderer), "valignment", "bottom"); |
| g_object_set (G_OBJECT (renderer), "wait-text", FALSE, NULL); |
| if (self->font_desc) |
| g_object_set (G_OBJECT (renderer), "font-desc", self->font_desc, NULL); |
| self->silent_property = "silent"; |
| self->silent_property_invert = FALSE; |
| } else { |
| self->silent_property = |
| _get_silent_property (renderer, &self->silent_property_invert); |
| if (_has_property_with_type (G_OBJECT (renderer), "subtitle-encoding", |
| G_TYPE_STRING)) |
| g_object_set (renderer, "subtitle-encoding", self->encoding, NULL); |
| if (_has_property_with_type (G_OBJECT (renderer), "font-desc", |
| G_TYPE_STRING)) |
| g_object_set (renderer, "font-desc", self->font_desc, NULL); |
| } |
| |
| return TRUE; |
| } |
| |
| /* subtitle_src==NULL means: use subtitle_sink ghostpad */ |
| static gboolean |
| _link_renderer (GstSubtitleOverlay * self, GstElement * renderer, |
| GstPad * subtitle_src) |
| { |
| GstPad *sink, *src; |
| gboolean is_video, is_hw; |
| |
| is_video = _is_video_pad (self->video_sinkpad, &is_hw); |
| |
| if (is_video) { |
| gboolean render_is_hw; |
| |
| /* First check that renderer also supports the video format */ |
| sink = _get_video_pad (renderer); |
| if (G_UNLIKELY (!sink)) { |
| GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); |
| return FALSE; |
| } |
| |
| if (is_video != _is_video_pad (sink, &render_is_hw) || |
| is_hw != render_is_hw) { |
| GST_DEBUG_OBJECT (self, "Renderer doesn't support %s video", |
| is_hw ? "surface" : "raw"); |
| gst_object_unref (sink); |
| return FALSE; |
| } |
| gst_object_unref (sink); |
| |
| if (!is_hw) { |
| /* First link everything internally */ |
| if (G_UNLIKELY (!_create_element (self, &self->post_colorspace, |
| COLORSPACE, NULL, "post-colorspace", FALSE))) { |
| return FALSE; |
| } |
| src = gst_element_get_static_pad (renderer, "src"); |
| if (G_UNLIKELY (!src)) { |
| GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); |
| return FALSE; |
| } |
| |
| sink = gst_element_get_static_pad (self->post_colorspace, "sink"); |
| if (G_UNLIKELY (!sink)) { |
| GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); |
| gst_object_unref (src); |
| return FALSE; |
| } |
| |
| if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { |
| GST_WARNING_OBJECT (self, "Can't link renderer with " COLORSPACE); |
| gst_object_unref (src); |
| gst_object_unref (sink); |
| return FALSE; |
| } |
| gst_object_unref (src); |
| gst_object_unref (sink); |
| |
| if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace, |
| COLORSPACE, NULL, "pre-colorspace", FALSE))) { |
| return FALSE; |
| } |
| |
| sink = _get_video_pad (renderer); |
| if (G_UNLIKELY (!sink)) { |
| GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); |
| return FALSE; |
| } |
| |
| src = gst_element_get_static_pad (self->pre_colorspace, "src"); |
| if (G_UNLIKELY (!src)) { |
| GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE); |
| gst_object_unref (sink); |
| return FALSE; |
| } |
| |
| if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { |
| GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to renderer"); |
| gst_object_unref (src); |
| gst_object_unref (sink); |
| return FALSE; |
| } |
| 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 " COLORSPACE); |
| return FALSE; |
| } |
| } else { |
| /* Set src ghostpad target in the harware accelerated case */ |
| |
| src = gst_element_get_static_pad (renderer, "src"); |
| if (G_UNLIKELY (!src)) { |
| GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); |
| return FALSE; |
| } |
| } |
| } else { /* No video pad */ |
| GstCaps *allowed_caps, *video_caps = NULL; |
| GstPad *video_peer; |
| gboolean is_subset = FALSE; |
| |
| video_peer = gst_pad_get_peer (self->video_sinkpad); |
| if (video_peer) { |
| video_caps = gst_pad_get_current_caps (video_peer); |
| if (!video_caps) { |
| video_caps = gst_pad_query_caps (video_peer, NULL); |
| } |
| gst_object_unref (video_peer); |
| } |
| |
| sink = _get_video_pad (renderer); |
| if (G_UNLIKELY (!sink)) { |
| GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); |
| if (video_caps) |
| gst_caps_unref (video_caps); |
| return FALSE; |
| } |
| allowed_caps = gst_pad_query_caps (sink, NULL); |
| gst_object_unref (sink); |
| |
| if (allowed_caps && video_caps) |
| is_subset = gst_caps_is_subset (video_caps, allowed_caps); |
| |
| if (allowed_caps) |
| gst_caps_unref (allowed_caps); |
| |
| if (video_caps) |
| gst_caps_unref (video_caps); |
| |
| if (G_UNLIKELY (!is_subset)) { |
| GST_WARNING_OBJECT (self, "Renderer with custom caps is not " |
| "compatible with video stream"); |
| return FALSE; |
| } |
| |
| src = gst_element_get_static_pad (renderer, "src"); |
| if (G_UNLIKELY (!src)) { |
| GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); |
| return FALSE; |
| } |
| } |
| |
| 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); |
| return FALSE; |
| } |
| gst_object_unref (src); |
| |
| /* Set the sink ghostpad targets */ |
| if (self->pre_colorspace) { |
| sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); |
| if (G_UNLIKELY (!sink)) { |
| GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); |
| return FALSE; |
| } |
| } else { |
| sink = _get_video_pad (renderer); |
| if (G_UNLIKELY (!sink)) { |
| GST_WARNING_OBJECT (self, "Can't get sink pad from %" GST_PTR_FORMAT, |
| renderer); |
| return FALSE; |
| } |
| } |
| |
| 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); |
| return FALSE; |
| } |
| gst_object_unref (sink); |
| |
| sink = _get_sub_pad (renderer); |
| if (G_UNLIKELY (!sink)) { |
| GST_WARNING_OBJECT (self, "Failed to get subpad"); |
| return FALSE; |
| } |
| |
| if (subtitle_src) { |
| if (G_UNLIKELY (gst_pad_link (subtitle_src, sink) != GST_PAD_LINK_OK)) { |
| GST_WARNING_OBJECT (self, "Failed to link subtitle srcpad with renderer"); |
| gst_object_unref (sink); |
| return FALSE; |
| } |
| } else { |
| 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); |
| return FALSE; |
| } |
| } |
| gst_object_unref (sink); |
| |
| return TRUE; |
| } |
| |
| static GstPadProbeReturn |
| _pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (user_data); |
| GstCaps *subcaps; |
| GList *l, *factories = NULL; |
| |
| if (GST_IS_EVENT (info->data)) { |
| if (!GST_EVENT_IS_SERIALIZED (info->data)) { |
| GST_DEBUG_OBJECT (pad, "Letting non-serialized event %s pass", |
| GST_EVENT_TYPE_NAME (info->data)); |
| return GST_PAD_PROBE_PASS; |
| } |
| if (GST_EVENT_TYPE (info->data) == GST_EVENT_STREAM_START) { |
| GST_DEBUG_OBJECT (pad, "Letting event %s pass", |
| GST_EVENT_TYPE_NAME (info->data)); |
| return GST_PAD_PROBE_PASS; |
| } |
| } |
| |
| GST_DEBUG_OBJECT (pad, "Pad blocked"); |
| |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| if (pad == self->video_block_pad) |
| self->video_sink_blocked = TRUE; |
| else if (pad == self->subtitle_block_pad) |
| self->subtitle_sink_blocked = TRUE; |
| |
| /* 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_current_caps (peer); |
| if (!subcaps) { |
| subcaps = gst_pad_query_caps (peer, NULL); |
| 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 after stream-start */ |
| 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; |
| } |
| |
| if (self->subtitle_error || (self->silent && !self->silent_property)) { |
| _setup_passthrough (self); |
| do_async_done (self); |
| goto out; |
| } |
| |
| /* 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 && pad_supports_caps (target, subcaps)) { |
| GST_DEBUG_OBJECT (pad, "Target accepts caps"); |
| |
| gst_object_unref (target); |
| |
| /* Unblock pads */ |
| unblock_video (self); |
| unblock_subtitle (self); |
| 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"); |
| block_video (self); |
| goto out; |
| } |
| |
| self->subtitle_flush = FALSE; |
| |
| /* Find our factories */ |
| g_mutex_lock (&self->factories_lock); |
| gst_subtitle_overlay_update_factory_list (self); |
| if (subcaps) { |
| factories = |
| gst_subtitle_overlay_get_factories_for_caps (self->factories, 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; |
| self->subtitle_error = TRUE; |
| } |
| } |
| g_mutex_unlock (&self->factories_lock); |
| |
| 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); |
| 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); |
| self->silent_property = 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; |
| |
| if (!is_renderer) { |
| GstCaps *parser_caps; |
| GList *overlay_factories, *k; |
| |
| if (!_setup_parser (self)) |
| continue; |
| |
| /* Find our factories */ |
| src = gst_element_get_static_pad (self->parser, "src"); |
| parser_caps = gst_pad_query_caps (src, NULL); |
| gst_object_unref (src); |
| |
| g_assert (parser_caps != NULL); |
| |
| g_mutex_lock (&self->factories_lock); |
| gst_subtitle_overlay_update_factory_list (self); |
| GST_DEBUG_OBJECT (self, |
| "Searching overlay factories for caps %" GST_PTR_FORMAT, parser_caps); |
| overlay_factories = |
| gst_subtitle_overlay_get_factories_for_caps (self->factories, |
| parser_caps); |
| g_mutex_unlock (&self->factories_lock); |
| |
| if (!overlay_factories) { |
| GST_WARNING_OBJECT (self, |
| "Found no suitable overlay factories for caps %" GST_PTR_FORMAT, |
| parser_caps); |
| gst_caps_unref (parser_caps); |
| continue; |
| } |
| gst_caps_unref (parser_caps); |
| |
| /* Sort the factories by rank */ |
| overlay_factories = |
| g_list_sort (overlay_factories, (GCompareFunc) _sort_by_ranks); |
| |
| for (k = overlay_factories; k; k = k->next) { |
| GstElementFactory *overlay_factory = k->data; |
| |
| GST_DEBUG_OBJECT (self, "Trying overlay factory '%s'", |
| GST_STR_NULL (gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST |
| (overlay_factory)))); |
| |
| /* Try this factory and link it, otherwise unlink everything |
| * again and remove the overlay. Up to this point only the |
| * parser was instantiated and setup, nothing was linked |
| */ |
| |
| 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); |
| self->silent_property = NULL; |
| _remove_element (self, &self->post_colorspace); |
| _remove_element (self, &self->overlay); |
| _remove_element (self, &self->pre_colorspace); |
| |
| if (!_create_element (self, &self->overlay, NULL, overlay_factory, |
| "overlay", FALSE)) { |
| GST_DEBUG_OBJECT (self, "Could not create element"); |
| continue; |
| } |
| |
| if (!_setup_renderer (self, self->overlay)) { |
| GST_DEBUG_OBJECT (self, "Could not setup element"); |
| continue; |
| } |
| |
| src = gst_element_get_static_pad (self->parser, "src"); |
| if (!_link_renderer (self, self->overlay, src)) { |
| GST_DEBUG_OBJECT (self, "Could not link element"); |
| gst_object_unref (src); |
| continue; |
| } |
| gst_object_unref (src); |
| |
| /* Everything done here, go out of loop */ |
| GST_DEBUG_OBJECT (self, "%s is a suitable element", |
| GST_STR_NULL (gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST |
| (overlay_factory)))); |
| break; |
| } |
| |
| if (overlay_factories) |
| gst_plugin_feature_list_free (overlay_factories); |
| |
| if (G_UNLIKELY (k == NULL)) { |
| GST_WARNING_OBJECT (self, "Failed to find usable overlay factory"); |
| continue; |
| } |
| |
| /* Now link subtitle sinkpad of the bin and the parser */ |
| sink = gst_element_get_static_pad (self->parser, "sink"); |
| if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST |
| (self->subtitle_sinkpad), sink)) { |
| gst_object_unref (sink); |
| continue; |
| } |
| gst_object_unref (sink); |
| |
| /* Everything done here, go out of loop */ |
| break; |
| } else { |
| /* Is renderer factory */ |
| |
| if (!_setup_renderer (self, self->renderer)) |
| continue; |
| |
| /* subtitle_src==NULL means: use subtitle_sink ghostpad */ |
| if (!_link_renderer (self, self->renderer, NULL)) |
| continue; |
| |
| /* Everything done here, go out of loop */ |
| break; |
| } |
| } |
| |
| if (G_UNLIKELY (l == NULL)) { |
| GST_ELEMENT_WARNING (self, CORE, FAILED, (NULL), |
| ("Failed to find any usable factories")); |
| self->subtitle_error = TRUE; |
| _setup_passthrough (self); |
| do_async_done (self); |
| goto out; |
| } |
| |
| GST_DEBUG_OBJECT (self, "Everything worked, unblocking pads"); |
| unblock_video (self); |
| unblock_subtitle (self); |
| do_async_done (self); |
| |
| out: |
| if (factories) |
| gst_plugin_feature_list_free (factories); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| return GST_PAD_PROBE_OK; |
| } |
| |
| 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"); |
| g_mutex_lock (&self->factories_lock); |
| if (G_UNLIKELY (!gst_subtitle_overlay_update_factory_list (self))) { |
| g_mutex_unlock (&self->factories_lock); |
| return GST_STATE_CHANGE_FAILURE; |
| } |
| g_mutex_unlock (&self->factories_lock); |
| |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| /* Set the internal pads to blocking */ |
| block_video (self); |
| block_subtitle (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| break; |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| GST_DEBUG_OBJECT (self, "State change READY->PAUSED"); |
| |
| self->fps_n = self->fps_d = 0; |
| |
| self->subtitle_flush = FALSE; |
| self->subtitle_error = FALSE; |
| |
| self->downstream_chain_error = 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)) { |
| do_async_done (self); |
| 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"); |
| |
| /* Set the pads back to blocking state */ |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| block_video (self); |
| block_subtitle (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| do_async_done (self); |
| |
| break; |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| GST_DEBUG_OBJECT (self, "State change READY->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 */ |
| unblock_video (self); |
| unblock_subtitle (self); |
| |
| /* Remove elements */ |
| self->silent_property = 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_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_subtitle_overlay_handle_message (GstBin * bin, GstMessage * message) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (bin); |
| |
| if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) { |
| GstObject *src = GST_MESSAGE_SRC (message); |
| |
| /* Convert error messages from the subtitle pipeline to |
| * warnings and switch to passthrough mode */ |
| if (src && ( |
| (self->overlay |
| && gst_object_has_as_ancestor (src, |
| GST_OBJECT_CAST (self->overlay))) || (self->parser |
| && gst_object_has_as_ancestor (src, |
| GST_OBJECT_CAST (self->parser))) || (self->renderer |
| && gst_object_has_as_ancestor (src, |
| GST_OBJECT_CAST (self->renderer))))) { |
| GError *err = NULL; |
| gchar *debug = NULL; |
| GstMessage *wmsg; |
| |
| gst_message_parse_error (message, &err, &debug); |
| GST_DEBUG_OBJECT (self, |
| "Got error message from subtitle element %s: %s (%s)", |
| GST_MESSAGE_SRC_NAME (message), GST_STR_NULL (err->message), |
| GST_STR_NULL (debug)); |
| |
| wmsg = gst_message_new_warning (src, err, debug); |
| gst_message_unref (message); |
| g_error_free (err); |
| g_free (debug); |
| message = wmsg; |
| |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| self->subtitle_error = TRUE; |
| |
| block_subtitle (self); |
| block_video (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| } |
| } |
| |
| GST_BIN_CLASS (parent_class)->handle_message (bin, message); |
| } |
| |
| static void |
| gst_subtitle_overlay_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY_CAST (object); |
| |
| switch (prop_id) { |
| case PROP_SILENT: |
| g_value_set_boolean (value, self->silent); |
| break; |
| case PROP_FONT_DESC: |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| g_value_set_string (value, self->font_desc); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| break; |
| case PROP_SUBTITLE_ENCODING: |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| g_value_set_string (value, self->encoding); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| 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_SILENT: |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| self->silent = g_value_get_boolean (value); |
| if (self->silent_property) { |
| gboolean silent = self->silent; |
| |
| if (self->silent_property_invert) |
| silent = !silent; |
| |
| if (self->overlay) |
| g_object_set (self->overlay, self->silent_property, silent, NULL); |
| else if (self->renderer) |
| g_object_set (self->renderer, self->silent_property, silent, NULL); |
| } else { |
| block_subtitle (self); |
| block_video (self); |
| } |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| break; |
| case PROP_FONT_DESC: |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| g_free (self->font_desc); |
| self->font_desc = g_value_dup_string (value); |
| if (self->overlay |
| && _has_property_with_type (G_OBJECT (self->overlay), "font-desc", |
| G_TYPE_STRING)) |
| g_object_set (self->overlay, "font-desc", self->font_desc, NULL); |
| else if (self->renderer |
| && _has_property_with_type (G_OBJECT (self->renderer), "font-desc", |
| G_TYPE_STRING)) |
| g_object_set (self->renderer, "font-desc", self->font_desc, NULL); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| break; |
| case PROP_SUBTITLE_ENCODING: |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| g_free (self->encoding); |
| self->encoding = g_value_dup_string (value); |
| if (self->renderer |
| && _has_property_with_type (G_OBJECT (self->renderer), |
| "subtitle-encoding", G_TYPE_STRING)) |
| g_object_set (self->renderer, "subtitle-encoding", self->encoding, |
| NULL); |
| if (self->parser |
| && _has_property_with_type (G_OBJECT (self->parser), |
| "subtitle-encoding", G_TYPE_STRING)) |
| g_object_set (self->parser, "subtitle-encoding", self->encoding, NULL); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_subtitle_overlay_class_init (GstSubtitleOverlayClass * klass) |
| { |
| GObjectClass *gobject_class = (GObjectClass *) klass; |
| GstElementClass *element_class = (GstElementClass *) klass; |
| GstBinClass *bin_class = (GstBinClass *) klass; |
| |
| gobject_class->finalize = gst_subtitle_overlay_finalize; |
| gobject_class->set_property = gst_subtitle_overlay_set_property; |
| gobject_class->get_property = gst_subtitle_overlay_get_property; |
| |
| g_object_class_install_property (gobject_class, PROP_SILENT, |
| g_param_spec_boolean ("silent", |
| "Silent", |
| "Whether to show subtitles", FALSE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| 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_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_SUBTITLE_ENCODING, |
| g_param_spec_string ("subtitle-encoding", "subtitle encoding", |
| "Encoding to assume if input subtitles are not in UTF-8 encoding. " |
| "If not set, the GST_SUBTITLE_ENCODING environment variable will " |
| "be checked for an encoding to use. If that is not set either, " |
| "ISO-8859-15 will be assumed.", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_add_static_pad_template (element_class, &srctemplate); |
| |
| gst_element_class_add_static_pad_template (element_class, |
| &video_sinktemplate); |
| gst_element_class_add_static_pad_template (element_class, |
| &subtitle_sinktemplate); |
| |
| gst_element_class_set_static_metadata (element_class, "Subtitle Overlay", |
| "Video/Overlay/Subtitle", |
| "Overlays a video stream with subtitles", |
| "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); |
| |
| element_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_change_state); |
| |
| bin_class->handle_message = |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_handle_message); |
| } |
| |
| static GstFlowReturn |
| gst_subtitle_overlay_src_proxy_chain (GstPad * proxypad, GstObject * parent, |
| GstBuffer * buffer) |
| { |
| GstPad *ghostpad; |
| GstSubtitleOverlay *self; |
| GstFlowReturn ret; |
| |
| ghostpad = GST_PAD_CAST (parent); |
| if (G_UNLIKELY (!ghostpad)) { |
| gst_buffer_unref (buffer); |
| return GST_FLOW_ERROR; |
| } |
| self = GST_SUBTITLE_OVERLAY_CAST (gst_pad_get_parent (ghostpad)); |
| if (G_UNLIKELY (!self || self->srcpad != ghostpad)) { |
| gst_buffer_unref (buffer); |
| gst_object_unref (ghostpad); |
| return GST_FLOW_ERROR; |
| } |
| |
| ret = gst_proxy_pad_chain_default (proxypad, parent, buffer); |
| |
| if (IS_VIDEO_CHAIN_IGNORE_ERROR (ret)) { |
| GST_ERROR_OBJECT (self, "Downstream chain error: %s", |
| gst_flow_get_name (ret)); |
| self->downstream_chain_error = TRUE; |
| } |
| |
| gst_object_unref (self); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_subtitle_overlay_src_proxy_event (GstPad * proxypad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstPad *ghostpad = NULL; |
| GstSubtitleOverlay *self = NULL; |
| gboolean ret = FALSE; |
| const GstStructure *s; |
| |
| ghostpad = GST_PAD_CAST (parent); |
| 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, |
| gst_event_get_structure (event)); |
| gst_event_unref (event); |
| event = NULL; |
| ret = TRUE; |
| } else { |
| ret = gst_pad_event_default (proxypad, parent, event); |
| event = NULL; |
| } |
| |
| out: |
| if (event) |
| gst_event_unref (event); |
| if (self) |
| gst_object_unref (self); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_subtitle_overlay_video_sink_setcaps (GstSubtitleOverlay * self, |
| GstCaps * caps) |
| { |
| GstPad *target; |
| gboolean ret = TRUE; |
| GstVideoInfo info; |
| |
| GST_DEBUG_OBJECT (self, "Setting caps: %" GST_PTR_FORMAT, caps); |
| |
| if (!gst_video_info_from_caps (&info, caps)) { |
| GST_ERROR_OBJECT (self, "Failed to parse caps"); |
| ret = FALSE; |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| goto out; |
| } |
| |
| target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->video_sinkpad)); |
| |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| |
| if (!target || !pad_supports_caps (target, caps)) { |
| GST_DEBUG_OBJECT (target, "Target did not accept caps -- reconfiguring"); |
| |
| block_subtitle (self); |
| block_video (self); |
| } |
| |
| if (self->fps_n != info.fps_n || self->fps_d != info.fps_d) { |
| GST_DEBUG_OBJECT (self, "New video fps: %d/%d", info.fps_n, info.fps_d); |
| self->fps_n = info.fps_n; |
| self->fps_d = info.fps_d; |
| gst_subtitle_overlay_set_fps (self); |
| } |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| if (target) |
| gst_object_unref (target); |
| |
| out: |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_subtitle_overlay_video_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent); |
| gboolean ret; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| ret = gst_subtitle_overlay_video_sink_setcaps (self, caps); |
| if (!ret) |
| goto done; |
| break; |
| } |
| default: |
| break; |
| } |
| |
| ret = gst_pad_event_default (pad, parent, gst_event_ref (event)); |
| |
| done: |
| gst_event_unref (event); |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_subtitle_overlay_video_sink_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent); |
| GstFlowReturn ret = gst_proxy_pad_chain_default (pad, parent, buffer); |
| |
| if (G_UNLIKELY (self->downstream_chain_error) || self->passthrough_identity) { |
| return ret; |
| } else if (IS_VIDEO_CHAIN_IGNORE_ERROR (ret)) { |
| GST_DEBUG_OBJECT (self, "Subtitle renderer produced chain error: %s", |
| gst_flow_get_name (ret)); |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| self->subtitle_error = TRUE; |
| block_subtitle (self); |
| block_video (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| return GST_FLOW_OK; |
| } |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_subtitle_overlay_subtitle_sink_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent); |
| |
| if (self->subtitle_error) { |
| gst_buffer_unref (buffer); |
| return GST_FLOW_OK; |
| } else { |
| GstFlowReturn ret = gst_proxy_pad_chain_default (pad, parent, buffer); |
| |
| if (IS_SUBTITLE_CHAIN_IGNORE_ERROR (ret)) { |
| GST_DEBUG_OBJECT (self, "Subtitle chain error: %s", |
| gst_flow_get_name (ret)); |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| self->subtitle_error = TRUE; |
| block_subtitle (self); |
| block_video (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| return GST_FLOW_OK; |
| } |
| |
| return ret; |
| } |
| } |
| |
| static GstCaps * |
| gst_subtitle_overlay_subtitle_sink_getcaps (GstPad * pad, GstCaps * filter) |
| { |
| GstCaps *ret, *subcaps; |
| |
| subcaps = gst_subtitle_overlay_create_factory_caps (); |
| if (filter) { |
| ret = gst_caps_intersect_full (filter, subcaps, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (subcaps); |
| } else { |
| ret = subcaps; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_subtitle_overlay_subtitle_sink_setcaps (GstSubtitleOverlay * self, |
| GstCaps * caps) |
| { |
| gboolean ret = TRUE; |
| GstPad *target = NULL; |
| |
| GST_DEBUG_OBJECT (self, "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 && pad_supports_caps (target, caps)) { |
| GST_DEBUG_OBJECT (self, "Target accepts caps"); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| goto out; |
| } |
| |
| GST_DEBUG_OBJECT (self, "Target did not accept caps"); |
| |
| self->subtitle_error = FALSE; |
| block_subtitle (self); |
| block_video (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| out: |
| if (target) |
| gst_object_unref (target); |
| |
| return ret; |
| } |
| |
| static GstPadLinkReturn |
| gst_subtitle_overlay_subtitle_sink_link (GstPad * pad, GstObject * parent, |
| GstPad * peer) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent); |
| GstCaps *caps; |
| |
| GST_DEBUG_OBJECT (pad, "Linking pad to peer %" GST_PTR_FORMAT, peer); |
| |
| caps = gst_pad_get_current_caps (peer); |
| if (!caps) { |
| caps = gst_pad_query_caps (peer, NULL); |
| 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); |
| |
| self->subtitle_error = FALSE; |
| |
| block_subtitle (self); |
| block_video (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| gst_caps_unref (caps); |
| } |
| |
| return GST_PAD_LINK_OK; |
| } |
| |
| static void |
| gst_subtitle_overlay_subtitle_sink_unlink (GstPad * pad, GstObject * parent) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent); |
| |
| /* 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); |
| |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| self->subtitle_error = FALSE; |
| |
| block_subtitle (self); |
| block_video (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| } |
| |
| static gboolean |
| gst_subtitle_overlay_subtitle_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (parent); |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (pad, "Got event %" GST_PTR_FORMAT, event); |
| |
| if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB && |
| gst_event_has_name (event, "playsink-custom-subtitle-flush")) { |
| GST_DEBUG_OBJECT (pad, "Custom subtitle flush event"); |
| GST_SUBTITLE_OVERLAY_LOCK (self); |
| self->subtitle_flush = TRUE; |
| self->subtitle_error = FALSE; |
| block_subtitle (self); |
| block_video (self); |
| GST_SUBTITLE_OVERLAY_UNLOCK (self); |
| |
| gst_event_unref (event); |
| event = NULL; |
| ret = TRUE; |
| goto out; |
| } |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| ret = gst_subtitle_overlay_subtitle_sink_setcaps (self, caps); |
| if (!ret) |
| goto out; |
| break; |
| } |
| case GST_EVENT_FLUSH_STOP: |
| case GST_EVENT_FLUSH_START: |
| case GST_EVENT_SEGMENT: |
| case GST_EVENT_EOS: |
| { |
| GstStructure *structure; |
| |
| /* 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_event_make_writable (event)); |
| structure = gst_event_writable_structure (event); |
| |
| gst_structure_id_set (structure, _subtitle_overlay_event_marker_id, |
| G_TYPE_BOOLEAN, TRUE, NULL); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| ret = gst_pad_event_default (pad, parent, gst_event_ref (event)); |
| |
| gst_event_unref (event); |
| |
| out: |
| return ret; |
| } |
| |
| static gboolean |
| gst_subtitle_overlay_subtitle_sink_query (GstPad * pad, GstObject * parent, |
| GstQuery * query) |
| { |
| gboolean ret; |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_ACCEPT_CAPS: |
| { |
| gst_query_set_accept_caps_result (query, TRUE); |
| ret = TRUE; |
| break; |
| } |
| case GST_QUERY_CAPS: |
| { |
| GstCaps *filter, *caps; |
| |
| gst_query_parse_caps (query, &filter); |
| caps = gst_subtitle_overlay_subtitle_sink_getcaps (pad, filter); |
| gst_query_set_caps_result (query, caps); |
| gst_caps_unref (caps); |
| ret = TRUE; |
| break; |
| } |
| default: |
| ret = gst_pad_query_default (pad, parent, query); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_subtitle_overlay_init (GstSubtitleOverlay * self) |
| { |
| GstPadTemplate *templ; |
| GstPad *proxypad = NULL; |
| |
| g_mutex_init (&self->lock); |
| g_mutex_init (&self->factories_lock); |
| |
| templ = gst_static_pad_template_get (&srctemplate); |
| self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ); |
| gst_object_unref (templ); |
| |
| proxypad = |
| GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (self->srcpad))); |
| gst_pad_set_event_function (proxypad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_src_proxy_event)); |
| gst_pad_set_chain_function (proxypad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_src_proxy_chain)); |
| gst_object_unref (proxypad); |
| |
| 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); |
| gst_object_unref (templ); |
| gst_pad_set_event_function (self->video_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_event)); |
| gst_pad_set_chain_function (self->video_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_chain)); |
| |
| proxypad = |
| GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD |
| (self->video_sinkpad))); |
| self->video_block_pad = proxypad; |
| gst_object_unref (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); |
| gst_object_unref (templ); |
| gst_pad_set_link_function (self->subtitle_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_link)); |
| gst_pad_set_unlink_function (self->subtitle_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_unlink)); |
| gst_pad_set_event_function (self->subtitle_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_event)); |
| gst_pad_set_query_function (self->subtitle_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_query)); |
| gst_pad_set_chain_function (self->subtitle_sinkpad, |
| GST_DEBUG_FUNCPTR (gst_subtitle_overlay_subtitle_sink_chain)); |
| |
| proxypad = |
| GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD |
| (self->subtitle_sinkpad))); |
| self->subtitle_block_pad = proxypad; |
| gst_object_unref (proxypad); |
| |
| gst_element_add_pad (GST_ELEMENT_CAST (self), self->subtitle_sinkpad); |
| |
| self->fps_n = 0; |
| self->fps_d = 0; |
| } |
| |
| 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); |
| } |