2 * Copyright (C) 2002 Erik Walthinsen <omega@cse.ogi.edu>
3 * 2002 Wim Taymans <wtay@chello.be>
5 * gstspider.c: element to automatically connect sinks and sources
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
25 * - handle automatic removal of unneeded elements
26 * - make the spider handle and send events (esp. new media)
27 * - decide if we plug pads or elements, currently it's a mess
28 * - allow disconnecting
29 * - implement proper saving/loading from xml
30 * - implement a way to allow merging/splitting (aka tee)
31 * - find ways to define which elements to use when plugging
33 * - improve typefinding
34 * - react to errors inside the pipeline
35 * - implement more properties, change the current
36 * - emit signals (most important: "NOT PLUGGABLE")
37 * - implement something for reporting the state of the spider
38 * to allow easier debugging.
39 * (could be useful for bins in general)
44 #include "gstspider.h"
45 #include "gstspideridentity.h"
46 #include "gstsearchfuncs.h"
48 /* signals and args */
60 /* generic templates */
61 GST_PADTEMPLATE_FACTORY (spider_src_factory,
68 GST_PADTEMPLATE_FACTORY (spider_sink_factory,
77 /* standard GObject stuff */
78 static void gst_spider_class_init (GstSpiderClass *klass);
79 static void gst_spider_init (GstSpider *spider);
80 static void gst_spider_dispose (GObject *object);
82 /* element class functions */
83 static GstPad* gst_spider_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name);
84 static void gst_spider_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
85 static void gst_spider_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
87 /* connection functions */
88 static GstSpiderConnection * gst_spider_connection_new (GstSpiderIdentity *sink, GstSpiderIdentity *src);
89 static void gst_spider_connection_destroy (GstSpiderConnection *conn);
90 static void gst_spider_connection_reset (GstSpiderConnection *conn, GstElement *to);
91 static void gst_spider_connection_add (GstSpiderConnection *conn, GstElement *element);
92 static GstSpiderConnection * gst_spider_connection_find (GstSpiderIdentity *sink, GstSpiderIdentity *src);
93 static GstSpiderConnection * gst_spider_connection_get (GstSpiderIdentity *sink, GstSpiderIdentity *src);
95 /* autoplugging functions */
96 static GstElement * gst_spider_find_element_to_plug (GstElement *src, GstElementFactory *fac, GstPadDirection dir);
97 static GstPadConnectReturn gst_spider_plug (GstSpiderConnection *conn);
98 static GstPadConnectReturn gst_spider_plug_from_srcpad (GstSpiderConnection *conn, GstPad *srcpad);
99 //static GstPadConnectReturn gst_spider_plug_peers (GstSpider *spider, GstPad *srcpad, GstPad *sinkpad);
100 static GstPadConnectReturn gst_spider_create_and_plug (GstSpiderConnection *conn, GList *plugpath);
102 /* random functions */
103 static gchar * gst_spider_unused_elementname (GstBin *bin, const gchar *startwith);
106 static void print_spider_contents (GstSpider *spider);
107 static void print_spider_connection (GstSpiderConnection *conn); */
109 /* === variables === */
110 static GstElementClass * parent_class = NULL;
113 static guint gst_spider_signals[LAST_SIGNAL] = { 0 };*/
115 /* let gstreamer know that we have some request pads available */
116 GST_PADTEMPLATE_FACTORY (gst_spider_sink_template_factory,
122 GST_PADTEMPLATE_FACTORY (gst_spider_src_template_factory,
129 /* GObject and GStreamer init functions */
131 gst_spider_get_type(void)
133 static GType spider_type = 0;
136 static const GTypeInfo spider_info = {
137 sizeof(GstSpiderClass),
140 (GClassInitFunc) gst_spider_class_init,
145 (GInstanceInitFunc)gst_spider_init,
147 spider_type = g_type_register_static (GST_TYPE_BIN, "GstSpider", &spider_info, 0);
153 gst_spider_class_init (GstSpiderClass *klass)
155 GObjectClass *gobject_class;
156 GstElementClass *gstelement_class;
158 gobject_class = (GObjectClass*) klass;
159 gstelement_class = (GstElementClass*) klass;
161 parent_class = g_type_class_ref(GST_TYPE_BIN);
164 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FACTORIES,
165 g_param_spec_pointer ("factories", "allowed factories", "allowed factories for autoplugging", G_PARAM_READWRITE));
167 gobject_class->set_property = gst_spider_set_property;
168 gobject_class->get_property = gst_spider_get_property;
169 gobject_class->dispose = gst_spider_dispose;
171 gst_element_class_add_padtemplate (gstelement_class, gst_spider_src_template_factory());
172 gst_element_class_add_padtemplate (gstelement_class, gst_spider_sink_template_factory());
174 gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_spider_request_new_pad);
177 gst_spider_init (GstSpider *spider)
179 /* use only elements which have sources and sinks and where the sinks have caps */
180 /* FIXME: How do we handle factories that are added after the spider was constructed? */
181 spider->factories = gst_autoplug_factories_filters_with_sink_caps ((GList *) gst_elementfactory_get_list ());
183 spider->connections = NULL;
187 gst_spider_dispose (GObject *object)
191 spider = GST_SPIDER (object);
192 g_list_free (spider->factories);
194 ((GObjectClass *) parent_class)->dispose (object);
197 gst_spider_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name)
201 GstSpiderIdentity *identity;
204 g_return_val_if_fail (templ != NULL, NULL);
205 g_return_val_if_fail (GST_IS_PADTEMPLATE (templ), NULL);
207 spider = GST_SPIDER (element);
209 /* create an identity object, so we have a pad */
210 switch ( GST_PADTEMPLATE_DIRECTION (templ))
213 padname = gst_spider_unused_elementname ((GstBin *)spider, "src_");
214 identity = gst_spider_identity_new_src (padname);
215 returnpad = identity->src;
218 padname = gst_spider_unused_elementname ((GstBin *)spider, "sink_");
219 identity = gst_spider_identity_new_sink (padname);
220 returnpad = identity->sink;
222 case GST_PAD_UNKNOWN:
224 g_warning("Spider: you must request a source or sink pad.");
228 /* FIXME: use the requested name for the pad */
230 gst_object_ref (GST_OBJECT (templ));
231 GST_PAD_PADTEMPLATE (returnpad) = templ;
233 gst_bin_add (GST_BIN (element), GST_ELEMENT (identity));
235 returnpad = gst_element_add_ghost_pad (element, returnpad, padname);
236 GST_DEBUG (GST_CAT_ELEMENT_PADS, "successuflly created requested pad %s:%s\n", GST_DEBUG_PAD_NAME (returnpad));
242 gst_spider_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
247 /* it's not null if we got it, but it might not be ours */
248 g_return_if_fail (GST_IS_SPIDER (object));
250 spider = GST_SPIDER (object);
254 list = (GList *) g_value_get_pointer (value);
257 g_return_if_fail (list->data != NULL);
258 g_return_if_fail (GST_IS_ELEMENTFACTORY (list->data));
259 list = g_list_next (list);
261 g_list_free (spider->factories);
262 spider->factories = (GList *) g_value_get_pointer (value);
269 gst_spider_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
273 /* it's not null if we got it, but it might not be ours */
274 spider = GST_SPIDER(object);
278 g_value_set_pointer (value, spider->factories);
281 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
285 /* get a name for an element that isn't used yet */
287 gst_spider_unused_elementname (GstBin *bin, const gchar *startwith)
289 gchar * name = g_strdup_printf ("%s%d", startwith, 0);
292 for (i = 0; gst_bin_get_by_name (bin, name) != NULL; )
295 name = g_strdup_printf ("%s%d", startwith, ++i);
301 gst_spider_connect_sometimes (GstElement *src, GstPad *pad, GstSpiderConnection *conn)
303 gboolean restart = FALSE;
304 gulong signal_id = conn->signal_id;
305 GstPad *sinkpad = conn->src->sink;
306 GstSpider *spider = GST_SPIDER (GST_OBJECT_PARENT (conn->sink));
308 /* check if restarting is necessary */
309 if (gst_element_get_state ((GstElement *) spider) == GST_STATE_PLAYING)
312 gst_element_set_state ((GstElement *) spider, GST_STATE_PAUSED);
315 /* try to autoplug the elements */
316 if (gst_spider_plug_from_srcpad (conn, pad) != GST_PAD_CONNECT_REFUSED) {
317 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "%s:%s was autoplugged to %s:%s, removing callback\n", GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (sinkpad));
318 g_signal_handler_disconnect (src, signal_id);
322 /* restart if needed */
325 gst_element_set_state ((GstElement *) spider, GST_STATE_PLAYING);
328 /* create a new connection from those two elements */
329 static GstSpiderConnection *
330 gst_spider_connection_new (GstSpiderIdentity *sink, GstSpiderIdentity *src)
334 GstSpiderConnection *conn = g_new0 (GstSpiderConnection, 1);
337 conn->current = (GstElement *) sink;
339 spider = GST_SPIDER (GST_OBJECT_PARENT (src));
340 spider->connections = g_list_prepend (spider->connections, conn);
345 gst_spider_connection_destroy (GstSpiderConnection *conn)
347 /* reset connection to unplugged */
348 gst_spider_connection_reset (conn, (GstElement *) conn->sink);
352 gst_spider_connection_reset (GstSpiderConnection *conn, GstElement *to)
354 GST_DEBUG (GST_CAT_AUTOPLUG, "resetting connection from %s to %s, currently at %s to %s\n", GST_ELEMENT_NAME (conn->sink),
355 GST_ELEMENT_NAME (conn->src), GST_ELEMENT_NAME (conn->current), GST_ELEMENT_NAME (to));
356 while ((conn->path != NULL) && ((GstElement *) conn->path->data != to))
358 gst_object_unref ((GstObject *) conn->path->data);
359 conn->path = g_list_delete_link (conn->path, conn->path);
361 if (conn->path == NULL)
363 conn->current = (GstElement *) conn->sink;
368 /* add an element to the connection */
370 gst_spider_connection_add (GstSpiderConnection *conn, GstElement *element)
372 gst_object_ref ((GstObject *) element);
373 gst_object_sink ((GstObject *) element);
374 conn->path = g_list_prepend (conn->path, element);
375 conn->current = element;
377 /* find the connection from those two elements */
378 static GstSpiderConnection *
379 gst_spider_connection_find (GstSpiderIdentity *sink, GstSpiderIdentity *src)
381 GstSpider *spider = (GstSpider *) GST_OBJECT_PARENT (src);
382 GList *list = spider->connections;
384 g_assert (spider == (GstSpider *) GST_OBJECT_PARENT (sink));
388 GstSpiderConnection *conn = (GstSpiderConnection *) list->data;
389 if (conn->src == src && conn->sink == sink)
391 list = g_list_next(list);
395 /* get a new connection from those two elements
396 * search first; if none is found, create a new one */
397 static GstSpiderConnection *
398 gst_spider_connection_get (GstSpiderIdentity *sink, GstSpiderIdentity *src)
400 GstSpiderConnection *ret;
402 if ((ret = gst_spider_connection_find (sink, src)) != NULL)
406 return gst_spider_connection_new (sink, src);
409 gst_spider_identity_plug (GstSpiderIdentity *ident)
414 GstSpiderConnection *conn;
417 g_return_if_fail (ident != NULL);
418 g_return_if_fail (GST_IS_SPIDER_IDENTITY (ident));
419 spider = GST_SPIDER (GST_ELEMENT_PARENT (ident));
420 g_assert (spider != NULL);
421 g_assert (GST_IS_SPIDER (spider));
423 /* return if we're already plugged */
424 if (ident->plugged) return;
426 /* get the direction of our ident */
427 if (GST_PAD_PEER (ident->sink))
429 if (GST_PAD_PEER (ident->src))
431 /* Hey, the ident is connected on both sides */
432 g_warning ("Trying to autoplug a connected element. Aborting...");
438 if (GST_PAD_PEER (ident->src))
442 /* the ident isn't connected on either side */
443 g_warning ("Trying to autoplug an unconnected element. Aborting...");
448 /* now iterate all possible pads and connect when needed */
449 padlist = gst_element_get_pad_list (GST_ELEMENT (spider));
452 GstPad *otherpad = (GstPad *) GST_GPAD_REALPAD (padlist->data);
453 GstSpiderIdentity *peer = (GstSpiderIdentity *) GST_PAD_PARENT (otherpad);
454 /* we only want to connect to the other side */
455 if (dir != GST_PAD_DIRECTION (otherpad))
457 /* we only connect to plugged in elements */
458 if (peer->plugged == TRUE)
460 /* plug in the right direction */
461 if (dir == GST_PAD_SINK)
463 conn = gst_spider_connection_get (ident, peer);
465 conn = gst_spider_connection_get (peer, ident);
467 if ((GstElement *) conn->sink == conn->current)
469 gst_spider_plug (conn);
473 padlist = g_list_next (padlist);
476 ident->plugged = TRUE;
479 gst_spider_identity_unplug (GstSpiderIdentity *ident)
481 GstSpider *spider = (GstSpider *) GST_OBJECT_PARENT (ident);
482 GList *list = spider->connections;
486 GstSpiderConnection *conn = list->data;
488 list = g_list_next (list);
489 if ((conn->src == ident) || (conn->sink == ident))
491 g_list_delete_link (spider->connections, cur);
492 gst_spider_connection_destroy (conn);
495 ident->plugged = FALSE;
497 /* connects src to sink using the elementfactories in plugpath
498 * plugpath will be removed afterwards */
499 static GstPadConnectReturn
500 gst_spider_create_and_plug (GstSpiderConnection *conn, GList *plugpath)
502 GstSpider *spider = (GstSpider *) GST_OBJECT_PARENT (conn->sink);
503 GList *endelements = NULL, *templist = NULL;
506 /* exit if plugging is already done */
507 if ((GstElement *) conn->src == conn->current)
508 return GST_PAD_CONNECT_DONE;
510 /* try to shorten the list at the end and not duplicate connection code */
511 if (plugpath != NULL)
513 templist = g_list_last (plugpath);
514 element = (GstElement *) conn->src;
515 while ((plugpath != NULL) && (element = gst_spider_find_element_to_plug (element, (GstElementFactory *) plugpath->data, GST_PAD_SINK)))
517 GList *cur = templist;
518 endelements = g_list_prepend (endelements, element);
519 templist = g_list_previous (templist);
520 g_list_delete_link (cur, cur);
524 /* do the connecting */
525 while (conn->current != (GstElement *) (endelements == NULL ? conn->src : endelements->data))
527 /* get sink element to plug, src is conn->current */
528 if (plugpath == NULL)
530 element = (GstElement *) (endelements == NULL ? conn->src : endelements->data);
532 element = gst_elementfactory_create ((GstElementFactory *) plugpath->data, NULL);
533 gst_bin_add (GST_BIN (spider), element);
535 /* insert and connect new element */
536 if (!gst_element_connect_elements (conn->current, element))
538 /* check if the src has SOMETIMES templates. If so, connect a callback */
539 GList *templs = gst_element_get_padtemplate_list (conn->current);
541 /* remove element that couldn't be connected, if it wasn't the endpoint */
542 if (element != (GstElement *) conn->src)
543 gst_bin_remove (GST_BIN (spider), element);
546 GstPadTemplate *templ = (GstPadTemplate *) templs->data;
547 if ((GST_PADTEMPLATE_DIRECTION (templ) == GST_PAD_SRC) && (GST_PADTEMPLATE_PRESENCE(templ) == GST_PAD_SOMETIMES))
549 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "adding callback to connect element %s to %s\n", GST_ELEMENT_NAME (conn->current), GST_ELEMENT_NAME (conn->src));
550 conn->signal_id = g_signal_connect (G_OBJECT (conn->current), "new_pad",
551 G_CALLBACK (gst_spider_connect_sometimes), conn);
552 g_list_free (plugpath);
553 return GST_PAD_CONNECT_DELAYED;
555 templs = g_list_next (templs);
557 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "no chance to connect element %s to %s\n", GST_ELEMENT_NAME (conn->current), GST_ELEMENT_NAME (conn->src));
558 g_list_free (plugpath);
559 return GST_PAD_CONNECT_REFUSED;
561 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "added element %s and attached it to element %s\n", GST_ELEMENT_NAME (element), GST_ELEMENT_NAME (conn->current));
562 gst_spider_connection_add (conn, element);
563 if (plugpath != NULL)
564 plugpath = g_list_delete_link (plugpath, plugpath);
567 /* ref all elements at the end */
570 gst_spider_connection_add (conn, endelements->data);
571 endelements = g_list_delete_link (endelements, endelements);
574 return GST_PAD_CONNECT_DONE;
576 /* checks, if src is already connected to an element from factory fac on direction dir */
578 gst_spider_find_element_to_plug (GstElement *src, GstElementFactory *fac, GstPadDirection dir)
580 GList *padlist = GST_ELEMENT_PADS (src);
584 GstPad *pad = (GstPad *) GST_PAD_REALIZE (padlist->data);
585 /* is the pad on the right side and is it connected? */
586 if ((GST_PAD_DIRECTION (pad) == dir) && (pad = GST_PAD (GST_RPAD_PEER (pad))))
588 /* is the element the pad is connected to of the right type? */
589 GstElement *element = GST_PAD_PARENT (pad);
590 if (GST_ELEMENT_CLASS (G_OBJECT_GET_CLASS (element))->elementfactory == fac) {
594 padlist = g_list_next (padlist);
599 /* try to establish the connection */
600 static GstPadConnectReturn
601 gst_spider_plug (GstSpiderConnection *conn)
603 if ((GstElement *) conn->src == conn->current)
604 return GST_PAD_CONNECT_DONE;
605 if ((GstElement *) conn->sink == conn->current)
606 return gst_spider_plug_from_srcpad (conn, conn->sink->src);
607 g_warning ("FIXME: autoplugging only possible from GstSpiderIdentity conn->sink yet (yep, that's technical)\n");
608 return GST_PAD_CONNECT_REFUSED;
610 /* try to establish the connection using this pad */
611 static GstPadConnectReturn
612 gst_spider_plug_from_srcpad (GstSpiderConnection *conn, GstPad *srcpad)
616 gboolean result = TRUE;
617 GstSpider *spider = (GstSpider *) GST_OBJECT_PARENT (conn->src);
618 GstElement *startelement = conn->current;
620 g_assert ((GstElement *) GST_OBJECT_PARENT (srcpad) == conn->current);
621 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "trying to plug from %s:%s to %s\n", GST_DEBUG_PAD_NAME (srcpad), GST_ELEMENT_NAME (conn->src));
623 /* find a path from src to sink */
624 /* FIXME: make that if go away and work anyway */
625 if (srcpad == conn->sink->src)
627 plugpath = gst_autoplug_sp (gst_pad_get_caps ((GstPad *) GST_RPAD_PEER (conn->sink->sink)), gst_pad_get_caps (conn->src->sink), spider->factories);
629 plugpath = gst_autoplug_sp (gst_pad_get_caps (srcpad), gst_pad_get_caps (conn->src->sink), spider->factories);
632 /* prints out the path that was found for plugging */
633 /* g_print ("found path from %s to %s:\n", GST_ELEMENT_NAME (conn->current), GST_ELEMENT_NAME (conn->src));
637 g_print("%s\n", GST_OBJECT_NAME (templist->data));
638 templist = g_list_next (templist);
641 /* if there is no way to plug: return */
642 if (plugpath == NULL) {
643 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "no chance to plug from %s to %s\n", GST_ELEMENT_NAME (conn->current), GST_ELEMENT_NAME (conn->src));
644 return GST_PAD_CONNECT_REFUSED;
646 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "found a connection that needs %d elements\n", g_list_length (plugpath));
648 /* now remove non-needed elements from the beginning of the path
649 * alter src to point to the new element where we need to start
650 * plugging and alter the plugpath to represent the elements, that must be plugged
652 element = conn->current;
653 while ((plugpath != NULL) && (element = gst_spider_find_element_to_plug (element, (GstElementFactory *) plugpath->data, GST_PAD_SRC)))
655 gst_spider_connection_add (conn, element);
656 plugpath = g_list_delete_link (plugpath, plugpath);
659 GST_DEBUG (GST_CAT_AUTOPLUG_ATTEMPT, "%d elements must be inserted to establish the connection\n", g_list_length (plugpath));
660 /* create the elements and plug them */
661 result = gst_spider_create_and_plug (conn, plugpath);
663 /* reset the "current" element */
664 if (result == GST_PAD_CONNECT_REFUSED)
666 gst_spider_connection_reset (conn, startelement);
672 GstElementDetails gst_spider_details = {
675 "Automatically connect sinks and sources",
677 "Benjamin Otte <in7y118@public.uni-hamburg.de>",
682 plugin_init (GModule *module, GstPlugin *plugin)
684 GstElementFactory *factory;
686 factory = gst_elementfactory_new("spider", GST_TYPE_SPIDER,
687 &gst_spider_details);
688 g_return_val_if_fail(factory != NULL, FALSE);
690 gst_elementfactory_add_padtemplate (factory, GST_PADTEMPLATE_GET (spider_src_factory));
691 gst_elementfactory_add_padtemplate (factory, GST_PADTEMPLATE_GET (spider_sink_factory));
693 gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
698 GstPluginDesc plugin_desc = {