gstreamer/gst/playback/gstplaysink.c
Josep Torra Valles d58f4fcf48 playsink: Reset the sink's state to NULL before unreffing it unless it's the same instance again
This makes sure that we don't destroy the last reference before the
element gets back to NULL state. Fixes assertion failures if a playbin2
instance is reused but different sinks are automatically chosen because
of different caps.
2010-02-12 19:43:13 +01:00

2645 lines
83 KiB
C

/* GStreamer
* Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <gst/gst.h>
#include <gst/gst-i18n-plugin.h>
#include <gst/pbutils/pbutils.h>
#include "gstplaysink.h"
GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug);
#define GST_CAT_DEFAULT gst_play_sink_debug
#define VOLUME_MAX_DOUBLE 10.0
#define DEFAULT_FLAGS GST_PLAY_FLAG_AUDIO | GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT | \
GST_PLAY_FLAG_SOFT_VOLUME
#define GST_PLAY_CHAIN(c) ((GstPlayChain *)(c))
/* holds the common data fields for the audio and video pipelines. We keep them
* in a structure to more easily have all the info available. */
typedef struct
{
GstPlaySink *playsink;
GstElement *bin;
gboolean added;
gboolean activated;
gboolean raw;
} GstPlayChain;
typedef struct
{
GstPlayChain chain;
GstPad *sinkpad;
GstElement *queue;
GstElement *conv;
GstElement *resample;
GstElement *volume; /* element with the volume property */
gboolean sink_volume; /* if the volume was provided by the sink */
GstElement *mute; /* element with the mute property */
GstElement *sink;
} GstPlayAudioChain;
typedef struct
{
GstPlayChain chain;
GstPad *sinkpad;
GstElement *queue;
GstElement *conv;
GstElement *scale;
GstElement *sink;
gboolean async;
} GstPlayVideoChain;
typedef struct
{
GstPlayChain chain;
GstPad *sinkpad;
GstElement *queue;
GstElement *conv;
GstElement *resample;
GstPad *blockpad; /* srcpad of resample, used for switching the vis */
GstPad *vissinkpad; /* visualisation sinkpad, */
GstElement *vis;
GstPad *vissrcpad; /* visualisation srcpad, */
GstPad *srcpad; /* outgoing srcpad, used to connect to the next
* chain */
} GstPlayVisChain;
typedef struct
{
GstPlayChain chain;
GstPad *sinkpad;
GstElement *queue;
GstElement *identity;
GstElement *overlay;
GstPad *videosinkpad;
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)
#define GST_PLAY_SINK_LOCK(playsink) g_static_rec_mutex_lock (GST_PLAY_SINK_GET_LOCK (playsink))
#define GST_PLAY_SINK_UNLOCK(playsink) g_static_rec_mutex_unlock (GST_PLAY_SINK_GET_LOCK (playsink))
struct _GstPlaySink
{
GstBin bin;
GStaticRecMutex lock;
gboolean async_pending;
gboolean need_async_start;
GstPlayFlags flags;
/* chains */
GstPlayAudioChain *audiochain;
GstPlayVideoChain *videochain;
GstPlayVisChain *vischain;
GstPlayTextChain *textchain;
/* audio */
GstPad *audio_pad;
gboolean audio_pad_raw;
/* audio tee */
GstElement *audio_tee;
GstPad *audio_tee_sink;
GstPad *audio_tee_asrc;
GstPad *audio_tee_vissrc;
/* video */
GstPad *video_pad;
gboolean video_pad_raw;
/* text */
GstPad *text_pad;
/* properties */
GstElement *audio_sink;
GstElement *video_sink;
GstElement *visualisation;
GstElement *text_sink;
gdouble volume;
gboolean mute;
gchar *font_desc; /* font description */
guint connection_speed; /* connection speed in bits/sec (0 = unknown) */
gint count;
gboolean volume_changed; /* volume/mute changed while no audiochain */
gboolean mute_changed; /* ... has been created yet */
};
struct _GstPlaySinkClass
{
GstBinClass parent_class;
gboolean (*reconfigure) (GstPlaySink * playsink);
};
static GstStaticPadTemplate audiorawtemplate =
GST_STATIC_PAD_TEMPLATE ("audio_raw_sink",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate audiotemplate =
GST_STATIC_PAD_TEMPLATE ("audio_sink",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate videorawtemplate =
GST_STATIC_PAD_TEMPLATE ("video_raw_sink",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate videotemplate =
GST_STATIC_PAD_TEMPLATE ("video_sink",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate texttemplate = GST_STATIC_PAD_TEMPLATE ("text_sink",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS_ANY);
/* props */
enum
{
PROP_0,
PROP_FLAGS,
PROP_MUTE,
PROP_VOLUME,
PROP_FONT_DESC,
PROP_VIS_PLUGIN,
PROP_LAST
};
/* signals */
enum
{
LAST_SIGNAL
};
static void gst_play_sink_class_init (GstPlaySinkClass * klass);
static void gst_play_sink_init (GstPlaySink * playsink);
static void gst_play_sink_dispose (GObject * object);
static void gst_play_sink_finalize (GObject * object);
static void gst_play_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * spec);
static void gst_play_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * spec);
static GstPad *gst_play_sink_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name);
static void gst_play_sink_release_request_pad (GstElement * element,
GstPad * pad);
static gboolean gst_play_sink_send_event (GstElement * element,
GstEvent * event);
static GstStateChangeReturn gst_play_sink_change_state (GstElement * element,
GstStateChange transition);
static void gst_play_sink_handle_message (GstBin * bin, GstMessage * message);
/* static guint gst_play_sink_signals[LAST_SIGNAL] = { 0 }; */
G_DEFINE_TYPE (GstPlaySink, gst_play_sink, GST_TYPE_BIN);
static void
gst_play_sink_class_init (GstPlaySinkClass * klass)
{
GObjectClass *gobject_klass;
GstElementClass *gstelement_klass;
GstBinClass *gstbin_klass;
gobject_klass = (GObjectClass *) klass;
gstelement_klass = (GstElementClass *) klass;
gstbin_klass = (GstBinClass *) klass;
gobject_klass->dispose = gst_play_sink_dispose;
gobject_klass->finalize = gst_play_sink_finalize;
gobject_klass->set_property = gst_play_sink_set_property;
gobject_klass->get_property = gst_play_sink_get_property;
/**
* GstPlaySink:flags
*
* Control the behaviour of playsink.
*/
g_object_class_install_property (gobject_klass, PROP_FLAGS,
g_param_spec_flags ("flags", "Flags", "Flags to control behaviour",
GST_TYPE_PLAY_FLAGS, DEFAULT_FLAGS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstPlaySink:volume:
*
* Get or set the current audio stream volume. 1.0 means 100%,
* 0.0 means mute. This uses a linear volume scale.
*
*/
g_object_class_install_property (gobject_klass, PROP_VOLUME,
g_param_spec_double ("volume", "Volume", "The audio volume, 1.0=100%",
0.0, VOLUME_MAX_DOUBLE, 1.0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_klass, PROP_MUTE,
g_param_spec_boolean ("mute", "Mute",
"Mute the audio channel without changing the volume", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_klass, PROP_FONT_DESC,
g_param_spec_string ("subtitle-font-desc",
"Subtitle font description",
"Pango font description of font "
"to be used for subtitle rendering", NULL,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_klass, PROP_VIS_PLUGIN,
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_signal_new ("reconfigure", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstPlaySinkClass,
reconfigure), NULL, NULL, gst_marshal_BOOLEAN__VOID, G_TYPE_BOOLEAN,
0, G_TYPE_NONE);
gst_element_class_add_pad_template (gstelement_klass,
gst_static_pad_template_get (&audiorawtemplate));
gst_element_class_add_pad_template (gstelement_klass,
gst_static_pad_template_get (&audiotemplate));
gst_element_class_add_pad_template (gstelement_klass,
gst_static_pad_template_get (&videorawtemplate));
gst_element_class_add_pad_template (gstelement_klass,
gst_static_pad_template_get (&videotemplate));
gst_element_class_add_pad_template (gstelement_klass,
gst_static_pad_template_get (&texttemplate));
gst_element_class_set_details_simple (gstelement_klass, "Player Sink",
"Generic/Bin/Sink",
"Convenience sink for multiple streams",
"Wim Taymans <wim.taymans@gmail.com>");
gstelement_klass->change_state =
GST_DEBUG_FUNCPTR (gst_play_sink_change_state);
gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_sink_send_event);
gstelement_klass->request_new_pad =
GST_DEBUG_FUNCPTR (gst_play_sink_request_new_pad);
gstelement_klass->release_pad =
GST_DEBUG_FUNCPTR (gst_play_sink_release_request_pad);
gstbin_klass->handle_message =
GST_DEBUG_FUNCPTR (gst_play_sink_handle_message);
klass->reconfigure = GST_DEBUG_FUNCPTR (gst_play_sink_reconfigure);
}
static void
gst_play_sink_init (GstPlaySink * playsink)
{
/* init groups */
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 = DEFAULT_FLAGS;
g_static_rec_mutex_init (&playsink->lock);
GST_OBJECT_FLAG_SET (playsink, GST_ELEMENT_IS_SINK);
}
static void
free_chain (GstPlayChain * chain)
{
if (chain) {
if (chain->bin)
gst_object_unref (chain->bin);
g_free (chain);
}
}
static void
gst_play_sink_dispose (GObject * object)
{
GstPlaySink *playsink;
playsink = GST_PLAY_SINK (object);
if (playsink->audio_sink != NULL) {
gst_element_set_state (playsink->audio_sink, GST_STATE_NULL);
gst_object_unref (playsink->audio_sink);
playsink->audio_sink = NULL;
}
if (playsink->video_sink != NULL) {
gst_element_set_state (playsink->video_sink, GST_STATE_NULL);
gst_object_unref (playsink->video_sink);
playsink->video_sink = NULL;
}
if (playsink->visualisation != NULL) {
gst_element_set_state (playsink->visualisation, GST_STATE_NULL);
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;
free_chain ((GstPlayChain *) playsink->audiochain);
playsink->audiochain = NULL;
free_chain ((GstPlayChain *) playsink->vischain);
playsink->vischain = NULL;
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;
}
if (playsink->audio_tee_vissrc) {
gst_element_release_request_pad (playsink->audio_tee,
playsink->audio_tee_vissrc);
gst_object_unref (playsink->audio_tee_vissrc);
playsink->audio_tee_vissrc = NULL;
}
if (playsink->audio_tee_asrc) {
gst_element_release_request_pad (playsink->audio_tee,
playsink->audio_tee_asrc);
gst_object_unref (playsink->audio_tee_asrc);
playsink->audio_tee_asrc = NULL;
}
g_free (playsink->font_desc);
playsink->font_desc = NULL;
G_OBJECT_CLASS (gst_play_sink_parent_class)->dispose (object);
}
static void
gst_play_sink_finalize (GObject * object)
{
GstPlaySink *playsink;
playsink = GST_PLAY_SINK (object);
g_static_rec_mutex_free (&playsink->lock);
G_OBJECT_CLASS (gst_play_sink_parent_class)->finalize (object);
}
void
gst_play_sink_set_sink (GstPlaySink * playsink, GstPlaySinkType type,
GstElement * sink)
{
GstElement **elem = NULL, *old = NULL;
GST_LOG ("Setting sink %" GST_PTR_FORMAT " as sink type %d", sink, type);
GST_PLAY_SINK_LOCK (playsink);
switch (type) {
case GST_PLAY_SINK_TYPE_AUDIO:
case GST_PLAY_SINK_TYPE_AUDIO_RAW:
elem = &playsink->audio_sink;
break;
case GST_PLAY_SINK_TYPE_VIDEO:
case GST_PLAY_SINK_TYPE_VIDEO_RAW:
elem = &playsink->video_sink;
break;
case GST_PLAY_SINK_TYPE_TEXT:
elem = &playsink->text_sink;
break;
default:
break;
}
if (elem) {
old = *elem;
if (sink)
gst_object_ref (sink);
*elem = sink;
}
GST_PLAY_SINK_UNLOCK (playsink);
if (old) {
if (old != sink)
gst_element_set_state (old, GST_STATE_NULL);
gst_object_unref (old);
}
}
GstElement *
gst_play_sink_get_sink (GstPlaySink * playsink, GstPlaySinkType type)
{
GstElement *result = NULL;
GstElement *elem = NULL, *chainp = NULL;
GST_PLAY_SINK_LOCK (playsink);
switch (type) {
case GST_PLAY_SINK_TYPE_AUDIO:
{
GstPlayAudioChain *chain;
if ((chain = (GstPlayAudioChain *) playsink->audiochain))
chainp = chain->sink;
elem = playsink->audio_sink;
break;
}
case GST_PLAY_SINK_TYPE_VIDEO:
{
GstPlayVideoChain *chain;
if ((chain = (GstPlayVideoChain *) playsink->videochain))
chainp = chain->sink;
elem = playsink->video_sink;
break;
}
case GST_PLAY_SINK_TYPE_TEXT:
{
GstPlayTextChain *chain;
if ((chain = (GstPlayTextChain *) playsink->textchain))
chainp = chain->sink;
elem = playsink->text_sink;
break;
}
default:
break;
}
if (chainp) {
/* we have an active chain with a sink, get the sink */
result = gst_object_ref (chainp);
}
/* nothing found, return last configured sink */
if (result == NULL && elem)
result = gst_object_ref (elem);
GST_PLAY_SINK_UNLOCK (playsink);
return result;
}
static void
gst_play_sink_vis_unblocked (GstPad * tee_pad, gboolean blocked,
gpointer user_data)
{
GstPlaySink *playsink;
playsink = GST_PLAY_SINK (user_data);
/* nothing to do here, we need a dummy callback here to make the async call
* non-blocking. */
GST_DEBUG_OBJECT (playsink, "vis pad unblocked");
}
static void
gst_play_sink_vis_blocked (GstPad * tee_pad, gboolean blocked,
gpointer user_data)
{
GstPlaySink *playsink;
GstPlayVisChain *chain;
playsink = GST_PLAY_SINK (user_data);
GST_PLAY_SINK_LOCK (playsink);
GST_DEBUG_OBJECT (playsink, "vis pad blocked");
/* now try to change the plugin in the running vis chain */
if (!(chain = (GstPlayVisChain *) playsink->vischain))
goto done;
/* unlink the old plugin and unghost the pad */
gst_pad_unlink (chain->blockpad, chain->vissinkpad);
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (chain->srcpad), NULL);
/* set the old plugin to NULL and remove */
gst_element_set_state (chain->vis, GST_STATE_NULL);
gst_bin_remove (GST_BIN_CAST (chain->chain.bin), chain->vis);
/* add new plugin and set state to playing */
chain->vis = playsink->visualisation;
gst_bin_add (GST_BIN_CAST (chain->chain.bin), chain->vis);
gst_element_set_state (chain->vis, GST_STATE_PLAYING);
/* get pads */
chain->vissinkpad = gst_element_get_static_pad (chain->vis, "sink");
chain->vissrcpad = gst_element_get_static_pad (chain->vis, "src");
/* link pads */
gst_pad_link (chain->blockpad, chain->vissinkpad);
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (chain->srcpad),
chain->vissrcpad);
done:
/* Unblock the pad */
gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_sink_vis_unblocked,
playsink);
GST_PLAY_SINK_UNLOCK (playsink);
}
void
gst_play_sink_set_vis_plugin (GstPlaySink * playsink, GstElement * vis)
{
GstPlayVisChain *chain;
/* setting NULL means creating the default vis plugin */
if (vis == NULL)
vis = gst_element_factory_make ("goom", "vis");
/* simply return if we don't have a vis plugin here */
if (vis == NULL)
return;
GST_PLAY_SINK_LOCK (playsink);
/* first store the new vis */
if (playsink->visualisation)
gst_object_unref (playsink->visualisation);
/* take ownership */
gst_object_ref_sink (vis);
playsink->visualisation = vis;
/* now try to change the plugin in the running vis chain, if we have no chain,
* we don't bother, any future vis chain will be created with the new vis
* plugin. */
if (!(chain = (GstPlayVisChain *) playsink->vischain))
goto done;
/* block the pad, the next time the callback is called we can change the
* visualisation. It's possible that this never happens or that the pad was
* already blocked. If the callback never happens, we don't have new data so
* we don't need the new vis plugin. If the pad was already blocked, the
* function returns FALSE but the previous pad block will do the right thing
* anyway. */
GST_DEBUG_OBJECT (playsink, "blocking vis pad");
gst_pad_set_blocked_async (chain->blockpad, TRUE, gst_play_sink_vis_blocked,
playsink);
done:
GST_PLAY_SINK_UNLOCK (playsink);
return;
}
GstElement *
gst_play_sink_get_vis_plugin (GstPlaySink * playsink)
{
GstElement *result = NULL;
GstPlayVisChain *chain;
GST_PLAY_SINK_LOCK (playsink);
if ((chain = (GstPlayVisChain *) playsink->vischain)) {
/* we have an active chain, get the sink */
if (chain->vis)
result = gst_object_ref (chain->vis);
}
/* nothing found, return last configured sink */
if (result == NULL && playsink->visualisation)
result = gst_object_ref (playsink->visualisation);
GST_PLAY_SINK_UNLOCK (playsink);
return result;
}
void
gst_play_sink_set_volume (GstPlaySink * playsink, gdouble volume)
{
GstPlayAudioChain *chain;
GST_PLAY_SINK_LOCK (playsink);
playsink->volume = volume;
chain = (GstPlayAudioChain *) playsink->audiochain;
if (chain && chain->volume) {
GST_LOG_OBJECT (playsink, "elements: volume=%" GST_PTR_FORMAT ", mute=%"
GST_PTR_FORMAT "; new volume=%.03f, mute=%d", chain->volume,
chain->mute, volume, playsink->mute);
/* if there is a mute element or we are not muted, set the volume */
if (chain->mute || !playsink->mute)
g_object_set (chain->volume, "volume", volume, NULL);
} else {
GST_LOG_OBJECT (playsink, "no volume element");
playsink->volume_changed = TRUE;
}
GST_PLAY_SINK_UNLOCK (playsink);
}
gdouble
gst_play_sink_get_volume (GstPlaySink * playsink)
{
gdouble result;
GstPlayAudioChain *chain;
GST_PLAY_SINK_LOCK (playsink);
chain = (GstPlayAudioChain *) playsink->audiochain;
result = playsink->volume;
if (chain && chain->volume) {
if (chain->mute || !playsink->mute) {
g_object_get (chain->volume, "volume", &result, NULL);
playsink->volume = result;
}
}
GST_PLAY_SINK_UNLOCK (playsink);
return result;
}
void
gst_play_sink_set_mute (GstPlaySink * playsink, gboolean mute)
{
GstPlayAudioChain *chain;
GST_PLAY_SINK_LOCK (playsink);
playsink->mute = mute;
chain = (GstPlayAudioChain *) playsink->audiochain;
if (chain) {
if (chain->mute) {
g_object_set (chain->mute, "mute", mute, NULL);
} else if (chain->volume) {
if (mute)
g_object_set (chain->volume, "volume", (gdouble) 0.0, NULL);
else
g_object_set (chain->volume, "volume", (gdouble) playsink->volume,
NULL);
}
} else {
playsink->mute_changed = TRUE;
}
GST_PLAY_SINK_UNLOCK (playsink);
}
gboolean
gst_play_sink_get_mute (GstPlaySink * playsink)
{
gboolean result;
GstPlayAudioChain *chain;
GST_PLAY_SINK_LOCK (playsink);
chain = (GstPlayAudioChain *) playsink->audiochain;
if (chain && chain->mute) {
g_object_get (chain->mute, "mute", &result, NULL);
playsink->mute = result;
} else {
result = playsink->mute;
}
GST_PLAY_SINK_UNLOCK (playsink);
return result;
}
static void
post_missing_element_message (GstPlaySink * playsink, const gchar * name)
{
GstMessage *msg;
msg = gst_missing_element_message_new (GST_ELEMENT_CAST (playsink), name);
gst_element_post_message (GST_ELEMENT_CAST (playsink), msg);
}
static gboolean
add_chain (GstPlayChain * chain, gboolean add)
{
if (chain->added == add)
return TRUE;
if (add)
gst_bin_add (GST_BIN_CAST (chain->playsink), chain->bin);
else {
gst_bin_remove (GST_BIN_CAST (chain->playsink), chain->bin);
/* we don't want to lose our sink status */
GST_OBJECT_FLAG_SET (chain->playsink, GST_ELEMENT_IS_SINK);
}
chain->added = add;
return TRUE;
}
static gboolean
activate_chain (GstPlayChain * chain, gboolean activate)
{
GstState state;
if (chain->activated == activate)
return TRUE;
GST_OBJECT_LOCK (chain->playsink);
state = GST_STATE_TARGET (chain->playsink);
GST_OBJECT_UNLOCK (chain->playsink);
if (activate)
gst_element_set_state (chain->bin, state);
else
gst_element_set_state (chain->bin, GST_STATE_NULL);
chain->activated = activate;
return TRUE;
}
static gboolean
element_is_sink (GstElement * element)
{
gboolean is_sink;
GST_OBJECT_LOCK (element);
is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_IS_SINK);
GST_OBJECT_UNLOCK (element);
GST_DEBUG_OBJECT (element, "is a sink: %s", (is_sink) ? "yes" : "no");
return is_sink;
}
static gboolean
element_has_property (GstElement * element, const gchar * pname, GType type)
{
GParamSpec *pspec;
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (element), pname);
if (pspec == NULL) {
GST_DEBUG_OBJECT (element, "no %s property", pname);
return FALSE;
}
if (type == G_TYPE_INVALID || type == pspec->value_type ||
g_type_is_a (pspec->value_type, type)) {
GST_DEBUG_OBJECT (element, "has %s property of type %s", pname,
(type == G_TYPE_INVALID) ? "any type" : g_type_name (type));
return TRUE;
}
GST_WARNING_OBJECT (element, "has %s property, but property is of type %s "
"and we expected it to be of type %s", pname,
g_type_name (pspec->value_type), g_type_name (type));
return FALSE;
}
typedef struct
{
const gchar *prop_name;
GType prop_type;
gboolean need_sink;
} FindPropertyHelper;
static gint
find_property (GstElement * element, FindPropertyHelper * helper)
{
if (helper->need_sink && !element_is_sink (element)) {
gst_object_unref (element);
return 1;
}
if (!element_has_property (element, helper->prop_name, helper->prop_type)) {
gst_object_unref (element);
return 1;
}
GST_INFO_OBJECT (element, "found %s with %s property", helper->prop_name,
(helper->need_sink) ? "sink" : "element");
return 0; /* keep it */
}
/* FIXME: why not move these functions into core? */
/* 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, GType expected_type)
{
GstElement *result = NULL;
GstIterator *it;
if (element_has_property (obj, name, expected_type)) {
result = obj;
} else if (GST_IS_BIN (obj)) {
FindPropertyHelper helper = { name, expected_type, TRUE };
it = gst_bin_iterate_recurse (GST_BIN_CAST (obj));
result = gst_iterator_find_custom (it,
(GCompareFunc) find_property, &helper);
gst_iterator_free (it);
/* we don't need the extra ref */
if (result)
gst_object_unref (result);
}
return result;
}
/* find an object in the hierarchy with a property named @name */
static GstElement *
gst_play_sink_find_property (GstPlaySink * playsink, GstElement * obj,
const gchar * name, GType expected_type)
{
GstElement *result = NULL;
GstIterator *it;
if (GST_IS_BIN (obj)) {
FindPropertyHelper helper = { name, expected_type, FALSE };
it = gst_bin_iterate_recurse (GST_BIN_CAST (obj));
result = gst_iterator_find_custom (it,
(GCompareFunc) find_property, &helper);
gst_iterator_free (it);
} else {
if (element_has_property (obj, name, expected_type)) {
result = obj;
gst_object_ref (obj);
}
}
return result;
}
static void
do_async_start (GstPlaySink * playsink)
{
GstMessage *message;
if (!playsink->need_async_start) {
GST_INFO_OBJECT (playsink, "no async_start needed");
return;
}
playsink->async_pending = TRUE;
GST_INFO_OBJECT (playsink, "Sending async_start message");
message = gst_message_new_async_start (GST_OBJECT_CAST (playsink), FALSE);
GST_BIN_CLASS (gst_play_sink_parent_class)->handle_message (GST_BIN_CAST
(playsink), message);
}
static void
do_async_done (GstPlaySink * playsink)
{
GstMessage *message;
if (playsink->async_pending) {
GST_INFO_OBJECT (playsink, "Sending async_done message");
message = gst_message_new_async_done (GST_OBJECT_CAST (playsink));
GST_BIN_CLASS (gst_play_sink_parent_class)->handle_message (GST_BIN_CAST
(playsink), message);
playsink->async_pending = FALSE;
}
playsink->need_async_start = FALSE;
}
/* try to change the state of an element. This function returns the element when
* the state change could be performed. When this function returns NULL an error
* occured and the element is unreffed if @unref is TRUE. */
static GstElement *
try_element (GstPlaySink * playsink, GstElement * element, gboolean unref)
{
GstStateChangeReturn ret;
if (element) {
ret = gst_element_set_state (element, GST_STATE_READY);
if (ret == GST_STATE_CHANGE_FAILURE) {
GST_DEBUG_OBJECT (playsink, "failed state change..");
gst_element_set_state (element, GST_STATE_NULL);
if (unref)
gst_object_unref (element);
element = NULL;
}
}
return element;
}
/* make the element (bin) that contains the elements needed to perform
* video display.
*
* +------------------------------------------------------------+
* | vbin |
* | +-------+ +----------+ +----------+ +---------+ |
* | | queue | |colorspace| |videoscale| |videosink| |
* | +-sink src-sink src-sink src-sink | |
* | | +-------+ +----------+ +----------+ +---------+ |
* sink-+ |
* +------------------------------------------------------------+
*
*/
static GstPlayVideoChain *
gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async,
gboolean queue)
{
GstPlayVideoChain *chain;
GstBin *bin;
GstPad *pad;
GstElement *head = NULL, *prev = NULL, *elem = NULL;
chain = g_new0 (GstPlayVideoChain, 1);
chain->chain.playsink = playsink;
chain->chain.raw = raw;
GST_DEBUG_OBJECT (playsink, "making video chain %p", chain);
if (playsink->video_sink) {
GST_DEBUG_OBJECT (playsink, "trying configured videosink");
chain->sink = try_element (playsink, playsink->video_sink, FALSE);
} else {
/* only try fallback if no specific sink was chosen */
if (chain->sink == NULL) {
GST_DEBUG_OBJECT (playsink, "trying autovideosink");
elem = gst_element_factory_make ("autovideosink", "videosink");
chain->sink = try_element (playsink, elem, TRUE);
}
if (chain->sink == NULL) {
/* if default sink from config.h is different then try it too */
if (strcmp (DEFAULT_VIDEOSINK, "autovideosink")) {
GST_DEBUG_OBJECT (playsink, "trying " DEFAULT_VIDEOSINK);
elem = gst_element_factory_make (DEFAULT_VIDEOSINK, "videosink");
chain->sink = try_element (playsink, elem, TRUE);
}
}
}
if (chain->sink == NULL)
goto no_sinks;
head = chain->sink;
/* if we can disable async behaviour of the sink, we can avoid adding a
* queue for the audio chain. */
elem =
gst_play_sink_find_property_sinks (playsink, chain->sink, "async",
G_TYPE_BOOLEAN);
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");
chain->async = TRUE;
}
/* create a bin to hold objects, as we create them we add them to this bin so
* that when something goes wrong we only need to unref the bin */
chain->chain.bin = gst_bin_new ("vbin");
bin = GST_BIN_CAST (chain->chain.bin);
gst_object_ref_sink (bin);
gst_bin_add (bin, chain->sink);
if (queue) {
/* decouple decoder from sink, this improves playback quite a lot since the
* decoder can continue while the sink blocks for synchronisation. We don't
* need a lot of buffers as this consumes a lot of memory and we don't want
* too little because else we would be context switching too quickly. */
chain->queue = gst_element_factory_make ("queue", "vqueue");
g_object_set (G_OBJECT (chain->queue), "max-size-buffers", 3,
"max-size-bytes", 0, "max-size-time", (gint64) 0, NULL);
gst_bin_add (bin, chain->queue);
head = prev = chain->queue;
} else {
head = chain->sink;
prev = NULL;
}
if (raw && !(playsink->flags & GST_PLAY_FLAG_NATIVE_VIDEO)) {
GST_DEBUG_OBJECT (playsink, "creating ffmpegcolorspace");
chain->conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
if (chain->conv == NULL) {
post_missing_element_message (playsink, "ffmpegcolorspace");
GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"ffmpegcolorspace"), ("video rendering might fail"));
} else {
gst_bin_add (bin, chain->conv);
if (prev) {
if (!gst_element_link_pads (prev, "src", chain->conv, "sink"))
goto link_failed;
} else {
head = chain->conv;
}
prev = chain->conv;
}
GST_DEBUG_OBJECT (playsink, "creating videoscale");
chain->scale = gst_element_factory_make ("videoscale", "vscale");
if (chain->scale == NULL) {
post_missing_element_message (playsink, "videoscale");
GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"videoscale"), ("possibly a liboil version mismatch?"));
} else {
gst_bin_add (bin, chain->scale);
if (prev) {
if (!gst_element_link_pads (prev, "src", chain->scale, "sink"))
goto link_failed;
} else {
head = chain->scale;
}
prev = chain->scale;
}
}
if (prev) {
GST_DEBUG_OBJECT (playsink, "linking to sink");
if (!gst_element_link_pads (prev, "src", chain->sink, NULL))
goto link_failed;
}
pad = gst_element_get_static_pad (head, "sink");
chain->sinkpad = gst_ghost_pad_new ("sink", pad);
gst_object_unref (pad);
gst_element_add_pad (chain->chain.bin, chain->sinkpad);
return chain;
/* ERRORS */
no_sinks:
{
if (!elem) {
post_missing_element_message (playsink, "autovideosink");
if (strcmp (DEFAULT_VIDEOSINK, "autovideosink")) {
post_missing_element_message (playsink, DEFAULT_VIDEOSINK);
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("Both autovideosink and %s elements are missing."),
DEFAULT_VIDEOSINK), (NULL));
} else {
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("The autovideosink element is missing.")), (NULL));
}
} else {
if (strcmp (DEFAULT_VIDEOSINK, "autovideosink")) {
GST_ELEMENT_ERROR (playsink, CORE, STATE_CHANGE,
(_("Both autovideosink and %s elements are not working."),
DEFAULT_VIDEOSINK), (NULL));
} else {
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("The autovideosink element is not working.")), (NULL));
}
}
free_chain ((GstPlayChain *) chain);
return NULL;
}
link_failed:
{
GST_ELEMENT_ERROR (playsink, CORE, PAD,
(NULL), ("Failed to configure the video sink."));
free_chain ((GstPlayChain *) chain);
return NULL;
}
}
static gboolean
setup_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async,
gboolean queue)
{
GstElement *elem;
GstPlayVideoChain *chain;
GstStateChangeReturn ret;
chain = playsink->videochain;
/* if the chain was active we don't do anything */
if (GST_PLAY_CHAIN (chain)->activated == TRUE)
return TRUE;
if (chain->chain.raw != raw)
return FALSE;
/* try to set the sink element to READY again */
ret = gst_element_set_state (chain->sink, GST_STATE_READY);
if (ret == GST_STATE_CHANGE_FAILURE)
return FALSE;
/* if we can disable async behaviour of the sink, we can avoid adding a
* queue for the audio chain. */
elem =
gst_play_sink_find_property_sinks (playsink, chain->sink, "async",
G_TYPE_BOOLEAN);
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");
chain->async = TRUE;
}
return TRUE;
}
/* make an element for playback of video with subtitles embedded.
*
* +--------------------------------------------+
* | tbin |
* | +--------+ +-----------------+ |
* | | queue | | subtitleoverlay | |
* video--src sink---video_sink | |
* | +--------+ | src--src
* text------------------text_sink | |
* | +-----------------+ |
* +--------------------------------------------+
*
*/
static GstPlayTextChain *
gen_text_chain (GstPlaySink * playsink)
{
GstPlayTextChain *chain;
GstBin *bin;
GstElement *elem;
GstPad *videosinkpad, *textsinkpad, *srcpad;
chain = g_new0 (GstPlayTextChain, 1);
chain->chain.playsink = playsink;
GST_DEBUG_OBJECT (playsink, "making text chain %p", chain);
chain->chain.bin = gst_bin_new ("tbin");
bin = GST_BIN_CAST (chain->chain.bin);
gst_object_ref_sink (bin);
videosinkpad = textsinkpad = srcpad = NULL;
/* first try to hook the text pad to the custom sink */
if (playsink->text_sink) {
GST_DEBUG_OBJECT (playsink, "trying configured textsink");
chain->sink = try_element (playsink, playsink->text_sink, FALSE);
if (chain->sink) {
elem =
gst_play_sink_find_property_sinks (playsink, chain->sink, "async",
G_TYPE_BOOLEAN);
if (elem) {
/* make sure the sparse subtitles don't participate in the preroll */
g_object_set (elem, "async", FALSE, 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;
}
/* try to set sync to true but it's no biggie when we can't */
if ((elem =
gst_play_sink_find_property_sinks (playsink, chain->sink,
"sync", G_TYPE_BOOLEAN)))
g_object_set (elem, "sync", TRUE, 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"));
}
}
if (textsinkpad == NULL) {
if (!(playsink->flags & GST_PLAY_FLAG_NATIVE_VIDEO)) {
/* make a little queue */
chain->queue = gst_element_factory_make ("queue", "vqueue");
g_object_set (G_OBJECT (chain->queue), "max-size-buffers", 3,
"max-size-bytes", 0, "max-size-time", (gint64) 0, NULL);
gst_bin_add (bin, chain->queue);
videosinkpad = gst_element_get_static_pad (chain->queue, "sink");
chain->overlay =
gst_element_factory_make ("subtitleoverlay", "suboverlay");
if (chain->overlay == NULL) {
post_missing_element_message (playsink, "subtitleoverlay");
GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"subtitleoverlay"), ("subtitle rendering disabled"));
} else {
gst_bin_add (bin, chain->overlay);
g_object_set (G_OBJECT (chain->overlay), "silent", FALSE, NULL);
if (playsink->font_desc) {
g_object_set (G_OBJECT (chain->overlay), "font-desc",
playsink->font_desc, NULL);
}
gst_element_link_pads (chain->queue, "src", chain->overlay,
"video_sink");
textsinkpad =
gst_element_get_static_pad (chain->overlay, "subtitle_sink");
srcpad = gst_element_get_static_pad (chain->overlay, "src");
}
}
}
if (videosinkpad == NULL) {
/* if we still don't have a videosink, we don't have an overlay. the only
* thing we can do is insert an identity and ghost the src
* and sink pads. */
chain->identity = gst_element_factory_make ("identity", "tidentity");
g_object_set (chain->identity, "signal-handoffs", FALSE, NULL);
g_object_set (chain->identity, "silent", TRUE, NULL);
gst_bin_add (bin, chain->identity);
srcpad = gst_element_get_static_pad (chain->identity, "src");
videosinkpad = gst_element_get_static_pad (chain->identity, "sink");
}
/* 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;
}
static void
notify_volume_cb (GObject * object, GParamSpec * pspec, GstPlaySink * playsink)
{
gdouble vol;
g_object_get (object, "volume", &vol, NULL);
playsink->volume = vol;
g_object_notify (G_OBJECT (playsink), "volume");
}
static void
notify_mute_cb (GObject * object, GParamSpec * pspec, GstPlaySink * playsink)
{
gboolean mute;
g_object_get (object, "mute", &mute, NULL);
playsink->mute = mute;
g_object_notify (G_OBJECT (playsink), "mute");
}
/* make the chain that contains the elements needed to perform
* audio playback.
*
* We add a tee as the first element so that we can link the visualisation chain
* to it when requested.
*
* +-------------------------------------------------------------+
* | abin |
* | +---------+ +----------+ +---------+ +---------+ |
* | |audioconv| |audioscale| | volume | |audiosink| |
* | +-srck src-sink src-sink src-sink | |
* | | +---------+ +----------+ +---------+ +---------+ |
* sink-+ |
* +-------------------------------------------------------------+
*/
static GstPlayAudioChain *
gen_audio_chain (GstPlaySink * playsink, gboolean raw, gboolean queue)
{
GstPlayAudioChain *chain;
GstBin *bin;
gboolean have_volume;
GstPad *pad;
GstElement *head, *prev, *elem = NULL;
chain = g_new0 (GstPlayAudioChain, 1);
chain->chain.playsink = playsink;
chain->chain.raw = raw;
GST_DEBUG_OBJECT (playsink, "making audio chain %p", chain);
if (playsink->audio_sink) {
GST_DEBUG_OBJECT (playsink, "trying configured audiosink %" GST_PTR_FORMAT,
playsink->audio_sink);
chain->sink = try_element (playsink, playsink->audio_sink, FALSE);
} else {
/* only try fallback if no specific sink was chosen */
if (chain->sink == NULL) {
GST_DEBUG_OBJECT (playsink, "trying autoaudiosink");
elem = gst_element_factory_make ("autoaudiosink", "audiosink");
chain->sink = try_element (playsink, elem, TRUE);
}
if (chain->sink == NULL) {
/* if default sink from config.h is different then try it too */
if (strcmp (DEFAULT_AUDIOSINK, "autoaudiosink")) {
GST_DEBUG_OBJECT (playsink, "trying " DEFAULT_AUDIOSINK);
elem = gst_element_factory_make (DEFAULT_AUDIOSINK, "audiosink");
chain->sink = try_element (playsink, elem, TRUE);
}
}
}
if (chain->sink == NULL)
goto no_sinks;
chain->chain.bin = gst_bin_new ("abin");
bin = GST_BIN_CAST (chain->chain.bin);
gst_object_ref_sink (bin);
gst_bin_add (bin, chain->sink);
if (queue) {
/* we have to add a queue when we need to decouple for the video sink in
* visualisations */
GST_DEBUG_OBJECT (playsink, "adding audio queue");
chain->queue = gst_element_factory_make ("queue", "aqueue");
gst_bin_add (bin, chain->queue);
prev = head = chain->queue;
} else {
head = chain->sink;
prev = NULL;
}
/* 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 =
gst_play_sink_find_property_sinks (playsink, chain->sink, "volume",
G_TYPE_DOUBLE);
if (elem) {
chain->volume = elem;
g_signal_connect (chain->volume, "notify::volume",
G_CALLBACK (notify_volume_cb), playsink);
GST_DEBUG_OBJECT (playsink, "the sink has a volume property");
have_volume = TRUE;
chain->sink_volume = TRUE;
/* 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. */
chain->mute =
gst_play_sink_find_property_sinks (playsink, chain->sink, "mute",
G_TYPE_BOOLEAN);
if (chain->mute) {
GST_DEBUG_OBJECT (playsink, "the sink has a mute property");
g_signal_connect (chain->mute, "notify::mute",
G_CALLBACK (notify_mute_cb), playsink);
}
/* use the sink to control the volume and mute */
if (playsink->volume_changed) {
g_object_set (G_OBJECT (chain->volume), "volume", playsink->volume, NULL);
playsink->volume_changed = FALSE;
}
if (playsink->mute_changed) {
if (chain->mute) {
g_object_set (chain->mute, "mute", playsink->mute, NULL);
} else {
if (playsink->mute)
g_object_set (chain->volume, "volume", (gdouble) 0.0, NULL);
}
playsink->mute_changed = FALSE;
}
} else {
/* no volume, we need to add a volume element when we can */
GST_DEBUG_OBJECT (playsink, "the sink has no volume property");
have_volume = FALSE;
chain->sink_volume = FALSE;
}
if (raw && !(playsink->flags & GST_PLAY_FLAG_NATIVE_AUDIO)) {
GST_DEBUG_OBJECT (playsink, "creating audioconvert");
chain->conv = gst_element_factory_make ("audioconvert", "aconv");
if (chain->conv == NULL) {
post_missing_element_message (playsink, "audioconvert");
GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"audioconvert"), ("possibly a liboil version mismatch?"));
} else {
gst_bin_add (bin, chain->conv);
if (prev) {
if (!gst_element_link_pads (prev, "src", chain->conv, "sink"))
goto link_failed;
} else {
head = chain->conv;
}
prev = chain->conv;
}
GST_DEBUG_OBJECT (playsink, "creating audioresample");
chain->resample = gst_element_factory_make ("audioresample", "aresample");
if (chain->resample == NULL) {
post_missing_element_message (playsink, "audioresample");
GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"audioresample"), ("possibly a liboil version mismatch?"));
} else {
gst_bin_add (bin, chain->resample);
if (prev) {
if (!gst_element_link_pads (prev, "src", chain->resample, "sink"))
goto link_failed;
} else {
head = chain->resample;
}
prev = chain->resample;
}
if (!have_volume && playsink->flags & GST_PLAY_FLAG_SOFT_VOLUME) {
GST_DEBUG_OBJECT (playsink, "creating volume");
chain->volume = gst_element_factory_make ("volume", "volume");
if (chain->volume == NULL) {
post_missing_element_message (playsink, "volume");
GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"volume"), ("possibly a liboil version mismatch?"));
} else {
have_volume = TRUE;
g_signal_connect (chain->volume, "notify::volume",
G_CALLBACK (notify_volume_cb), playsink);
/* volume also has the mute property */
chain->mute = chain->volume;
g_signal_connect (chain->mute, "notify::mute",
G_CALLBACK (notify_mute_cb), playsink);
/* configure with the latest volume and mute */
g_object_set (G_OBJECT (chain->volume), "volume", playsink->volume,
NULL);
g_object_set (G_OBJECT (chain->mute), "mute", playsink->mute, NULL);
gst_bin_add (bin, chain->volume);
if (prev) {
if (!gst_element_link_pads (prev, "src", chain->volume, "sink"))
goto link_failed;
} else {
head = chain->volume;
}
prev = chain->volume;
}
}
}
if (prev) {
/* we only have to link to the previous element if we have something in
* front of the sink */
GST_DEBUG_OBJECT (playsink, "linking to sink");
if (!gst_element_link_pads (prev, "src", chain->sink, NULL))
goto link_failed;
}
/* 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")), ("Volume/mute is not available"));
}
/* and ghost the sinkpad of the headmost element */
GST_DEBUG_OBJECT (playsink, "ghosting sink pad");
pad = gst_element_get_static_pad (head, "sink");
chain->sinkpad = gst_ghost_pad_new ("sink", pad);
gst_object_unref (pad);
gst_element_add_pad (chain->chain.bin, chain->sinkpad);
return chain;
/* ERRORS */
no_sinks:
{
if (!elem) {
post_missing_element_message (playsink, "autoaudiosink");
if (strcmp (DEFAULT_AUDIOSINK, "autoaudiosink")) {
post_missing_element_message (playsink, DEFAULT_AUDIOSINK);
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("Both autoaudiosink and %s elements are missing."),
DEFAULT_AUDIOSINK), (NULL));
} else {
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("The autoaudiosink element is missing.")), (NULL));
}
} else {
if (strcmp (DEFAULT_AUDIOSINK, "autoaudiosink")) {
GST_ELEMENT_ERROR (playsink, CORE, STATE_CHANGE,
(_("Both autoaudiosink and %s elements are not working."),
DEFAULT_AUDIOSINK), (NULL));
} else {
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("The autoaudiosink element is not working.")), (NULL));
}
}
free_chain ((GstPlayChain *) chain);
return NULL;
}
link_failed:
{
GST_ELEMENT_ERROR (playsink, CORE, PAD,
(NULL), ("Failed to configure the audio sink."));
free_chain ((GstPlayChain *) chain);
return NULL;
}
}
static gboolean
setup_audio_chain (GstPlaySink * playsink, gboolean raw, gboolean queue)
{
GstElement *elem;
GstPlayAudioChain *chain;
GstStateChangeReturn ret;
chain = playsink->audiochain;
/* if the chain was active we don't do anything */
if (GST_PLAY_CHAIN (chain)->activated == TRUE)
return TRUE;
if (chain->chain.raw != raw)
return FALSE;
/* try to set the sink element to READY again */
ret = gst_element_set_state (chain->sink, GST_STATE_READY);
if (ret == GST_STATE_CHANGE_FAILURE)
return FALSE;
/* 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 =
gst_play_sink_find_property_sinks (playsink, chain->sink, "volume",
G_TYPE_DOUBLE);
if (elem) {
chain->volume = elem;
if (playsink->volume_changed) {
GST_DEBUG_OBJECT (playsink, "the sink has a volume property, setting %f",
playsink->volume);
/* use the sink to control the volume */
g_object_set (G_OBJECT (chain->volume), "volume", playsink->volume, NULL);
playsink->volume_changed = FALSE;
}
g_signal_connect (chain->volume, "notify::volume",
G_CALLBACK (notify_volume_cb), playsink);
/* 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. */
chain->mute =
gst_play_sink_find_property_sinks (playsink, chain->sink, "mute",
G_TYPE_BOOLEAN);
if (chain->mute) {
GST_DEBUG_OBJECT (playsink, "the sink has a mute property");
g_signal_connect (chain->mute, "notify::mute",
G_CALLBACK (notify_mute_cb), playsink);
}
} else {
/* no volume, we need to add a volume element when we can */
GST_DEBUG_OBJECT (playsink, "the sink has no volume property");
if (!raw) {
GST_LOG_OBJECT (playsink, "non-raw format, can't do soft volume control");
if (chain->volume)
g_signal_handlers_disconnect_by_func (chain->volume, notify_volume_cb,
playsink);
if (chain->mute)
g_signal_handlers_disconnect_by_func (chain->mute, notify_mute_cb,
playsink);
chain->volume = NULL;
chain->mute = NULL;
} else {
/* both last and current chain are raw audio, there should be a volume
* element already, unless the sink changed from one with a volume
* property to one that hasn't got a volume property, in which case we
* re-generate the chain */
if (chain->volume == NULL) {
GST_DEBUG_OBJECT (playsink, "no existing volume element to re-use");
return FALSE;
}
GST_DEBUG_OBJECT (playsink, "reusing existing volume element");
}
}
return TRUE;
}
/*
* +-------------------------------------------------------------------+
* | visbin |
* | +----------+ +------------+ +----------+ +-------+ |
* | | visqueue | | audioconv | | audiores | | vis | |
* | +-sink src-sink + samp src-sink src-sink src-+ |
* | | +----------+ +------------+ +----------+ +-------+ | |
* sink-+ +-src
* +-------------------------------------------------------------------+
*
*/
static GstPlayVisChain *
gen_vis_chain (GstPlaySink * playsink)
{
GstPlayVisChain *chain;
GstBin *bin;
gboolean res;
GstPad *pad;
GstElement *elem;
chain = g_new0 (GstPlayVisChain, 1);
chain->chain.playsink = playsink;
GST_DEBUG_OBJECT (playsink, "making vis chain %p", chain);
chain->chain.bin = gst_bin_new ("visbin");
bin = GST_BIN_CAST (chain->chain.bin);
gst_object_ref_sink (bin);
/* we're queuing raw audio here, we can remove this queue when we can disable
* async behaviour in the video sink. */
chain->queue = gst_element_factory_make ("queue", "visqueue");
gst_bin_add (bin, chain->queue);
chain->conv = gst_element_factory_make ("audioconvert", "aconv");
if (chain->conv == NULL)
goto no_audioconvert;
gst_bin_add (bin, chain->conv);
chain->resample = gst_element_factory_make ("audioresample", "aresample");
if (chain->resample == NULL)
goto no_audioresample;
gst_bin_add (bin, chain->resample);
/* this pad will be used for blocking the dataflow and switching the vis
* plugin */
chain->blockpad = gst_element_get_static_pad (chain->resample, "src");
if (playsink->visualisation) {
GST_DEBUG_OBJECT (playsink, "trying configure vis");
chain->vis = try_element (playsink, playsink->visualisation, FALSE);
}
if (chain->vis == NULL) {
GST_DEBUG_OBJECT (playsink, "trying goom");
elem = gst_element_factory_make ("goom", "vis");
chain->vis = try_element (playsink, elem, TRUE);
}
if (chain->vis == NULL)
goto no_goom;
gst_bin_add (bin, chain->vis);
res = gst_element_link_pads (chain->queue, "src", chain->conv, "sink");
res &= gst_element_link_pads (chain->conv, "src", chain->resample, "sink");
res &= gst_element_link_pads (chain->resample, "src", chain->vis, "sink");
if (!res)
goto link_failed;
chain->vissinkpad = gst_element_get_static_pad (chain->vis, "sink");
chain->vissrcpad = gst_element_get_static_pad (chain->vis, "src");
pad = gst_element_get_static_pad (chain->queue, "sink");
chain->sinkpad = gst_ghost_pad_new ("sink", pad);
gst_object_unref (pad);
gst_element_add_pad (chain->chain.bin, chain->sinkpad);
chain->srcpad = gst_ghost_pad_new ("src", chain->vissrcpad);
gst_element_add_pad (chain->chain.bin, chain->srcpad);
return chain;
/* ERRORS */
no_audioconvert:
{
post_missing_element_message (playsink, "audioconvert");
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"audioconvert"), ("possibly a liboil version mismatch?"));
free_chain ((GstPlayChain *) chain);
return NULL;
}
no_audioresample:
{
post_missing_element_message (playsink, "audioresample");
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"audioresample"), (NULL));
free_chain ((GstPlayChain *) chain);
return NULL;
}
no_goom:
{
post_missing_element_message (playsink, "goom");
GST_ELEMENT_ERROR (playsink, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"goom"), (NULL));
free_chain ((GstPlayChain *) chain);
return NULL;
}
link_failed:
{
GST_ELEMENT_ERROR (playsink, CORE, PAD,
(NULL), ("Failed to configure the visualisation element."));
free_chain ((GstPlayChain *) chain);
return NULL;
}
}
/* this function is called when all the request pads are requested and when we
* have to construct the final pipeline. Based on the flags we construct the
* final output pipelines.
*/
gboolean
gst_play_sink_reconfigure (GstPlaySink * playsink)
{
GstPlayFlags flags;
gboolean need_audio, need_video, need_vis, need_text;
GST_DEBUG_OBJECT (playsink, "reconfiguring");
/* assume we need nothing */
need_audio = need_video = need_vis = need_text = FALSE;
GST_PLAY_SINK_LOCK (playsink);
GST_OBJECT_LOCK (playsink);
/* get flags, there are protected with the object lock */
flags = playsink->flags;
GST_OBJECT_UNLOCK (playsink);
/* figure out which components we need */
if (flags & GST_PLAY_FLAG_TEXT && playsink->text_pad) {
/* we have a text_pad and we need text rendering, in this case we need a
* video_pad to combine the video with the text */
if (!playsink->video_pad)
goto subs_but_no_video;
/* we have subtitles and we are requested to show it, we also need to show
* video in this case. */
need_video = TRUE;
need_text = TRUE;
} else if (((flags & GST_PLAY_FLAG_VIDEO)
|| (flags & GST_PLAY_FLAG_NATIVE_VIDEO)) && playsink->video_pad) {
/* we have video and we are requested to show it */
need_video = TRUE;
}
if (playsink->audio_pad) {
if ((flags & GST_PLAY_FLAG_AUDIO) || (flags & GST_PLAY_FLAG_NATIVE_AUDIO)) {
need_audio = TRUE;
}
if (playsink->audio_pad_raw) {
/* only can do vis with raw uncompressed audio */
if (flags & GST_PLAY_FLAG_VIS && !need_video) {
/* also add video when we add visualisation */
need_video = TRUE;
need_vis = TRUE;
}
}
}
GST_DEBUG_OBJECT (playsink, "audio:%d, video:%d, vis:%d, text:%d", need_audio,
need_video, need_vis, need_text);
/* set up video pipeline */
if (need_video) {
gboolean raw, async, queue;
/* we need a raw sink when we do vis or when we have a raw pad */
raw = need_vis ? TRUE : playsink->video_pad_raw;
/* we try to set the sink async=FALSE when we need vis, this way we can
* avoid a queue in the audio chain. */
async = !need_vis;
/* If subtitles are requested there already is a queue in the video chain */
queue = (need_text == FALSE);
GST_DEBUG_OBJECT (playsink, "adding video, raw %d",
playsink->video_pad_raw);
if (playsink->videochain) {
/* try to reactivate the chain */
if (!setup_video_chain (playsink, raw, async, queue)) {
add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
free_chain ((GstPlayChain *) playsink->videochain);
playsink->videochain = NULL;
}
}
if (!playsink->videochain) {
playsink->videochain = gen_video_chain (playsink, raw, async, queue);
}
if (playsink->videochain) {
GST_DEBUG_OBJECT (playsink, "adding video chain");
add_chain (GST_PLAY_CHAIN (playsink->videochain), TRUE);
activate_chain (GST_PLAY_CHAIN (playsink->videochain), TRUE);
/* if we are not part of vis or subtitles, set the ghostpad target */
if (!need_vis && !need_text && !playsink->text_pad
&& !playsink->textchain) {
GST_DEBUG_OBJECT (playsink, "ghosting video sinkpad");
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
playsink->videochain->sinkpad);
}
}
} else {
GST_DEBUG_OBJECT (playsink, "no video needed");
if (playsink->videochain) {
GST_DEBUG_OBJECT (playsink, "removing video chain");
if (playsink->vischain) {
GstPad *srcpad;
GST_DEBUG_OBJECT (playsink, "unlinking vis chain");
/* also had visualisation, release the tee srcpad before we then
* unlink the video from it */
if (playsink->audio_tee_vissrc) {
gst_element_release_request_pad (playsink->audio_tee,
playsink->audio_tee_vissrc);
gst_object_unref (playsink->audio_tee_vissrc);
playsink->audio_tee_vissrc = NULL;
}
srcpad =
gst_element_get_static_pad (playsink->vischain->chain.bin, "src");
gst_pad_unlink (srcpad, playsink->videochain->sinkpad);
}
add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
}
if (playsink->video_pad)
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad), NULL);
}
if (need_text) {
GST_DEBUG_OBJECT (playsink, "adding text");
if (!playsink->textchain) {
GST_DEBUG_OBJECT (playsink, "creating text chain");
playsink->textchain = gen_text_chain (playsink);
}
if (playsink->textchain) {
GST_DEBUG_OBJECT (playsink, "adding text chain");
g_object_set (G_OBJECT (playsink->textchain->overlay), "silent", FALSE,
NULL);
add_chain (GST_PLAY_CHAIN (playsink->textchain), TRUE);
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->text_pad),
playsink->textchain->textsinkpad);
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad),
playsink->textchain->videosinkpad);
gst_pad_link (playsink->textchain->srcpad, playsink->videochain->sinkpad);
activate_chain (GST_PLAY_CHAIN (playsink->textchain), TRUE);
}
} else {
GST_DEBUG_OBJECT (playsink, "no text needed");
/* we have no subtitles/text or we are requested to not show them */
if (playsink->textchain) {
if (playsink->text_pad == NULL) {
/* no text pad, remove the chain entirely */
GST_DEBUG_OBJECT (playsink, "removing text chain");
add_chain (GST_PLAY_CHAIN (playsink->textchain), FALSE);
activate_chain (GST_PLAY_CHAIN (playsink->textchain), FALSE);
} else {
/* we have a chain and a textpad, turn the subtitles off */
GST_DEBUG_OBJECT (playsink, "turning off the text");
g_object_set (G_OBJECT (playsink->textchain->overlay), "silent", TRUE,
NULL);
}
}
if (!need_video && playsink->video_pad)
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->video_pad), NULL);
if (playsink->text_pad && !playsink->textchain)
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (playsink->text_pad), NULL);
}
if (need_audio) {
gboolean raw, queue;
GST_DEBUG_OBJECT (playsink, "adding audio");
/* get a raw sink if we are asked for a raw pad */
raw = playsink->audio_pad_raw;
if (need_vis && playsink->videochain) {
/* If we are dealing with visualisations, we need to add a queue to
* decouple the audio from the video part. We only have to do this when
* the video part is async=true */
queue = ((GstPlayVideoChain *) playsink->videochain)->async;
GST_DEBUG_OBJECT (playsink, "need audio queue for vis: %d", queue);
} else {
/* no vis, we can avoid a queue */
GST_DEBUG_OBJECT (playsink, "don't need audio queue");
queue = FALSE;
}
if (playsink->audiochain) {
/* try to reactivate the chain */
if (!setup_audio_chain (playsink, raw, queue)) {
GST_DEBUG_OBJECT (playsink, "removing current audio chain");
if (playsink->audio_tee_asrc) {
gst_element_release_request_pad (playsink->audio_tee,
playsink->audio_tee_asrc);
gst_object_unref (playsink->audio_tee_asrc);
playsink->audio_tee_asrc = NULL;
}
add_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
activate_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
playsink->audiochain->volume = NULL;
playsink->audiochain->mute = NULL;
free_chain ((GstPlayChain *) playsink->audiochain);
playsink->audiochain = NULL;
playsink->volume_changed = playsink->mute_changed = FALSE;
}
}
if (!playsink->audiochain) {
GST_DEBUG_OBJECT (playsink, "creating new audio chain");
playsink->audiochain = gen_audio_chain (playsink, raw, queue);
}
if (playsink->audiochain) {
GST_DEBUG_OBJECT (playsink, "adding audio chain");
if (playsink->audio_tee_asrc == NULL) {
playsink->audio_tee_asrc =
gst_element_get_request_pad (playsink->audio_tee, "src%d");
}
add_chain (GST_PLAY_CHAIN (playsink->audiochain), TRUE);
activate_chain (GST_PLAY_CHAIN (playsink->audiochain), TRUE);
gst_pad_link (playsink->audio_tee_asrc, playsink->audiochain->sinkpad);
}
} else {
GST_DEBUG_OBJECT (playsink, "no audio needed");
/* we have no audio or we are requested to not play audio */
if (playsink->audiochain) {
GST_DEBUG_OBJECT (playsink, "removing audio chain");
/* release the audio pad */
if (playsink->audio_tee_asrc) {
gst_element_release_request_pad (playsink->audio_tee,
playsink->audio_tee_asrc);
gst_object_unref (playsink->audio_tee_asrc);
playsink->audio_tee_asrc = NULL;
}
if (playsink->audiochain->sink_volume) {
playsink->audiochain->volume = NULL;
playsink->audiochain->mute = NULL;
}
add_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
activate_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
}
}
if (need_vis) {
GstPad *srcpad;
if (!playsink->vischain)
playsink->vischain = gen_vis_chain (playsink);
GST_DEBUG_OBJECT (playsink, "adding visualisation");
if (playsink->vischain) {
GST_DEBUG_OBJECT (playsink, "setting up vis chain");
srcpad =
gst_element_get_static_pad (playsink->vischain->chain.bin, "src");
add_chain (GST_PLAY_CHAIN (playsink->vischain), TRUE);
activate_chain (GST_PLAY_CHAIN (playsink->vischain), TRUE);
if (playsink->audio_tee_vissrc == NULL) {
playsink->audio_tee_vissrc =
gst_element_get_request_pad (playsink->audio_tee, "src%d");
}
gst_pad_link (playsink->audio_tee_vissrc, playsink->vischain->sinkpad);
gst_pad_link (srcpad, playsink->videochain->sinkpad);
gst_object_unref (srcpad);
}
} else {
GST_DEBUG_OBJECT (playsink, "no vis needed");
if (playsink->vischain) {
if (playsink->audio_tee_vissrc) {
gst_element_release_request_pad (playsink->audio_tee,
playsink->audio_tee_vissrc);
gst_object_unref (playsink->audio_tee_vissrc);
playsink->audio_tee_vissrc = NULL;
}
GST_DEBUG_OBJECT (playsink, "removing vis chain");
add_chain (GST_PLAY_CHAIN (playsink->vischain), FALSE);
activate_chain (GST_PLAY_CHAIN (playsink->vischain), FALSE);
}
}
do_async_done (playsink);
GST_PLAY_SINK_UNLOCK (playsink);
return TRUE;
/* ERRORS */
subs_but_no_video:
{
GST_ELEMENT_ERROR (playsink, STREAM, FORMAT,
(_("Can't play a text file without video.")),
("Have text pad but no video pad"));
GST_PLAY_SINK_UNLOCK (playsink);
return FALSE;
}
}
/**
* gst_play_sink_set_flags:
* @playsink: a #GstPlaySink
* @flags: #GstPlayFlags
*
* Configure @flags on @playsink. The flags control the behaviour of @playsink
* when constructing the sink pipelins.
*
* Returns: TRUE if the flags could be configured.
*/
gboolean
gst_play_sink_set_flags (GstPlaySink * playsink, GstPlayFlags flags)
{
g_return_val_if_fail (GST_IS_PLAY_SINK (playsink), FALSE);
GST_OBJECT_LOCK (playsink);
playsink->flags = flags;
GST_OBJECT_UNLOCK (playsink);
return TRUE;
}
/**
* gst_play_sink_get_flags:
* @playsink: a #GstPlaySink
*
* Get the flags of @playsink. That flags control the behaviour of the sink when
* it constructs the sink pipelines.
*
* Returns: the currently configured #GstPlayFlags.
*/
GstPlayFlags
gst_play_sink_get_flags (GstPlaySink * playsink)
{
GstPlayFlags res;
g_return_val_if_fail (GST_IS_PLAY_SINK (playsink), 0);
GST_OBJECT_LOCK (playsink);
res = playsink->flags;
GST_OBJECT_UNLOCK (playsink);
return res;
}
void
gst_play_sink_set_font_desc (GstPlaySink * playsink, const gchar * desc)
{
GstPlayTextChain *chain;
GST_PLAY_SINK_LOCK (playsink);
chain = (GstPlayTextChain *) playsink->textchain;
g_free (playsink->font_desc);
playsink->font_desc = g_strdup (desc);
if (chain && chain->overlay) {
g_object_set (chain->overlay, "font-desc", desc, NULL);
}
GST_PLAY_SINK_UNLOCK (playsink);
}
gchar *
gst_play_sink_get_font_desc (GstPlaySink * playsink)
{
gchar *result = NULL;
GstPlayTextChain *chain;
GST_PLAY_SINK_LOCK (playsink);
chain = (GstPlayTextChain *) playsink->textchain;
if (chain && chain->overlay) {
g_object_get (chain->overlay, "font-desc", &result, NULL);
playsink->font_desc = g_strdup (result);
} else {
result = g_strdup (playsink->font_desc);
}
GST_PLAY_SINK_UNLOCK (playsink);
return result;
}
/**
* gst_play_sink_get_last_frame:
* @playsink: a #GstPlaySink
*
* Get the last displayed frame from @playsink. This frame is in the native
* format of the sink element, the caps on the result buffer contain the format
* of the frame data.
*
* Returns: a #GstBuffer with the frame data or %NULL when no video frame is
* available.
*/
GstBuffer *
gst_play_sink_get_last_frame (GstPlaySink * playsink)
{
GstBuffer *result = NULL;
GstPlayVideoChain *chain;
GST_PLAY_SINK_LOCK (playsink);
GST_DEBUG_OBJECT (playsink, "taking last frame");
/* get the video chain if we can */
if ((chain = (GstPlayVideoChain *) playsink->videochain)) {
GST_DEBUG_OBJECT (playsink, "found video chain");
/* see if the chain is active */
if (chain->chain.activated && chain->sink) {
GstElement *elem;
GST_DEBUG_OBJECT (playsink, "video chain active and has a sink");
/* find and get the last-buffer property now */
if ((elem =
gst_play_sink_find_property (playsink, chain->sink,
"last-buffer", GST_TYPE_BUFFER))) {
GST_DEBUG_OBJECT (playsink, "getting last-buffer property");
g_object_get (elem, "last-buffer", &result, NULL);
gst_object_unref (elem);
}
}
}
GST_PLAY_SINK_UNLOCK (playsink);
return result;
}
/**
* gst_play_sink_request_pad
* @playsink: a #GstPlaySink
* @type: a #GstPlaySinkType
*
* Create or return a pad of @type.
*
* Returns: a #GstPad of @type or %NULL when the pad could not be created.
*/
GstPad *
gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type)
{
GstPad *res = NULL;
gboolean created = FALSE;
gboolean raw = FALSE;
gboolean activate = TRUE;
const gchar *pad_name = NULL;
GST_DEBUG_OBJECT (playsink, "request pad type %d", type);
GST_PLAY_SINK_LOCK (playsink);
switch (type) {
case GST_PLAY_SINK_TYPE_AUDIO_RAW:
pad_name = "audio_raw_sink";
raw = TRUE;
case GST_PLAY_SINK_TYPE_AUDIO:
if (pad_name == NULL)
pad_name = "audio_sink";
if (!playsink->audio_tee) {
GST_LOG_OBJECT (playsink, "creating tee");
/* create tee when needed. This element will feed the audio sink chain
* and the vis chain. */
playsink->audio_tee = gst_element_factory_make ("tee", "audiotee");
playsink->audio_tee_sink =
gst_element_get_static_pad (playsink->audio_tee, "sink");
gst_bin_add (GST_BIN_CAST (playsink), playsink->audio_tee);
gst_element_set_state (playsink->audio_tee, GST_STATE_PAUSED);
} else {
gst_element_set_state (playsink->audio_tee, GST_STATE_PAUSED);
}
if (!playsink->audio_pad) {
GST_LOG_OBJECT (playsink, "ghosting tee sinkpad");
playsink->audio_pad =
gst_ghost_pad_new (pad_name, playsink->audio_tee_sink);
created = TRUE;
}
playsink->audio_pad_raw = raw;
res = playsink->audio_pad;
break;
case GST_PLAY_SINK_TYPE_VIDEO_RAW:
pad_name = "video_raw_sink";
raw = TRUE;
case GST_PLAY_SINK_TYPE_VIDEO:
if (pad_name == NULL)
pad_name = "video_sink";
if (!playsink->video_pad) {
GST_LOG_OBJECT (playsink, "ghosting videosink");
playsink->video_pad =
gst_ghost_pad_new_no_target (pad_name, GST_PAD_SINK);
created = TRUE;
}
playsink->video_pad_raw = raw;
res = playsink->video_pad;
break;
case GST_PLAY_SINK_TYPE_TEXT:
GST_LOG_OBJECT (playsink, "ghosting text");
if (!playsink->text_pad) {
playsink->text_pad =
gst_ghost_pad_new_no_target ("text_sink", GST_PAD_SINK);
created = TRUE;
}
res = playsink->text_pad;
break;
case GST_PLAY_SINK_TYPE_FLUSHING:
{
gchar *padname;
/* we need a unique padname for the flushing pad. */
padname = g_strdup_printf ("flushing_%d", playsink->count);
res = gst_ghost_pad_new_no_target (padname, GST_PAD_SINK);
g_free (padname);
playsink->count++;
activate = FALSE;
created = TRUE;
break;
}
default:
res = NULL;
break;
}
GST_PLAY_SINK_UNLOCK (playsink);
if (created && res) {
/* we have to add the pad when it's active or we get an error when the
* element is 'running' */
gst_pad_set_active (res, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (playsink), res);
if (!activate)
gst_pad_set_active (res, activate);
}
return res;
}
static GstPad *
gst_play_sink_request_new_pad (GstElement * element, GstPadTemplate * templ,
const gchar * name)
{
GstPlaySink *psink;
GstPad *pad;
GstElementClass *klass;
GstPlaySinkType type;
const gchar *tplname;
g_return_val_if_fail (templ != NULL, NULL);
GST_DEBUG_OBJECT (element, "name:%s", name);
psink = GST_PLAY_SINK (element);
klass = GST_ELEMENT_GET_CLASS (element);
tplname = GST_PAD_TEMPLATE_NAME_TEMPLATE (templ);
/* Figure out the GstPlaySinkType based on the template */
if (!strcmp (tplname, "audio_sink"))
type = GST_PLAY_SINK_TYPE_AUDIO;
else if (!strcmp (tplname, "aduio_raw_sink"))
type = GST_PLAY_SINK_TYPE_AUDIO_RAW;
else if (!strcmp (tplname, "video_sink"))
type = GST_PLAY_SINK_TYPE_VIDEO;
else if (!strcmp (tplname, "video_raw_sink"))
type = GST_PLAY_SINK_TYPE_VIDEO_RAW;
else if (!strcmp (tplname, "text_sink"))
type = GST_PLAY_SINK_TYPE_TEXT;
else
goto unknown_template;
pad = gst_play_sink_request_pad (psink, type);
return pad;
unknown_template:
GST_WARNING_OBJECT (element, "Unknown pad template");
return NULL;
}
void
gst_play_sink_release_pad (GstPlaySink * playsink, GstPad * pad)
{
GstPad **res = NULL;
gboolean untarget = TRUE;
GST_DEBUG_OBJECT (playsink, "release pad %" GST_PTR_FORMAT, pad);
GST_PLAY_SINK_LOCK (playsink);
if (pad == playsink->video_pad) {
res = &playsink->video_pad;
} else if (pad == playsink->audio_pad) {
res = &playsink->audio_pad;
} else if (pad == playsink->text_pad) {
res = &playsink->text_pad;
} else {
/* try to release the given pad anyway, these could be the FLUSHING pads. */
res = &pad;
untarget = FALSE;
}
GST_PLAY_SINK_UNLOCK (playsink);
if (*res) {
GST_DEBUG_OBJECT (playsink, "deactivate pad %" GST_PTR_FORMAT, *res);
gst_pad_set_active (*res, FALSE);
if (untarget) {
GST_DEBUG_OBJECT (playsink, "untargeting pad %" GST_PTR_FORMAT, *res);
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (*res), NULL);
}
GST_DEBUG_OBJECT (playsink, "remove pad %" GST_PTR_FORMAT, *res);
gst_element_remove_pad (GST_ELEMENT_CAST (playsink), *res);
*res = NULL;
}
}
static void
gst_play_sink_release_request_pad (GstElement * element, GstPad * pad)
{
GstPlaySink *psink = GST_PLAY_SINK (element);
gst_play_sink_release_pad (psink, pad);
}
static void
gst_play_sink_handle_message (GstBin * bin, GstMessage * message)
{
GstPlaySink *playsink;
playsink = GST_PLAY_SINK_CAST (bin);
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_STEP_DONE:
{
GstFormat format;
guint64 amount;
gdouble rate;
gboolean flush, intermediate, eos;
guint64 duration;
GST_INFO_OBJECT (playsink, "Handling step-done message");
gst_message_parse_step_done (message, &format, &amount, &rate, &flush,
&intermediate, &duration, &eos);
if (format == GST_FORMAT_BUFFERS) {
/* for the buffer format, we align the other streams */
if (playsink->audiochain) {
GstEvent *event;
event =
gst_event_new_step (GST_FORMAT_TIME, duration, rate, flush,
intermediate);
if (!gst_element_send_event (playsink->audiochain->chain.bin, event)) {
GST_DEBUG_OBJECT (playsink, "Event failed when sent to audio sink");
}
}
}
GST_BIN_CLASS (gst_play_sink_parent_class)->handle_message (bin, message);
break;
}
default:
GST_BIN_CLASS (gst_play_sink_parent_class)->handle_message (bin, message);
break;
}
}
/* Send an event to our sinks until one of them works; don't then send to the
* remaining sinks (unlike GstBin)
* Special case: If a text sink is set we need to send the event
* to them in case it's source is different from the a/v stream's source.
*/
static gboolean
gst_play_sink_send_event_to_sink (GstPlaySink * playsink, GstEvent * event)
{
gboolean res = TRUE;
if (playsink->textchain && playsink->textchain->sink) {
gst_event_ref (event);
if ((res = gst_element_send_event (playsink->textchain->chain.bin, event))) {
GST_DEBUG_OBJECT (playsink, "Sent event succesfully to text sink");
} else {
GST_DEBUG_OBJECT (playsink, "Event failed when sent to text sink");
}
}
if (playsink->videochain) {
gst_event_ref (event);
if ((res = gst_element_send_event (playsink->videochain->chain.bin, event))) {
GST_DEBUG_OBJECT (playsink, "Sent event succesfully to video sink");
goto done;
}
GST_DEBUG_OBJECT (playsink, "Event failed when sent to video sink");
}
if (playsink->audiochain) {
gst_event_ref (event);
if ((res = gst_element_send_event (playsink->audiochain->chain.bin, event))) {
GST_DEBUG_OBJECT (playsink, "Sent event succesfully to audio sink");
goto done;
}
GST_DEBUG_OBJECT (playsink, "Event failed when sent to audio sink");
}
done:
gst_event_unref (event);
return res;
}
/* We only want to send the event to a single sink (overriding GstBin's
* behaviour), but we want to keep GstPipeline's behaviour - wrapping seek
* events appropriately. So, this is a messy duplication of code. */
static gboolean
gst_play_sink_send_event (GstElement * element, GstEvent * event)
{
gboolean res = FALSE;
GstEventType event_type = GST_EVENT_TYPE (event);
GstPlaySink *playsink;
playsink = GST_PLAY_SINK_CAST (element);
switch (event_type) {
case GST_EVENT_SEEK:
GST_DEBUG_OBJECT (element, "Sending event to a sink");
res = gst_play_sink_send_event_to_sink (playsink, event);
break;
case GST_EVENT_STEP:
{
GstFormat format;
guint64 amount;
gdouble rate;
gboolean flush, intermediate;
gst_event_parse_step (event, &format, &amount, &rate, &flush,
&intermediate);
if (format == GST_FORMAT_BUFFERS) {
/* for buffers, we will try to step video frames, for other formats we
* send the step to all sinks */
res = gst_play_sink_send_event_to_sink (playsink, event);
} else {
res =
GST_ELEMENT_CLASS (gst_play_sink_parent_class)->send_event (element,
event);
}
break;
}
default:
res =
GST_ELEMENT_CLASS (gst_play_sink_parent_class)->send_event (element,
event);
break;
}
return res;
}
static GstStateChangeReturn
gst_play_sink_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
GstStateChangeReturn bret;
GstPlaySink *playsink;
playsink = GST_PLAY_SINK (element);
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
playsink->need_async_start = TRUE;
/* we want to go async to PAUSED until we managed to configure and add the
* sinks */
do_async_start (playsink);
ret = GST_STATE_CHANGE_ASYNC;
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
case GST_STATE_CHANGE_READY_TO_NULL:
if (playsink->audiochain && playsink->audiochain->sink_volume) {
/* remove our links to the mute and volume elements when they were
* provided by a sink */
playsink->audiochain->volume = NULL;
playsink->audiochain->mute = NULL;
}
ret = GST_STATE_CHANGE_SUCCESS;
break;
default:
/* all other state changes return SUCCESS by default, this value can be
* overridden by the result of the children */
ret = GST_STATE_CHANGE_SUCCESS;
break;
}
/* do the state change of the children */
bret =
GST_ELEMENT_CLASS (gst_play_sink_parent_class)->change_state (element,
transition);
/* now look at the result of our children and adjust the return value */
switch (bret) {
case GST_STATE_CHANGE_FAILURE:
/* failure, we stop */
goto activate_failed;
case GST_STATE_CHANGE_NO_PREROLL:
/* some child returned NO_PREROLL. This is strange but we never know. We
* commit our async state change (if any) and return the NO_PREROLL */
do_async_done (playsink);
ret = bret;
break;
case GST_STATE_CHANGE_ASYNC:
/* some child was async, return this */
ret = bret;
break;
default:
/* return our previously configured return value */
break;
}
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
break;
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
/* FIXME Release audio device when we implement that */
playsink->need_async_start = TRUE;
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
case GST_STATE_CHANGE_READY_TO_NULL:
/* remove sinks we added */
if (playsink->videochain) {
activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
}
if (playsink->audiochain) {
activate_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
add_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
}
if (playsink->vischain) {
activate_chain (GST_PLAY_CHAIN (playsink->vischain), FALSE);
add_chain (GST_PLAY_CHAIN (playsink->vischain), FALSE);
}
if (playsink->textchain) {
activate_chain (GST_PLAY_CHAIN (playsink->textchain), FALSE);
add_chain (GST_PLAY_CHAIN (playsink->textchain), FALSE);
}
do_async_done (playsink);
break;
default:
break;
}
return ret;
/* ERRORS */
activate_failed:
{
GST_DEBUG_OBJECT (element,
"element failed to change states -- activation problem?");
return GST_STATE_CHANGE_FAILURE;
}
}
static void
gst_play_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * spec)
{
GstPlaySink *playsink = GST_PLAY_SINK (object);
switch (prop_id) {
case PROP_FLAGS:
gst_play_sink_set_flags (playsink, g_value_get_flags (value));
break;
case PROP_VOLUME:
gst_play_sink_set_volume (playsink, g_value_get_double (value));
break;
case PROP_MUTE:
gst_play_sink_set_mute (playsink, g_value_get_boolean (value));
break;
case PROP_FONT_DESC:
gst_play_sink_set_font_desc (playsink, g_value_get_string (value));
break;
case PROP_VIS_PLUGIN:
gst_play_sink_set_vis_plugin (playsink, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
break;
}
}
static void
gst_play_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * spec)
{
GstPlaySink *playsink = GST_PLAY_SINK (object);
switch (prop_id) {
case PROP_FLAGS:
g_value_set_flags (value, gst_play_sink_get_flags (playsink));
break;
case PROP_VOLUME:
g_value_set_double (value, gst_play_sink_get_volume (playsink));
break;
case PROP_MUTE:
g_value_set_boolean (value, gst_play_sink_get_mute (playsink));
break;
case PROP_FONT_DESC:
g_value_take_string (value, gst_play_sink_get_font_desc (playsink));
break;
case PROP_VIS_PLUGIN:
g_value_take_object (value, gst_play_sink_get_vis_plugin (playsink));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
break;
}
}
gboolean
gst_play_sink_plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (gst_play_sink_debug, "playsink", 0, "play bin");
return gst_element_register (plugin, "playsink", GST_RANK_NONE,
GST_TYPE_PLAY_SINK);
}