playbin: Add support for custom stream-combiners

This allows to chose something else than input-selector
for multiple audio/video/text streams, e.g. an adder could
be used for audio.

It is needed for example to implement some of the more
advanced HTML5 video features.

https://bugzilla.gnome.org/show_bug.cgi?id=698851
This commit is contained in:
Brendan Long 2013-05-17 17:23:46 -06:00 committed by Sebastian Dröge
parent 14192c031a
commit ba5f6cfe72

View file

@ -271,6 +271,11 @@ struct _GstSourceSelect
* is linked
*/
gulong block_id;
gboolean has_active_pad; /* stream combiner has the "active-pad" property */
gboolean has_always_ok; /* stream combiner's sink pads have the "always-ok" property */
gboolean has_tags; /* stream combiner's sink pads have the "tags" property */
};
#define GST_SOURCE_GROUP_GET_LOCK(group) (&((GstSourceGroup*)(group))->lock)
@ -441,6 +446,10 @@ struct _GstPlayBin
GstElement *video_sink; /* configured video sink, or NULL */
GstElement *text_sink; /* configured text sink, or NULL */
GstElement *audio_stream_combiner; /* configured audio stream combiner, or NULL */
GstElement *video_stream_combiner; /* configured video stream combiner, or NULL */
GstElement *text_stream_combiner; /* configured text stream combiner, or NULL */
GSequence *aelements; /* a list of GstAVElements for audio stream */
GSequence *velements; /* a list of GstAVElements for video stream */
@ -533,6 +542,9 @@ enum
PROP_VIDEO_SINK,
PROP_VIS_PLUGIN,
PROP_TEXT_SINK,
PROP_VIDEO_STREAM_COMBINER,
PROP_AUDIO_STREAM_COMBINER,
PROP_TEXT_STREAM_COMBINER,
PROP_VOLUME,
PROP_MUTE,
PROP_SAMPLE,
@ -826,6 +838,36 @@ gst_play_bin_class_init (GstPlayBinClass * klass)
g_param_spec_object ("text-sink", "Text plugin",
"the text output element to use (NULL = default subtitleoverlay)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstPlayBin:video-stream-combiner
*
* Get or set the current video stream combiner. By default, an input-selector
* is created and deleted as-needed.
*/
g_object_class_install_property (gobject_klass, PROP_VIDEO_STREAM_COMBINER,
g_param_spec_object ("video-stream-combiner", "Video stream combiner",
"Current video stream combiner (NULL = input-selector)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstPlayBin:audio-stream-combiner
*
* Get or set the current audio stream combiner. By default, an input-selector
* is created and deleted as-needed.
*/
g_object_class_install_property (gobject_klass, PROP_AUDIO_STREAM_COMBINER,
g_param_spec_object ("audio-stream-combiner", "Audio stream combiner",
"Current audio stream combiner (NULL = input-selector)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstPlayBin:text-stream-combiner
*
* Get or set the current text stream combiner. By default, an input-selector
* is created and deleted as-needed.
*/
g_object_class_install_property (gobject_klass, PROP_TEXT_STREAM_COMBINER,
g_param_spec_object ("text-stream-combiner", "Text stream combiner",
"Current text stream combiner (NULL = input-selector)",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstPlayBin:volume:
@ -1629,12 +1671,13 @@ gst_play_bin_get_text_pad (GstPlayBin * playbin, gint stream)
static GstTagList *
get_tags (GstPlayBin * playbin, GPtrArray * channels, gint stream)
get_tags (GstPlayBin * playbin, GstSourceGroup * group, GPtrArray * channels,
gint stream)
{
GstTagList *result;
GstPad *sinkpad;
if (!channels || stream >= channels->len)
if (!channels || stream >= channels->len || !group->selector[stream].has_tags)
return NULL;
sinkpad = g_ptr_array_index (channels, stream);
@ -1651,7 +1694,7 @@ gst_play_bin_get_video_tags (GstPlayBin * playbin, gint stream)
GST_PLAY_BIN_LOCK (playbin);
group = get_group (playbin);
result = get_tags (playbin, group->video_channels, stream);
result = get_tags (playbin, group, group->video_channels, stream);
GST_PLAY_BIN_UNLOCK (playbin);
return result;
@ -1665,7 +1708,7 @@ gst_play_bin_get_audio_tags (GstPlayBin * playbin, gint stream)
GST_PLAY_BIN_LOCK (playbin);
group = get_group (playbin);
result = get_tags (playbin, group->audio_channels, stream);
result = get_tags (playbin, group, group->audio_channels, stream);
GST_PLAY_BIN_UNLOCK (playbin);
return result;
@ -1679,7 +1722,7 @@ gst_play_bin_get_text_tags (GstPlayBin * playbin, gint stream)
GST_PLAY_BIN_LOCK (playbin);
group = get_group (playbin);
result = get_tags (playbin, group->text_channels, stream);
result = get_tags (playbin, group, group->text_channels, stream);
GST_PLAY_BIN_UNLOCK (playbin);
return result;
@ -1693,7 +1736,8 @@ gst_play_bin_convert_sample (GstPlayBin * playbin, GstCaps * caps)
/* Returns current stream number, or -1 if none has been selected yet */
static int
get_current_stream_number (GstPlayBin * playbin, GPtrArray * channels)
get_current_stream_number (GstPlayBin * playbin, GstSourceSelect * select,
GPtrArray * channels)
{
/* Internal API cleanup would make this easier... */
int i;
@ -1701,6 +1745,12 @@ get_current_stream_number (GstPlayBin * playbin, GPtrArray * channels)
GstObject *selector = NULL;
int ret = -1;
if (!select->has_active_pad) {
GST_WARNING_OBJECT (playbin,
"selector doesn't have the \"active-pad\" property");
return ret;
}
for (i = 0; i < channels->len; i++) {
pad = g_ptr_array_index (channels, i);
if ((selector = gst_pad_get_parent (pad))) {
@ -1756,6 +1806,8 @@ gst_play_bin_set_current_video_stream (GstPlayBin * playbin, gint stream)
playbin->current_video, stream);
group = get_group (playbin);
if (!group->selector[PLAYBIN_STREAM_VIDEO].has_active_pad)
goto no_active_pad;
if (!(channels = group->video_channels))
goto no_channels;
@ -1793,6 +1845,13 @@ gst_play_bin_set_current_video_stream (GstPlayBin * playbin, gint stream)
}
return TRUE;
no_active_pad:
{
GST_PLAY_BIN_UNLOCK (playbin);
GST_WARNING_OBJECT (playbin,
"can't switch video, the stream combiner's sink pads don't have the \"active-pad\" property");
return FALSE;
}
no_channels:
{
GST_PLAY_BIN_UNLOCK (playbin);
@ -1814,6 +1873,8 @@ gst_play_bin_set_current_audio_stream (GstPlayBin * playbin, gint stream)
playbin->current_audio, stream);
group = get_group (playbin);
if (!group->selector[PLAYBIN_STREAM_AUDIO].has_active_pad)
goto no_active_pad;
if (!(channels = group->audio_channels))
goto no_channels;
@ -1850,6 +1911,13 @@ gst_play_bin_set_current_audio_stream (GstPlayBin * playbin, gint stream)
}
return TRUE;
no_active_pad:
{
GST_PLAY_BIN_UNLOCK (playbin);
GST_WARNING_OBJECT (playbin,
"can't switch audio, the stream combiner's sink pads don't have the \"active-pad\" property");
return FALSE;
}
no_channels:
{
GST_PLAY_BIN_UNLOCK (playbin);
@ -1962,6 +2030,8 @@ gst_play_bin_set_current_text_stream (GstPlayBin * playbin, gint stream)
playbin->current_text, stream);
group = get_group (playbin);
if (!group->selector[PLAYBIN_STREAM_TEXT].has_active_pad)
goto no_active_pad;
if (!(channels = group->text_channels))
goto no_channels;
@ -2048,6 +2118,13 @@ gst_play_bin_set_current_text_stream (GstPlayBin * playbin, gint stream)
}
return TRUE;
no_active_pad:
{
GST_PLAY_BIN_UNLOCK (playbin);
GST_WARNING_OBJECT (playbin,
"can't switch text, the stream combiner's sink pads don't have the \"active-pad\" property");
return FALSE;
}
no_channels:
{
GST_PLAY_BIN_UNLOCK (playbin);
@ -2077,6 +2154,30 @@ gst_play_bin_set_sink (GstPlayBin * playbin, GstElement ** elem,
GST_PLAY_BIN_UNLOCK (playbin);
}
static void
gst_play_bin_set_stream_combiner (GstPlayBin * playbin, GstElement ** elem,
const gchar * dbg, GstElement * combiner)
{
GST_INFO_OBJECT (playbin, "Setting %s stream combiner to %" GST_PTR_FORMAT,
dbg, combiner);
GST_PLAY_BIN_LOCK (playbin);
if (*elem != combiner) {
GstElement *old;
old = *elem;
if (combiner)
gst_object_ref_sink (combiner);
*elem = combiner;
if (old)
gst_object_unref (old);
}
GST_LOG_OBJECT (playbin, "%s stream combiner now %" GST_PTR_FORMAT, dbg,
*elem);
GST_PLAY_BIN_UNLOCK (playbin);
}
static void
gst_play_bin_set_encoding (GstPlayBin * playbin, const gchar * encoding)
{
@ -2142,6 +2243,18 @@ gst_play_bin_set_property (GObject * object, guint prop_id,
gst_play_bin_set_sink (playbin, &playbin->text_sink, "text",
g_value_get_object (value));
break;
case PROP_VIDEO_STREAM_COMBINER:
gst_play_bin_set_stream_combiner (playbin,
&playbin->video_stream_combiner, "video", g_value_get_object (value));
break;
case PROP_AUDIO_STREAM_COMBINER:
gst_play_bin_set_stream_combiner (playbin,
&playbin->audio_stream_combiner, "audio", g_value_get_object (value));
break;
case PROP_TEXT_STREAM_COMBINER:
gst_play_bin_set_stream_combiner (playbin,
&playbin->text_stream_combiner, "text", g_value_get_object (value));
break;
case PROP_VOLUME:
gst_play_sink_set_volume (playbin->playsink, g_value_get_double (value));
break;
@ -2200,6 +2313,22 @@ gst_play_bin_get_current_sink (GstPlayBin * playbin, GstElement ** elem,
return sink;
}
static GstElement *
gst_play_bin_get_current_stream_combiner (GstPlayBin * playbin,
GstElement ** elem, const gchar * dbg, int stream_type)
{
GstElement *combiner;
GST_PLAY_BIN_LOCK (playbin);
if ((combiner = playbin->curr_group->selector[stream_type].selector))
gst_object_ref (combiner);
else if ((combiner = *elem))
gst_object_ref (combiner);
GST_PLAY_BIN_UNLOCK (playbin);
return combiner;
}
static void
gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
@ -2334,6 +2463,21 @@ gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value,
gst_play_bin_get_current_sink (playbin, &playbin->text_sink,
"text", GST_PLAY_SINK_TYPE_TEXT));
break;
case PROP_VIDEO_STREAM_COMBINER:
g_value_take_object (value,
gst_play_bin_get_current_stream_combiner (playbin,
&playbin->video_stream_combiner, "video", PLAYBIN_STREAM_VIDEO));
break;
case PROP_AUDIO_STREAM_COMBINER:
g_value_take_object (value,
gst_play_bin_get_current_stream_combiner (playbin,
&playbin->audio_stream_combiner, "audio", PLAYBIN_STREAM_VIDEO));
break;
case PROP_TEXT_STREAM_COMBINER:
g_value_take_object (value,
gst_play_bin_get_current_stream_combiner (playbin,
&playbin->text_stream_combiner, "text", PLAYBIN_STREAM_VIDEO));
break;
case PROP_VOLUME:
g_value_set_double (value, gst_play_sink_get_volume (playbin->playsink));
break;
@ -2634,7 +2778,7 @@ selector_active_pad_changed (GObject * selector, GParamSpec * pspec,
case GST_PLAY_SINK_TYPE_VIDEO_RAW:
property = "current-video";
playbin->current_video = get_current_stream_number (playbin,
group->video_channels);
select, group->video_channels);
if (playbin->video_pending_flush_finish) {
playbin->video_pending_flush_finish = FALSE;
@ -2648,7 +2792,7 @@ selector_active_pad_changed (GObject * selector, GParamSpec * pspec,
case GST_PLAY_SINK_TYPE_AUDIO_RAW:
property = "current-audio";
playbin->current_audio = get_current_stream_number (playbin,
group->audio_channels);
select, group->audio_channels);
if (playbin->audio_pending_flush_finish) {
playbin->audio_pending_flush_finish = FALSE;
@ -2661,7 +2805,7 @@ selector_active_pad_changed (GObject * selector, GParamSpec * pspec,
case GST_PLAY_SINK_TYPE_TEXT:
property = "current-text";
playbin->current_text = get_current_stream_number (playbin,
group->text_channels);
select, group->text_channels);
if (playbin->text_pending_flush_finish) {
playbin->text_pending_flush_finish = FALSE;
@ -2779,6 +2923,7 @@ pad_added_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
GstSourceSelect *select = NULL;
gint i, pass;
gboolean changed = FALSE;
GstElement *custom_combiner = NULL;
playbin = group->playbin;
@ -2808,6 +2953,16 @@ pad_added_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
gst_caps_unref (media_caps);
}
}
/* get custom stream combiner if there is one */
if (select) {
if (i == PLAYBIN_STREAM_AUDIO) {
custom_combiner = playbin->audio_stream_combiner;
} else if (i == PLAYBIN_STREAM_TEXT) {
custom_combiner = playbin->text_stream_combiner;
} else if (i == PLAYBIN_STREAM_VIDEO) {
custom_combiner = playbin->video_stream_combiner;
}
}
}
/* no selector found for the media type, don't bother linking it to a
* selector. This will leave the pad unlinked and thus ignored. */
@ -2818,7 +2973,11 @@ pad_added_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
if (select->selector == NULL && playbin->have_selector) {
/* no selector, create one */
GST_DEBUG_OBJECT (playbin, "creating new input selector");
select->selector = gst_element_factory_make ("input-selector", NULL);
if (custom_combiner)
select->selector = gst_object_ref (custom_combiner);
else
select->selector = gst_element_factory_make ("input-selector", NULL);
if (select->selector == NULL) {
/* post the missing selector message only once */
playbin->have_selector = FALSE;
@ -2829,15 +2988,23 @@ pad_added_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
(_("Missing element '%s' - check your GStreamer installation."),
"input-selector"), (NULL));
} else {
/* sync-mode=1, use clock */
if (select->type == GST_PLAY_SINK_TYPE_TEXT)
g_object_set (select->selector, "sync-streams", TRUE,
"sync-mode", 1, "cache-buffers", TRUE, NULL);
else
g_object_set (select->selector, "sync-streams", TRUE, NULL);
/* find out which properties the stream combiner supports */
select->has_active_pad =
g_object_class_find_property (G_OBJECT_GET_CLASS (select->selector),
"active-pad") != NULL;
g_signal_connect (select->selector, "notify::active-pad",
G_CALLBACK (selector_active_pad_changed), playbin);
if (!custom_combiner) {
/* sync-mode=1, use clock */
if (select->type == GST_PLAY_SINK_TYPE_TEXT)
g_object_set (select->selector, "sync-streams", TRUE,
"sync-mode", 1, "cache-buffers", TRUE, NULL);
else
g_object_set (select->selector, "sync-streams", TRUE, NULL);
}
if (select->has_active_pad)
g_signal_connect (select->selector, "notify::active-pad",
G_CALLBACK (selector_active_pad_changed), playbin);
GST_DEBUG_OBJECT (playbin, "adding new selector %p", select->selector);
gst_bin_add (GST_BIN_CAST (playbin), select->selector);
@ -2867,29 +3034,40 @@ pad_added_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
/* get sinkpad for the new stream */
if (select->selector) {
if ((sinkpad = gst_element_get_request_pad (select->selector, "sink_%u"))) {
gulong notify_tags_handler = 0;
NotifyTagsData *ntdata;
GST_DEBUG_OBJECT (playbin, "got pad %s:%s from selector",
GST_DEBUG_PAD_NAME (sinkpad));
/* find out which properties the sink pad supports */
select->has_always_ok =
g_object_class_find_property (G_OBJECT_GET_CLASS (sinkpad),
"always-ok") != NULL;
select->has_tags =
g_object_class_find_property (G_OBJECT_GET_CLASS (sinkpad),
"tags") != NULL;
/* store the selector for the pad */
g_object_set_data (G_OBJECT (sinkpad), "playbin.select", select);
/* connect to the notify::tags signal for our
* own *-tags-changed signals
*/
ntdata = g_new0 (NotifyTagsData, 1);
ntdata->playbin = playbin;
ntdata->stream_id = select->channels->len;
ntdata->type = select->type;
if (select->has_tags) {
gulong notify_tags_handler = 0;
NotifyTagsData *ntdata;
notify_tags_handler =
g_signal_connect_data (G_OBJECT (sinkpad), "notify::tags",
G_CALLBACK (notify_tags_cb), ntdata, (GClosureNotify) g_free,
(GConnectFlags) 0);
g_object_set_data (G_OBJECT (sinkpad), "playbin.notify_tags_handler",
(gpointer) (guintptr) notify_tags_handler);
/* connect to the notify::tags signal for our
* own *-tags-changed signals
*/
ntdata = g_new0 (NotifyTagsData, 1);
ntdata->playbin = playbin;
ntdata->stream_id = select->channels->len;
ntdata->type = select->type;
notify_tags_handler =
g_signal_connect_data (G_OBJECT (sinkpad), "notify::tags",
G_CALLBACK (notify_tags_cb), ntdata, (GClosureNotify) g_free,
(GConnectFlags) 0);
g_object_set_data (G_OBJECT (sinkpad), "playbin.notify_tags_handler",
(gpointer) (guintptr) notify_tags_handler);
}
/* store the pad in the array */
GST_DEBUG_OBJECT (playbin, "pad %p added to array", sinkpad);
@ -2926,34 +3104,35 @@ pad_added_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
if (changed) {
int signal;
gboolean always_ok = (decodebin == group->suburidecodebin);
switch (select->type) {
case GST_PLAY_SINK_TYPE_VIDEO:
case GST_PLAY_SINK_TYPE_VIDEO_RAW:
/* we want to return NOT_LINKED for unselected pads but only for pads
* from the normal uridecodebin. This makes sure that subtitle streams
* are not raced past audio/video from decodebin's multiqueue.
* For pads from suburidecodebin OK should always be returned, otherwise
* it will most likely stop. */
g_object_set (sinkpad, "always-ok", always_ok, NULL);
signal = SIGNAL_VIDEO_CHANGED;
break;
case GST_PLAY_SINK_TYPE_AUDIO:
case GST_PLAY_SINK_TYPE_AUDIO_RAW:
g_object_set (sinkpad, "always-ok", always_ok, NULL);
signal = SIGNAL_AUDIO_CHANGED;
break;
case GST_PLAY_SINK_TYPE_TEXT:
g_object_set (sinkpad, "always-ok", always_ok, NULL);
signal = SIGNAL_TEXT_CHANGED;
break;
default:
signal = -1;
}
if (signal >= 0)
if (signal >= 0) {
/* we want to return NOT_LINKED for unselected pads but only for pads
* from the normal uridecodebin. This makes sure that subtitle streams
* are not raced past audio/video from decodebin's multiqueue.
* For pads from suburidecodebin OK should always be returned, otherwise
* it will most likely stop. */
if (select->has_always_ok) {
gboolean always_ok = (decodebin == group->suburidecodebin);
g_object_set (sinkpad, "always-ok", always_ok, NULL);
}
g_signal_emit (G_OBJECT (playbin), gst_play_bin_signals[signal], 0, NULL);
}
}
done:
@ -3007,14 +3186,16 @@ pad_removed_cb (GstElement * decodebin, GstPad * pad, GstSourceGroup * group)
goto not_linked;
if ((select = g_object_get_data (G_OBJECT (peer), "playbin.select"))) {
gulong notify_tags_handler;
if (select->has_tags) {
gulong notify_tags_handler;
notify_tags_handler =
(guintptr) g_object_get_data (G_OBJECT (peer),
"playbin.notify_tags_handler");
if (notify_tags_handler != 0)
g_signal_handler_disconnect (G_OBJECT (peer), notify_tags_handler);
g_object_set_data (G_OBJECT (peer), "playbin.notify_tags_handler", NULL);
notify_tags_handler =
(guintptr) g_object_get_data (G_OBJECT (peer),
"playbin.notify_tags_handler");
if (notify_tags_handler != 0)
g_signal_handler_disconnect (G_OBJECT (peer), notify_tags_handler);
g_object_set_data (G_OBJECT (peer), "playbin.notify_tags_handler", NULL);
}
/* remove the pad from the array */
g_ptr_array_remove (select->channels, peer);