gstreamer/gst/playback/gstplaysinkconvertbin.c
Sebastian Dröge 9084bc151b playsinkconvertbin: Make sure to return all allowed caps in the GET_CAPS query
Add all the caps that we can convert to to the filter caps,
otherwise downstream might just return EMPTY caps because
it doesn't handle the filter caps but we could still convert
to these caps, causing us to return EMPTY caps although
conversion would be possible.

https://bugzilla.gnome.org/show_bug.cgi?id=688803
2013-03-21 13:42:41 +01:00

663 lines
19 KiB
C

/* GStreamer
* Copyright (C) <2011> Sebastian Dröge <sebastian.droege@collabora.co.uk>
* Copyright (C) <2011> Vincent Penquerch <vincent.penquerch@collabora.co.uk>
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstplaysinkconvertbin.h"
#include <gst/pbutils/pbutils.h>
#include <gst/gst-i18n-plugin.h>
GST_DEBUG_CATEGORY_STATIC (gst_play_sink_convert_bin_debug);
#define GST_CAT_DEFAULT gst_play_sink_convert_bin_debug
#define parent_class gst_play_sink_convert_bin_parent_class
static gboolean gst_play_sink_convert_bin_sink_setcaps (GstPlaySinkConvertBin *
self, GstCaps * caps);
G_DEFINE_TYPE (GstPlaySinkConvertBin, gst_play_sink_convert_bin, 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, gboolean audio)
{
gint i, n;
GstStructure *s;
const gchar *name;
const gchar *prefix = audio ? "audio/x-raw" : "video/x-raw";
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_equal (name, prefix))
return TRUE;
}
return FALSE;
}
static void
gst_play_sink_convert_bin_post_missing_element_message (GstPlaySinkConvertBin *
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");
gst_pad_send_event (pad, gst_event_new_flush_start ());
gst_pad_send_event (pad, gst_event_new_flush_stop (FALSE));
if (segment->format != GST_FORMAT_UNDEFINED) {
event = gst_event_new_segment (segment);
gst_pad_send_event (pad, event);
}
gst_object_unref (pad);
}
void
gst_play_sink_convert_bin_add_conversion_element (GstPlaySinkConvertBin * self,
GstElement * el)
{
self->conversion_elements = g_list_append (self->conversion_elements, el);
gst_bin_add (GST_BIN (self), gst_object_ref (el));
}
GstElement *
gst_play_sink_convert_bin_add_conversion_element_factory (GstPlaySinkConvertBin
* self, const char *factory, const char *name)
{
GstElement *el;
el = gst_element_factory_make (factory, name);
if (el == NULL) {
gst_play_sink_convert_bin_post_missing_element_message (self, factory);
GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
factory),
(self->audio ? "audio rendering might fail" :
"video rendering might fail"));
} else {
gst_play_sink_convert_bin_add_conversion_element (self, el);
}
return el;
}
void
gst_play_sink_convert_bin_add_identity (GstPlaySinkConvertBin * self)
{
if (self->identity)
return;
self->identity = gst_element_factory_make ("identity", "identity");
if (self->identity == NULL) {
gst_play_sink_convert_bin_post_missing_element_message (self, "identity");
GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN,
(_("Missing element '%s' - check your GStreamer installation."),
"identity"), (self->audio ?
"audio rendering might fail" : "video rendering might fail")
);
} else {
g_object_set (self->identity, "silent", TRUE, "signal-handoffs", FALSE,
NULL);
gst_bin_add (GST_BIN_CAST (self), self->identity);
}
}
static void
gst_play_sink_convert_bin_set_targets (GstPlaySinkConvertBin * self,
gboolean passthrough)
{
GstPad *pad;
GstElement *head, *tail;
GST_DEBUG_OBJECT (self, "Setting pad targets with passthrough %d",
passthrough);
if (self->conversion_elements == NULL || passthrough) {
GST_DEBUG_OBJECT (self, "no conversion elements, using identity (%p) as "
"head/tail", self->identity);
if (!passthrough) {
GST_WARNING_OBJECT (self,
"Doing passthrough as no converter elements were added");
}
head = tail = self->identity;
} else {
head = GST_ELEMENT (g_list_first (self->conversion_elements)->data);
tail = GST_ELEMENT (g_list_last (self->conversion_elements)->data);
GST_DEBUG_OBJECT (self, "conversion elements in use, picking "
"head:%s and tail:%s", GST_OBJECT_NAME (head), GST_OBJECT_NAME (tail));
}
g_return_if_fail (head != NULL);
g_return_if_fail (tail != NULL);
pad = gst_element_get_static_pad (head, "sink");
GST_DEBUG_OBJECT (self, "Ghosting bin sink pad to %" GST_PTR_FORMAT, pad);
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), pad);
gst_object_unref (pad);
pad = gst_element_get_static_pad (tail, "src");
GST_DEBUG_OBJECT (self, "Ghosting bin src pad to %" GST_PTR_FORMAT, pad);
gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), pad);
gst_object_unref (pad);
}
static void
gst_play_sink_convert_bin_remove_element (GstElement * element,
GstPlaySinkConvertBin * self)
{
gst_element_set_state (element, GST_STATE_NULL);
gst_object_unref (element);
gst_bin_remove (GST_BIN_CAST (self), element);
}
static void
gst_play_sink_convert_bin_on_element_added (GstElement * element,
GstPlaySinkConvertBin * self)
{
gst_element_sync_state_with_parent (element);
distribute_running_time (element, &self->segment);
}
static GstPadProbeReturn
pad_blocked_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
GstPlaySinkConvertBin *self = user_data;
GstPad *peer;
GstCaps *caps;
gboolean raw;
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
GST_DEBUG_OBJECT (self, "Pad blocked");
/* There must be a peer at this point */
peer = gst_pad_get_peer (self->sinkpad);
caps = gst_pad_get_current_caps (peer);
if (!caps)
caps = gst_pad_query_caps (peer, NULL);
gst_object_unref (peer);
raw = is_raw_caps (caps, self->audio);
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;
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 (raw) {
GST_DEBUG_OBJECT (self, "Switching to raw conversion pipeline");
if (self->conversion_elements)
g_list_foreach (self->conversion_elements,
(GFunc) gst_play_sink_convert_bin_on_element_added, self);
} else {
GST_DEBUG_OBJECT (self, "Switch to passthrough pipeline");
gst_play_sink_convert_bin_on_element_added (self->identity, self);
}
gst_play_sink_convert_bin_set_targets (self, !raw);
unblock:
self->sink_proxypad_block_id = 0;
GST_PLAY_SINK_CONVERT_BIN_UNLOCK (self);
return GST_PAD_PROBE_REMOVE;
}
static gboolean
gst_play_sink_convert_bin_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
GstPlaySinkConvertBin *self = GST_PLAY_SINK_CONVERT_BIN (parent);
gboolean ret;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
ret = gst_play_sink_convert_bin_sink_setcaps (self, caps);
break;
}
default:
break;
}
ret = gst_pad_event_default (pad, parent, gst_event_ref (event));
if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
GstSegment seg;
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
gst_event_copy_segment (event, &seg);
GST_DEBUG_OBJECT (self, "Segment before %" GST_SEGMENT_FORMAT,
&self->segment);
self->segment = seg;
GST_DEBUG_OBJECT (self, "Segment after %" GST_SEGMENT_FORMAT,
&self->segment);
GST_PLAY_SINK_CONVERT_BIN_UNLOCK (self);
} else if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) {
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
GST_DEBUG_OBJECT (self, "Resetting segment");
gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
GST_PLAY_SINK_CONVERT_BIN_UNLOCK (self);
}
gst_event_unref (event);
return ret;
}
static void
block_proxypad (GstPlaySinkConvertBin * self)
{
if (self->sink_proxypad_block_id == 0) {
self->sink_proxypad_block_id =
gst_pad_add_probe (self->sink_proxypad,
GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, pad_blocked_cb, self, NULL);
}
}
static void
unblock_proxypad (GstPlaySinkConvertBin * self)
{
if (self->sink_proxypad_block_id != 0) {
gst_pad_remove_probe (self->sink_proxypad, self->sink_proxypad_block_id);
self->sink_proxypad_block_id = 0;
}
}
static gboolean
gst_play_sink_convert_bin_sink_setcaps (GstPlaySinkConvertBin * self,
GstCaps * caps)
{
GstStructure *s;
const gchar *name;
gboolean reconfigure = FALSE;
gboolean raw;
GST_DEBUG_OBJECT (self, "setcaps");
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
s = gst_caps_get_structure (caps, 0);
name = gst_structure_get_name (s);
if (self->audio) {
raw = g_str_equal (name, "audio/x-raw");
} else {
raw = g_str_equal (name, "video/x-raw");
}
GST_DEBUG_OBJECT (self, "raw %d, self->raw %d, blocked %d",
raw, self->raw, gst_pad_is_blocked (self->sink_proxypad));
if (raw) {
if (!gst_pad_is_blocked (self->sink_proxypad)) {
GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD (self->sinkpad));
if (!self->raw || (target && !gst_pad_query_accept_caps (target, caps))) {
if (!self->raw)
GST_DEBUG_OBJECT (self, "Changing caps from non-raw to raw");
else
GST_DEBUG_OBJECT (self, "Changing caps in an incompatible way");
reconfigure = TRUE;
block_proxypad (self);
}
if (target)
gst_object_unref (target);
}
} else {
if (self->raw && !gst_pad_is_blocked (self->sink_proxypad)) {
GST_DEBUG_OBJECT (self, "Changing caps from raw to non-raw");
reconfigure = TRUE;
block_proxypad (self);
}
}
/* 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_CONVERT_BIN_UNLOCK (self);
GST_DEBUG_OBJECT (self, "Setting sink caps %" GST_PTR_FORMAT, caps);
return TRUE;
}
static GstCaps *
gst_play_sink_convert_bin_getcaps (GstPad * pad, GstCaps * filter)
{
GstPlaySinkConvertBin *self =
GST_PLAY_SINK_CONVERT_BIN (gst_pad_get_parent (pad));
GstCaps *ret;
GstPad *otherpad, *peer;
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
if (pad == self->srcpad) {
otherpad = self->sinkpad;
} else if (pad == self->sinkpad) {
otherpad = self->srcpad;
} else {
GST_ERROR_OBJECT (pad, "Not one of our pads");
otherpad = NULL;
}
if (otherpad) {
peer = gst_pad_get_peer (otherpad);
if (peer) {
GstCaps *peer_caps;
GstCaps *downstream_filter = NULL;
/* Add all the caps that we can convert to to the filter caps,
* otherwise downstream might just return EMPTY caps because
* it doesn't handle the filter caps but we could still convert
* to these caps */
if (filter) {
downstream_filter = gst_caps_copy (filter);
downstream_filter =
gst_caps_merge (downstream_filter,
gst_caps_ref (self->converter_caps));
}
peer_caps = gst_pad_query_caps (peer, downstream_filter);
gst_object_unref (peer);
if (self->converter_caps && is_raw_caps (peer_caps, self->audio)) {
ret = gst_caps_merge (peer_caps, gst_caps_ref (self->converter_caps));
} else {
ret = peer_caps;
}
} else {
ret = gst_caps_ref (self->converter_caps);
}
} else {
ret = gst_caps_new_any ();
}
GST_PLAY_SINK_CONVERT_BIN_UNLOCK (self);
gst_object_unref (self);
if (filter) {
GstCaps *intersection =
gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (ret);
ret = intersection;
}
GST_DEBUG_OBJECT (pad, "Returning caps %" GST_PTR_FORMAT, ret);
return ret;
}
static gboolean
gst_play_sink_convert_bin_acceptcaps (GstPad * pad, GstCaps * caps)
{
GstCaps *allowed_caps;
gboolean ret;
allowed_caps = gst_pad_query_caps (pad, NULL);
ret = gst_caps_is_subset (caps, allowed_caps);
gst_caps_unref (allowed_caps);
return ret;
}
static gboolean
gst_play_sink_convert_bin_query (GstPad * pad, GstObject * parent,
GstQuery * query)
{
gboolean res = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_ACCEPT_CAPS:
{
GstCaps *caps;
gst_query_parse_accept_caps (query, &caps);
gst_query_set_accept_caps_result (query,
gst_play_sink_convert_bin_acceptcaps (pad, caps));
res = TRUE;
break;
}
case GST_QUERY_CAPS:
{
GstCaps *filter, *caps;
gst_query_parse_caps (query, &filter);
caps = gst_play_sink_convert_bin_getcaps (pad, filter);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
res = TRUE;
break;
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
return res;
}
void
gst_play_sink_convert_bin_remove_elements (GstPlaySinkConvertBin * self)
{
if (self->conversion_elements) {
g_list_foreach (self->conversion_elements,
(GFunc) gst_play_sink_convert_bin_remove_element, self);
g_list_free (self->conversion_elements);
self->conversion_elements = NULL;
}
if (self->converter_caps) {
gst_caps_unref (self->converter_caps);
self->converter_caps = NULL;
}
}
static void
gst_play_sink_convert_bin_dispose (GObject * object)
{
GstPlaySinkConvertBin *self = GST_PLAY_SINK_CONVERT_BIN_CAST (object);
gst_play_sink_convert_bin_remove_elements (self);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_play_sink_convert_bin_finalize (GObject * object)
{
GstPlaySinkConvertBin *self = GST_PLAY_SINK_CONVERT_BIN_CAST (object);
gst_object_unref (self->sink_proxypad);
g_mutex_clear (&self->lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
void
gst_play_sink_convert_bin_cache_converter_caps (GstPlaySinkConvertBin * self)
{
GstElement *head;
GstPad *pad;
if (self->converter_caps) {
gst_caps_unref (self->converter_caps);
self->converter_caps = NULL;
}
if (!self->conversion_elements) {
GST_INFO_OBJECT (self, "No conversion elements");
return;
}
head = GST_ELEMENT (g_list_first (self->conversion_elements)->data);
pad = gst_element_get_static_pad (head, "sink");
if (!pad) {
GST_WARNING_OBJECT (self, "No sink pad found");
return;
}
self->converter_caps = gst_pad_query_caps (pad, NULL);
GST_INFO_OBJECT (self, "Converter caps: %" GST_PTR_FORMAT,
self->converter_caps);
gst_object_unref (pad);
}
static GstStateChangeReturn
gst_play_sink_convert_bin_change_state (GstElement * element,
GstStateChange transition)
{
GstStateChangeReturn ret;
GstPlaySinkConvertBin *self = GST_PLAY_SINK_CONVERT_BIN_CAST (element);
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
unblock_proxypad (self);
GST_PLAY_SINK_CONVERT_BIN_UNLOCK (self);
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
gst_play_sink_convert_bin_set_targets (self, TRUE);
self->raw = FALSE;
GST_PLAY_SINK_CONVERT_BIN_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_CONVERT_BIN_LOCK (self);
gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
gst_play_sink_convert_bin_set_targets (self, TRUE);
self->raw = FALSE;
GST_PLAY_SINK_CONVERT_BIN_UNLOCK (self);
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
GST_PLAY_SINK_CONVERT_BIN_LOCK (self);
unblock_proxypad (self);
GST_PLAY_SINK_CONVERT_BIN_UNLOCK (self);
break;
default:
break;
}
return ret;
}
static void
gst_play_sink_convert_bin_class_init (GstPlaySinkConvertBinClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GST_DEBUG_CATEGORY_INIT (gst_play_sink_convert_bin_debug,
"playsinkconvertbin", 0, "play bin");
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->dispose = gst_play_sink_convert_bin_dispose;
gobject_class->finalize = gst_play_sink_convert_bin_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_static_metadata (gstelement_class,
"Player Sink Converter Bin", "Bin/Converter",
"Convenience bin for audio/video conversion",
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_play_sink_convert_bin_change_state);
}
static void
gst_play_sink_convert_bin_init (GstPlaySinkConvertBin * self)
{
GstPadTemplate *templ;
g_mutex_init (&self->lock);
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);
gst_pad_set_event_function (self->sinkpad,
GST_DEBUG_FUNCPTR (gst_play_sink_convert_bin_sink_event));
gst_pad_set_query_function (self->sinkpad,
GST_DEBUG_FUNCPTR (gst_play_sink_convert_bin_query));
self->sink_proxypad =
GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (self->sinkpad)));
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_query_function (self->srcpad,
GST_DEBUG_FUNCPTR (gst_play_sink_convert_bin_query));
gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
gst_object_unref (templ);
gst_play_sink_convert_bin_add_identity (self);
}