element: Allow multiple conversion specifiers for request pads

This allows pad template names like "src_%u_%u", but it does not allow
multiple specifiers of string type %s as that would lead to ambiguities.

https://bugzilla.gnome.org/show_bug.cgi?id=761225
This commit is contained in:
Wonchul Lee 2016-04-29 16:26:49 +09:00 committed by Sebastian Dröge
parent 83cac0f7b6
commit f80dfc9b06
4 changed files with 466 additions and 98 deletions

View file

@ -920,6 +920,119 @@ gst_element_get_static_pad (GstElement * element, const gchar * name)
return result;
}
static gboolean
gst_element_is_valid_request_template_name (const gchar * templ_name,
const gchar * name)
{
gchar *endptr;
const gchar *templ_name_ptr, *name_ptr;
gboolean next_specifier;
guint templ_postfix_len = 0, name_postfix_len = 0;
g_return_val_if_fail (templ_name != NULL, FALSE);
g_return_val_if_fail (name != NULL, FALSE);
/* Is this the template name? */
if (strcmp (templ_name, name) == 0)
return TRUE;
/* otherwise check all the specifiers */
do {
/* Because of sanity checks in gst_pad_template_new(), we know that %s
* and %d and %u, occurring at the template_name */
templ_name_ptr = strchr (templ_name, '%');
/* check characters ahead of the specifier */
if (!templ_name_ptr || strlen (name) <= templ_name_ptr - templ_name
|| strncmp (templ_name, name, templ_name_ptr - templ_name) != 0) {
return FALSE;
}
/* %s is not allowed for multiple specifiers, just a single specifier can be
* accepted in gst_pad_template_new() and can not be mixed with other
* specifier '%u' and '%d' */
if (*(templ_name_ptr + 1) == 's' && g_strcmp0 (templ_name, name) == 0) {
return TRUE;
}
name_ptr = name + (templ_name_ptr - templ_name);
/* search next specifier, each of specifier should be separated by '_' */
templ_name = strchr (templ_name_ptr, '_');
name = strchr (name_ptr, '_');
/* don't match the number of specifiers */
if ((templ_name && !name) || (!templ_name && name))
return FALSE;
if (templ_name && name)
next_specifier = TRUE;
else
next_specifier = FALSE;
/* check characters followed by the specifier */
if (*(templ_name_ptr + 2) != '\0' && *(templ_name_ptr + 2) != '_') {
if (next_specifier) {
templ_postfix_len = templ_name - (templ_name_ptr + 2);
name_postfix_len = name - name_ptr;
} else {
templ_postfix_len = strlen (templ_name_ptr + 2);
name_postfix_len = strlen (name_ptr);
}
if (strncmp (templ_name_ptr + 2,
name_ptr + name_postfix_len - templ_postfix_len,
templ_postfix_len) != 0) {
return FALSE;
}
}
/* verify the specifier */
if (*(name_ptr) == '%') {
guint len;
len = (next_specifier) ? name - name_ptr : strlen (name_ptr);
if (strncmp (name_ptr, templ_name_ptr, len) != 0)
return FALSE;
} else {
const gchar *specifier;
gchar *target = NULL;
/* extract specifier when it has postfix characters */
if (name_postfix_len > templ_postfix_len) {
target = g_strndup (name_ptr, name_postfix_len - templ_postfix_len);
}
specifier = target ? target : name_ptr;
if (*(templ_name_ptr + 1) == 'd') {
gint64 tmp;
/* it's an int */
tmp = g_ascii_strtoll (specifier, &endptr, 10);
if (tmp < G_MININT || tmp > G_MAXINT || (*endptr != '\0'
&& *endptr != '_'))
return FALSE;
} else if (*(templ_name_ptr + 1) == 'u') {
guint64 tmp;
/* it's an int */
tmp = g_ascii_strtoull (specifier, &endptr, 10);
if (tmp > G_MAXUINT || (*endptr != '\0' && *endptr != '_'))
return FALSE;
}
g_free (target);
}
templ_name++;
name++;
} while (next_specifier);
return TRUE;
}
static GstPad *
_gst_element_request_pad (GstElement * element, GstPadTemplate * templ,
const gchar * name, const GstCaps * caps)
@ -934,38 +1047,8 @@ _gst_element_request_pad (GstElement * element, GstPadTemplate * templ,
if (name) {
GstPad *pad;
/* Is this the template name? */
if (strstr (name, "%") || !strchr (templ->name_template, '%')) {
g_return_val_if_fail (strcmp (name, templ->name_template) == 0, NULL);
} else {
const gchar *str, *data;
gchar *endptr;
/* Otherwise check if it's a valid name for the name template */
str = strchr (templ->name_template, '%');
g_return_val_if_fail (str != NULL, NULL);
g_return_val_if_fail (strncmp (templ->name_template, name,
str - templ->name_template) == 0, NULL);
g_return_val_if_fail (strlen (name) > str - templ->name_template, NULL);
data = name + (str - templ->name_template);
/* Can either be %s or %d or %u, do sanity checking for %d */
if (*(str + 1) == 'd') {
gint64 tmp;
/* it's an int */
tmp = g_ascii_strtoll (data, &endptr, 10);
g_return_val_if_fail (tmp >= G_MININT && tmp <= G_MAXINT
&& *endptr == '\0', NULL);
} else if (*(str + 1) == 'u') {
guint64 tmp;
/* it's an int */
tmp = g_ascii_strtoull (data, &endptr, 10);
g_return_val_if_fail (tmp <= G_MAXUINT && *endptr == '\0', NULL);
}
}
g_return_val_if_fail (gst_element_is_valid_request_template_name
(templ->name_template, name), NULL);
pad = gst_element_get_static_pad (element, name);
if (pad) {
@ -1011,8 +1094,6 @@ gst_element_get_request_pad (GstElement * element, const gchar * name)
const gchar *req_name = NULL;
gboolean templ_found = FALSE;
GList *list;
const gchar *data;
gchar *str, *endptr = NULL;
GstElementClass *class;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
@ -1020,13 +1101,10 @@ gst_element_get_request_pad (GstElement * element, const gchar * name)
class = GST_ELEMENT_GET_CLASS (element);
/* if the name contains a %, we assume it's the complete template name. Get
* the template and try to get a pad */
if (strstr (name, "%")) {
templ = gst_element_class_get_request_pad_template (class, name);
req_name = NULL;
if (templ)
templ_found = TRUE;
templ = gst_element_class_get_request_pad_template (class, name);
if (templ) {
req_name = strstr (name, "%") ? NULL : name;
templ_found = TRUE;
} else {
/* there is no % in the name, try to find a matching template */
list = class->padtemplates;
@ -1035,48 +1113,12 @@ gst_element_get_request_pad (GstElement * element, const gchar * name)
if (templ->presence == GST_PAD_REQUEST) {
GST_CAT_DEBUG (GST_CAT_PADS, "comparing %s to %s", name,
templ->name_template);
/* see if we find an exact match */
if (strcmp (name, templ->name_template) == 0) {
if (gst_element_is_valid_request_template_name (templ->name_template,
name)) {
templ_found = TRUE;
req_name = name;
break;
}
/* Because of sanity checks in gst_pad_template_new(), we know that %s
and %d and %u, occurring at the end of the name_template, are the only
possibilities. */
else if ((str = strchr (templ->name_template, '%'))
&& strncmp (templ->name_template, name,
str - templ->name_template) == 0
&& strlen (name) > str - templ->name_template) {
data = name + (str - templ->name_template);
if (*(str + 1) == 'd') {
glong tmp;
/* it's an int */
tmp = strtol (data, &endptr, 10);
if (tmp != G_MINLONG && tmp != G_MAXLONG && endptr &&
*endptr == '\0') {
templ_found = TRUE;
req_name = name;
break;
}
} else if (*(str + 1) == 'u') {
gulong tmp;
/* it's an int */
tmp = strtoul (data, &endptr, 10);
if (tmp != G_MAXULONG && endptr && *endptr == '\0') {
templ_found = TRUE;
req_name = name;
break;
}
} else {
/* it's a string */
templ_found = TRUE;
req_name = name;
break;
}
}
}
list = list->next;
}

View file

@ -221,14 +221,14 @@ gst_pad_template_dispose (GObject * object)
* since it doesn't make sense.
* SOMETIMES padtemplates can do whatever they want, they are provided by the
* element.
* REQUEST padtemplates can be reverse-parsed (the user asks for 'sink1', the
* 'sink%d' template is automatically selected), so we need to restrict their
* naming.
* REQUEST padtemplates can have multiple specifiers in case of %d and %u, like
* src_%u_%u, but %s only can be used once in the template.
*/
static gboolean
name_is_valid (const gchar * name, GstPadPresence presence)
{
const gchar *str;
const gchar *str, *underscore = NULL;
gboolean has_s = FALSE;
if (presence == GST_PAD_ALWAYS) {
if (strchr (name, '%')) {
@ -237,20 +237,38 @@ name_is_valid (const gchar * name, GstPadPresence presence)
return FALSE;
}
} else if (presence == GST_PAD_REQUEST) {
if ((str = strchr (name, '%')) && strchr (str + 1, '%')) {
g_warning ("invalid name template %s: only one conversion specification"
" allowed in GST_PAD_REQUEST padtemplate", name);
return FALSE;
}
if (str && (*(str + 1) != 's' && *(str + 1) != 'd' && *(str + 1) != 'u')) {
g_warning ("invalid name template %s: conversion specification must be of"
" type '%%d', '%%u' or '%%s' for GST_PAD_REQUEST padtemplate", name);
return FALSE;
}
if (str && (*(str + 2) != '\0')) {
g_warning ("invalid name template %s: conversion specification must"
" appear at the end of the GST_PAD_REQUEST padtemplate name", name);
return FALSE;
str = strchr (name, '%');
while (str) {
if (*(str + 1) != 's' && *(str + 1) != 'd' && *(str + 1) != 'u') {
g_warning
("invalid name template %s: conversion specification must be of"
" type '%%d', '%%u' or '%%s' for GST_PAD_REQUEST padtemplate",
name);
return FALSE;
}
if (*(str + 1) == 's' && (*(str + 2) != '\0' || has_s)) {
g_warning
("invalid name template %s: conversion specification of type '%%s'"
"only can be used once in the GST_PAD_REQUEST padtemplate at the "
"very end and not allowed any other characters with '%%s'", name);
return FALSE;
}
if (*(str + 1) == 's') {
has_s = TRUE;
}
underscore = strchr (str, '_');
str = strchr (str + 1, '%');
if (str && (!underscore || (underscore && str < underscore))) {
g_warning
("invalid name template %s: each of conversion specifications "
"must be separated by an underscore", name);
return FALSE;
}
}
}

View file

@ -18,8 +18,12 @@
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gst/check/gstcheck.h>
#include <gst/gstelement.h>
GST_START_TEST (test_add_remove_pad)
{
@ -477,6 +481,309 @@ GST_START_TEST (test_property_notify_message)
gst_element_remove_property_notify_watch (pipeline, deep_watch_id1);
gst_element_remove_property_notify_watch (pipeline, deep_watch_id2);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
}
GST_END_TEST;
typedef struct _GstTestElement3
{
GstElement parent;
} GstTestElement3;
typedef struct _GstTestElement3Class
{
GstElementClass parent;
} GstTestElement3Class;
static GstPad *
gst_test_element3_request_new_pad (GstElement * element, GstPadTemplate * templ,
const gchar * name, const GstCaps * caps)
{
GstPad *pad;
gchar *str;
gchar *pad_name;
gint n_conversion = 0;
static gint i = 0;
str = templ->name_template;
while ((str = strchr (str, '%'))) {
n_conversion++;
str++;
}
if (strcmp (templ->name_template, "src_%ublah_blah%ublah") == 0)
pad_name = g_strdup_printf ("src_%ublah_blah_%ublah", i, i + 1);
else if (n_conversion == 1) {
pad_name = g_strdup_printf ("src_%u", i);
} else if (n_conversion == 2) {
pad_name = g_strdup_printf ("src_%u_%u", i, i + 1);
} else if (n_conversion == 3) {
pad_name = g_strdup_printf ("src_%u_%u_%u", i, i + 1, i + 2);
} else {
pad_name = g_strdup (name);
}
pad = gst_pad_new_from_template (templ, pad_name);
gst_element_add_pad (element, pad);
i++;
g_free (pad_name);
return pad;
}
static void
gst_test_element3_release_pad (GstElement * element, GstPad * pad)
{
gst_element_remove_pad (element, pad);
}
static void
gst_test_element3_init (GstTestElement3 * test)
{
GstPadTemplate *pad_template;
GstPad *sinkpad;
pad_template =
gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (test), "sink");
g_return_if_fail (pad_template != NULL);
sinkpad = gst_pad_new_from_template (pad_template, "sink");
gst_element_add_pad (GST_ELEMENT (test), sinkpad);
}
static void
gst_test_element3_class_init (GstTestElement3Class * klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
gst_element_class_set_metadata (element_class, "Test element 3",
"Element", "For testing request pad template", "Foo Bar <foo@bar.com>");
element_class->request_new_pad =
GST_DEBUG_FUNCPTR (gst_test_element3_request_new_pad);
element_class->release_pad =
GST_DEBUG_FUNCPTR (gst_test_element3_release_pad);
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%u", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%u_%u", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%u_%u_%u", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%ublah_blah%ublah", GST_PAD_SRC,
GST_PAD_REQUEST, GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%d", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%d_%d", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%d_%d_%d", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%s", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src_%u_%s", GST_PAD_SRC, GST_PAD_REQUEST,
GST_CAPS_ANY));
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
GST_CAPS_ANY));
}
static GType
gst_test_element3_get_type (void)
{
static GType gst_test_element3_type = G_TYPE_NONE;
if (gst_test_element3_type == G_TYPE_NONE) {
static const GTypeInfo gst_test_element3_info = {
sizeof (GstTestElement3Class),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) gst_test_element3_class_init,
NULL,
NULL,
sizeof (GstTestElement3),
0,
(GInstanceInitFunc) gst_test_element3_init,
NULL
};
gst_test_element3_type =
g_type_register_static (gst_element_get_type (), "GstTestElement3",
&gst_test_element3_info, 0);
}
return gst_test_element3_type;
}
static gboolean
gst_test_element3_plugin_init (GstPlugin * plugin)
{
gst_element_register (plugin, "test3", GST_RANK_NONE,
gst_test_element3_get_type ());
return TRUE;
}
GST_START_TEST (test_request_pad_templates)
{
GstTestElement3 *test;
GstElement *pipeline, *sink;
GstPad *pad;
GHashTable *padnames;
GHashTableIter iter;
gpointer key, value;
const gchar *pad_name, *templ_name;
GSList *padname_blacklists = NULL, *item;
GError *err = NULL;
padnames = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (padnames, (gpointer) "src_0", (gpointer) "src_%u");
g_hash_table_insert (padnames, (gpointer) "src_%u", (gpointer) "src_%u");
g_hash_table_insert (padnames, (gpointer) "src_%u_%u",
(gpointer) "src_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_0_%u", (gpointer) "src_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_%u_0", (gpointer) "src_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_0_1", (gpointer) "src_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_%u_%u_%u",
(gpointer) "src_%u_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_0_%u_%u",
(gpointer) "src_%u_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_0_1_%u",
(gpointer) "src_%u_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_0_1_2",
(gpointer) "src_%u_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_%u_0_%u",
(gpointer) "src_%u_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_%u_0_1",
(gpointer) "src_%u_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_%u_%u_0",
(gpointer) "src_%u_%u_%u");
g_hash_table_insert (padnames, (gpointer) "src_%ublah_blah%ublah",
(gpointer) "src_%ublah_blah%ublah");
g_hash_table_insert (padnames, (gpointer) "src_%d", (gpointer) "src_%d");
g_hash_table_insert (padnames, (gpointer) "src_%d_%d",
(gpointer) "src_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_1_%d", (gpointer) "src_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_%d_%d_%d",
(gpointer) "src_%d_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_1_2_%d",
(gpointer) "src_%d_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_1_%d_2",
(gpointer) "src_%d_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_%d_2_1",
(gpointer) "src_%d_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_%d_%d_1",
(gpointer) "src_%d_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_%d_1_%d",
(gpointer) "src_%d_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_1_%d_%d",
(gpointer) "src_%d_%d_%d");
g_hash_table_insert (padnames, (gpointer) "src_%s", (gpointer) "src_%s");
g_hash_table_insert (padnames, (gpointer) "src_%u_%s",
(gpointer) "src_%u_%s");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%u%u");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%u_%d");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%u_%u_");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%u_%s_%s");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%s_%u");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%s_%s");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%s_%s_%s");
padname_blacklists =
g_slist_append (padname_blacklists, (gpointer) "src_%s_blah");
test = g_object_new (gst_test_element3_get_type (), NULL);
/* check available request pad names */
g_hash_table_iter_init (&iter, padnames);
while (g_hash_table_iter_next (&iter, &key, &value)) {
pad_name = (const gchar *) key;
templ_name = (const gchar *) value;
pad = gst_element_get_request_pad (GST_ELEMENT (test), pad_name);
fail_unless (pad != NULL);
gst_element_release_request_pad (GST_ELEMENT (test), pad);
gst_object_unref (pad);
pad = gst_element_request_pad (GST_ELEMENT (test),
gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (test),
templ_name), pad_name, NULL);
fail_unless (pad != NULL);
gst_element_release_request_pad (GST_ELEMENT (test), pad);
gst_object_unref (pad);
}
item = padname_blacklists;
/* check invalid request pad name */
while (item) {
pad_name = (const gchar *) (item->data);
item = g_slist_next (item);
pad = gst_element_get_request_pad (GST_ELEMENT (test), pad_name);
fail_unless (pad == NULL);
}
/* check it working with some APIs
* gst_element_link/link_pads */
sink = gst_element_factory_make ("fakesink", "sink");
fail_unless (gst_element_link (GST_ELEMENT (test), sink));
gst_element_unlink (GST_ELEMENT (test), sink);
fail_unless (gst_element_link_pads (GST_ELEMENT (test), "src_%u_%u", sink,
"sink"));
gst_element_unlink (GST_ELEMENT (test), sink);
g_object_unref (test);
g_object_unref (sink);
/* gst_parse_launch */
gst_plugin_register_static (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"test3",
"request pad template test",
gst_test_element3_plugin_init,
VERSION, GST_LICENSE, PACKAGE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
pipeline =
gst_parse_launch ("fakesrc ! test3 name=t ! fakesink t. ! fakesink",
&err);
fail_unless (pipeline && err == NULL);
if (err) {
g_error_free (err);
}
g_slist_free (padname_blacklists);
g_hash_table_unref (padnames);
gst_object_unref (pipeline);
}
@ -496,6 +803,7 @@ gst_element_suite (void)
tcase_add_test (tc_chain, test_link_no_pads);
tcase_add_test (tc_chain, test_pad_templates);
tcase_add_test (tc_chain, test_property_notify_message);
tcase_add_test (tc_chain, test_request_pad_templates);
return s;
}

View file

@ -584,7 +584,7 @@ GST_START_TEST (test_name_is_valid)
ASSERT_WARNING (name_is_valid ("src%s%s", GST_PAD_REQUEST));
ASSERT_WARNING (name_is_valid ("src%c", GST_PAD_REQUEST));
ASSERT_WARNING (name_is_valid ("src%", GST_PAD_REQUEST));
ASSERT_WARNING (name_is_valid ("src%dsrc", GST_PAD_REQUEST));
fail_unless (name_is_valid ("src%dsrc", GST_PAD_REQUEST));
fail_unless (name_is_valid ("src", GST_PAD_SOMETIMES));
fail_unless (name_is_valid ("src%c", GST_PAD_SOMETIMES));