Alicia Boya García ee97c89c51 gstreamer: parse: Log bus error messages during construction
Suppose you invoke gst-launch with this invalid pipeline:

```
$ gst-launch-1.0  videotestsrc num-buffers=10 ! x264enc name=enc ! mux.sink_0 \
    mpegtsmux name=mux ! fakesink
0:00:00.018631594 351169      0xb523090 ERROR           GST_PIPELINE
subprojects/gstreamer/gst/parse/grammar.y:1151:gst_parse_perform_link:
could not link enc to mux
WARNING: erroneous pipeline: could not link enc to mux
```

The error message you get is not very helpful. This is a pity, because
this is where the error comes from:

```c
static GstPad *
gst_base_ts_mux_request_new_pad (GstElement * element, GstPadTemplate * templ,
    const gchar * name, const GstCaps * caps)
{ // [...]
    GST_ELEMENT_ERROR (element, STREAM, MUX,
        ("Invalid Elementary stream PID (0x%02u < 0x40)", pid), (NULL));
    return NULL;
```

mpegtsmux posted an error with an explanation of why the linking failed.
However, since the error ocurred within gst_parse_launchv(), gst-launch
could not have set a bus handler, and the error message got discarded.

This patch attempts to make gst-launch more user-friendly by setting a
temporary bus handler during early bin construction to catch error
messages like this.

The errors are logged as ERROR level in the GST_PIPELINE category.
However, this is not enough, as GST_LEVEL_DEFAULT defaults to
GST_LEVEL_NONE in releases. In other words, outside of the dev
environment, GStreamer won't print ERROR logs by default.

To make sure the errors can reach users of packaged versions of
GStreamer, a new AtomicRcBox-based struct is added: reason_receiver_t.
graph_t owns a reference to reason_receiver_t and so does the temporary
bus handler.

When the temporary bus handler receives an error message, the `reason`
field of `reason_receiver_t` is filled with the error message.
Later, when SET_ERROR() is called as a consequence of the operation that
posted the error having returned failure, the reason message is
extracted and added to the GError message.

This is how the resulting error would look in the example from above:

    WARNING: erroneous pipeline: could not link enc to mux --
    GstMpegTsMux <mux> posted an error message: Invalid Elementary
    stream PID (0x00 < 0x40)

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8417>
2025-02-15 00:04:46 +00:00

1876 lines
55 KiB
Plaintext

