From 7849db42b8408f345275929a56cf1a6f34260fd9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Mar 2009 17:47:41 +0100 Subject: [PATCH] playbin2: improve subtitle handling Add property to playbin2 to configure a custom sink that receives the raw subtitle buffers instead of using a textoverlay. Improve the property finding code to make it more usable. Use property find code to find async properties in custom sinks that are bins. Improve text overlay code to gracefully handle missing elements. --- gst/playback/gstplaybin2.c | 17 ++- gst/playback/gstplaysink.c | 265 +++++++++++++++++++++++++------------ gst/playback/gstplaysink.h | 3 + 3 files changed, 202 insertions(+), 83 deletions(-) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index 5d15cdef6f..cd6a27683e 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -411,6 +411,7 @@ struct _GstPlayBinClass #define DEFAULT_AUDIO_SINK NULL #define DEFAULT_VIDEO_SINK NULL #define DEFAULT_VIS_PLUGIN NULL +#define DEFAULT_TEXT_SINK NULL #define DEFAULT_VOLUME 1.0 #define DEFAULT_MUTE FALSE #define DEFAULT_FRAME NULL @@ -436,13 +437,15 @@ enum PROP_AUDIO_SINK, PROP_VIDEO_SINK, PROP_VIS_PLUGIN, + PROP_TEXT_SINK, PROP_VOLUME, PROP_MUTE, PROP_FRAME, PROP_FONT_DESC, PROP_CONNECTION_SPEED, PROP_BUFFER_SIZE, - PROP_BUFFER_DURATION + PROP_BUFFER_DURATION, + PROP_LAST }; /* signals */ @@ -690,6 +693,10 @@ gst_play_bin_class_init (GstPlayBinClass * klass) g_param_spec_object ("vis-plugin", "Vis plugin", "the visualization element to use (NULL = default)", GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_klass, PROP_TEXT_SINK, + g_param_spec_object ("text-sink", "Text plugin", + "the text output element to use (NULL = default textoverlay)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_klass, PROP_VOLUME, g_param_spec_double ("volume", "Volume", "The audio volume", @@ -1424,6 +1431,10 @@ gst_play_bin_set_property (GObject * object, guint prop_id, gst_play_sink_set_vis_plugin (playbin->playsink, g_value_get_object (value)); break; + case PROP_TEXT_SINK: + gst_play_sink_set_text_sink (playbin->playsink, + g_value_get_object (value)); + break; case PROP_VOLUME: gst_play_sink_set_volume (playbin->playsink, g_value_get_double (value)); break; @@ -1558,6 +1569,10 @@ gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value, g_value_set_object (value, gst_play_sink_get_vis_plugin (playbin->playsink)); break; + case PROP_TEXT_SINK: + g_value_set_object (value, + gst_play_sink_get_text_sink (playbin->playsink)); + break; case PROP_VOLUME: g_value_set_double (value, gst_play_sink_get_volume (playbin->playsink)); break; diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c index 7484079c54..33644a5019 100644 --- a/gst/playback/gstplaysink.c +++ b/gst/playback/gstplaysink.c @@ -95,6 +95,7 @@ typedef struct GstPad *textsinkpad; GstPad *srcpad; /* outgoing srcpad, used to connect to the next * chain */ + GstElement *sink; /* custom sink to receive subtitle buffers */ } GstPlayTextChain; #define GST_PLAY_SINK_GET_LOCK(playsink) (((GstPlaySink *)playsink)->lock) @@ -130,6 +131,7 @@ struct _GstPlaySink GstElement *audio_sink; GstElement *video_sink; GstElement *visualisation; + GstElement *text_sink; gfloat volume; gboolean mute; gchar *font_desc; /* font description */ @@ -212,6 +214,7 @@ gst_play_sink_init (GstPlaySink * playsink) playsink->video_sink = NULL; playsink->audio_sink = NULL; playsink->visualisation = NULL; + playsink->text_sink = NULL; playsink->volume = 1.0; playsink->font_desc = NULL; playsink->flags = GST_PLAY_FLAG_SOFT_VOLUME; @@ -251,6 +254,11 @@ gst_play_sink_dispose (GObject * object) gst_object_unref (playsink->visualisation); playsink->visualisation = NULL; } + if (playsink->text_sink != NULL) { + gst_element_set_state (playsink->text_sink, GST_STATE_NULL); + gst_object_unref (playsink->text_sink); + playsink->text_sink = NULL; + } free_chain ((GstPlayChain *) playsink->videochain); playsink->videochain = NULL; @@ -261,7 +269,6 @@ gst_play_sink_dispose (GObject * object) free_chain ((GstPlayChain *) playsink->textchain); playsink->textchain = NULL; - if (playsink->audio_tee_sink) { gst_object_unref (playsink->audio_tee_sink); playsink->audio_tee_sink = NULL; @@ -485,6 +492,41 @@ gst_play_sink_get_vis_plugin (GstPlaySink * playsink) return result; } +void +gst_play_sink_set_text_sink (GstPlaySink * playsink, GstElement * sink) +{ + GST_PLAY_SINK_LOCK (playsink); + if (playsink->text_sink) + gst_object_unref (playsink->text_sink); + + if (sink) { + gst_object_ref (sink); + gst_object_sink (sink); + } + playsink->text_sink = sink; + GST_PLAY_SINK_UNLOCK (playsink); +} + +GstElement * +gst_play_sink_get_text_sink (GstPlaySink * playsink) +{ + GstElement *result = NULL; + GstPlayTextChain *chain; + + GST_PLAY_SINK_LOCK (playsink); + if ((chain = (GstPlayTextChain *) playsink->textchain)) { + /* we have an active chain, get the sink */ + if (chain->sink) + result = gst_object_ref (chain->sink); + } + /* nothing found, return last configured sink */ + if (result == NULL && playsink->text_sink) + result = gst_object_ref (playsink->text_sink); + GST_PLAY_SINK_UNLOCK (playsink); + + return result; +} + void gst_play_sink_set_volume (GstPlaySink * playsink, gdouble volume) { @@ -663,7 +705,9 @@ find_property_sink (GstElement * element, const gchar * name) return res; } -/* find a sink in the hierarchy with a property named @name */ +/* find a sink in the hierarchy with a property named @name. This function does + * not increase the refcount of the returned object and thus remains valid as + * long as the bin is valid. */ static GstElement * gst_play_sink_find_property_sinks (GstPlaySink * playsink, GstElement * obj, const gchar * name) @@ -671,16 +715,16 @@ gst_play_sink_find_property_sinks (GstPlaySink * playsink, GstElement * obj, GstElement *result = NULL; GstIterator *it; - if (GST_IS_BIN (obj)) { + if (g_object_class_find_property (G_OBJECT_GET_CLASS (obj), name)) { + result = obj; + } else if (GST_IS_BIN (obj)) { it = gst_bin_iterate_recurse (GST_BIN_CAST (obj)); result = gst_iterator_find_custom (it, (GCompareFunc) find_property_sink, (gpointer) name); gst_iterator_free (it); - } else { - if (g_object_class_find_property (G_OBJECT_GET_CLASS (obj), name)) { - result = obj; - gst_object_ref (obj); - } + /* we don't need the extra ref */ + if (result) + gst_object_unref (result); } return result; } @@ -751,12 +795,12 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async) goto no_sinks; /* if we can disable async behaviour of the sink, we can avoid adding a - * queue for the audio chain. We can't use the deep property here because the - * sink might change it's internal sink element later. */ - if (g_object_class_find_property (G_OBJECT_GET_CLASS (chain->sink), "async")) { - GST_DEBUG_OBJECT (playsink, "setting async property to %d on video sink", - async); - g_object_set (chain->sink, "async", async, NULL); + * queue for the audio chain. */ + elem = gst_play_sink_find_property_sinks (playsink, chain->sink, "async"); + if (elem) { + GST_DEBUG_OBJECT (playsink, "setting async property to %d on element %s", + async, GST_ELEMENT_NAME (elem)); + g_object_set (elem, "async", async, NULL); chain->async = async; } else { GST_DEBUG_OBJECT (playsink, "no async property on the sink"); @@ -788,7 +832,7 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async) post_missing_element_message (playsink, "ffmpegcolorspace"); GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN, (_("Missing element '%s' - check your GStreamer installation."), - "ffmpegcolorspace"), (NULL)); + "ffmpegcolorspace"), ("video rendering might fail")); } else { gst_bin_add (bin, chain->conv); if (!gst_element_link_pads (prev, "src", chain->conv, "sink")) @@ -851,7 +895,7 @@ link_failed: * | tbin +-------------+ | * | +-----+ | textoverlay | | * | | csp | +--video_sink | | - * video_sink-sink src+ +-text_sink src--+ | + * sink-------sink src+ +-text_sink src--+ | * | +-----+ | +-------------+ +-- src * text_sink-------------+ | * +----------------------------------------------+ @@ -861,7 +905,8 @@ gen_text_chain (GstPlaySink * playsink) { GstPlayTextChain *chain; GstBin *bin; - GstPad *pad; + GstElement *elem; + GstPad *videosinkpad, *textsinkpad, *srcpad; chain = g_new0 (GstPlayTextChain, 1); chain->chain.playsink = playsink; @@ -873,65 +918,127 @@ gen_text_chain (GstPlaySink * playsink) gst_object_ref (bin); gst_object_sink (bin); - chain->conv = gst_element_factory_make ("ffmpegcolorspace", "tconv"); - if (chain->conv == NULL) - goto no_colorspace; - gst_bin_add (bin, chain->conv); + videosinkpad = textsinkpad = srcpad = NULL; - chain->overlay = gst_element_factory_make ("textoverlay", "overlay"); - if (chain->overlay == NULL) - goto no_overlay; - gst_bin_add (bin, chain->overlay); - - /* Set some parameters */ - g_object_set (G_OBJECT (chain->overlay), - "halign", "center", "valign", "bottom", NULL); - if (playsink->font_desc) { - g_object_set (G_OBJECT (chain->overlay), "font-desc", playsink->font_desc, - NULL); + /* first try to hook the text pad to the custom sink */ + if (playsink->text_sink) { + GST_DEBUG_OBJECT (playsink, "trying configured textsink"); + elem = gst_object_ref (playsink->text_sink); + chain->sink = try_element (playsink, elem); + if (chain->sink) { + elem = gst_play_sink_find_property_sinks (playsink, chain->sink, "async"); + if (elem) { + g_object_set (elem, "async", TRUE, NULL); + /* we have a custom sink, this will be our textsinkpad */ + textsinkpad = gst_element_get_static_pad (chain->sink, "sink"); + if (textsinkpad) { + /* we're all fine now and we can add the sink to the chain */ + GST_DEBUG_OBJECT (playsink, "adding custom text sink"); + gst_bin_add (bin, chain->sink); + } else { + GST_WARNING_OBJECT (playsink, + "can't find a sink pad on custom text sink"); + gst_object_unref (chain->sink); + chain->sink = NULL; + } + } else { + GST_WARNING_OBJECT (playsink, + "can't find async property in custom text sink"); + } + } + if (textsinkpad == NULL) { + GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN, + (_("Custom text sink element is not usable.")), + ("fallback to default textoverlay")); + } } - g_object_set (G_OBJECT (chain->overlay), "wait-text", FALSE, NULL); - /* Link */ - gst_element_link_pads (chain->conv, "src", chain->overlay, "video_sink"); + if (textsinkpad == NULL) { + if (!(playsink->flags & GST_PLAY_FLAG_NATIVE_VIDEO)) { + /* no custom sink, try to setup the colorspace and textoverlay elements */ + chain->conv = gst_element_factory_make ("ffmpegcolorspace", "tconv"); + if (chain->conv == NULL) { + /* not really needed, it might work without colorspace */ + post_missing_element_message (playsink, "ffmpegcolorspace"); + GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "ffmpegcolorspace"), ("subtitle rendering might fail")); + } else { + gst_bin_add (bin, chain->conv); + videosinkpad = gst_element_get_static_pad (chain->conv, "sink"); + } + } - /* Add ghost pads on the subtitle bin */ - pad = gst_element_get_static_pad (chain->overlay, "text_sink"); - chain->textsinkpad = gst_ghost_pad_new ("text_sink", pad); - gst_object_unref (pad); - gst_element_add_pad (chain->chain.bin, chain->textsinkpad); + chain->overlay = gst_element_factory_make ("textoverlay", "overlay"); + if (chain->overlay == NULL) { + post_missing_element_message (playsink, "textoverlay"); + GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "textoverlay"), ("subtitle rendering disabled")); + } else { + gst_bin_add (bin, chain->overlay); - pad = gst_element_get_static_pad (chain->conv, "sink"); - chain->videosinkpad = gst_ghost_pad_new ("sink", pad); - gst_object_unref (pad); - gst_element_add_pad (chain->chain.bin, chain->videosinkpad); + /* Set some parameters */ + g_object_set (G_OBJECT (chain->overlay), + "halign", "center", "valign", "bottom", NULL); + if (playsink->font_desc) { + g_object_set (G_OBJECT (chain->overlay), "font-desc", + playsink->font_desc, NULL); + } + g_object_set (G_OBJECT (chain->overlay), "wait-text", FALSE, NULL); - pad = gst_element_get_static_pad (chain->overlay, "src"); - chain->srcpad = gst_ghost_pad_new ("src", pad); - gst_object_unref (pad); - gst_element_add_pad (chain->chain.bin, chain->srcpad); + textsinkpad = gst_element_get_static_pad (chain->overlay, "text_sink"); + + srcpad = gst_element_get_static_pad (chain->overlay, "src"); + + if (videosinkpad) { + /* if we had a videosinkpad, we had a converter and we can link it, we + * know that this will work */ + gst_element_link_pads (chain->conv, "src", chain->overlay, + "video_sink"); + } else { + /* no videopad, expose our own video pad then */ + videosinkpad = + gst_element_get_static_pad (chain->overlay, "video_sink"); + } + } + } + + if (videosinkpad == NULL) { + /* if we still don't have a videosink, we don't have a converter nor an + * overlay. the only thing we can do is insert an identity and ghost the src + * and sink pads. */ + chain->conv = gst_element_factory_make ("identity", "tidentity"); + gst_bin_add (bin, chain->conv); + srcpad = gst_element_get_static_pad (chain->conv, "src"); + videosinkpad = gst_element_get_static_pad (chain->conv, "sink"); + } else { + /* we have a videosink but maybe not a srcpad because there was no + * overlay */ + if (srcpad == NULL) { + /* ghost the source pad of the converter then */ + srcpad = gst_element_get_static_pad (chain->conv, "src"); + } + } + + /* expose the ghostpads */ + if (videosinkpad) { + chain->videosinkpad = gst_ghost_pad_new ("sink", videosinkpad); + gst_object_unref (videosinkpad); + gst_element_add_pad (chain->chain.bin, chain->videosinkpad); + } + if (textsinkpad) { + chain->textsinkpad = gst_ghost_pad_new ("text_sink", textsinkpad); + gst_object_unref (textsinkpad); + gst_element_add_pad (chain->chain.bin, chain->textsinkpad); + } + if (srcpad) { + chain->srcpad = gst_ghost_pad_new ("src", srcpad); + gst_object_unref (srcpad); + gst_element_add_pad (chain->chain.bin, chain->srcpad); + } return chain; - - /* ERRORS */ -no_colorspace: - { - post_missing_element_message (playsink, "ffmpegcolorspace"); - GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN, - (_("Missing element '%s' - check your GStreamer installation."), - "ffmpegcolorspace"), (NULL)); - free_chain ((GstPlayChain *) chain); - return NULL; - } -no_overlay: - { - post_missing_element_message (playsink, "textoverlay"); - GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN, - (_("Missing element '%s' - check your GStreamer installation."), - "textoverlay"), (NULL)); - free_chain ((GstPlayChain *) chain); - return NULL; - } } /* make the chain that contains the elements needed to perform @@ -982,7 +1089,6 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw, gboolean queue) if (chain->sink == NULL) goto no_sinks; - chain->chain.bin = gst_bin_new ("abin"); bin = GST_BIN_CAST (chain->chain.bin); gst_object_ref (bin); @@ -1003,26 +1109,21 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw, gboolean queue) /* check if the sink, or something within the sink, has the volume property. * If it does we don't need to add a volume element. */ - elem = NULL; - if (g_object_class_find_property (G_OBJECT_GET_CLASS (chain->sink), "volume")) { - elem = gst_object_ref (chain->sink); - } else if (GST_IS_BIN (chain->sink)) { - elem = gst_play_sink_find_property_sinks (playsink, chain->sink, "volume"); - } - if (elem) { + chain->volume = + gst_play_sink_find_property_sinks (playsink, chain->sink, "volume"); + if (chain->volume) { GST_DEBUG_OBJECT (playsink, "the sink has a volume property"); have_volume = TRUE; /* use the sink to control the volume */ - chain->volume = elem; - g_object_set (G_OBJECT (elem), "volume", playsink->volume, NULL); + g_object_set (G_OBJECT (chain->volume), "volume", playsink->volume, NULL); /* if the sink also has a mute property we can use this as well. We'll only * use the mute property if there is a volume property. We can simulate the * mute with the volume otherwise. */ - if (g_object_class_find_property (G_OBJECT_GET_CLASS (elem), "mute")) { + chain->mute = + gst_play_sink_find_property_sinks (playsink, chain->sink, "mute"); + if (chain->mute) { GST_DEBUG_OBJECT (playsink, "the sink has a mute property"); - chain->mute = elem; } - gst_object_unref (elem); } else { /* no volume, we need to add a volume element when we can */ GST_DEBUG_OBJECT (playsink, "the sink has no volume property"); @@ -1108,7 +1209,7 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw, gboolean queue) /* post a warning if we have no way to configure the volume */ if (!have_volume) { GST_ELEMENT_WARNING (playsink, STREAM, NOT_IMPLEMENTED, - (_("No volume control found")), ("No volume control found")); + (_("No volume control found")), ("Volume/mute is not available")); } /* and ghost the sinkpad of the headmost element */ diff --git a/gst/playback/gstplaysink.h b/gst/playback/gstplaysink.h index 50585441c6..aa62521457 100644 --- a/gst/playback/gstplaysink.h +++ b/gst/playback/gstplaysink.h @@ -76,6 +76,9 @@ GstElement * gst_play_sink_get_audio_sink (GstPlaySink * playsink); void gst_play_sink_set_vis_plugin (GstPlaySink * playsink, GstElement * vis); GstElement * gst_play_sink_get_vis_plugin (GstPlaySink * playsink); +void gst_play_sink_set_text_sink (GstPlaySink * playsink, GstElement * sink); +GstElement * gst_play_sink_get_text_sink (GstPlaySink * playsink); + void gst_play_sink_set_volume (GstPlaySink *playsink, gdouble volume); gdouble gst_play_sink_get_volume (GstPlaySink *playsink);