mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-04-11 10:34:11 +00:00
manual: remove outdated autoplugging section
Remove autoplugging chapter and point to decodebin/playbin examples.
This commit is contained in:
parent
ff4522fe66
commit
775594fa3b
3 changed files with 4 additions and 387 deletions
|
@ -234,390 +234,9 @@ main (gint argc,
|
|||
|
||||
<sect1 id="section-dynamic">
|
||||
<title>Dynamically autoplugging a pipeline</title>
|
||||
<warning><para>
|
||||
The code in this section is broken, outdated and overly complicated.
|
||||
Also, you should use decodebin, playbin or uridecodebin to get
|
||||
decoders plugged automatically.
|
||||
</para></warning>
|
||||
<para>
|
||||
In this chapter we will see how you can create a dynamic pipeline. A
|
||||
dynamic pipeline is a pipeline that is updated or created while data
|
||||
is flowing through it. We will create a partial pipeline first and add
|
||||
more elements while the pipeline is playing. The basis of this player
|
||||
will be the application that we wrote in the previous section (<xref
|
||||
linkend="section-typefinding"/>) to identify unknown media streams.
|
||||
</para>
|
||||
<!-- example-begin dynamic.c a --><!--
|
||||
#include <gst/gst.h>
|
||||
|
||||
GstElement *pipeline;
|
||||
--><!-- example-end dynamic.c a -->
|
||||
<para>
|
||||
Once the type of the media has been found, we will find elements in
|
||||
the registry that can decode this streamtype. For this, we will get
|
||||
all element factories (which we've seen before in <xref
|
||||
linkend="section-elements-create"/>) and find the ones with the
|
||||
given media type and capabilities on their sinkpad. Note that we will
|
||||
only use parsers, demuxers and decoders. We will not use factories for
|
||||
any other element types, or we might get into a loop of encoders and
|
||||
decoders. For this, we will want to build a list of <quote>allowed</quote>
|
||||
factories right after initializing &GStreamer;.
|
||||
</para>
|
||||
<programlisting><!-- example-begin dynamic.c b -->
|
||||
static GList *factories;
|
||||
|
||||
/*
|
||||
* This function is called by the registry loader. Its return value
|
||||
* (TRUE or FALSE) decides whether the given feature will be included
|
||||
* in the list that we're generating further down.
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
cb_feature_filter (GstPluginFeature *feature,
|
||||
gpointer data)
|
||||
{
|
||||
const gchar *klass;
|
||||
guint rank;
|
||||
|
||||
/* we only care about element factories */
|
||||
if (!GST_IS_ELEMENT_FACTORY (feature))
|
||||
return FALSE;
|
||||
|
||||
/* only parsers, demuxers and decoders */
|
||||
klass = gst_element_factory_get_metadata (GST_ELEMENT_FACTORY (feature), GST_ELEMENT_METADATA_KLASS);
|
||||
if (g_strrstr (klass, "Demux") == NULL &&
|
||||
g_strrstr (klass, "Decoder") == NULL &&
|
||||
g_strrstr (klass, "Parse") == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* only select elements with autoplugging rank */
|
||||
rank = gst_plugin_feature_get_rank (feature);
|
||||
if (rank < GST_RANK_MARGINAL)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called to sort features by rank.
|
||||
*/
|
||||
|
||||
static gint
|
||||
cb_compare_ranks (GstPluginFeature *f1,
|
||||
GstPluginFeature *f2)
|
||||
{
|
||||
return gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1);
|
||||
}
|
||||
|
||||
static void
|
||||
init_factories (void)
|
||||
{
|
||||
/* first filter out the interesting element factories */
|
||||
factories = gst_registry_feature_filter (
|
||||
gst_registry_get (),
|
||||
(GstPluginFeatureFilter) cb_feature_filter, FALSE, NULL);
|
||||
|
||||
/* sort them according to their ranks */
|
||||
factories = g_list_sort (factories, (GCompareFunc) cb_compare_ranks);
|
||||
}
|
||||
<!-- example-end dynamic.c b --></programlisting>
|
||||
<para>
|
||||
From this list of element factories, we will select the one that most
|
||||
likely will help us decoding a media stream to a given output type.
|
||||
For each newly created element, we will again try to autoplug new
|
||||
elements to its source pad(s). Also, if the element has dynamic pads
|
||||
(which we've seen before in <xref linkend="section-pads-dynamic"/>),
|
||||
we will listen for newly created source pads and handle those, too.
|
||||
The following code replaces the <function>cb_type_found</function>
|
||||
from the previous section with a function to initiate autoplugging,
|
||||
which will continue with the above approach.
|
||||
</para>
|
||||
<programlisting><!-- example-begin dynamic.c c -->
|
||||
static void try_to_plug (GstPad *pad, GstCaps *caps);
|
||||
|
||||
static GstElement *audiosink;
|
||||
|
||||
static void
|
||||
cb_newpad (GstElement *element,
|
||||
GstPad *pad,
|
||||
gpointer data)
|
||||
{
|
||||
GstCaps *caps;
|
||||
|
||||
caps = gst_pad_query_caps (pad, NULL);
|
||||
try_to_plug (pad, caps);
|
||||
gst_caps_unref (caps);
|
||||
}
|
||||
|
||||
static void
|
||||
close_link (GstPad *srcpad,
|
||||
GstElement *sinkelement,
|
||||
const gchar *padname,
|
||||
const GList *templlist)
|
||||
{
|
||||
GstPad *pad;
|
||||
gboolean has_dynamic_pads = FALSE;
|
||||
|
||||
g_print ("Plugging pad %s:%s to newly created %s:%s\n",
|
||||
gst_object_get_name (GST_OBJECT (gst_pad_get_parent (srcpad))),
|
||||
gst_pad_get_name (srcpad),
|
||||
gst_object_get_name (GST_OBJECT (sinkelement)), padname);
|
||||
|
||||
/* add the element to the pipeline and set correct state */
|
||||
if (sinkelement != audiosink) {
|
||||
gst_bin_add (GST_BIN (pipeline), sinkelement);
|
||||
gst_element_set_state (sinkelement, GST_STATE_READY);
|
||||
}
|
||||
pad = gst_element_get_static_pad (sinkelement, padname);
|
||||
gst_pad_link (srcpad, pad);
|
||||
if (sinkelement != audiosink) {
|
||||
gst_element_set_state (sinkelement, GST_STATE_PAUSED);
|
||||
}
|
||||
gst_object_unref (GST_OBJECT (pad));
|
||||
|
||||
/* if we have static source pads, link those. If we have dynamic
|
||||
* source pads, listen for pad-added signals on the element */
|
||||
for ( ; templlist != NULL; templlist = templlist->next) {
|
||||
GstStaticPadTemplate *templ = templlist->data;
|
||||
|
||||
/* only sourcepads, no request pads */
|
||||
if (templ->direction != GST_PAD_SRC ||
|
||||
templ->presence == GST_PAD_REQUEST) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (templ->presence) {
|
||||
case GST_PAD_ALWAYS: {
|
||||
GstPad *pad = gst_element_get_static_pad (sinkelement, templ->name_template);
|
||||
GstCaps *caps = gst_pad_query_caps (pad, NULL);
|
||||
|
||||
/* link */
|
||||
try_to_plug (pad, caps);
|
||||
gst_object_unref (GST_OBJECT (pad));
|
||||
gst_caps_unref (caps);
|
||||
break;
|
||||
}
|
||||
case GST_PAD_SOMETIMES:
|
||||
has_dynamic_pads = TRUE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* listen for newly created pads if this element supports that */
|
||||
if (has_dynamic_pads) {
|
||||
g_signal_connect (sinkelement, "pad-added", G_CALLBACK (cb_newpad), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
try_to_plug (GstPad *pad,
|
||||
GstCaps *caps)
|
||||
{
|
||||
GstObject *parent = GST_OBJECT (GST_OBJECT_PARENT (pad));
|
||||
const gchar *media;
|
||||
const GList *item;
|
||||
GstCaps *res, *audiocaps;
|
||||
|
||||
/* don't plug if we're already plugged - FIXME: memleak for pad */
|
||||
if (GST_PAD_IS_LINKED (gst_element_get_static_pad (audiosink, "sink"))) {
|
||||
g_print ("Omitting link for pad %s:%s because we're already linked\n",
|
||||
GST_OBJECT_NAME (parent), GST_OBJECT_NAME (pad));
|
||||
return;
|
||||
}
|
||||
|
||||
/* as said above, we only try to plug audio... Omit video */
|
||||
media = gst_structure_get_name (gst_caps_get_structure (caps, 0));
|
||||
if (g_strrstr (media, "video")) {
|
||||
g_print ("Omitting link for pad %s:%s because media type %s is non-audio\n",
|
||||
GST_OBJECT_NAME (parent), GST_OBJECT_NAME (pad), media);
|
||||
return;
|
||||
}
|
||||
|
||||
/* can it link to the audiopad? */
|
||||
audiocaps = gst_pad_query_caps (gst_element_get_static_pad (audiosink, "sink"),
|
||||
NULL);
|
||||
res = gst_caps_intersect (caps, audiocaps);
|
||||
if (res && !gst_caps_is_empty (res)) {
|
||||
g_print ("Found pad to link to audiosink - plugging is now done\n");
|
||||
close_link (pad, audiosink, "sink", NULL);
|
||||
gst_caps_unref (audiocaps);
|
||||
gst_caps_unref (res);
|
||||
return;
|
||||
}
|
||||
gst_caps_unref (audiocaps);
|
||||
gst_caps_unref (res);
|
||||
|
||||
/* try to plug from our list */
|
||||
for (item = factories; item != NULL; item = item->next) {
|
||||
GstElementFactory *factory = GST_ELEMENT_FACTORY (item->data);
|
||||
const GList *pads;
|
||||
|
||||
for (pads = gst_element_factory_get_static_pad_templates (factory);
|
||||
pads != NULL; pads = pads->next) {
|
||||
GstStaticPadTemplate *templ = pads->data;
|
||||
|
||||
/* find the sink template - need an always pad*/
|
||||
if (templ->direction != GST_PAD_SINK ||
|
||||
templ->presence != GST_PAD_ALWAYS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* can it link? */
|
||||
res = gst_caps_intersect (caps,
|
||||
gst_static_caps_get (&templ->static_caps));
|
||||
if (res && !gst_caps_is_empty (res)) {
|
||||
GstElement *element;
|
||||
gchar *name_template = g_strdup (templ->name_template);
|
||||
|
||||
/* close link and return */
|
||||
gst_caps_unref (res);
|
||||
element = gst_element_factory_create (factory, NULL);
|
||||
close_link (pad, element, name_template,
|
||||
gst_element_factory_get_static_pad_templates (factory));
|
||||
g_free (name_template);
|
||||
return;
|
||||
}
|
||||
gst_caps_unref (res);
|
||||
|
||||
/* we only check one sink template per factory, so move on to the
|
||||
* next factory now */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we get here, no item was found */
|
||||
g_print ("No compatible pad found to decode %s on %s:%s\n",
|
||||
media, GST_OBJECT_NAME (parent), GST_OBJECT_NAME (pad));
|
||||
}
|
||||
|
||||
static void
|
||||
cb_typefound (GstElement *typefind,
|
||||
guint probability,
|
||||
GstCaps *caps,
|
||||
gpointer data)
|
||||
{
|
||||
gchar *s;
|
||||
GstPad *pad;
|
||||
|
||||
s = gst_caps_to_string (caps);
|
||||
g_print ("Detected media type %s\n", s);
|
||||
g_free (s);
|
||||
|
||||
/* actually plug now */
|
||||
pad = gst_element_get_static_pad (typefind, "src");
|
||||
try_to_plug (pad, caps);
|
||||
gst_object_unref (GST_OBJECT (pad));
|
||||
}
|
||||
<!-- example-end dynamic.c c --></programlisting>
|
||||
<para>
|
||||
By doing all this, we will be able to make a simple autoplugger that
|
||||
can automatically setup a pipeline for any media type. In the example
|
||||
above, we did this for audio only. However, we can also do this
|
||||
for video to create a player that plays both audio and video.
|
||||
</para>
|
||||
<!-- example-begin dynamic.c d --><!--
|
||||
static gboolean
|
||||
my_bus_callback (GstBus *bus,
|
||||
GstMessage *message,
|
||||
gpointer data)
|
||||
{
|
||||
GMainLoop *loop = data;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR: {
|
||||
GError *err;
|
||||
gchar *debug;
|
||||
|
||||
gst_message_parse_error (message, &err, &debug);
|
||||
g_print ("Error: %s\n", err->message);
|
||||
g_error_free (err);
|
||||
g_free (debug);
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS:
|
||||
/* end-of-stream */
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* remove from queue */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc,
|
||||
gchar *argv[])
|
||||
{
|
||||
GMainLoop *loop;
|
||||
GstElement *typefind, *realsink;
|
||||
GstBus *bus;
|
||||
GError *err = NULL;
|
||||
gchar *p;
|
||||
|
||||
/* init GStreamer and ourselves */
|
||||
gst_init (&argc, &argv);
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
init_factories ();
|
||||
|
||||
/* args */
|
||||
if (argc != 2) {
|
||||
g_print ("Usage: %s <filename>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* pipeline */
|
||||
p = g_strdup_printf ("filesrc location=\"%s\" ! typefind name=tf", argv[1]);
|
||||
pipeline = gst_parse_launch (p, &err);
|
||||
g_free (p);
|
||||
|
||||
if (err) {
|
||||
g_error ("Could not construct pipeline: %s", err->message);
|
||||
g_error_free (err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_add_watch (bus, my_bus_callback, NULL);
|
||||
gst_object_unref (bus);
|
||||
|
||||
typefind = gst_bin_get_by_name (GST_BIN (pipeline), "tf");
|
||||
g_signal_connect (typefind, "have-type", G_CALLBACK (cb_typefound), NULL);
|
||||
gst_object_unref (GST_OBJECT (typefind));
|
||||
audiosink = gst_element_factory_make ("audioconvert", "aconv");
|
||||
realsink = gst_element_factory_make ("alsasink", "audiosink");
|
||||
gst_bin_add_many (GST_BIN (pipeline), audiosink, realsink, NULL);
|
||||
gst_element_link (audiosink, realsink);
|
||||
gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
|
||||
/* run */
|
||||
g_main_loop_run (loop);
|
||||
|
||||
/* exit */
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (GST_OBJECT (pipeline));
|
||||
|
||||
return 0;
|
||||
}
|
||||
--><!-- example-end dynamic.c d -->
|
||||
<para>
|
||||
The example above is a good first try for an autoplugger. Next steps
|
||||
would be to listen for <quote>pad-removed</quote> signals, so we
|
||||
can dynamically change the plugged pipeline if the stream changes
|
||||
(this happens for DVB or Ogg radio). Also, you might want special-case
|
||||
code for input with known content (such as a DVD or an audio-CD),
|
||||
and much, much more. Moreover, you'll want many checks to prevent
|
||||
infinite loops during autoplugging, maybe you'll want to implement
|
||||
shortest-path-finding to make sure the most optimal pipeline is chosen,
|
||||
and so on. Basically, the features that you implement in an autoplugger
|
||||
depend on what you want to use it for. For full-blown implementations,
|
||||
see the <quote>playbin</quote> and <quote>decodebin</quote> elements in
|
||||
<xref linkend="chapter-components"/>.
|
||||
See <xref linkend="chapter-components"/> for using the high level
|
||||
object that you can use to dynamically construct pipelines.
|
||||
</para>
|
||||
</sect1>
|
||||
</chapter>
|
||||
|
|
1
tests/examples/manual/.gitignore
vendored
1
tests/examples/manual/.gitignore
vendored
|
@ -10,7 +10,6 @@ Makefile.in
|
|||
appsink
|
||||
appsrc
|
||||
blockprobe
|
||||
dynamic
|
||||
dynformat
|
||||
elementget
|
||||
elementmake
|
||||
|
|
|
@ -21,7 +21,6 @@ TESTS_ENVIRONMENT = \
|
|||
EXTRA_DIST = extract.pl
|
||||
|
||||
EXAMPLES = \
|
||||
dynamic \
|
||||
$(GNOME) \
|
||||
elementcreate \
|
||||
elementmake \
|
||||
|
@ -53,7 +52,7 @@ BUILT_SOURCES = \
|
|||
helloworld.c \
|
||||
init.c \
|
||||
query.c \
|
||||
typefind.c dynamic.c \
|
||||
typefind.c \
|
||||
blockprobe.c \
|
||||
probe.c \
|
||||
appsrc.c \
|
||||
|
@ -90,7 +89,7 @@ init.c: $(top_srcdir)/docs/manual/basics-init.xml
|
|||
query.c: $(top_srcdir)/docs/manual/advanced-position.xml
|
||||
$(PERL_PATH) $(srcdir)/extract.pl $@ $<
|
||||
|
||||
typefind.c dynamic.c: $(top_srcdir)/docs/manual/advanced-autoplugging.xml
|
||||
typefind.c: $(top_srcdir)/docs/manual/advanced-autoplugging.xml
|
||||
$(PERL_PATH) $(srcdir)/extract.pl $@ $<
|
||||
|
||||
blockprobe.c: $(top_srcdir)/docs/manual/advanced-dataaccess.xml
|
||||
|
|
Loading…
Reference in a new issue