%{
#include "../gst_private.h"
#include <glib-object.h>
#include <glib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <glib/gi18n-lib.h>
#include "../gstconfig.h"
#include "../gstparse.h"
#include "../gstinfo.h"
#include "../gsterror.h"
#include "../gststructure.h"
#include "../gsturi.h"
#include "../gstutils.h"
#include "../gstvalue.h"
#include "../gstchildproxy.h"
#include "types.h"
/* All error messages in this file are user-visible and need to be translated.
* Don't start the message with a capital, and don't end them with a period,
* as they will be presented inside a sentence/error.
*/
#define YYERROR_VERBOSE 1
#define YYENABLE_NLS 0
#ifndef YYLTYPE_IS_TRIVIAL
#define YYLTYPE_IS_TRIVIAL 0
#endif
/*******************************************************************************************
*** Tracing memory leaks
*******************************************************************************************/
#ifdef __GST_PARSE_TRACE
static guint __strings;
static guint __links;
static guint __chains;
static guint __elements;
gchar *
__gst_parse_strdup (gchar *org)
{
gchar *ret;
__strings++;
ret = g_strdup (org);
/* g_print ("ALLOCATED STR (%3u): %p %s\n", __strings, ret, ret); */
return ret;
}
void
__gst_parse_strfree (gchar *str)
{
if (str) {
/* g_print ("FREEING STR (%3u): %p %s\n", __strings - 1, str, str); */
g_free (str);
g_return_if_fail (__strings > 0);
__strings--;
}
}
link_t *__gst_parse_link_new (void)
{
link_t *ret;
__links++;
ret = g_new0 (link_t, 1);
/* g_print ("ALLOCATED LINK (%3u): %p\n", __links, ret); */
return ret;
}
void
__gst_parse_link_free (link_t *data)
{
if (data) {
/* g_print ("FREEING LINK (%3u): %p\n", __links - 1, data); */
g_free (data);
g_return_if_fail (__links > 0);
__links--;
}
}
chain_t *
__gst_parse_chain_new (void)
{
chain_t *ret;
__chains++;
ret = g_new0 (chain_t, 1);
/* g_print ("@%p: ALLOCATED CHAIN (%3u):\n", ret, __chains); */
return ret;
}
void
__gst_parse_chain_free (chain_t *data)
{
/* g_print ("@%p: FREEING CHAIN (%3u):\n", data, __chains - 1); */
g_free (data);
g_return_if_fail (__chains > 0);
__chains--;
}
element_t *
__gst_parse_element_new (void)
{
element_t *ret;
__elements++;
ret = g_new0 (element_t, 1);
/* g_print ("@%p: ALLOCATED ELEMENT (%3u):\n", ret, __elements); */
return ret;
}
void
__gst_parse_element_free (element_t *data)
{
/* g_print ("@%p: FREEING ELEMENT (%3u):\n", data, __elements - 1); */
g_free (data);
g_return_if_fail (__elements > 0);
__elements--;
}
#endif /* __GST_PARSE_TRACE */
/*******************************************************************************************
*** define SET_ERROR macro/function
*
* SET_ERROR() must only be called from the thread running priv_gst_parse_launch() and must
* only be used for errors that occur during the initial construction of the chain, i.e.
* before priv_gst_parse_launch() returns.
*******************************************************************************************/
static void error_append_probable_reason (graph_t *graph);
#ifdef G_HAVE_ISO_VARARGS
# define SET_ERROR(graph, type, ...) \
G_STMT_START { \
GError** error = (graph)->error; \
GST_CAT_ERROR (GST_CAT_PIPELINE, __VA_ARGS__); \
if ((error) && !*(error)) { \
g_set_error ((error), GST_PARSE_ERROR, (type), __VA_ARGS__); \
error_append_probable_reason ((graph)); \
} \
} G_STMT_END
#elif defined(G_HAVE_GNUC_VARARGS)
# define SET_ERROR(graph, type, args...) \
G_STMT_START { \
GError** error = (graph)->error; \
GST_CAT_ERROR (GST_CAT_PIPELINE, args ); \
if (error && !*error) { \
g_set_error (error, GST_PARSE_ERROR, (type), args ); \
error_append_probable_reason ((graph)); \
} \
} G_STMT_END
#else
static inline void
SET_ERROR (graph_t *graph, gint type, const char *format, ...)
{
GError** error = graph->error;
if (error) {
if (*error) {
g_warning ("error while parsing");
} else {
va_list varargs;
char *string;
va_start (varargs, format);
string = g_strdup_vprintf (format, varargs);
va_end (varargs);
g_set_error (error, GST_PARSE_ERROR, type, string);
g_free (string);
error_append_probable_reason ((graph));
}
}
}
#endif /* G_HAVE_ISO_VARARGS */
static void error_append_probable_reason (graph_t *graph) {
g_return_if_fail (*graph->error);
GError* error = *graph->error;
reason_receiver_t* receiver = graph->error_probable_reason_receiver;
/* If by the time SET_ERROR() was called no bus was set, there is no receiver
* and no error from the bus to append. */
if (!receiver)
return;
g_mutex_lock (&receiver->mutex);
if (receiver->reason) {
gchar *new_message = g_strdup_printf ("%s -- %s", error->message,
receiver->reason);
g_free (error->message);
error->message = new_message;
}
g_mutex_unlock (&receiver->mutex);
}
/*** define YYPRINTF macro/function if we're debugging */
/* bison 1.35 calls this macro with side effects, we need to make sure the
side effects work - crappy bison */
#ifndef GST_DISABLE_GST_DEBUG
# define YYDEBUG 1
# ifdef G_HAVE_ISO_VARARGS
/* # define YYFPRINTF(a, ...) GST_CAT_DEBUG (GST_CAT_PIPELINE, __VA_ARGS__) */
# define YYFPRINTF(a, ...) \
G_STMT_START { \
GST_CAT_LOG (GST_CAT_PIPELINE, __VA_ARGS__); \
} G_STMT_END
# elif defined(G_HAVE_GNUC_VARARGS)
# define YYFPRINTF(a, args...) \
G_STMT_START { \
GST_CAT_LOG (GST_CAT_PIPELINE, args); \
} G_STMT_END
# else
static inline void
YYPRINTF(const char *format, ...)
{
va_list varargs;
gchar *temp;
va_start (varargs, format);
temp = g_strdup_vprintf (format, varargs);
GST_CAT_LOG (GST_CAT_PIPELINE, "%s", temp);
g_free (temp);
va_end (varargs);
}
# endif /* G_HAVE_ISO_VARARGS */
#endif /* GST_DISABLE_GST_DEBUG */
/*
* include headers generated by bison & flex, after defining (or not defining) YYDEBUG
*/
#include "grammar.tab.h"
#include "parse_lex.h"
/*******************************************************************************************
*** report missing elements/bins/..
*******************************************************************************************/
static void add_missing_element(graph_t *graph,gchar *name){
if ((graph)->ctx){
(graph)->ctx->missing_elements = g_list_append ((graph)->ctx->missing_elements, g_strdup (name));
}
}
static gchar *
str_unwrap (const gchar * s)
{
gchar *ret;
gchar *read, *write;
/* NULL string returns NULL */
if (s == NULL)
return NULL;
/* make copy of original string to hold the result. This
* string will always be smaller than the original */
ret = g_strdup (s);
read = ret;
write = ret;
while (*read) {
if (GST_ASCII_IS_STRING (*read)) {
/* normal chars are just copied */
*write++ = *read++;
} else if (*read == '\\') {
/* got an escape char, move to next position to read a tripplet
* of octal numbers */
read++;
/* is the next char a possible first octal number? */
if (*read >= '0' && *read <= '3') {
/* parse other 2 numbers, if one of them is not in the range of
* an octal number, we error. We also catch the case where a zero
* byte is found here. */
if (read[1] < '0' || read[1] > '7' || read[2] < '0' || read[2] > '7')
goto beach;
/* now convert the octal number to a byte again. */
*write++ = ((read[0] - '0') << 6) +
((read[1] - '0') << 3) + (read[2] - '0');
read += 3;
} else {
/* if we run into a \0 here, we definitely won't get a quote later */
if (*read == 0)
goto beach;
/* else copy \X sequence */
*write++ = *read++;
}
} else if (*read == '\0') {
goto beach;
} else {
*write++ = *read++;
}
}
/* null terminate result string and return */
*write = '\0';
return ret;
beach:
g_free (ret);
return NULL;
}
/*******************************************************************************************
*** helpers for pipeline-setup
*******************************************************************************************/
#define TRY_SETUP_LINK(l) G_STMT_START { \
if( (!(l)->src.element) && (!(l)->src.name) ){ \
SET_ERROR (graph, GST_PARSE_ERROR_LINK, _("link has no source [sink=%s@%p]"), \
(l)->sink.name ? (l)->sink.name : "", \
(l)->sink.element); \
gst_parse_free_link (l); \
}else if( (!(l)->sink.element) && (!(l)->sink.name) ){ \
SET_ERROR (graph, GST_PARSE_ERROR_LINK, _("link has no sink [source=%s@%p]"), \
(l)->src.name ? (l)->src.name : "", \
(l)->src.element); \
gst_parse_free_link (l); \
}else{ \
graph->links = g_slist_append (graph->links, l ); \
} \
} G_STMT_END
typedef struct {
gchar *src_pad;
gchar *sink_pad;
GstElement *sink;
GstCaps *caps;
gulong pad_added_signal_id, no_more_pads_signal_id;
gboolean all_pads;
} DelayedLink;
typedef struct {
gchar *name;
gchar *value_str;
gulong signal_id;
} DelayedSet;
static int gst_resolve_reference(reference_t *rr, GstElement *pipeline){
GstBin *bin;
if(rr->element) return 0; /* already resolved! */
if(!rr->name) return -2; /* no chance! */
if (GST_IS_BIN (pipeline)){
bin = GST_BIN (pipeline);
rr->element = gst_bin_get_by_name_recurse_up (bin, rr->name);
} else {
rr->element = strcmp (GST_ELEMENT_NAME (pipeline), rr->name) == 0 ?
gst_object_ref(pipeline) : NULL;
}
if(rr->element) return 0; /* resolved */
else return -1; /* not found */
}
static void gst_parse_free_delayed_set (DelayedSet *set)
{
g_free(set->name);
g_free(set->value_str);
g_free(set);
}
static void gst_parse_new_child (GstChildProxy *child_proxy, GObject *object,
const gchar * name, gpointer data);
static void gst_parse_add_delayed_set (GstChildProxy *proxy, gchar *name, gchar *value_str)
{
DelayedSet *data = g_new0 (DelayedSet, 1);
gchar **names, **current;
GObject *parent, *child;
GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, proxy, "delaying property set %s to %s",
name, value_str);
data->name = g_strdup (name);
data->value_str = g_strdup (value_str);
data->signal_id = g_signal_connect_data (proxy, "child-added",
G_CALLBACK (gst_parse_new_child), data, (GClosureNotify)
gst_parse_free_delayed_set, (GConnectFlags) 0);
current = names = g_strsplit (name, "::", -1);
parent = gst_child_proxy_get_child_by_name (GST_CHILD_PROXY (proxy), current[0]);
current++;
while (parent && current[0]) {
if (!GST_IS_CHILD_PROXY (parent)) {
GST_INFO ("Not recursing into non-proxy child %p", parent);
break;
}
child = gst_child_proxy_get_child_by_name (GST_CHILD_PROXY (parent), current[0]);
if (!child && current[1]) {
char *sub_name = g_strjoinv ("::", &current[0]);
gst_parse_add_delayed_set (GST_CHILD_PROXY (parent), sub_name, value_str);
g_free (sub_name);
}
gst_object_unref (parent);
parent = child;
current++;
}
if (parent)
gst_object_unref (parent);
g_strfreev (names);
}
static gboolean
gst_parse_separate_prop_from_children (const gchar *name, gchar **children, gchar **property)
{
static const gchar *separator = "::";
const gchar *prop = NULL;
g_return_val_if_fail (name, FALSE);
g_return_val_if_fail (children, FALSE);
g_return_val_if_fail (property, FALSE);
/* Given "child1::child2::prop" isolate "prop" */
prop = g_strrstr (name, separator);
if (!prop) {
GST_WARNING ("%s is not a valid childproxy path", name);
return FALSE;
}
/* Make a copy of prop skipping "::" */
*property = g_strdup (prop + 2);
/* Extract "child1::child2" from "child1::child2::prop" */
*children =
g_strndup (name, strlen (name) - strlen (prop));
return TRUE;
}
static void gst_parse_new_child(GstChildProxy *child_proxy, GObject *object,
const gchar * name, gpointer data)
{
DelayedSet *set = (DelayedSet *) data;
GParamSpec *pspec;
GValue v = { 0, };
GObject *target = NULL;
GType value_type;
GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, child_proxy, "new child %s, checking property %s",
name, set->name);
if (gst_child_proxy_lookup (child_proxy, set->name, &target, &pspec)) {
gboolean got_value = FALSE;
value_type = pspec->value_type;
GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, child_proxy, "parsing delayed property %s as a %s from %s",
pspec->name, g_type_name (value_type), set->value_str);
g_value_init (&v, value_type);
if (gst_value_deserialize_with_pspec (&v, set->value_str, pspec))
got_value = TRUE;
else if (g_type_is_a (value_type, GST_TYPE_ELEMENT)) {
GstElement *bin;
bin = gst_parse_bin_from_description_full (set->value_str, TRUE, NULL,
GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS | GST_PARSE_FLAG_PLACE_IN_BIN, NULL);
if (bin) {
g_value_set_object (&v, bin);
got_value = TRUE;
}
}
g_signal_handler_disconnect (child_proxy, set->signal_id);
if (!got_value)
goto error;
g_object_set_property (target, pspec->name, &v);
} else {
gint len = strlen (name);
/*
* We've been notified that a new child has beed added, but the
* property was still not found. Three things could be happening:
*
* 1. The target property is of the form obj_name::child_name::property
* and is for a child that does not (yet) exist:
* We need to add the delayed property setting handler on that new child.
*
* 2. The target property is of the form obj_name::property or
* obj_name::child_name::property and the child already exists:
* We warn about a nonexistent property
*
* 3. The target property is of the form other_obj_name::child_name::property
* or other_obj_name::property:
* We ignore this case as another delayed set will catch it.
*/
/* Cases 1,2: The child just added corresponds to this delayed set */
if ((strlen (set->name) > (len+2)) && !strncmp (set->name, name, len)
&& !strncmp (&set->name[len], "::", 2)) {
gchar *children = NULL;
gchar *prop = NULL;
GObject *child = NULL;
if (!gst_parse_separate_prop_from_children (set->name, &children, &prop)) {
/* Malformed property name, ignore */
return;
}
child = gst_child_proxy_get_child_by_name_recurse (child_proxy, children);
g_free (children);
g_free (prop);
/* Case 1: A child in the hierarchy does not exist yet, add a new delayed set */
if (NULL == child) {
gst_parse_add_delayed_set (child_proxy, set->name, set->value_str);
}
/* Case 2: The target child exists already but there's no such property */
else {
gst_object_unref (child);
GST_ELEMENT_WARNING(GST_ELEMENT (child_proxy), PARSE, NO_SUCH_PROPERTY,
(_("No such property.")), (_("no property \"%s\" in element \"%s\""),
set->name, GST_ELEMENT_NAME(child_proxy)));
}
}
/* Case 3: The child just added does not correspond to this delayed set, just ignore
* else { }
*/
}
out:
if (G_IS_VALUE (&v))
g_value_unset (&v);
if (target)
gst_object_unref (target);
return;
error:
GST_CAT_ERROR (GST_CAT_PIPELINE, "could not set property \"%s\" in %"
GST_PTR_FORMAT, pspec->name, target);
goto out;
}
static gchar *
gst_parse_split_assignment (gchar *value) {
gchar *pos = value;
/* parse the string, so the property name is null-terminated and pos points
to the beginning of the value */
while (!g_ascii_isspace (*pos) && (*pos != '=')) pos++;
if (*pos == '=') {
*pos = '\0';
} else {
*pos = '\0';
pos++;
while (g_ascii_isspace (*pos)) pos++;
}
pos++;
while (g_ascii_isspace (*pos)) pos++;
/* truncate a string if it is delimited with double quotes */
if (*pos == '"' && pos[strlen (pos) - 1] == '"') {
pos++;
pos[strlen (pos) - 1] = '\0';
}
gst_parse_unescape (pos);
return pos;
}
static gboolean
collect_value (GParamSpec *pspec, gchar *value_str, GValue *v)
{
gboolean got_value = FALSE;
GST_CAT_LOG (GST_CAT_PIPELINE, "parsing property %s as a %s",
pspec->name, g_type_name (pspec->value_type));
g_value_init (v, pspec->value_type);
if (gst_value_deserialize_with_pspec (v, value_str, pspec))
got_value = TRUE;
else if (g_type_is_a (pspec->value_type, GST_TYPE_ELEMENT)) {
GstElement *bin;
bin = gst_parse_bin_from_description_full (value_str, TRUE, NULL,
GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS | GST_PARSE_FLAG_PLACE_IN_BIN, NULL);
if (bin) {
g_value_set_object (v, bin);
got_value = TRUE;
}
}
return got_value;
}
static void gst_parse_element_preset (gchar *value, GstElement *element, graph_t *graph)
{
/* do nothing if preset is for missing element or its not a preset element */
if (element == NULL)
goto out;
if (!GST_IS_PRESET(element))
goto not_a_preset;
/* do nothing if no preset is given */
if (value == NULL || *value == '\0')
goto out;
gst_parse_unescape (value);
if (!gst_preset_load_preset (GST_PRESET (element), value))
goto error;
out:
return;
not_a_preset:
SET_ERROR (graph, GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY,
_("Element \"%s\" is not a GstPreset"),
GST_ELEMENT_NAME (element));
goto out;
error:
SET_ERROR (graph, GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY,
_("could not set preset \"%s\" in element \"%s\""),
value, GST_ELEMENT_NAME (element));
goto out;
}
typedef struct
{
gchar *name;
gchar *value;
} proxied_property_t;
static void
proxied_property_free (proxied_property_t *pp) {
g_free (pp);
}
static GstElement * gst_parse_element_make (graph_t *graph, element_t *data) {
GstElementFactory *loaded_factory;
GstElementFactory *factory = gst_element_factory_find (data->factory_name);
GObjectClass *klass;
GParamSpec *pspec = NULL;
GSList *tmp;
gboolean is_proxy;
GSList *proxied = NULL;
guint n_params = 0;
guint n_params_alloc = 16;
const gchar **names_array;
GValue *values_array;
GstElement *ret = NULL;
if (!factory) {
SET_ERROR (graph, GST_PARSE_ERROR_NO_SUCH_ELEMENT, _("no element \"%s\""), data->factory_name);
return NULL;
}
loaded_factory =
GST_ELEMENT_FACTORY (gst_plugin_feature_load (GST_PLUGIN_FEATURE
(factory)));
gst_object_unref (factory);
klass = g_type_class_ref (gst_element_factory_get_element_type (loaded_factory));
is_proxy = g_type_is_a (gst_element_factory_get_element_type (loaded_factory), GST_TYPE_CHILD_PROXY);
names_array = g_new0 (const gchar *, n_params_alloc);
values_array = g_new0 (GValue, n_params_alloc);
for (tmp = data->values; tmp; tmp = tmp->next) {
gchar *name = tmp->data;
gchar *value = gst_parse_split_assignment (tmp->data);
if (is_proxy && strstr (name, "::") != NULL) {
proxied_property_t *pp = g_new (proxied_property_t, 1);
pp->name = name;
pp->value = value;
proxied = g_slist_prepend (proxied, pp);
continue;
}
pspec = g_object_class_find_property (klass, name);
if (pspec != NULL) {
if (G_UNLIKELY (n_params == n_params_alloc)) {
n_params_alloc *= 2u;
names_array =
g_realloc (names_array, sizeof (const gchar *) * n_params_alloc);
values_array = g_realloc (values_array, sizeof (GValue) * n_params_alloc);
memset (&values_array[n_params], 0,
sizeof (GValue) * (n_params_alloc - n_params));
}
if (!collect_value (pspec, value, &values_array[n_params])) {
SET_ERROR (graph, GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY,
_("could not set property \"%s\" in element \"%s\" to \"%s\""),
name, data->factory_name, value);
g_value_unset (&values_array[n_params]);
goto done;
} else {
names_array[n_params] = name;
}
++n_params;
} else {
SET_ERROR (graph, GST_PARSE_ERROR_NO_SUCH_PROPERTY, \
_("no property \"%s\" in element \"%s\""), name, \
data->factory_name);
goto done;
}
}
ret = gst_element_factory_create_with_properties (factory, n_params, names_array,
values_array);
for (tmp = proxied; tmp; tmp = tmp->next) {
GObject *target = NULL;
proxied_property_t *pp = tmp->data;
if (!gst_child_proxy_lookup (GST_CHILD_PROXY (ret), pp->name, &target, &pspec)) {
/* the property was not found. if the target child doesn't exist
then we do a delayed set waiting for new elements to be added. If
the child was found, we fail since the property doesn't exist.
*/
gchar *children = NULL;
gchar *property = NULL;
if (!gst_parse_separate_prop_from_children (pp->name, &children, &property)) {
/* malformed childproxy path, skip */
continue;
}
target = gst_child_proxy_get_child_by_name_recurse (GST_CHILD_PROXY (ret), children);
g_free (children);
g_free (property);
if (target == NULL) {
gst_parse_add_delayed_set (GST_CHILD_PROXY (ret), pp->name, pp->value);
} else {
gst_object_unref (target);
SET_ERROR (graph, GST_PARSE_ERROR_NO_SUCH_PROPERTY, \
_("no property \"%s\" in element \"%s\""), pp->name, \
GST_ELEMENT_NAME (ret));
goto done;
}
} else {
GValue v = { 0, };
if (!collect_value (pspec, pp->value, &v)) {
SET_ERROR (graph, GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY,
_("could not set property \"%s\" in child of element \"%s\" to \"%s\""),
pp->name, data->factory_name, pp->value);
g_value_unset (&v);
goto done;
} else {
g_object_set_property (target, pspec->name, &v);
g_value_unset (&v);
}
gst_object_unref (target);
}
}
for (tmp = data->presets; tmp; tmp = tmp->next) {
gst_parse_element_preset (tmp->data, ret, graph);
}
done:
g_slist_free_full (proxied, (GDestroyNotify) proxied_property_free);
gst_object_unref (loaded_factory);
g_type_class_unref (klass);
g_free (names_array);
while (n_params--)
g_value_unset (&values_array[n_params]);
g_free (values_array);
return ret;
}
static gboolean gst_parse_child_proxy_find_child (
GstChildProxy * child_proxy, const gchar *name)
{
gchar **names = NULL, **current = NULL;
GObject *obj = NULL;
gboolean found = FALSE;
obj = G_OBJECT (g_object_ref (child_proxy));
current = names = g_strsplit (name, "::", -1);
/* find the owner of the property */
while (current[1]) {
GObject *next = NULL;
/* Cannot ask for the child of a non-childproxy */
if (!GST_IS_CHILD_PROXY (obj)) {
break;
}
next = gst_child_proxy_get_child_by_name (GST_CHILD_PROXY (obj),
current[0]);
/* The child doesn't exist yet */
if (!next) {
break;
}
gst_object_unref (obj);
obj = next;
current++;
}
gst_object_unref (obj);
g_strfreev (names);
/* The remaining name is the property, we have found the object */
if (current[1] == NULL) {
found = TRUE;
}
return found;
}
static void gst_parse_element_set (gchar *value, GstElement *element, graph_t *graph)
{
GParamSpec *pspec = NULL;
gchar *pos;
GValue v = { 0, };
GObject *target = NULL;
/* do nothing if assignment is for missing element */
if (element == NULL)
goto out;
pos = gst_parse_split_assignment (value);
if (GST_IS_CHILD_PROXY (element) && strstr (value, "::") != NULL) {
if (!gst_child_proxy_lookup (GST_CHILD_PROXY (element), value, &target, &pspec)) {
/* the property was not found. if the target child doesn't exist
then we do a delayed set waiting for new elements to be added. If
the child was found, we fail since the property doesn't exist.
*/
if (!gst_parse_child_proxy_find_child (GST_CHILD_PROXY (element), value)) {
gst_parse_add_delayed_set (GST_CHILD_PROXY (element), value, pos);
} else {
goto error;
}
}
} else {
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (element), value);
if (pspec != NULL) {
target = G_OBJECT (g_object_ref (element));
GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, target, "found %s property", value);
} else {
SET_ERROR (graph, GST_PARSE_ERROR_NO_SUCH_PROPERTY, \
_("no property \"%s\" in element \"%s\""), value, \
GST_ELEMENT_NAME (element));
}
}
if (pspec != NULL && target != NULL) {
if (!collect_value (pspec, pos, &v)) {
goto error;
} else {
g_object_set_property (target, pspec->name, &v);
}
}
out:
gst_parse_strfree (value);
if (G_IS_VALUE (&v))
g_value_unset (&v);
if (target)
gst_object_unref (target);
return;
error:
SET_ERROR (graph, GST_PARSE_ERROR_COULD_NOT_SET_PROPERTY,
_("could not set property \"%s\" in element \"%s\" to \"%s\""),
value, GST_ELEMENT_NAME (element), pos);
goto out;
}
static void gst_parse_free_reference (reference_t *rr)
{
if(rr->element) gst_object_unref(rr->element);
gst_parse_strfree (rr->name);
g_slist_foreach (rr->pads, (GFunc) gst_parse_strfree, NULL);
g_slist_free (rr->pads);
}
static void gst_parse_free_link (link_t *link)
{
gst_parse_free_reference (&(link->src));
gst_parse_free_reference (&(link->sink));
if (link->caps) gst_caps_unref (link->caps);
gst_parse_link_free (link);
}
static void gst_parse_free_chain (chain_t *ch)
{
GSList *walk;
gst_parse_free_reference (&(ch->first));
gst_parse_free_reference (&(ch->last));
for(walk=ch->elements;walk;walk=walk->next)
gst_object_unref (walk->data);
g_slist_free (ch->elements);
gst_parse_chain_free (ch);
}
static void gst_parse_free_element (element_t *el)
{
g_slist_free_full (el->values, gst_parse_strfree);
g_slist_free_full (el->presets, gst_parse_strfree);
gst_parse_strfree (el->factory_name);
gst_parse_element_free (el);
}
static void gst_parse_free_delayed_link (DelayedLink *link)
{
g_free (link->src_pad);
g_free (link->sink_pad);
if (link->caps) gst_caps_unref (link->caps);
g_free (link);
}
#define PRETTY_PAD_NAME_FMT "%s %s of %s named %s"
#define PRETTY_PAD_NAME_ARGS(elem, pad_name) \
(pad_name ? "pad" : "some"), (pad_name ? pad_name : "pad"), \
G_OBJECT_TYPE_NAME(elem), GST_STR_NULL (GST_ELEMENT_NAME (elem))
static void gst_parse_no_more_pads (GstElement *src, gpointer data)
{
DelayedLink *link = data;
/* Don't warn for all-pads links, as we expect those to
* still be active at no-more-pads */
if (!link->all_pads) {
GST_ELEMENT_WARNING(src, PARSE, DELAYED_LINK,
(_("Delayed linking failed.")),
("failed delayed linking " PRETTY_PAD_NAME_FMT " to " PRETTY_PAD_NAME_FMT,
PRETTY_PAD_NAME_ARGS (src, link->src_pad),
PRETTY_PAD_NAME_ARGS (link->sink, link->sink_pad)));
}
/* we keep the handlers connected, so that in case an element still adds a pad
* despite no-more-pads, we will consider it for pending delayed links */
}
static void gst_parse_found_pad (GstElement *src, GstPad *pad, gpointer data)
{
DelayedLink *link = data;
GST_CAT_INFO (GST_CAT_PIPELINE,
"trying delayed linking %s " PRETTY_PAD_NAME_FMT " to " PRETTY_PAD_NAME_FMT,
link->all_pads ? "all pads" : "one pad",
PRETTY_PAD_NAME_ARGS (src, link->src_pad),
PRETTY_PAD_NAME_ARGS (link->sink, link->sink_pad));
if (gst_element_link_pads_filtered (src, link->src_pad, link->sink,
link->sink_pad, link->caps)) {
/* do this here, we don't want to get any problems later on when
* unlocking states */
GST_CAT_DEBUG (GST_CAT_PIPELINE,
"delayed linking %s " PRETTY_PAD_NAME_FMT " to " PRETTY_PAD_NAME_FMT " worked",
link->all_pads ? "all pads" : "one pad",
PRETTY_PAD_NAME_ARGS (src, link->src_pad),
PRETTY_PAD_NAME_ARGS (link->sink, link->sink_pad));
/* releases 'link' */
if (!link->all_pads) {
g_signal_handler_disconnect (src, link->no_more_pads_signal_id);
g_signal_handler_disconnect (src, link->pad_added_signal_id);
}
}
}
/* both padnames and the caps may be NULL */
static gboolean
gst_parse_perform_delayed_link (GstElement *src, const gchar *src_pad,
GstElement *sink, const gchar *sink_pad,
GstCaps *caps, gboolean all_pads)
{
GList *templs = gst_element_class_get_pad_template_list (
GST_ELEMENT_GET_CLASS (src));
for (; templs; templs = templs->next) {
GstPadTemplate *templ = (GstPadTemplate *) templs->data;
if ((GST_PAD_TEMPLATE_DIRECTION (templ) == GST_PAD_SRC) &&
(GST_PAD_TEMPLATE_PRESENCE(templ) == GST_PAD_SOMETIMES))
{
DelayedLink *data = g_new (DelayedLink, 1);
data->all_pads = all_pads;
/* TODO: maybe we should check if src_pad matches this template's names */
GST_CAT_DEBUG (GST_CAT_PIPELINE,
"trying delayed link " PRETTY_PAD_NAME_FMT " to " PRETTY_PAD_NAME_FMT,
PRETTY_PAD_NAME_ARGS (src, src_pad),
PRETTY_PAD_NAME_ARGS (sink, sink_pad));
data->src_pad = g_strdup (src_pad);
data->sink = sink;
data->sink_pad = g_strdup (sink_pad);
if (caps) {
data->caps = gst_caps_copy (caps);
} else {
data->caps = NULL;
}
data->pad_added_signal_id = g_signal_connect_data (src, "pad-added",
G_CALLBACK (gst_parse_found_pad), data,
(GClosureNotify) gst_parse_free_delayed_link, (GConnectFlags) 0);
data->no_more_pads_signal_id = g_signal_connect (src, "no-more-pads",
G_CALLBACK (gst_parse_no_more_pads), data);
return TRUE;
}
}
return FALSE;
}
static gboolean
gst_parse_element_can_do_caps (GstElement * e, GstPadDirection dir,
GstCaps * link_caps)
{
gboolean can_do = FALSE, done = FALSE;
GstIterator *it;
it = (dir == GST_PAD_SRC) ? gst_element_iterate_src_pads (e) : gst_element_iterate_sink_pads (e);
while (!done && !can_do) {
GValue v = G_VALUE_INIT;
GstPad *pad;
GstCaps *caps;
switch (gst_iterator_next (it, &v)) {
case GST_ITERATOR_OK:
pad = g_value_get_object (&v);
caps = gst_pad_get_current_caps (pad);
if (caps == NULL)
caps = gst_pad_query_caps (pad, NULL);
can_do = gst_caps_can_intersect (caps, link_caps);
GST_TRACE ("can_do: %d for %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT,
can_do, caps, link_caps);
gst_caps_unref (caps);
g_value_unset (&v);
break;
case GST_ITERATOR_DONE:
case GST_ITERATOR_ERROR:
done = TRUE;
break;
case GST_ITERATOR_RESYNC:
gst_iterator_resync (it);
break;
}
}
gst_iterator_free (it);
return can_do;
}
static gchar*
format_pad_names(const GSList* list)
{
if (!list)
return g_strdup ("(any)");
GString* str = g_string_new (NULL);
gboolean is_first = TRUE;
while (list) {
if (!is_first)
g_string_append (str, ", ");
g_string_append (str, (const gchar *) list->data);
list = list->next;
is_first = FALSE;
}
return g_string_free (str, FALSE);
}
/*
* performs a link and frees the struct. src and sink elements must be given
* return values 0 - link performed
* 1 - link delayed
* <0 - error
*/
static gint
gst_parse_perform_link (link_t *link, graph_t *graph)
{
GstElement *src = link->src.element;
GstElement *sink = link->sink.element;
GSList *srcs = link->src.pads;
GSList *sinks = link->sink.pads;
g_assert (GST_IS_ELEMENT (src));
g_assert (GST_IS_ELEMENT (sink));
#ifndef GST_DISABLE_GST_DEBUG
gchar* srcpad_names = format_pad_names (srcs);
gchar* sinkpad_names = format_pad_names (sinks);
GST_CAT_INFO (GST_CAT_PIPELINE,
"linking pads {%s} of %s named %s to pads {%s} of %s named %s with caps \"%" GST_PTR_FORMAT "\"",
srcpad_names, G_OBJECT_TYPE_NAME (src), GST_STR_NULL (GST_ELEMENT_NAME (src)),
sinkpad_names, G_OBJECT_TYPE_NAME (sink), GST_STR_NULL (GST_ELEMENT_NAME (sink)),
link->caps);
g_free (srcpad_names);
g_free (sinkpad_names);
#endif
if (!srcs || !sinks) {
gboolean found_one = gst_element_link_pads_filtered (src,
srcs ? (const gchar *) srcs->data : NULL, sink,
sinks ? (const gchar *) sinks->data : NULL, link->caps);
if (found_one) {
if (!link->all_pads)
goto success; /* Linked one, and not an all-pads link = we're done */
/* Try and link more available pads */
while (gst_element_link_pads_filtered (src,
srcs ? (const gchar *) srcs->data : NULL, sink,
sinks ? (const gchar *) sinks->data : NULL, link->caps));
}
/* We either didn't find any static pads, or this is a all-pads link,
* in which case watch for future pads and link those. Not a failure
* in the all-pads case if there's no sometimes pads to watch */
if (gst_parse_perform_delayed_link (src,
srcs ? (const gchar *) srcs->data : NULL,
sink, sinks ? (const gchar *) sinks->data : NULL, link->caps,
link->all_pads) || link->all_pads) {
goto success;
} else {
goto error;
}
}
if (g_slist_length (link->src.pads) != g_slist_length (link->sink.pads)) {
goto error;
}
while (srcs && sinks) {
const gchar *src_pad = (const gchar *) srcs->data;
const gchar *sink_pad = (const gchar *) sinks->data;
srcs = g_slist_next (srcs);
sinks = g_slist_next (sinks);
if (gst_element_link_pads_filtered (src, src_pad, sink, sink_pad,
link->caps)) {
continue;
} else {
if (gst_parse_perform_delayed_link (src, src_pad,
sink, sink_pad,
link->caps, link->all_pads)) {
continue;
} else {
goto error;
}
}
}
success:
gst_parse_free_link (link);
return 0;
error:
if (link->caps != NULL) {
gboolean src_can_do_caps, sink_can_do_caps;
gchar *caps_str = gst_caps_to_string (link->caps);
src_can_do_caps =
gst_parse_element_can_do_caps (src, GST_PAD_SRC, link->caps);
sink_can_do_caps =
gst_parse_element_can_do_caps (sink, GST_PAD_SINK, link->caps);
if (!src_can_do_caps && sink_can_do_caps) {
SET_ERROR (graph, GST_PARSE_ERROR_LINK,
_("could not link %s to %s, %s can't handle caps %s"),
GST_ELEMENT_NAME (src), GST_ELEMENT_NAME (sink),
GST_ELEMENT_NAME (src), caps_str);
} else if (src_can_do_caps && !sink_can_do_caps) {
SET_ERROR (graph, GST_PARSE_ERROR_LINK,
_("could not link %s to %s, %s can't handle caps %s"),
GST_ELEMENT_NAME (src), GST_ELEMENT_NAME (sink),
GST_ELEMENT_NAME (sink), caps_str);
} else if (!src_can_do_caps && !sink_can_do_caps) {
SET_ERROR (graph, GST_PARSE_ERROR_LINK,
_("could not link %s to %s, neither element can handle caps %s"),
GST_ELEMENT_NAME (src), GST_ELEMENT_NAME (sink), caps_str);
} else {
SET_ERROR (graph, GST_PARSE_ERROR_LINK,
_("could not link %s to %s with caps %s"),
GST_ELEMENT_NAME (src), GST_ELEMENT_NAME (sink), caps_str);
}
g_free (caps_str);
} else {
SET_ERROR (graph, GST_PARSE_ERROR_LINK,
_("could not link %s to %s"), GST_ELEMENT_NAME (src),
GST_ELEMENT_NAME (sink));
}
gst_parse_free_link (link);
return -1;
}
static int yyerror (void *scanner, graph_t *graph, const char *s);
%}
%union {
gchar *ss;
chain_t *cc;
link_t *ll;
reference_t rr;
element_t *ee;
GSList *pp;
graph_t *gg;
}
/* No grammar ambiguities expected, FAIL otherwise */
%expect 0
%token <ss> PARSE_URL
%token <ss> IDENTIFIER
%left <ss> REF PADREF BINREF
%token <ss> ASSIGNMENT PRESET
%token <ss> LINK
%token <ss> LINK_ALL
%type <ss> binopener
%type <gg> graph
%type <cc> chain bin chainlist openchain elementary
%type <rr> reference
%type <ll> link
%type <ee> element
%type <pp> morepads pads assignments
%destructor { gst_parse_strfree ($$); } <ss>
%destructor { if($$)
gst_parse_free_chain($$); } <cc>
%destructor { gst_parse_free_link ($$); } <ll>
%destructor { gst_parse_free_reference(&($$));} <rr>
%destructor { gst_parse_free_element ($$); } <ee>
%destructor { GSList *walk;
for(walk=$$;walk;walk=walk->next)
gst_parse_strfree (walk->data);
g_slist_free ($$); } <pp>
%left '(' ')'
%left ','
%right '.'
%left '!' '=' ':'
%lex-param { void *scanner }
%parse-param { void *scanner }
%parse-param { graph_t *graph }
@BISON_PURE_PARSER@
%start graph
%%
/*************************************************************
* Grammar explanation:
* _element_s are specified by an identifier of their type.
* a name can be give in the optional property-assignments
* coffeeelement
* fakesrc name=john
* identity silence=false name=frodo
* (cont'd)
**************************************************************/
element: IDENTIFIER {
$$ = gst_parse_element_new();
$$->factory_name = $1;
/* silence unused but set warnings/errors */
(void) yynerrs;
}
| element PRESET {
$$->presets = g_slist_append ($$->presets, $2);
$$ = $1;
}
| element ASSIGNMENT {
$$->values = g_slist_append ($$->values, $2);
$$ = $1;
}
;
/*************************************************************
* Grammar explanation: (cont'd)
* a graph has (pure) _element_s, _bin_s and _link_s.
* since bins are special elements, bins and elements can
* be generalized as _elementary_.
* The construction of _bin_s will be discussed later.
* (cont'd)
*
**************************************************************/
elementary:
element {
GstElement *element = NULL;
$$ = gst_parse_chain_new ();
if ($1 && !(element = gst_parse_element_make (graph, $1))) {
add_missing_element(graph, $1->factory_name);
} else {
/* g_print ("@%p: CHAINing elementary\n", $$); */
$$->first.element = element ? gst_object_ref(element) : NULL;
$$->last.element = element ? gst_object_ref(element) : NULL;
$$->first.name = $$->last.name = NULL;
$$->first.pads = $$->last.pads = NULL;
$$->elements = element ? g_slist_prepend (NULL, element) : NULL;
}
gst_parse_free_element ($1);
}
| bin { $$=$1; }
;
/*************************************************************
* Grammar explanation: (cont'd)
* a _chain_ is a list of _elementary_s that have _link_s in between
* which are represented through infix-notation.
*
* fakesrc ! sometransformation ! fakesink
*
* every _link_ can be augmented with _pads_.
*
* coffeesrc .sound ! speakersink
* multisrc .movie,ads ! .projector,smallscreen multisink
*
* and every _link_ can be setup to filter media-types
* mediasrc ! audio/x-raw, signed=TRUE ! stereosink
*
* User HINT:
* if the lexer does not recognize your media-type it
* will make it an element name. that results in errors
* like
* NO SUCH ELEMENT: no element audio7x-raw
* '7' vs. '/' in https://en.wikipedia.org/wiki/QWERTZ
*
* Parsing HINT:
* in the parser we need to differ between chains that can
* be extended by more elementaries (_openchain_) and others
* that are syntactically closed (handled later in this file).
* (e.g. fakesrc ! sinkreferencename.padname)
**************************************************************/
chain: openchain { $$=$1;
if($$->last.name){
SET_ERROR (graph, GST_PARSE_ERROR_SYNTAX,
_("unexpected reference \"%s\" - ignoring"), $$->last.name);
gst_parse_strfree($$->last.name);
$$->last.name=NULL;
}
if($$->last.pads){
SET_ERROR (graph, GST_PARSE_ERROR_SYNTAX,
_("unexpected pad-reference \"%s\" - ignoring"), (gchar*)$$->last.pads->data);
g_slist_foreach ($$->last.pads, (GFunc) gst_parse_strfree, NULL);
g_slist_free ($$->last.pads);
$$->last.pads=NULL;
}
}
;
openchain:
elementary pads { $$=$1;
$$->last.pads = g_slist_concat ($$->last.pads, $2);
/* g_print ("@%p@%p: FKI elementary pads\n", $1, $$->last.pads); */
}
| openchain link pads elementary pads
{
$2->src = $1->last;
$2->sink = $4->first;
$2->sink.pads = g_slist_concat ($3, $2->sink.pads);
TRY_SETUP_LINK($2);
$4->first = $1->first;
$4->elements = g_slist_concat ($1->elements, $4->elements);
gst_parse_chain_free($1);
$$ = $4;
$$->last.pads = g_slist_concat ($$->last.pads, $5);
}
;
link: LINK { $$ = gst_parse_link_new ();
$$->all_pads = FALSE;
if ($1) {
gchar *str = str_unwrap ($1);
$$->caps = gst_caps_from_string (str);
g_free (str);
if ($$->caps == NULL)
SET_ERROR (graph, GST_PARSE_ERROR_LINK, _("could not parse caps \"%s\""), $1);
gst_parse_strfree ($1);
}
}
| LINK_ALL { $$ = gst_parse_link_new ();
$$->all_pads = TRUE;
if ($1) {
gchar *str = str_unwrap ($1);
$$->caps = gst_caps_from_string (str);
g_free (str);
if ($$->caps == NULL)
SET_ERROR (graph, GST_PARSE_ERROR_LINK, _("could not parse caps \"%s\""), $1);
gst_parse_strfree ($1);
}
}
;
pads: /* NOP */ { $$ = NULL; }
| PADREF morepads { $$ = $2;
$$ = g_slist_prepend ($$, $1);
}
;
morepads: /* NOP */ { $$ = NULL; }
| ',' IDENTIFIER morepads { $$ = g_slist_prepend ($3, $2); }
;
/*************************************************************
* Grammar explanation: (cont'd)
* the first and last elements of a _chain_ can be give
* as URL. This creates special elements that fit the URL.
*
* fakesrc ! http://fake-sink.org
* http://somesource.org ! fakesink
**************************************************************/
chain: openchain link PARSE_URL { GstElement *element =
gst_element_make_from_uri (GST_URI_SINK, $3, NULL, NULL);
/* FIXME: get and parse error properly */
if (!element) {
SET_ERROR (graph, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
_("no sink element for URI \"%s\""), $3);
}
$$ = $1;
$2->sink.element = element?gst_object_ref(element):NULL;
$2->src = $1->last;
TRY_SETUP_LINK($2);
$$->last.element = NULL;
$$->last.name = NULL;
$$->last.pads = NULL;
if(element) $$->elements = g_slist_append ($$->elements, element);
g_free ($3);
}
;
openchain:
PARSE_URL { GstElement *element =
gst_element_make_from_uri (GST_URI_SRC, $1, NULL, NULL);
/* FIXME: get and parse error properly */
if (!element) {
SET_ERROR (graph, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
_("no source element for URI \"%s\""), $1);
}
$$ = gst_parse_chain_new ();
/* g_print ("@%p: CHAINing srcURL\n", $$); */
$$->first.element = NULL;
$$->first.name = NULL;
$$->first.pads = NULL;
$$->last.element = element ? gst_object_ref(element):NULL;
$$->last.name = NULL;
$$->last.pads = NULL;
$$->elements = element ? g_slist_prepend (NULL, element) : NULL;
g_free($1);
}
;
/*************************************************************
* Grammar explanation: (cont'd)
* the first and last elements of a _chain_ can be linked
* to a named _reference_ (with optional pads).
*
* fakesrc ! nameOfSinkElement.
* fakesrc ! nameOfSinkElement.Padname
* fakesrc ! nameOfSinkElement.Padname, anotherPad
* nameOfSource.Padname ! fakesink
**************************************************************/
chain: openchain link reference { $$ = $1;
$2->sink= $3;
$2->src = $1->last;
TRY_SETUP_LINK($2);
$$->last.element = NULL;
$$->last.name = NULL;
$$->last.pads = NULL;
}
;
openchain:
reference { $$ = gst_parse_chain_new ();
$$->last=$1;
$$->first.element = NULL;
$$->first.name = NULL;
$$->first.pads = NULL;
$$->elements = NULL;
}
;
reference: REF morepads {
gchar *padname = $1;
GSList *pads = $2;
if (padname) {
while (*padname != '.') padname++;
*padname = '\0';
padname++;
if (*padname != '\0')
pads = g_slist_prepend (pads, gst_parse_strdup (padname));
}
$$.element=NULL;
$$.name=$1;
$$.pads=pads;
}
;
/*************************************************************
* Grammar explanation: (cont'd)
* a _chainlist_ is just a list of _chain_s.
*
* You can specify _link_s with named
* _reference_ on each side. That
* works already after the explanations above.
* someSourceName.Pad ! someSinkName.
* someSourceName.Pad,anotherPad ! someSinkName.Apad,Bpad
*
* If a syntax error occurs, the already finished _chain_s
* and _links_ are kept intact.
*************************************************************/
chainlist: /* NOP */ { $$ = NULL; }
| chainlist chain { if ($1){
gst_parse_free_reference(&($1->last));
gst_parse_free_reference(&($2->first));
$2->first = $1->first;
$2->elements = g_slist_concat ($1->elements, $2->elements);
gst_parse_chain_free ($1);
}
$$ = $2;
}
| chainlist error { $$=$1;
GST_CAT_DEBUG (GST_CAT_PIPELINE,"trying to recover from syntax error");
SET_ERROR (graph, GST_PARSE_ERROR_SYNTAX, _("syntax error"));
}
;
/*************************************************************
* Grammar explanation: (cont'd)
* _bins_
*************************************************************/
assignments: /* NOP */ { $$ = NULL; }
| ASSIGNMENT assignments { $$ = g_slist_prepend ($2, $1); }
;
binopener: '(' { $$ = gst_parse_strdup("bin"); }
| BINREF { $$ = $1; }
;
bin: binopener assignments chainlist ')' {
chain_t *chain = $3;
GSList *walk;
GstBin *bin = (GstBin *) gst_element_factory_make ($1, NULL);
if (!chain) {
SET_ERROR (graph, GST_PARSE_ERROR_EMPTY_BIN,
_("specified empty bin \"%s\", not allowed"), $1);
chain = gst_parse_chain_new ();
chain->first.element = chain->last.element = NULL;
chain->first.name = chain->last.name = NULL;
chain->first.pads = chain->last.pads = NULL;
chain->elements = NULL;
}
if (!bin) {
add_missing_element(graph, $1);
SET_ERROR (graph, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
_("no bin \"%s\", unpacking elements"), $1);
/* clear property-list */
g_slist_foreach ($2, (GFunc) gst_parse_strfree, NULL);
g_slist_free ($2);
$2 = NULL;
} else {
for (walk = chain->elements; walk; walk = walk->next )
gst_bin_add (bin, GST_ELEMENT (walk->data));
g_slist_free (chain->elements);
chain->elements = g_slist_prepend (NULL, bin);
}
$$ = chain;
/* set the properties now
* HINT: property-list cleared above, if bin==NULL
*/
for (walk = $2; walk; walk = walk->next)
gst_parse_element_set ((gchar *) walk->data,
GST_ELEMENT (bin), graph);
g_slist_free ($2);
gst_parse_strfree ($1);
}
;
/*************************************************************
* Grammar explanation: (cont'd)
* _graph_
*************************************************************/
graph: chainlist { $$ = graph;
$$->chain = $1;
if(!$1) {
SET_ERROR (graph, GST_PARSE_ERROR_EMPTY, _("empty pipeline not allowed"));
}
}
;
%%
static int
yyerror (void *scanner, graph_t *graph, const char *s)
{
/* FIXME: This should go into the GError somehow, but how? */
GST_WARNING ("Error during parsing: %s", s);
return -1;
}
static GstBusSyncReply
parse_launch_handle_message_sync (GstBus * bus, GstMessage * message, gpointer user_data)
{
reason_receiver_t *receiver = (reason_receiver_t *) user_data;
GstObject *src = GST_MESSAGE_SRC (message);
GstMessageType type = GST_MESSAGE_TYPE (message);
switch (type) {
case GST_MESSAGE_ERROR:
{
GError *error;
gchar *debug_msg;
gchar *explanation;
const GstStructure *details;
gst_message_parse_error (message, &error, &debug_msg);
gst_message_parse_error_details (message, &details);
explanation = gst_info_strdup_printf ("%s %" GST_PTR_FORMAT " posted "
"an error message: %s", src ? G_OBJECT_TYPE_NAME(src) : "", src,
error->message);
/* We lock the mutex a bit early so that if errors occur simultaneously
* from two threads the following log lines are logged one error at a
* time, just for convenience of the person reading the logs. */
g_mutex_lock (&receiver->mutex);
GST_CAT_ERROR (GST_CAT_PIPELINE, "%s", explanation);
if (debug_msg)
GST_CAT_ERROR (GST_CAT_PIPELINE, "Debug message: %s", debug_msg);
if (details)
GST_CAT_ERROR (GST_CAT_PIPELINE, "Details: %" GST_PTR_FORMAT, details);
/* This message will be suffixed when SET_ERROR() is called.
* This allows the message to be seen in gst-launch even if no logging is
* enabled, which is the default for GStreamer releases. */
if (!receiver->reason)
receiver->reason = g_strdup (explanation);
g_mutex_unlock (&receiver->mutex);
g_free (explanation);
g_error_free (error);
g_free (debug_msg);
break;
}
default:
break;
}
return GST_BUS_PASS;
}
static reason_receiver_t*
reason_receiver_new (void) {
reason_receiver_t *receiver = g_atomic_rc_box_new0 (reason_receiver_t);
g_mutex_init (&receiver->mutex);
receiver->reason = NULL;
return receiver;
}
static void
reason_receiver_clear (reason_receiver_t *receiver) {
g_mutex_clear (&receiver->mutex);
g_free (receiver->reason);
}
#define reason_receiver_ref(receiver) g_atomic_rc_box_acquire (receiver)
static void
reason_receiver_unref (reason_receiver_t *receiver) {
g_atomic_rc_box_release_full (receiver,
(GDestroyNotify) reason_receiver_clear);
}
GstElement *
priv_gst_parse_launch (const gchar *str, GError **error, GstParseContext *ctx,
GstParseFlags flags)
{
graph_t g;
gchar *dstr;
GSList *walk;
GstElement *ret;
yyscan_t scanner;
GstBin *bin = NULL;
GstBus *temp_bus = NULL, *old_bus = NULL;
g_return_val_if_fail (str != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g.chain = NULL;
g.links = NULL;
g.error = error;
g.ctx = ctx;
g.flags = flags;
g.error_probable_reason_receiver = NULL;
#ifdef __GST_PARSE_TRACE
GST_CAT_DEBUG (GST_CAT_PIPELINE, "TRACE: tracing enabled");
__strings = __chains = __links = __elements = 0;
#endif /* __GST_PARSE_TRACE */
/* g_print("Now scanning: %s\n", str); */
dstr = g_strdup (str);
priv_gst_parse_yylex_init (&scanner);
priv_gst_parse_yy_scan_string (dstr, scanner);
#if YYDEBUG
yydebug = 1;
#endif
if (yyparse (scanner, &g) != 0) {
SET_ERROR (&g, GST_PARSE_ERROR_SYNTAX,
"Unrecoverable syntax error while parsing pipeline %s", str);
priv_gst_parse_yylex_destroy (scanner);
g_free (dstr);
goto error1;
}
priv_gst_parse_yylex_destroy (scanner);
g_free (dstr);
GST_CAT_DEBUG (GST_CAT_PIPELINE, "got %u elements and %u links",
g.chain ? g_slist_length (g.chain->elements) : 0,
g_slist_length (g.links));
/* ensure chain is not NULL */
if (!g.chain){
g.chain=gst_parse_chain_new ();
g.chain->elements=NULL;
g.chain->first.element=NULL;
g.chain->first.name=NULL;
g.chain->first.pads=NULL;
g.chain->last.element=NULL;
g.chain->last.name=NULL;
g.chain->last.pads=NULL;
};
/* ensure elements is not empty */
if(!g.chain->elements){
g.chain->elements= g_slist_prepend (NULL, NULL);
};
/* put all elements in our bin if necessary */
if(g.chain->elements->next){
if (flags & GST_PARSE_FLAG_PLACE_IN_BIN)
bin = GST_BIN (gst_element_factory_make ("bin", NULL));
else
bin = GST_BIN (gst_element_factory_make ("pipeline", NULL));
g_assert (bin);
/* Assign the bin a temporary bus to catch any error messages posted during
* the construction of the pipeline and log them in a way that is visible
* by default in gst-launch. */
old_bus = gst_element_get_bus (GST_ELEMENT (bin));
temp_bus = gst_bus_new ();
g.error_probable_reason_receiver = reason_receiver_new ();
gst_bus_set_sync_handler (temp_bus, parse_launch_handle_message_sync,
reason_receiver_ref (g.error_probable_reason_receiver),
(GDestroyNotify) reason_receiver_unref);
gst_element_set_bus (GST_ELEMENT (bin), temp_bus);
for (walk = g.chain->elements; walk; walk = walk->next) {
if (walk->data != NULL)
gst_bin_add (bin, GST_ELEMENT (walk->data));
}
g_slist_free (g.chain->elements);
g.chain->elements = g_slist_prepend (NULL, bin);
}
ret = (GstElement *) g.chain->elements->data;
g_slist_free (g.chain->elements);
g.chain->elements=NULL;
gst_parse_free_chain (g.chain);
g.chain = NULL;
/* resolve and perform links */
for (walk = g.links; walk; walk = walk->next) {
link_t *l = (link_t *) walk->data;
int err;
err=gst_resolve_reference( &(l->src), ret);
if (err) {
if(-1==err){
SET_ERROR (&g, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No src-element named \"%s\" - omitting link", l->src.name);
}else{
/* probably a missing element which we've handled already */
SET_ERROR (&g, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No src-element found - omitting link");
}
gst_parse_free_link (l);
continue;
}
err=gst_resolve_reference( &(l->sink), ret);
if (err) {
if(-1==err){
SET_ERROR (&g, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No sink-element named \"%s\" - omitting link", l->src.name);
}else{
/* probably a missing element which we've handled already */
SET_ERROR (&g, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No sink-element found - omitting link");
}
gst_parse_free_link (l);
continue;
}
gst_parse_perform_link (l, &g);
}
g_slist_free (g.links);
out:
#ifdef __GST_PARSE_TRACE
GST_CAT_DEBUG (GST_CAT_PIPELINE,
"TRACE: %u strings, %u chains, %u links and %u elements left", __strings, __chains,
__links, __elements);
if (__strings || __chains || __links || __elements) {
g_warning ("TRACE: %u strings, %u chains, %u links and %u elements left", __strings,
__chains, __links, __elements);
}
#endif /* __GST_PARSE_TRACE */
if (bin && temp_bus) {
gst_element_set_bus (GST_ELEMENT (bin), old_bus);
reason_receiver_unref (g.error_probable_reason_receiver);
g.error_probable_reason_receiver = NULL;
g_clear_object (&temp_bus);
g_clear_object (&old_bus);
}
return ret;
error1:
if (g.chain) {
gst_parse_free_chain (g.chain);
g.chain=NULL;
}
g_slist_foreach (g.links, (GFunc)gst_parse_free_link, NULL);
g_slist_free (g.links);
if (error)
g_assert (*error);
ret = NULL;
goto out;
}