diff --git a/gst/playback/Makefile.am b/gst/playback/Makefile.am index 3adb56db78..2fe90e6e85 100644 --- a/gst/playback/Makefile.am +++ b/gst/playback/Makefile.am @@ -18,6 +18,8 @@ libgstplaybin_la_SOURCES = \ gststreaminfo.c \ gststreamselector.c \ gstsubtitleoverlay.c \ + gstplaysinkvideoconvert.c \ + gstplaysinkaudioconvert.c \ gststreamsynchronizer.c nodist_libgstplaybin_la_SOURCES = $(built_sources) @@ -57,6 +59,8 @@ noinst_HEADERS = \ gststreamselector.h \ gstrawcaps.h \ gstsubtitleoverlay.h \ + gstplaysinkvideoconvert.h \ + gstplaysinkaudioconvert.h \ gststreamsynchronizer.h BUILT_SOURCES = $(built_headers) $(built_sources) diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c index 3661af363c..1449d007f3 100644 --- a/gst/playback/gstplaysink.c +++ b/gst/playback/gstplaysink.c @@ -31,6 +31,8 @@ #include "gstplaysink.h" #include "gststreamsynchronizer.h" +#include "gstplaysinkvideoconvert.h" +#include "gstplaysinkaudioconvert.h" GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug); #define GST_CAT_DEFAULT gst_play_sink_debug @@ -59,7 +61,6 @@ typedef struct 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 */ @@ -81,7 +82,6 @@ typedef struct GstPad *sinkpad; GstElement *queue; GstElement *conv; - GstElement *scale; GstElement *sink; gboolean async; GstElement *ts_offset; @@ -1278,46 +1278,19 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async) head = prev = chain->queue; } - 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")); + if (!(playsink->flags & GST_PLAY_FLAG_NATIVE_VIDEO)) { + GST_DEBUG_OBJECT (playsink, "creating videoconverter"); + chain->conv = + g_object_new (GST_TYPE_PLAY_SINK_VIDEO_CONVERT, "name", "vconv", NULL); + gst_bin_add (bin, chain->conv); + if (prev) { + if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink", + GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) + goto link_failed; } else { - gst_bin_add (bin, chain->conv); - if (prev) { - if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink", - GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) - 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 { - /* Add black borders if necessary to keep the DAR */ - g_object_set (chain->scale, "add-borders", TRUE, NULL); - gst_bin_add (bin, chain->scale); - if (prev) { - if (!gst_element_link_pads_full (prev, "src", chain->scale, "sink", - GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) - goto link_failed; - } else { - head = chain->scale; - } - prev = chain->scale; + head = chain->conv; } + prev = chain->conv; } if (prev) { @@ -1388,8 +1361,7 @@ setup_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async) chain = playsink->videochain; - if (chain->chain.raw != raw) - return FALSE; + chain->chain.raw = raw; /* if the chain was active we don't do anything */ if (GST_PLAY_CHAIN (chain)->activated == TRUE) @@ -1768,54 +1740,32 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw) chain->sink_volume = FALSE; } - if (raw && !(playsink->flags & GST_PLAY_FLAG_NATIVE_AUDIO)) { + if (!(playsink->flags & GST_PLAY_FLAG_NATIVE_AUDIO) || (!have_volume + && playsink->flags & GST_PLAY_FLAG_SOFT_VOLUME)) { 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?")); + chain->conv = + g_object_new (GST_TYPE_PLAY_SINK_AUDIO_CONVERT, "name", "aconv", NULL); + gst_bin_add (bin, chain->conv); + if (prev) { + if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink", + GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) + goto link_failed; } else { - gst_bin_add (bin, chain->conv); - if (prev) { - if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink", - GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) - goto link_failed; - } else { - head = chain->conv; - } - prev = chain->conv; + 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_full (prev, "src", chain->resample, "sink", - GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) - goto link_failed; - } else { - head = chain->resample; - } - prev = chain->resample; - } + GST_PLAY_SINK_AUDIO_CONVERT_CAST (chain->conv)->use_converters = + !(playsink->flags & GST_PLAY_FLAG_NATIVE_AUDIO); + GST_PLAY_SINK_AUDIO_CONVERT_CAST (chain->conv)->use_volume = (!have_volume + && playsink->flags & GST_PLAY_FLAG_SOFT_VOLUME); 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 { + GstPlaySinkAudioConvert *conv = + GST_PLAY_SINK_AUDIO_CONVERT_CAST (chain->conv); + + if (conv->volume) { + chain->volume = conv->volume; have_volume = TRUE; g_signal_connect (chain->volume, "notify::volume", @@ -1830,16 +1780,6 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw) 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_full (prev, "src", chain->volume, "sink", - GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) - goto link_failed; - } else { - head = chain->volume; - } - prev = chain->volume; } } } @@ -1921,8 +1861,7 @@ setup_audio_chain (GstPlaySink * playsink, gboolean raw) chain = playsink->audiochain; - if (chain->chain.raw != raw) - return FALSE; + chain->chain.raw = raw; /* if the chain was active we don't do anything */ if (GST_PLAY_CHAIN (chain)->activated == TRUE) @@ -1967,29 +1906,35 @@ setup_audio_chain (GstPlaySink * playsink, gboolean raw) g_signal_connect (chain->mute, "notify::mute", G_CALLBACK (notify_mute_cb), playsink); } + + GST_PLAY_SINK_AUDIO_CONVERT_CAST (chain->conv)->use_volume = FALSE; } else { + GstPlaySinkAudioConvert *conv = + GST_PLAY_SINK_AUDIO_CONVERT_CAST (chain->conv); + /* no volume, we need to add a volume element when we can */ + conv->use_volume = TRUE; 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"); - disconnect_chain (chain, 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"); - /* undo background state change done earlier */ - gst_element_set_state (chain->sink, GST_STATE_NULL); - return FALSE; - } + /* Disconnect signals */ + disconnect_chain (chain, playsink); - GST_DEBUG_OBJECT (playsink, "reusing existing volume element"); + if (conv->volume) { + chain->volume = conv->volume; + chain->mute = chain->volume; + + g_signal_connect (chain->volume, "notify::volume", + G_CALLBACK (notify_volume_cb), playsink); + + 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_DEBUG_OBJECT (playsink, "reusing existing volume element"); } return TRUE; } diff --git a/gst/playback/gstplaysinkaudioconvert.c b/gst/playback/gstplaysinkaudioconvert.c new file mode 100644 index 0000000000..43c5272f19 --- /dev/null +++ b/gst/playback/gstplaysinkaudioconvert.c @@ -0,0 +1,522 @@ +/* GStreamer + * Copyright (C) <2011> Sebastian Dröge + * + * 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 "gstplaysinkaudioconvert.h" + +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_play_sink_audio_convert_debug); +#define GST_CAT_DEFAULT gst_play_sink_audio_convert_debug + +#define parent_class gst_play_sink_audio_convert_parent_class + +G_DEFINE_TYPE (GstPlaySinkAudioConvert, gst_play_sink_audio_convert, + GST_TYPE_BIN); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static gboolean +is_raw_caps (GstCaps * caps) +{ + gint i, n; + GstStructure *s; + const gchar *name; + + n = gst_caps_get_size (caps); + for (i = 0; i < n; i++) { + s = gst_caps_get_structure (caps, i); + name = gst_structure_get_name (s); + if (!g_str_has_prefix (name, "audio/x-raw")) + return FALSE; + } + + return TRUE; +} + +static void +post_missing_element_message (GstPlaySinkAudioConvert * self, + const gchar * name) +{ + GstMessage *msg; + + msg = gst_missing_element_message_new (GST_ELEMENT_CAST (self), name); + gst_element_post_message (GST_ELEMENT_CAST (self), msg); +} + +static void +distribute_running_time (GstElement * element, const GstSegment * segment) +{ + GstEvent *event; + GstPad *pad; + + pad = gst_element_get_static_pad (element, "sink"); + + if (segment->accum) { + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, 0, segment->accum, 0); + gst_pad_push_event (pad, event); + } + + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, + segment->start, segment->stop, segment->time); + gst_pad_push_event (pad, event); + + gst_object_unref (pad); +} + +static void +pad_blocked_cb (GstPad * pad, gboolean blocked, GstPlaySinkAudioConvert * self) +{ + GstPad *peer; + GstCaps *caps; + gboolean raw; + + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + self->sink_proxypad_blocked = blocked; + GST_DEBUG_OBJECT (self, "Pad blocked: %d", blocked); + if (!blocked) + goto done; + + /* There must be a peer at this point */ + peer = gst_pad_get_peer (self->sinkpad); + caps = gst_pad_get_negotiated_caps (peer); + if (!caps) + caps = gst_pad_get_caps_reffed (peer); + gst_object_unref (peer); + + raw = is_raw_caps (caps); + GST_DEBUG_OBJECT (self, "Caps %" GST_PTR_FORMAT " are raw: %d", caps, raw); + gst_caps_unref (caps); + + if (raw == self->raw) + goto unblock; + self->raw = raw; + + if (raw) { + GstBin *bin = GST_BIN_CAST (self); + GstElement *head = NULL, *prev = NULL; + GstPad *pad; + + GST_DEBUG_OBJECT (self, "Creating raw conversion pipeline"); + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + + if (self->use_converters) { + self->conv = gst_element_factory_make ("audioconvert", "conv"); + if (self->conv == NULL) { + post_missing_element_message (self, "audioconvert"); + GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "audioconvert"), ("audio rendering might fail")); + } else { + gst_bin_add (bin, self->conv); + gst_element_sync_state_with_parent (self->conv); + distribute_running_time (self->conv, &self->segment); + prev = head = self->conv; + } + + self->resample = gst_element_factory_make ("audioresample", "resample"); + if (self->resample == NULL) { + post_missing_element_message (self, "audioresample"); + GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "audioresample"), ("possibly a liboil version mismatch?")); + } else { + gst_bin_add (bin, self->resample); + gst_element_sync_state_with_parent (self->resample); + distribute_running_time (self->resample, &self->segment); + if (prev) { + if (!gst_element_link_pads_full (prev, "src", self->resample, "sink", + GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) + goto link_failed; + } else { + head = self->resample; + } + prev = self->resample; + } + } + + if (self->use_volume && self->volume) { + gst_bin_add (bin, gst_object_ref (self->volume)); + gst_element_sync_state_with_parent (self->volume); + distribute_running_time (self->volume, &self->segment); + if (prev) { + if (!gst_element_link_pads_full (prev, "src", self->volume, "sink", + GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) + goto link_failed; + } else { + head = self->volume; + } + prev = self->volume; + } + + if (head) { + pad = gst_element_get_static_pad (head, "sink"); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), pad); + gst_object_unref (pad); + } + + if (prev) { + pad = gst_element_get_static_pad (prev, "src"); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), pad); + gst_object_unref (pad); + } + + if (!head && !prev) { + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + } + + GST_DEBUG_OBJECT (self, "Raw conversion pipeline created"); + } else { + GstBin *bin = GST_BIN_CAST (self); + + GST_DEBUG_OBJECT (self, "Removing raw conversion pipeline"); + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + + if (self->conv) { + gst_element_set_state (self->conv, GST_STATE_NULL); + gst_bin_remove (bin, self->conv); + self->conv = NULL; + } + if (self->resample) { + gst_element_set_state (self->resample, GST_STATE_NULL); + gst_bin_remove (bin, self->resample); + self->resample = NULL; + } + if (self->volume) { + gst_element_set_state (self->volume, GST_STATE_NULL); + if (GST_OBJECT_PARENT (self->volume) == GST_OBJECT_CAST (self)) { + gst_bin_remove (GST_BIN_CAST (self), self->volume); + } + } + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + + GST_DEBUG_OBJECT (self, "Raw conversion pipeline removed"); + } + +unblock: + gst_pad_set_blocked_async_full (self->sink_proxypad, FALSE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + +done: + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + return; + +link_failed: + { + GST_ELEMENT_ERROR (self, CORE, PAD, + (NULL), ("Failed to configure the audio converter.")); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + gst_pad_set_blocked_async_full (self->sink_proxypad, FALSE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + return; + } +} + +static gboolean +gst_play_sink_audio_convert_sink_event (GstPad * pad, GstEvent * event) +{ + GstPlaySinkAudioConvert *self = + GST_PLAY_SINK_AUDIO_CONVERT (gst_pad_get_parent (pad)); + gboolean ret; + + ret = self->sink_event (pad, gst_event_ref (event)); + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + gboolean update; + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, position; + + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + GST_DEBUG_OBJECT (self, "Segment before %" GST_SEGMENT_FORMAT, + &self->segment); + gst_segment_set_newsegment_full (&self->segment, update, rate, applied_rate, + format, start, stop, position); + GST_DEBUG_OBJECT (self, "Segment after %" GST_SEGMENT_FORMAT, + &self->segment); + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + } else if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) { + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + GST_DEBUG_OBJECT (self, "Resetting segment"); + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + } + + gst_event_unref (event); + gst_object_unref (self); + + return ret; +} + +static gboolean +gst_play_sink_audio_convert_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstPlaySinkAudioConvert *self = + GST_PLAY_SINK_AUDIO_CONVERT (gst_pad_get_parent (pad)); + gboolean ret; + GstStructure *s; + const gchar *name; + gboolean reconfigure = FALSE; + + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + s = gst_caps_get_structure (caps, 0); + name = gst_structure_get_name (s); + + if (g_str_has_prefix (name, "audio/x-raw-")) { + if (!self->raw && !gst_pad_is_blocked (self->sink_proxypad)) { + GST_DEBUG_OBJECT (self, "Changing caps from non-raw to raw"); + reconfigure = TRUE; + gst_pad_set_blocked_async_full (self->sink_proxypad, TRUE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + } + } else { + if (self->raw && !gst_pad_is_blocked (self->sink_proxypad)) { + GST_DEBUG_OBJECT (self, "Changing caps from raw to non-raw"); + reconfigure = TRUE; + gst_pad_set_blocked_async_full (self->sink_proxypad, TRUE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + } + } + + /* Otherwise the setcaps below fails */ + if (reconfigure) { + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + } + + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + ret = self->sink_setcaps (pad, caps); + + GST_DEBUG_OBJECT (self, "Setting sink caps %" GST_PTR_FORMAT ": %d", caps, + ret); + + gst_object_unref (self); + + return ret; +} + +static GstCaps * +gst_play_sink_audio_convert_getcaps (GstPad * pad) +{ + GstPlaySinkAudioConvert *self = + GST_PLAY_SINK_AUDIO_CONVERT (gst_pad_get_parent (pad)); + GstCaps *ret; + GstPad *otherpad, *peer; + + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + if (pad == self->srcpad) + otherpad = gst_object_ref (self->sinkpad); + else + otherpad = gst_object_ref (self->srcpad); + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + + peer = gst_pad_get_peer (otherpad); + if (peer) { + ret = gst_pad_get_caps_reffed (peer); + gst_object_unref (peer); + } else { + ret = gst_caps_new_any (); + } + + gst_object_unref (otherpad); + gst_object_unref (self); + + return ret; +} + +static void +gst_play_sink_audio_convert_finalize (GObject * object) +{ + GstPlaySinkAudioConvert *self = GST_PLAY_SINK_AUDIO_CONVERT_CAST (object); + + if (self->volume) + gst_object_unref (self->volume); + + gst_object_unref (self->sink_proxypad); + g_mutex_free (self->lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstStateChangeReturn +gst_play_sink_audio_convert_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret; + GstPlaySinkAudioConvert *self = GST_PLAY_SINK_AUDIO_CONVERT_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + if (gst_pad_is_blocked (self->sink_proxypad)) + gst_pad_set_blocked_async_full (self->sink_proxypad, FALSE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + if (self->conv) { + gst_element_set_state (self->conv, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (self), self->conv); + self->conv = NULL; + } + if (self->resample) { + gst_element_set_state (self->resample, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (self), self->resample); + self->resample = NULL; + } + if (self->volume) { + gst_element_set_state (self->volume, GST_STATE_NULL); + if (GST_OBJECT_PARENT (self->volume) == GST_OBJECT_CAST (self)) { + gst_bin_remove (GST_BIN_CAST (self), self->volume); + } + } + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + self->raw = FALSE; + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self); + if (!gst_pad_is_blocked (self->sink_proxypad)) + gst_pad_set_blocked_async_full (self->sink_proxypad, TRUE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self); + default: + break; + } + + return ret; +} + +static void +gst_play_sink_audio_convert_class_init (GstPlaySinkAudioConvertClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + GST_DEBUG_CATEGORY_INIT (gst_play_sink_audio_convert_debug, + "playsinkaudioconvert", 0, "play bin"); + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = gst_play_sink_audio_convert_finalize; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&srctemplate)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sinktemplate)); + gst_element_class_set_details_simple (gstelement_class, + "Player Sink Audio Converter", "Audio/Bin/Converter", + "Convenience bin for audio conversion", + "Sebastian Dröge "); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_change_state); +} + +static void +gst_play_sink_audio_convert_init (GstPlaySinkAudioConvert * self) +{ + GstPadTemplate *templ; + GstIterator *it; + + self->lock = g_mutex_new (); + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + + templ = gst_static_pad_template_get (&sinktemplate); + self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", templ); + self->sink_event = GST_PAD_EVENTFUNC (self->sinkpad); + gst_pad_set_event_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_sink_event)); + self->sink_setcaps = GST_PAD_SETCAPSFUNC (self->sinkpad); + gst_pad_set_setcaps_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_sink_setcaps)); + gst_pad_set_getcaps_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_getcaps)); + + it = gst_pad_iterate_internal_links (self->sinkpad); + g_assert (it); + gst_iterator_next (it, (gpointer *) & self->sink_proxypad); + g_assert (self->sink_proxypad); + gst_iterator_free (it); + + gst_element_add_pad (GST_ELEMENT_CAST (self), self->sinkpad); + gst_object_unref (templ); + + templ = gst_static_pad_template_get (&srctemplate); + self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ); + gst_pad_set_getcaps_function (self->srcpad, + GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_getcaps)); + gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad); + gst_object_unref (templ); + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + + /* FIXME: Only create this on demand but for now we need + * it to always exist because of playsink's volume proxying + * logic. + */ + self->volume = gst_element_factory_make ("volume", "volume"); + if (self->volume) + gst_object_ref_sink (self->volume); +} diff --git a/gst/playback/gstplaysinkaudioconvert.h b/gst/playback/gstplaysinkaudioconvert.h new file mode 100644 index 0000000000..b0016b9179 --- /dev/null +++ b/gst/playback/gstplaysinkaudioconvert.h @@ -0,0 +1,91 @@ +/* GStreamer + * Copyright (C) <2011> Sebastian Dröge + * + * 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. + */ + +#include + +#ifndef __GST_PLAY_SINK_AUDIO_CONVERT_H__ +#define __GST_PLAY_SINK_AUDIO_CONVERT_H__ + +G_BEGIN_DECLS +#define GST_TYPE_PLAY_SINK_AUDIO_CONVERT \ + (gst_play_sink_audio_convert_get_type()) +#define GST_PLAY_SINK_AUDIO_CONVERT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY_SINK_AUDIO_CONVERT, GstPlaySinkAudioConvert)) +#define GST_PLAY_SINK_AUDIO_CONVERT_CAST(obj) \ + ((GstPlaySinkAudioConvert *) obj) +#define GST_PLAY_SINK_AUDIO_CONVERT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAY_SINK_AUDIO_CONVERT, GstPlaySinkAudioConvertClass)) +#define GST_IS_PLAY_SINK_AUDIO_CONVERT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY_SINK_AUDIO_CONVERT)) +#define GST_IS_PLAY_SINK_AUDIO_CONVERT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAY_SINK_AUDIO_CONVERT)) + +#define GST_PLAY_SINK_AUDIO_CONVERT_LOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "locking from thread %p", \ + g_thread_self ()); \ + g_mutex_lock (GST_PLAY_SINK_AUDIO_CONVERT_CAST(obj)->lock); \ + GST_LOG_OBJECT (obj, \ + "locked from thread %p", \ + g_thread_self ()); \ +} G_STMT_END + +#define GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "unlocking from thread %p", \ + g_thread_self ()); \ + g_mutex_unlock (GST_PLAY_SINK_AUDIO_CONVERT_CAST(obj)->lock); \ +} G_STMT_END + +typedef struct _GstPlaySinkAudioConvert GstPlaySinkAudioConvert; +typedef struct _GstPlaySinkAudioConvertClass GstPlaySinkAudioConvertClass; + +struct _GstPlaySinkAudioConvert +{ + GstBin parent; + + /* < private > */ + GMutex *lock; + + GstPad *sinkpad, *sink_proxypad; + GstPadEventFunction sink_event; + GstPadSetCapsFunction sink_setcaps; + gboolean sink_proxypad_blocked; + GstSegment segment; + + GstPad *srcpad; + + gboolean raw; + GstElement *conv, *resample; + + /* < pseudo public > */ + GstElement *volume; + gboolean use_volume; + gboolean use_converters; +}; + +struct _GstPlaySinkAudioConvertClass +{ + GstBinClass parent; +}; + +GType gst_play_sink_audio_convert_get_type (void); + +G_END_DECLS +#endif /* __GST_PLAY_SINK_AUDIO_CONVERT_H__ */ diff --git a/gst/playback/gstplaysinkvideoconvert.c b/gst/playback/gstplaysinkvideoconvert.c new file mode 100644 index 0000000000..c544462cb6 --- /dev/null +++ b/gst/playback/gstplaysinkvideoconvert.c @@ -0,0 +1,485 @@ +/* GStreamer + * Copyright (C) <2011> Sebastian Dröge + * + * 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 "gstplaysinkvideoconvert.h" + +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_play_sink_video_convert_debug); +#define GST_CAT_DEFAULT gst_play_sink_video_convert_debug + +#define parent_class gst_play_sink_video_convert_parent_class + +G_DEFINE_TYPE (GstPlaySinkVideoConvert, gst_play_sink_video_convert, + GST_TYPE_BIN); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static gboolean +is_raw_caps (GstCaps * caps) +{ + gint i, n; + GstStructure *s; + const gchar *name; + + n = gst_caps_get_size (caps); + for (i = 0; i < n; i++) { + s = gst_caps_get_structure (caps, i); + name = gst_structure_get_name (s); + if (!g_str_has_prefix (name, "video/x-raw")) + return FALSE; + } + + return TRUE; +} + +static void +post_missing_element_message (GstPlaySinkVideoConvert * self, + const gchar * name) +{ + GstMessage *msg; + + msg = gst_missing_element_message_new (GST_ELEMENT_CAST (self), name); + gst_element_post_message (GST_ELEMENT_CAST (self), msg); +} + +static void +distribute_running_time (GstElement * element, const GstSegment * segment) +{ + GstEvent *event; + GstPad *pad; + + pad = gst_element_get_static_pad (element, "sink"); + + if (segment->accum) { + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, 0, segment->accum, 0); + gst_pad_push_event (pad, event); + } + + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, + segment->start, segment->stop, segment->time); + gst_pad_push_event (pad, event); + + gst_object_unref (pad); +} + +static void +pad_blocked_cb (GstPad * pad, gboolean blocked, GstPlaySinkVideoConvert * self) +{ + GstPad *peer; + GstCaps *caps; + gboolean raw; + + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + self->sink_proxypad_blocked = blocked; + GST_DEBUG_OBJECT (self, "Pad blocked: %d", blocked); + if (!blocked) + goto done; + + /* There must be a peer at this point */ + peer = gst_pad_get_peer (self->sinkpad); + caps = gst_pad_get_negotiated_caps (peer); + if (!caps) + caps = gst_pad_get_caps_reffed (peer); + gst_object_unref (peer); + + raw = is_raw_caps (caps); + GST_DEBUG_OBJECT (self, "Caps %" GST_PTR_FORMAT " are raw: %d", caps, raw); + gst_caps_unref (caps); + + if (raw == self->raw) + goto unblock; + self->raw = raw; + + if (raw) { + GstBin *bin = GST_BIN_CAST (self); + GstElement *head = NULL, *prev = NULL; + GstPad *pad; + + GST_DEBUG_OBJECT (self, "Creating raw conversion pipeline"); + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + + self->conv = gst_element_factory_make ("ffmpegcolorspace", "conv"); + if (self->conv == NULL) { + post_missing_element_message (self, "ffmpegcolorspace"); + GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "ffmpegcolorspace"), ("video rendering might fail")); + } else { + gst_bin_add (bin, self->conv); + gst_element_sync_state_with_parent (self->conv); + distribute_running_time (self->conv, &self->segment); + prev = head = self->conv; + } + + self->scale = gst_element_factory_make ("videoscale", "scale"); + if (self->scale == NULL) { + post_missing_element_message (self, "videoscale"); + GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + "videoscale"), ("possibly a liboil version mismatch?")); + } else { + /* Add black borders if necessary to keep the DAR */ + g_object_set (self->scale, "add-borders", TRUE, NULL); + gst_bin_add (bin, self->scale); + gst_element_sync_state_with_parent (self->scale); + distribute_running_time (self->scale, &self->segment); + if (prev) { + if (!gst_element_link_pads_full (prev, "src", self->scale, "sink", + GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) + goto link_failed; + } else { + head = self->scale; + } + prev = self->scale; + } + + if (head) { + pad = gst_element_get_static_pad (head, "sink"); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), pad); + gst_object_unref (pad); + } + + if (prev) { + pad = gst_element_get_static_pad (prev, "src"); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), pad); + gst_object_unref (pad); + } + + if (!head && !prev) { + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + } + + GST_DEBUG_OBJECT (self, "Raw conversion pipeline created"); + } else { + GstBin *bin = GST_BIN_CAST (self); + + GST_DEBUG_OBJECT (self, "Removing raw conversion pipeline"); + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + + if (self->conv) { + gst_element_set_state (self->conv, GST_STATE_NULL); + gst_bin_remove (bin, self->conv); + self->conv = NULL; + } + if (self->scale) { + gst_element_set_state (self->scale, GST_STATE_NULL); + gst_bin_remove (bin, self->scale); + self->scale = NULL; + } + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + + GST_DEBUG_OBJECT (self, "Raw conversion pipeline removed"); + } + +unblock: + gst_pad_set_blocked_async_full (self->sink_proxypad, FALSE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + +done: + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + return; + +link_failed: + { + GST_ELEMENT_ERROR (self, CORE, PAD, + (NULL), ("Failed to configure the video converter.")); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + gst_pad_set_blocked_async_full (self->sink_proxypad, FALSE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + return; + } +} + +static gboolean +gst_play_sink_video_convert_sink_event (GstPad * pad, GstEvent * event) +{ + GstPlaySinkVideoConvert *self = + GST_PLAY_SINK_VIDEO_CONVERT (gst_pad_get_parent (pad)); + gboolean ret; + + ret = self->sink_event (pad, gst_event_ref (event)); + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + gboolean update; + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, position; + + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + GST_DEBUG_OBJECT (self, "Segment before %" GST_SEGMENT_FORMAT, + &self->segment); + gst_segment_set_newsegment_full (&self->segment, update, rate, applied_rate, + format, start, stop, position); + GST_DEBUG_OBJECT (self, "Segment after %" GST_SEGMENT_FORMAT, + &self->segment); + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + } else if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) { + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + GST_DEBUG_OBJECT (self, "Resetting segment"); + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + } + + gst_event_unref (event); + gst_object_unref (self); + + return ret; +} + +static gboolean +gst_play_sink_video_convert_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstPlaySinkVideoConvert *self = + GST_PLAY_SINK_VIDEO_CONVERT (gst_pad_get_parent (pad)); + gboolean ret; + GstStructure *s; + const gchar *name; + gboolean reconfigure = FALSE; + + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + s = gst_caps_get_structure (caps, 0); + name = gst_structure_get_name (s); + + if (g_str_has_prefix (name, "video/x-raw-")) { + if (!self->raw && !gst_pad_is_blocked (self->sink_proxypad)) { + GST_DEBUG_OBJECT (self, "Changing caps from non-raw to raw"); + reconfigure = TRUE; + gst_pad_set_blocked_async_full (self->sink_proxypad, TRUE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + } + } else { + if (self->raw && !gst_pad_is_blocked (self->sink_proxypad)) { + GST_DEBUG_OBJECT (self, "Changing caps from raw to non-raw"); + reconfigure = TRUE; + gst_pad_set_blocked_async_full (self->sink_proxypad, TRUE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + } + } + + /* Otherwise the setcaps below fails */ + if (reconfigure) { + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL); + } + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + + ret = self->sink_setcaps (pad, caps); + + GST_DEBUG_OBJECT (self, "Setting sink caps %" GST_PTR_FORMAT ": %d", caps, + ret); + + gst_object_unref (self); + + return ret; +} + +static GstCaps * +gst_play_sink_video_convert_getcaps (GstPad * pad) +{ + GstPlaySinkVideoConvert *self = + GST_PLAY_SINK_VIDEO_CONVERT (gst_pad_get_parent (pad)); + GstCaps *ret; + GstPad *otherpad, *peer; + + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + if (pad == self->srcpad) + otherpad = gst_object_ref (self->sinkpad); + else + otherpad = gst_object_ref (self->srcpad); + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + + peer = gst_pad_get_peer (otherpad); + if (peer) { + ret = gst_pad_get_caps_reffed (peer); + gst_object_unref (peer); + } else { + ret = gst_caps_new_any (); + } + + gst_object_unref (otherpad); + gst_object_unref (self); + + return ret; +} + +static void +gst_play_sink_video_convert_finalize (GObject * object) +{ + GstPlaySinkVideoConvert *self = GST_PLAY_SINK_VIDEO_CONVERT_CAST (object); + + gst_object_unref (self->sink_proxypad); + g_mutex_free (self->lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstStateChangeReturn +gst_play_sink_video_convert_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret; + GstPlaySinkVideoConvert *self = GST_PLAY_SINK_VIDEO_CONVERT_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + if (gst_pad_is_blocked (self->sink_proxypad)) + gst_pad_set_blocked_async_full (self->sink_proxypad, FALSE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + if (self->conv) { + gst_element_set_state (self->conv, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (self), self->conv); + self->conv = NULL; + } + if (self->scale) { + gst_element_set_state (self->scale, GST_STATE_NULL); + gst_bin_remove (GST_BIN_CAST (self), self->scale); + self->scale = NULL; + } + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); + self->raw = FALSE; + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_PLAY_SINK_VIDEO_CONVERT_LOCK (self); + if (!gst_pad_is_blocked (self->sink_proxypad)) + gst_pad_set_blocked_async_full (self->sink_proxypad, TRUE, + (GstPadBlockCallback) pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK (self); + default: + break; + } + + return ret; +} + +static void +gst_play_sink_video_convert_class_init (GstPlaySinkVideoConvertClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + GST_DEBUG_CATEGORY_INIT (gst_play_sink_video_convert_debug, + "playsinkvideoconvert", 0, "play bin"); + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = gst_play_sink_video_convert_finalize; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&srctemplate)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sinktemplate)); + gst_element_class_set_details_simple (gstelement_class, + "Player Sink Video Converter", "Video/Bin/Converter", + "Convenience bin for video conversion", + "Sebastian Dröge "); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_change_state); +} + +static void +gst_play_sink_video_convert_init (GstPlaySinkVideoConvert * self) +{ + GstPadTemplate *templ; + GstIterator *it; + + self->lock = g_mutex_new (); + gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); + + templ = gst_static_pad_template_get (&sinktemplate); + self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", templ); + self->sink_event = GST_PAD_EVENTFUNC (self->sinkpad); + gst_pad_set_event_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_sink_event)); + self->sink_setcaps = GST_PAD_SETCAPSFUNC (self->sinkpad); + gst_pad_set_setcaps_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_sink_setcaps)); + gst_pad_set_getcaps_function (self->sinkpad, + GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_getcaps)); + + it = gst_pad_iterate_internal_links (self->sinkpad); + g_assert (it); + gst_iterator_next (it, (gpointer *) & self->sink_proxypad); + g_assert (self->sink_proxypad); + gst_iterator_free (it); + + gst_element_add_pad (GST_ELEMENT_CAST (self), self->sinkpad); + gst_object_unref (templ); + + templ = gst_static_pad_template_get (&srctemplate); + self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ); + gst_pad_set_getcaps_function (self->srcpad, + GST_DEBUG_FUNCPTR (gst_play_sink_video_convert_getcaps)); + gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad); + gst_object_unref (templ); + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), + self->sink_proxypad); +} diff --git a/gst/playback/gstplaysinkvideoconvert.h b/gst/playback/gstplaysinkvideoconvert.h new file mode 100644 index 0000000000..be15dd212c --- /dev/null +++ b/gst/playback/gstplaysinkvideoconvert.h @@ -0,0 +1,86 @@ +/* GStreamer + * Copyright (C) <2011> Sebastian Dröge + * + * 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. + */ + +#include + +#ifndef __GST_PLAY_SINK_VIDEO_CONVERT_H__ +#define __GST_PLAY_SINK_VIDEO_CONVERT_H__ + +G_BEGIN_DECLS +#define GST_TYPE_PLAY_SINK_VIDEO_CONVERT \ + (gst_play_sink_video_convert_get_type()) +#define GST_PLAY_SINK_VIDEO_CONVERT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PLAY_SINK_VIDEO_CONVERT, GstPlaySinkVideoConvert)) +#define GST_PLAY_SINK_VIDEO_CONVERT_CAST(obj) \ + ((GstPlaySinkVideoConvert *) obj) +#define GST_PLAY_SINK_VIDEO_CONVERT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PLAY_SINK_VIDEO_CONVERT, GstPlaySinkVideoConvertClass)) +#define GST_IS_PLAY_SINK_VIDEO_CONVERT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PLAY_SINK_VIDEO_CONVERT)) +#define GST_IS_PLAY_SINK_VIDEO_CONVERT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PLAY_SINK_VIDEO_CONVERT)) + +#define GST_PLAY_SINK_VIDEO_CONVERT_LOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "locking from thread %p", \ + g_thread_self ()); \ + g_mutex_lock (GST_PLAY_SINK_VIDEO_CONVERT_CAST(obj)->lock); \ + GST_LOG_OBJECT (obj, \ + "locked from thread %p", \ + g_thread_self ()); \ +} G_STMT_END + +#define GST_PLAY_SINK_VIDEO_CONVERT_UNLOCK(obj) G_STMT_START { \ + GST_LOG_OBJECT (obj, \ + "unlocking from thread %p", \ + g_thread_self ()); \ + g_mutex_unlock (GST_PLAY_SINK_VIDEO_CONVERT_CAST(obj)->lock); \ +} G_STMT_END + +typedef struct _GstPlaySinkVideoConvert GstPlaySinkVideoConvert; +typedef struct _GstPlaySinkVideoConvertClass GstPlaySinkVideoConvertClass; + +struct _GstPlaySinkVideoConvert +{ + GstBin parent; + + /* < private > */ + GMutex *lock; + + GstPad *sinkpad, *sink_proxypad; + GstPadEventFunction sink_event; + GstPadSetCapsFunction sink_setcaps; + gboolean sink_proxypad_blocked; + GstSegment segment; + + GstPad *srcpad; + + gboolean raw; + GstElement *conv, *scale; +}; + +struct _GstPlaySinkVideoConvertClass +{ + GstBinClass parent; +}; + +GType gst_play_sink_video_convert_get_type (void); + +G_END_DECLS +#endif /* __GST_PLAY_SINK_VIDEO_CONVERT_H__ */