capsfilter: Add an optional delayed caps change mode

In this mode we accept previously set filter caps until
upstream renegotiates to something that is compatible
to the current filter caps.

This allows dynamic caps changes in the pipeline even
if there is a queue between any conversion element
and the capsfilter. Without this we would get not-negotiated
errors if timing is bad.

https://bugzilla.gnome.org/show_bug.cgi?id=739002
This commit is contained in:
Sebastian Dröge 2014-10-22 14:07:09 +02:00
parent b77446bc6d
commit 9606e04895
3 changed files with 284 additions and 2 deletions

View file

@ -43,9 +43,11 @@
enum
{
PROP_0,
PROP_FILTER_CAPS
PROP_FILTER_CAPS,
PROP_CAPS_CHANGE_MODE
};
#define DEFAULT_CAPS_CHANGE_MODE (GST_CAPS_FILTER_CAPS_CHANGE_MODE_IMMEDIATE)
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
@ -61,6 +63,26 @@ static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_DEBUG_CATEGORY_STATIC (gst_capsfilter_debug);
#define GST_CAT_DEFAULT gst_capsfilter_debug
/* TODO: Add a drop-buffers mode */
#define GST_TYPE_CAPS_FILTER_CAPS_CHANGE_MODE (gst_caps_filter_caps_change_mode_get_type())
static GType
gst_caps_filter_caps_change_mode_get_type (void)
{
static GType type = 0;
static const GEnumValue data[] = {
{GST_CAPS_FILTER_CAPS_CHANGE_MODE_IMMEDIATE,
"Only accept the current filter caps", "immediate"},
{GST_CAPS_FILTER_CAPS_CHANGE_MODE_DELAYED,
"Temporarily accept previous filter caps", "delayed"},
{0, NULL, NULL},
};
if (!type) {
type = g_enum_register_static ("GstCapsFilterCapsChangeMode", data);
}
return type;
}
#define _do_init \
GST_DEBUG_CATEGORY_INIT (gst_capsfilter_debug, "capsfilter", 0, \
"capsfilter element");
@ -107,6 +129,13 @@ gst_capsfilter_class_init (GstCapsFilterClass * klass)
G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_CAPS_CHANGE_MODE,
g_param_spec_enum ("caps-change-mode", _("Caps Change Mode"),
_("Filter caps change behaviour"),
GST_TYPE_CAPS_FILTER_CAPS_CHANGE_MODE, DEFAULT_CAPS_CHANGE_MODE,
G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING |
G_PARAM_STATIC_STRINGS));
gstelement_class = GST_ELEMENT_CLASS (klass);
gst_element_class_set_static_metadata (gstelement_class,
"CapsFilter",
@ -136,6 +165,7 @@ gst_capsfilter_init (GstCapsFilter * filter)
gst_base_transform_set_gap_aware (trans, TRUE);
gst_base_transform_set_prefer_passthrough (trans, FALSE);
filter->filter_caps = gst_caps_new_any ();
filter->caps_change_mode = DEFAULT_CAPS_CHANGE_MODE;
}
static void
@ -160,6 +190,17 @@ gst_capsfilter_set_property (GObject * object, guint prop_id,
GST_OBJECT_LOCK (capsfilter);
old_caps = capsfilter->filter_caps;
capsfilter->filter_caps = new_caps;
if (old_caps
&& capsfilter->caps_change_mode ==
GST_CAPS_FILTER_CAPS_CHANGE_MODE_DELAYED) {
capsfilter->previous_caps =
g_list_prepend (capsfilter->previous_caps, gst_caps_ref (old_caps));
} else if (capsfilter->caps_change_mode !=
GST_CAPS_FILTER_CAPS_CHANGE_MODE_DELAYED) {
g_list_free_full (capsfilter->previous_caps,
(GDestroyNotify) gst_caps_unref);
capsfilter->previous_caps = NULL;
}
GST_OBJECT_UNLOCK (capsfilter);
gst_caps_unref (old_caps);
@ -169,6 +210,9 @@ gst_capsfilter_set_property (GObject * object, guint prop_id,
gst_base_transform_reconfigure_sink (GST_BASE_TRANSFORM (object));
break;
}
case PROP_CAPS_CHANGE_MODE:
capsfilter->caps_change_mode = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -187,6 +231,9 @@ gst_capsfilter_get_property (GObject * object, guint prop_id, GValue * value,
gst_value_set_caps (value, capsfilter->filter_caps);
GST_OBJECT_UNLOCK (capsfilter);
break;
case PROP_CAPS_CHANGE_MODE:
g_value_set_enum (value, capsfilter->caps_change_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -211,11 +258,15 @@ gst_capsfilter_transform_caps (GstBaseTransform * base,
{
GstCapsFilter *capsfilter = GST_CAPSFILTER (base);
GstCaps *ret, *filter_caps, *tmp;
gboolean retried = FALSE;
GstCapsFilterCapsChangeMode caps_change_mode;
GST_OBJECT_LOCK (capsfilter);
filter_caps = gst_caps_ref (capsfilter->filter_caps);
caps_change_mode = capsfilter->caps_change_mode;
GST_OBJECT_UNLOCK (capsfilter);
retry:
if (filter) {
tmp =
gst_caps_intersect_full (filter, filter_caps, GST_CAPS_INTERSECT_FIRST);
@ -231,6 +282,26 @@ gst_capsfilter_transform_caps (GstBaseTransform * base,
filter_caps);
GST_DEBUG_OBJECT (capsfilter, "intersect: %" GST_PTR_FORMAT, ret);
if (gst_caps_is_empty (ret)
&& caps_change_mode ==
GST_CAPS_FILTER_CAPS_CHANGE_MODE_DELAYED && capsfilter->previous_caps
&& !retried) {
GList *l;
GST_DEBUG_OBJECT (capsfilter,
"Current filter caps are not compatible, retry with previous");
GST_OBJECT_LOCK (capsfilter);
gst_caps_unref (filter_caps);
gst_caps_unref (ret);
filter_caps = gst_caps_new_empty ();
for (l = capsfilter->previous_caps; l; l = l->next) {
filter_caps = gst_caps_merge (filter_caps, gst_caps_ref (l->data));
}
GST_OBJECT_UNLOCK (capsfilter);
retried = TRUE;
goto retry;
}
gst_caps_unref (filter_caps);
return ret;
@ -250,6 +321,25 @@ gst_capsfilter_accept_caps (GstBaseTransform * base,
ret = gst_caps_can_intersect (caps, filter_caps);
GST_DEBUG_OBJECT (capsfilter, "can intersect: %d", ret);
if (!ret
&& capsfilter->caps_change_mode ==
GST_CAPS_FILTER_CAPS_CHANGE_MODE_DELAYED) {
GList *l;
GST_OBJECT_LOCK (capsfilter);
for (l = capsfilter->previous_caps; l; l = l->next) {
ret = gst_caps_can_intersect (caps, l->data);
if (ret)
break;
}
GST_OBJECT_UNLOCK (capsfilter);
/* Tell upstream to reconfigure, it's still
* looking at old caps */
if (ret)
gst_base_transform_reconfigure_sink (base);
}
if (ret) {
/* if we can intersect, see if the other end also accepts */
if (direction == GST_PAD_SRC)
@ -381,6 +471,7 @@ static gboolean
gst_capsfilter_sink_event (GstBaseTransform * trans, GstEvent * event)
{
GstCapsFilter *filter = GST_CAPSFILTER (trans);
gboolean ret;
if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) {
GList *l;
@ -422,7 +513,39 @@ gst_capsfilter_sink_event (GstBaseTransform * trans, GstEvent * event)
done:
GST_LOG_OBJECT (trans, "Forwarding %s event", GST_EVENT_TYPE_NAME (event));
return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
ret =
GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans,
gst_event_ref (event));
if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS
&& filter->caps_change_mode == GST_CAPS_FILTER_CAPS_CHANGE_MODE_DELAYED) {
GList *l;
GstCaps *caps;
gst_event_parse_caps (event, &caps);
/* Remove all previous caps up to one that works.
* Note that this might keep some leftover caps if there
* are multiple compatible caps */
GST_OBJECT_LOCK (filter);
for (l = g_list_last (filter->previous_caps); l; l = l->prev) {
if (gst_caps_can_intersect (caps, l->data)) {
while (l->next) {
gst_caps_unref (l->next->data);
l = g_list_delete_link (l, l->next);
}
break;
}
}
if (!l && gst_caps_can_intersect (caps, filter->filter_caps)) {
g_list_free_full (filter->previous_caps, (GDestroyNotify) gst_caps_unref);
filter->previous_caps = NULL;
}
GST_OBJECT_UNLOCK (filter);
}
gst_event_unref (event);
return ret;
}
static gboolean
@ -433,5 +556,10 @@ gst_capsfilter_stop (GstBaseTransform * trans)
g_list_free_full (filter->pending_events, (GDestroyNotify) gst_event_unref);
filter->pending_events = NULL;
GST_OBJECT_LOCK (filter);
g_list_free_full (filter->previous_caps, (GDestroyNotify) gst_caps_unref);
filter->previous_caps = NULL;
GST_OBJECT_UNLOCK (filter);
return TRUE;
}

View file

@ -44,6 +44,11 @@ G_BEGIN_DECLS
typedef struct _GstCapsFilter GstCapsFilter;
typedef struct _GstCapsFilterClass GstCapsFilterClass;
typedef enum {
GST_CAPS_FILTER_CAPS_CHANGE_MODE_IMMEDIATE,
GST_CAPS_FILTER_CAPS_CHANGE_MODE_DELAYED
} GstCapsFilterCapsChangeMode;
/**
* GstCapsFilter:
*
@ -53,7 +58,10 @@ struct _GstCapsFilter {
GstBaseTransform trans;
GstCaps *filter_caps;
GstCapsFilterCapsChangeMode caps_change_mode;
GList *pending_events;
GList *previous_caps;
};
struct _GstCapsFilterClass {

View file

@ -36,6 +36,16 @@ static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_STATIC_CAPS (CAPS_TEMPLATE_STRING)
);
static GstStaticPadTemplate any_sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate any_srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GList *events = NULL;
static gboolean
@ -325,6 +335,141 @@ GST_START_TEST (test_push_pending_events)
GST_END_TEST;
GST_START_TEST (test_caps_change_mode_delayed)
{
GstElement *filter;
GstPad *mysinkpad;
GstPad *mysrcpad;
GstSegment segment;
GstEvent *event;
GstCaps *caps;
filter = gst_check_setup_element ("capsfilter");
mysinkpad = gst_check_setup_sink_pad (filter, &any_sinktemplate);
gst_pad_set_event_function (mysinkpad, test_pad_eventfunc);
gst_pad_set_active (mysinkpad, TRUE);
mysrcpad = gst_check_setup_src_pad (filter, &any_srctemplate);
gst_pad_set_active (mysrcpad, TRUE);
g_object_set (filter, "caps-change-mode", 1, NULL);
fail_unless_equals_int (gst_element_set_state (filter, GST_STATE_PLAYING),
GST_STATE_CHANGE_SUCCESS);
/* push the stream start */
fail_unless (gst_pad_push_event (mysrcpad,
gst_event_new_stream_start ("test-stream")));
fail_unless_equals_int (g_list_length (events), 1);
event = events->data;
fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START);
g_list_free_full (events, (GDestroyNotify) gst_event_unref);
events = NULL;
/* push a caps */
caps = gst_caps_from_string ("audio/x-raw, "
"channels=(int)2, " "rate = (int)44100");
g_object_set (filter, "caps", caps, NULL);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_caps (caps)));
gst_caps_unref (caps);
fail_unless_equals_int (g_list_length (events), 1);
event = events->data;
fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_CAPS);
g_list_free_full (events, (GDestroyNotify) gst_event_unref);
events = NULL;
/* push a segment */
gst_segment_init (&segment, GST_FORMAT_TIME);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));
fail_unless_equals_int (g_list_length (events), 1);
event = events->data;
fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT);
g_list_free_full (events, (GDestroyNotify) gst_event_unref);
events = NULL;
/* push a buffer */
fail_unless_equals_int (gst_pad_push (mysrcpad,
gst_buffer_new_wrapped (g_malloc0 (1024), 1024)), GST_FLOW_OK);
fail_unless_equals_int (g_list_length (buffers), 1);
g_list_free_full (buffers, (GDestroyNotify) gst_buffer_unref);
buffers = NULL;
/* Set new incompatible caps */
caps = gst_caps_from_string ("audio/x-raw, "
"channels=(int)2, " "rate = (int)48000");
g_object_set (filter, "caps", caps, NULL);
/* push a buffer without updating the caps */
fail_unless_equals_int (gst_pad_push (mysrcpad,
gst_buffer_new_wrapped (g_malloc0 (1024), 1024)), GST_FLOW_OK);
fail_unless_equals_int (g_list_length (buffers), 1);
g_list_free_full (buffers, (GDestroyNotify) gst_buffer_unref);
buffers = NULL;
/* No caps event here, we're still at the old caps */
fail_unless (g_list_length (events) == 0);
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_caps (caps)));
gst_caps_unref (caps);
fail_unless_equals_int (g_list_length (events), 1);
event = events->data;
fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_CAPS);
g_list_free_full (events, (GDestroyNotify) gst_event_unref);
events = NULL;
/* Push a new buffer, now we have the new caps */
fail_unless_equals_int (gst_pad_push (mysrcpad,
gst_buffer_new_wrapped (g_malloc0 (1024), 1024)), GST_FLOW_OK);
fail_unless_equals_int (g_list_length (buffers), 1);
g_list_free_full (buffers, (GDestroyNotify) gst_buffer_unref);
buffers = NULL;
/* Set back old caps */
caps = gst_caps_from_string ("audio/x-raw, "
"channels=(int)2, " "rate = (int)44100");
g_object_set (filter, "caps", caps, NULL);
gst_caps_unref (caps);
/* push a buffer without updating the caps */
fail_unless_equals_int (gst_pad_push (mysrcpad,
gst_buffer_new_wrapped (g_malloc0 (1024), 1024)), GST_FLOW_OK);
fail_unless_equals_int (g_list_length (buffers), 1);
g_list_free_full (buffers, (GDestroyNotify) gst_buffer_unref);
buffers = NULL;
/* Now set new caps again but the old caps are currently pushed */
caps = gst_caps_from_string ("audio/x-raw, "
"channels=(int)2, " "rate = (int)48000");
g_object_set (filter, "caps", caps, NULL);
gst_caps_unref (caps);
/* Race condition simulation here! */
caps = gst_caps_from_string ("audio/x-raw, "
"channels=(int)2, " "rate = (int)44100");
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_caps (caps)));
gst_caps_unref (caps);
fail_unless_equals_int (g_list_length (events), 1);
event = events->data;
fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_CAPS);
g_list_free_full (events, (GDestroyNotify) gst_event_unref);
events = NULL;
fail_unless_equals_int (gst_pad_push (mysrcpad,
gst_buffer_new_wrapped (g_malloc0 (1024), 1024)), GST_FLOW_OK);
fail_unless_equals_int (g_list_length (buffers), 1);
g_list_free_full (buffers, (GDestroyNotify) gst_buffer_unref);
buffers = NULL;
/* cleanup */
GST_DEBUG ("cleanup");
gst_pad_set_active (mysrcpad, FALSE);
gst_pad_set_active (mysinkpad, FALSE);
gst_check_teardown_src_pad (filter);
gst_check_teardown_sink_pad (filter);
gst_check_teardown_element (filter);
}
GST_END_TEST;
static Suite *
capsfilter_suite (void)
{
@ -337,6 +482,7 @@ capsfilter_suite (void)
tcase_add_test (tc_chain, test_caps_query);
tcase_add_test (tc_chain, test_accept_caps_query);
tcase_add_test (tc_chain, test_push_pending_events);
tcase_add_test (tc_chain, test_caps_change_mode_delayed);
return s;
}