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.
This commit is contained in:
Wim Taymans 2009-03-12 17:47:41 +01:00
parent 566583e871
commit 7849db42b8
3 changed files with 202 additions and 83 deletions

View File

@ -411,6 +411,7 @@ struct _GstPlayBinClass
#define DEFAULT_AUDIO_SINK NULL #define DEFAULT_AUDIO_SINK NULL
#define DEFAULT_VIDEO_SINK NULL #define DEFAULT_VIDEO_SINK NULL
#define DEFAULT_VIS_PLUGIN NULL #define DEFAULT_VIS_PLUGIN NULL
#define DEFAULT_TEXT_SINK NULL
#define DEFAULT_VOLUME 1.0 #define DEFAULT_VOLUME 1.0
#define DEFAULT_MUTE FALSE #define DEFAULT_MUTE FALSE
#define DEFAULT_FRAME NULL #define DEFAULT_FRAME NULL
@ -436,13 +437,15 @@ enum
PROP_AUDIO_SINK, PROP_AUDIO_SINK,
PROP_VIDEO_SINK, PROP_VIDEO_SINK,
PROP_VIS_PLUGIN, PROP_VIS_PLUGIN,
PROP_TEXT_SINK,
PROP_VOLUME, PROP_VOLUME,
PROP_MUTE, PROP_MUTE,
PROP_FRAME, PROP_FRAME,
PROP_FONT_DESC, PROP_FONT_DESC,
PROP_CONNECTION_SPEED, PROP_CONNECTION_SPEED,
PROP_BUFFER_SIZE, PROP_BUFFER_SIZE,
PROP_BUFFER_DURATION PROP_BUFFER_DURATION,
PROP_LAST
}; };
/* signals */ /* signals */
@ -690,6 +693,10 @@ gst_play_bin_class_init (GstPlayBinClass * klass)
g_param_spec_object ("vis-plugin", "Vis plugin", g_param_spec_object ("vis-plugin", "Vis plugin",
"the visualization element to use (NULL = default)", "the visualization element to use (NULL = default)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); 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_object_class_install_property (gobject_klass, PROP_VOLUME,
g_param_spec_double ("volume", "Volume", "The audio 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, gst_play_sink_set_vis_plugin (playbin->playsink,
g_value_get_object (value)); g_value_get_object (value));
break; break;
case PROP_TEXT_SINK:
gst_play_sink_set_text_sink (playbin->playsink,
g_value_get_object (value));
break;
case PROP_VOLUME: case PROP_VOLUME:
gst_play_sink_set_volume (playbin->playsink, g_value_get_double (value)); gst_play_sink_set_volume (playbin->playsink, g_value_get_double (value));
break; break;
@ -1558,6 +1569,10 @@ gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
g_value_set_object (value, g_value_set_object (value,
gst_play_sink_get_vis_plugin (playbin->playsink)); gst_play_sink_get_vis_plugin (playbin->playsink));
break; break;
case PROP_TEXT_SINK:
g_value_set_object (value,
gst_play_sink_get_text_sink (playbin->playsink));
break;
case PROP_VOLUME: case PROP_VOLUME:
g_value_set_double (value, gst_play_sink_get_volume (playbin->playsink)); g_value_set_double (value, gst_play_sink_get_volume (playbin->playsink));
break; break;

View File

@ -95,6 +95,7 @@ typedef struct
GstPad *textsinkpad; GstPad *textsinkpad;
GstPad *srcpad; /* outgoing srcpad, used to connect to the next GstPad *srcpad; /* outgoing srcpad, used to connect to the next
* chain */ * chain */
GstElement *sink; /* custom sink to receive subtitle buffers */
} GstPlayTextChain; } GstPlayTextChain;
#define GST_PLAY_SINK_GET_LOCK(playsink) (((GstPlaySink *)playsink)->lock) #define GST_PLAY_SINK_GET_LOCK(playsink) (((GstPlaySink *)playsink)->lock)
@ -130,6 +131,7 @@ struct _GstPlaySink
GstElement *audio_sink; GstElement *audio_sink;
GstElement *video_sink; GstElement *video_sink;
GstElement *visualisation; GstElement *visualisation;
GstElement *text_sink;
gfloat volume; gfloat volume;
gboolean mute; gboolean mute;
gchar *font_desc; /* font description */ gchar *font_desc; /* font description */
@ -212,6 +214,7 @@ gst_play_sink_init (GstPlaySink * playsink)
playsink->video_sink = NULL; playsink->video_sink = NULL;
playsink->audio_sink = NULL; playsink->audio_sink = NULL;
playsink->visualisation = NULL; playsink->visualisation = NULL;
playsink->text_sink = NULL;
playsink->volume = 1.0; playsink->volume = 1.0;
playsink->font_desc = NULL; playsink->font_desc = NULL;
playsink->flags = GST_PLAY_FLAG_SOFT_VOLUME; playsink->flags = GST_PLAY_FLAG_SOFT_VOLUME;
@ -251,6 +254,11 @@ gst_play_sink_dispose (GObject * object)
gst_object_unref (playsink->visualisation); gst_object_unref (playsink->visualisation);
playsink->visualisation = NULL; 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); free_chain ((GstPlayChain *) playsink->videochain);
playsink->videochain = NULL; playsink->videochain = NULL;
@ -261,7 +269,6 @@ gst_play_sink_dispose (GObject * object)
free_chain ((GstPlayChain *) playsink->textchain); free_chain ((GstPlayChain *) playsink->textchain);
playsink->textchain = NULL; playsink->textchain = NULL;
if (playsink->audio_tee_sink) { if (playsink->audio_tee_sink) {
gst_object_unref (playsink->audio_tee_sink); gst_object_unref (playsink->audio_tee_sink);
playsink->audio_tee_sink = NULL; playsink->audio_tee_sink = NULL;
@ -485,6 +492,41 @@ gst_play_sink_get_vis_plugin (GstPlaySink * playsink)
return result; 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 void
gst_play_sink_set_volume (GstPlaySink * playsink, gdouble volume) gst_play_sink_set_volume (GstPlaySink * playsink, gdouble volume)
{ {
@ -663,7 +705,9 @@ find_property_sink (GstElement * element, const gchar * name)
return res; 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 * static GstElement *
gst_play_sink_find_property_sinks (GstPlaySink * playsink, GstElement * obj, gst_play_sink_find_property_sinks (GstPlaySink * playsink, GstElement * obj,
const gchar * name) const gchar * name)
@ -671,16 +715,16 @@ gst_play_sink_find_property_sinks (GstPlaySink * playsink, GstElement * obj,
GstElement *result = NULL; GstElement *result = NULL;
GstIterator *it; 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)); it = gst_bin_iterate_recurse (GST_BIN_CAST (obj));
result = gst_iterator_find_custom (it, result = gst_iterator_find_custom (it,
(GCompareFunc) find_property_sink, (gpointer) name); (GCompareFunc) find_property_sink, (gpointer) name);
gst_iterator_free (it); gst_iterator_free (it);
} else { /* we don't need the extra ref */
if (g_object_class_find_property (G_OBJECT_GET_CLASS (obj), name)) { if (result)
result = obj; gst_object_unref (result);
gst_object_ref (obj);
}
} }
return result; return result;
} }
@ -751,12 +795,12 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async)
goto no_sinks; goto no_sinks;
/* if we can disable async behaviour of the sink, we can avoid adding a /* 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 * queue for the audio chain. */
* sink might change it's internal sink element later. */ elem = gst_play_sink_find_property_sinks (playsink, chain->sink, "async");
if (g_object_class_find_property (G_OBJECT_GET_CLASS (chain->sink), "async")) { if (elem) {
GST_DEBUG_OBJECT (playsink, "setting async property to %d on video sink", GST_DEBUG_OBJECT (playsink, "setting async property to %d on element %s",
async); async, GST_ELEMENT_NAME (elem));
g_object_set (chain->sink, "async", async, NULL); g_object_set (elem, "async", async, NULL);
chain->async = async; chain->async = async;
} else { } else {
GST_DEBUG_OBJECT (playsink, "no async property on the sink"); 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"); post_missing_element_message (playsink, "ffmpegcolorspace");
GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN, GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."), (_("Missing element '%s' - check your GStreamer installation."),
"ffmpegcolorspace"), (NULL)); "ffmpegcolorspace"), ("video rendering might fail"));
} else { } else {
gst_bin_add (bin, chain->conv); gst_bin_add (bin, chain->conv);
if (!gst_element_link_pads (prev, "src", chain->conv, "sink")) if (!gst_element_link_pads (prev, "src", chain->conv, "sink"))
@ -851,7 +895,7 @@ link_failed:
* | tbin +-------------+ | * | tbin +-------------+ |
* | +-----+ | textoverlay | | * | +-----+ | textoverlay | |
* | | csp | +--video_sink | | * | | csp | +--video_sink | |
* video_sink-sink src+ +-text_sink src--+ | * sink-------sink src+ +-text_sink src--+ |
* | +-----+ | +-------------+ +-- src * | +-----+ | +-------------+ +-- src
* text_sink-------------+ | * text_sink-------------+ |
* +----------------------------------------------+ * +----------------------------------------------+
@ -861,7 +905,8 @@ gen_text_chain (GstPlaySink * playsink)
{ {
GstPlayTextChain *chain; GstPlayTextChain *chain;
GstBin *bin; GstBin *bin;
GstPad *pad; GstElement *elem;
GstPad *videosinkpad, *textsinkpad, *srcpad;
chain = g_new0 (GstPlayTextChain, 1); chain = g_new0 (GstPlayTextChain, 1);
chain->chain.playsink = playsink; chain->chain.playsink = playsink;
@ -873,65 +918,127 @@ gen_text_chain (GstPlaySink * playsink)
gst_object_ref (bin); gst_object_ref (bin);
gst_object_sink (bin); gst_object_sink (bin);
chain->conv = gst_element_factory_make ("ffmpegcolorspace", "tconv"); videosinkpad = textsinkpad = srcpad = NULL;
if (chain->conv == NULL)
goto no_colorspace;
gst_bin_add (bin, chain->conv);
chain->overlay = gst_element_factory_make ("textoverlay", "overlay"); /* first try to hook the text pad to the custom sink */
if (chain->overlay == NULL) if (playsink->text_sink) {
goto no_overlay; GST_DEBUG_OBJECT (playsink, "trying configured textsink");
gst_bin_add (bin, chain->overlay); elem = gst_object_ref (playsink->text_sink);
chain->sink = try_element (playsink, elem);
/* Set some parameters */ if (chain->sink) {
g_object_set (G_OBJECT (chain->overlay), elem = gst_play_sink_find_property_sinks (playsink, chain->sink, "async");
"halign", "center", "valign", "bottom", NULL); if (elem) {
if (playsink->font_desc) { g_object_set (elem, "async", TRUE, NULL);
g_object_set (G_OBJECT (chain->overlay), "font-desc", playsink->font_desc, /* we have a custom sink, this will be our textsinkpad */
NULL); 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 */ if (textsinkpad == NULL) {
gst_element_link_pads (chain->conv, "src", chain->overlay, "video_sink"); 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 */ chain->overlay = gst_element_factory_make ("textoverlay", "overlay");
pad = gst_element_get_static_pad (chain->overlay, "text_sink"); if (chain->overlay == NULL) {
chain->textsinkpad = gst_ghost_pad_new ("text_sink", pad); post_missing_element_message (playsink, "textoverlay");
gst_object_unref (pad); GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
gst_element_add_pad (chain->chain.bin, chain->textsinkpad); (_("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"); /* Set some parameters */
chain->videosinkpad = gst_ghost_pad_new ("sink", pad); g_object_set (G_OBJECT (chain->overlay),
gst_object_unref (pad); "halign", "center", "valign", "bottom", NULL);
gst_element_add_pad (chain->chain.bin, chain->videosinkpad); 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"); textsinkpad = gst_element_get_static_pad (chain->overlay, "text_sink");
chain->srcpad = gst_ghost_pad_new ("src", pad);
gst_object_unref (pad); srcpad = gst_element_get_static_pad (chain->overlay, "src");
gst_element_add_pad (chain->chain.bin, chain->srcpad);
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; 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 /* 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) if (chain->sink == NULL)
goto no_sinks; goto no_sinks;
chain->chain.bin = gst_bin_new ("abin"); chain->chain.bin = gst_bin_new ("abin");
bin = GST_BIN_CAST (chain->chain.bin); bin = GST_BIN_CAST (chain->chain.bin);
gst_object_ref (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. /* 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. */ * If it does we don't need to add a volume element. */
elem = NULL; chain->volume =
if (g_object_class_find_property (G_OBJECT_GET_CLASS (chain->sink), "volume")) { gst_play_sink_find_property_sinks (playsink, chain->sink, "volume");
elem = gst_object_ref (chain->sink); if (chain->volume) {
} else if (GST_IS_BIN (chain->sink)) {
elem = gst_play_sink_find_property_sinks (playsink, chain->sink, "volume");
}
if (elem) {
GST_DEBUG_OBJECT (playsink, "the sink has a volume property"); GST_DEBUG_OBJECT (playsink, "the sink has a volume property");
have_volume = TRUE; have_volume = TRUE;
/* use the sink to control the volume */ /* use the sink to control the volume */
chain->volume = elem; g_object_set (G_OBJECT (chain->volume), "volume", playsink->volume, NULL);
g_object_set (G_OBJECT (elem), "volume", playsink->volume, NULL);
/* if the sink also has a mute property we can use this as well. We'll only /* 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 * use the mute property if there is a volume property. We can simulate the
* mute with the volume otherwise. */ * 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"); GST_DEBUG_OBJECT (playsink, "the sink has a mute property");
chain->mute = elem;
} }
gst_object_unref (elem);
} else { } else {
/* no volume, we need to add a volume element when we can */ /* no volume, we need to add a volume element when we can */
GST_DEBUG_OBJECT (playsink, "the sink has no volume property"); 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 */ /* post a warning if we have no way to configure the volume */
if (!have_volume) { if (!have_volume) {
GST_ELEMENT_WARNING (playsink, STREAM, NOT_IMPLEMENTED, 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 */ /* and ghost the sinkpad of the headmost element */

View File

@ -76,6 +76,9 @@ GstElement * gst_play_sink_get_audio_sink (GstPlaySink * playsink);
void gst_play_sink_set_vis_plugin (GstPlaySink * playsink, GstElement * vis); void gst_play_sink_set_vis_plugin (GstPlaySink * playsink, GstElement * vis);
GstElement * gst_play_sink_get_vis_plugin (GstPlaySink * playsink); 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); void gst_play_sink_set_volume (GstPlaySink *playsink, gdouble volume);
gdouble gst_play_sink_get_volume (GstPlaySink *playsink); gdouble gst_play_sink_get_volume (GstPlaySink *playsink);