gstreamer/gst/gstplugin.c
Thomas Vander Stichele 580fc5d003 silly me
Original commit message from CVS:
silly me
2002-04-12 18:55:16 +00:00

928 lines
23 KiB
C

/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
*
* gstplugin.c: Plugin subsystem for loading elements, types, and libs
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include "gst_private.h"
#include "gstplugin.h"
#include "gstversion.h"
#include "gstregistry.h"
#include "config.h"
static GModule *main_module;
GList *_gst_plugin_static = NULL;
/* global list of plugins and its sequence number */
GList *_gst_plugins = NULL;
gint _gst_plugins_seqno = 0;
/* list of paths to check for plugins */
GList *_gst_plugin_paths = NULL;
GList *_gst_libraries = NULL;
gint _gst_libraries_seqno = 0;
/* whether or not to spew library load issues */
gboolean _gst_plugin_spew = FALSE;
/* whether or not to warn if registry needs rebuild (gst-register sets
* this to false.) */
gboolean _gst_warn_old_registry = TRUE;
/* whether or not the main app will be writing to the registry */
gboolean _gst_init_registry_write = FALSE;
#ifndef GST_DISABLE_REGISTRY
static gboolean plugin_times_older_than (time_t regtime);
static time_t get_time (const char * path);
#endif
static void gst_plugin_register_statics (GModule *module);
static GstPlugin* gst_plugin_register_func (GstPluginDesc *desc, GstPlugin *plugin,
GModule *module);
void
_gst_plugin_initialize (void)
{
GList *gst_plugin_default_paths = NULL;
#ifndef GST_DISABLE_REGISTRY
GstRegistryRead *gst_reg;
gchar *gst_registry;
xmlDocPtr doc = NULL;
#endif
main_module = g_module_open (NULL, G_MODULE_BIND_LAZY);
gst_plugin_register_statics (main_module);
#ifdef PLUGINS_USE_BUILDDIR
/* location libgstelements.so */
gst_plugin_default_paths = g_list_prepend (gst_plugin_default_paths,
PLUGINS_BUILDDIR "/gst/elements");
gst_plugin_default_paths = g_list_prepend (gst_plugin_default_paths,
PLUGINS_BUILDDIR "/gst/types");
gst_plugin_default_paths = g_list_prepend (gst_plugin_default_paths,
PLUGINS_BUILDDIR "/gst/autoplug");
gst_plugin_default_paths = g_list_prepend (gst_plugin_default_paths,
PLUGINS_BUILDDIR "/gst/schedulers");
#else
/* add the main (installed) library path */
gst_plugin_default_paths = g_list_prepend (gst_plugin_default_paths, PLUGINS_DIR);
#endif /* PLUGINS_USE_BUILDDIR */
#ifndef GST_DISABLE_REGISTRY
/* FIXME:
* we want to check both the global and the local registry here
* at first, we check if there is a local one, and if there is only use
* that one.
* Later, we would like to read the global one first, then have each
* plugin also in the local one override the global one.
*/
gst_reg = gst_registry_read_get ();
if (gst_reg->local_reg)
gst_registry = gst_reg->local_reg;
else
gst_registry = gst_reg->global_reg;
if (_gst_init_registry_write)
{
/* delete it before writing */
GST_INFO (GST_CAT_PLUGIN_LOADING, " Removing registry %s if it exists", gst_registry);
unlink (gst_registry);
}
if (g_file_test (gst_registry, G_FILE_TEST_EXISTS))
{
doc = xmlParseFile (gst_registry);
GST_INFO (GST_CAT_PLUGIN_LOADING, " Reading registry from %s", gst_registry);
}
else
{
GST_INFO (GST_CAT_PLUGIN_LOADING, " Not reading registry");
doc = NULL;
}
if (!doc ||
!doc->xmlRootNode ||
doc->xmlRootNode->name == 0 ||
strcmp (doc->xmlRootNode->name, "GST-PluginRegistry") ||
!plugin_times_older_than(get_time(gst_registry)))
{
if (_gst_warn_old_registry &&
!plugin_times_older_than(get_time(gst_registry)))
g_warning ("gstplugin: registry needs rebuild: run gst-register\n");
_gst_plugin_paths = g_list_concat (_gst_plugin_paths, gst_plugin_default_paths);
#ifdef PLUGINS_USE_BUILDDIR
/* we prepend the lib dir first for performance reasons */
_gst_plugin_paths = g_list_prepend (_gst_plugin_paths, PLUGINS_BUILDDIR "/libs/gst");
#endif
gst_plugin_load_all ();
/* gst_plugin_unload_all (); */
return;
}
/* this will pull in the plugin paths for us */
gst_plugin_load_thyself (doc->xmlRootNode);
xmlFreeDoc (doc);
#else
_gst_plugin_paths = g_list_concat (_gst_plugin_paths, gst_plugin_default_paths);
#endif /* GST_DISABLE_REGISTRY */
}
void
_gst_plugin_register_static (GstPluginDesc *desc)
{
_gst_plugin_static = g_list_prepend (_gst_plugin_static, desc);
}
static void
gst_plugin_register_statics (GModule *module)
{
GList *walk = _gst_plugin_static;
while (walk) {
GstPluginDesc *desc = (GstPluginDesc *) walk->data;
GstPlugin *plugin;
plugin = g_new0 (GstPlugin, 1);
plugin->filename = NULL;
plugin->module = NULL;
plugin = gst_plugin_register_func (desc, plugin, module);
if (plugin) {
_gst_plugins = g_list_prepend (_gst_plugins, plugin);
_gst_plugins_seqno++;
plugin->module = module;
}
walk = g_list_next (walk);
}
}
/**
* gst_plugin_add_path:
* @path: the directory to add to the search path
*
* Add a directory to the path searched for plugins.
*/
void
gst_plugin_add_path (const gchar *path)
{
_gst_plugin_paths = g_list_prepend (_gst_plugin_paths,g_strdup(path));
}
#ifndef GST_DISABLE_REGISTRY
static time_t
get_time(const char * path)
{
struct stat statbuf;
if (stat(path, &statbuf)) return 0;
if (statbuf.st_mtime > statbuf.st_ctime) return statbuf.st_mtime;
return statbuf.st_ctime;
}
static gboolean
plugin_times_older_than_recurse(gchar *path, time_t regtime)
{
DIR *dir;
struct dirent *dirent;
gchar *pluginname;
time_t pathtime = get_time(path);
if (pathtime > regtime) {
GST_INFO (GST_CAT_PLUGIN_LOADING,
"time for %s was %ld; more recent than registry time of %ld\n",
path, (long)pathtime, (long)regtime);
return FALSE;
}
dir = opendir(path);
if (dir) {
while ((dirent = readdir(dir))) {
/* don't want to recurse in place or backwards */
if (strcmp(dirent->d_name,".") && strcmp(dirent->d_name,"..")) {
pluginname = g_strjoin("/",path,dirent->d_name,NULL);
if (!plugin_times_older_than_recurse(pluginname , regtime)) {
g_free (pluginname);
closedir(dir);
return FALSE;
}
g_free (pluginname);
}
}
closedir(dir);
}
return TRUE;
}
static gboolean
plugin_times_older_than(time_t regtime)
{
/* return true iff regtime is more recent than the times of all the files
* in the plugin dirs.
*/
GList *path;
path = _gst_plugin_paths;
while (path != NULL) {
GST_DEBUG (GST_CAT_PLUGIN_LOADING,
"comparing plugin times from %s with %ld\n",
(gchar *)path->data, (long) regtime);
if(!plugin_times_older_than_recurse(path->data, regtime))
return FALSE;
path = g_list_next(path);
}
return TRUE;
}
#endif
static gboolean
gst_plugin_load_recurse (gchar *directory, gchar *name)
{
DIR *dir;
struct dirent *dirent;
gboolean loaded = FALSE;
gchar *dirname;
/* g_print("recursive load of '%s' in '%s'\n", name, directory); */
dir = opendir(directory);
if (dir) {
while ((dirent = readdir(dir))) {
/* don't want to recurse in place or backwards */
if (strcmp(dirent->d_name,".") && strcmp(dirent->d_name,"..")) {
dirname = g_strjoin("/",directory,dirent->d_name,NULL);
loaded = gst_plugin_load_recurse(dirname,name);
g_free(dirname);
if (loaded && name) {
closedir(dir);
return TRUE;
}
}
}
closedir(dir);
} else {
if (strstr(directory,".so")) {
gchar *temp;
if (name) {
if ((temp = strstr(directory,name)) &&
(!strcmp(temp,name))) {
loaded = gst_plugin_load_absolute(directory);
}
} else if ((temp = strstr(directory,".so")) &&
(!strcmp(temp,".so"))) {
loaded = gst_plugin_load_absolute(directory);
}
}
}
return loaded;
}
/**
* gst_plugin_load_all:
*
* Load all plugins in the path (in the global GList* _gst_plugin_paths).
*/
void
gst_plugin_load_all (void)
{
GList *path;
path = _gst_plugin_paths;
if (path == NULL) { g_warning ("gst_plugin_load_all: path is NULL !"); }
while (path != NULL) {
GST_DEBUG (GST_CAT_PLUGIN_LOADING,"loading plugins from %s",(gchar *)path->data);
gst_plugin_load_recurse(path->data,NULL);
path = g_list_next(path);
}
GST_INFO (GST_CAT_PLUGIN_LOADING,"loaded %d plugins", _gst_plugins_seqno);
}
/**
* gst_plugin_unload_all:
*
* Unload all plugins in memory.
*/
void
gst_plugin_unload_all (void)
{
GList *walk = _gst_plugins;
while (walk) {
GstPlugin *plugin = (GstPlugin *) walk->data;
GST_INFO (GST_CAT_PLUGIN_LOADING, "unloading plugin %s", plugin->name);
if (plugin->module) {
GList *features = gst_plugin_get_feature_list (plugin);
while (features) {
GstPluginFeature *feature = GST_PLUGIN_FEATURE (features->data);
GST_INFO (GST_CAT_PLUGIN_LOADING, "unloading feature %s", GST_OBJECT_NAME (feature));
gst_plugin_feature_unload_thyself (feature);
features = g_list_next (features);
}
if (g_module_close (plugin->module)) {
plugin->module = NULL;
}
else {
g_warning ("error closing module");
}
}
walk = g_list_next (walk);
}
}
/**
* gst_library_load:
* @name: name of library to load
*
* Load the named library. Name should be given as
* &quot;liblibrary.so&quot;.
*
* Returns: whether the library was loaded or not
*/
gboolean
gst_library_load (const gchar *name)
{
gboolean res;
GList *libraries = _gst_libraries;
while (libraries) {
if (!strcmp((gchar *)libraries->data, name)) return TRUE;
libraries = g_list_next(libraries);
}
/* for now this is the same */
res = gst_plugin_load(name);
if (res) {
_gst_libraries = g_list_prepend(_gst_libraries, g_strdup (name));
}
return res;
}
/**
* gst_plugin_load:
* @name: name of plugin to load
*
* Load the named plugin. Name should be given as
* &quot;libplugin.so&quot;.
*
* Returns: whether the plugin was loaded or not
*/
gboolean
gst_plugin_load (const gchar *name)
{
GList *path;
gchar *libspath;
gchar *pluginname;
GstPlugin *plugin;
g_return_val_if_fail (name != NULL, FALSE);
plugin = gst_plugin_find (name);
if (plugin && plugin->module)
return TRUE;
path = _gst_plugin_paths;
while (path != NULL) {
pluginname = g_module_build_path(path->data,name);
if (gst_plugin_load_absolute(pluginname)) {
g_free(pluginname);
return TRUE;
}
g_free(pluginname);
libspath = g_strconcat(path->data,"/.libs",NULL);
/* g_print("trying to load '%s'\n",g_module_build_path(libspath,name)); */
pluginname = g_module_build_path(libspath,name);
g_free(libspath);
if (gst_plugin_load_absolute(pluginname)) {
g_free(pluginname);
return TRUE;
}
g_free(pluginname);
/* g_print("trying to load '%s' from '%s'\n",name,path->data); */
pluginname = g_module_build_path("",name);
if (gst_plugin_load_recurse(path->data,pluginname)) {
g_free(pluginname);
return TRUE;
}
g_free(pluginname);
path = g_list_next(path);
}
return FALSE;
}
/**
* gst_plugin_load_absolute:
* @name: name of plugin to load
*
* Load the named plugin. Name should be given as
* &quot;/path/to/plugin/libplugin.so&quot;.
*
* Returns: whether the plugin was loaded or not
*/
gboolean
gst_plugin_load_absolute (const gchar *filename)
{
GstPlugin *plugin = NULL;
GList *plugins = _gst_plugins;
g_return_val_if_fail (filename != NULL, FALSE);
GST_DEBUG (GST_CAT_PLUGIN_LOADING, "plugin \"%s\" absolute loading", filename);
while (plugins) {
GstPlugin *testplugin = (GstPlugin *)plugins->data;
if (testplugin->filename) {
if (!strcmp (testplugin->filename, filename)) {
plugin = testplugin;
break;
}
}
plugins = g_list_next (plugins);
}
if (!plugin) {
plugin = g_new0 (GstPlugin, 1);
plugin->filename = g_strdup (filename);
_gst_plugins = g_list_prepend (_gst_plugins, plugin);
_gst_plugins_seqno++;
}
return gst_plugin_load_plugin (plugin);
}
static gboolean
gst_plugin_check_version (gint major, gint minor)
{
/* return NULL if the major and minor version numbers are not compatible */
/* with ours. */
if (major != GST_VERSION_MAJOR || minor != GST_VERSION_MINOR)
return FALSE;
return TRUE;
}
static GstPlugin*
gst_plugin_register_func (GstPluginDesc *desc, GstPlugin *plugin, GModule *module)
{
if (!gst_plugin_check_version (desc->major_version, desc->minor_version)) {
GST_INFO (GST_CAT_PLUGIN_LOADING,"plugin \"%s\" has incompatible version, not loading",
plugin->filename);
g_free(plugin);
return NULL;
}
plugin->name = g_strdup(desc->name);
if (!((desc->plugin_init) (module, plugin))) {
GST_INFO (GST_CAT_PLUGIN_LOADING,"plugin \"%s\" failed to initialise",
plugin->filename);
g_free(plugin);
return NULL;
}
return plugin;
}
/**
* gst_plugin_load_plugin:
* @plugin: The plugin to load
*
* Load the given plugin.
*
* Returns: whether or not the plugin loaded
*/
gboolean
gst_plugin_load_plugin (GstPlugin *plugin)
{
GModule *module;
GstPluginDesc *desc;
struct stat file_status;
gchar *filename;
g_return_val_if_fail (plugin != NULL, FALSE);
if (plugin->module)
return TRUE;
filename = plugin->filename;
GST_DEBUG (GST_CAT_PLUGIN_LOADING, "plugin \"%s\" loading", filename);
if (g_module_supported () == FALSE) {
g_warning ("gstplugin: wow, you built this on a platform without dynamic loading???\n");
return FALSE;
}
if (stat (filename, &file_status)) {
/* g_print("problem opening file %s\n",filename); */
return FALSE;
}
module = g_module_open (filename, G_MODULE_BIND_LAZY);
if (module != NULL) {
if (g_module_symbol (module, "plugin_desc", (gpointer *)&desc)) {
GST_DEBUG (GST_CAT_PLUGIN_LOADING,"loading plugin \"%s\"...", filename);
plugin->filename = g_strdup (filename);
plugin = gst_plugin_register_func (desc, plugin, module);
if (plugin != NULL) {
GST_INFO (GST_CAT_PLUGIN_LOADING,"plugin \"%s\" loaded",
plugin->filename);
plugin->module = module;
return TRUE;
}
}
return TRUE;
} else if (_gst_plugin_spew) {
/* FIXME this should be some standard gst mechanism!!! */
g_printerr ("error loading plugin %s, reason: %s\n", filename, g_module_error());
}
else {
GST_INFO (GST_CAT_PLUGIN_LOADING, "error loading plugin %s, reason: %s\n", filename, g_module_error());
}
return FALSE;
}
/**
* gst_plugin_get_name:
* @plugin: plugin to get the name of
*
* Get the short name of the plugin
*
* Returns: the name of the plugin
*/
const gchar*
gst_plugin_get_name (GstPlugin *plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->name;
}
/**
* gst_plugin_set_name:
* @plugin: plugin to set name of
* @name: new name
*
* Sets the name (should be short) of the plugin.
*/
void
gst_plugin_set_name (GstPlugin *plugin, const gchar *name)
{
g_return_if_fail (plugin != NULL);
if (plugin->name)
g_free (plugin->name);
plugin->name = g_strdup (name);
}
/**
* gst_plugin_set_longname:
* @plugin: plugin to set long name of
* @longname: new long name
*
* Sets the long name (should be descriptive) of the plugin.
*/
void
gst_plugin_set_longname (GstPlugin *plugin, const gchar *longname)
{
g_return_if_fail(plugin != NULL);
if (plugin->longname)
g_free(plugin->longname);
plugin->longname = g_strdup(longname);
}
/**
* gst_plugin_get_longname:
* @plugin: plugin to get long name of
*
* Get the long descriptive name of the plugin
*
* Returns: the long name of the plugin
*/
const gchar*
gst_plugin_get_longname (GstPlugin *plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->longname;
}
/**
* gst_plugin_get_filename:
* @plugin: plugin to get the filename of
*
* get the filename of the plugin
*
* Returns: the filename of the plugin
*/
const gchar*
gst_plugin_get_filename (GstPlugin *plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->filename;
}
/**
* gst_plugin_is_loaded:
* @plugin: plugin to query
*
* queries if the plugin is loaded into memory
*
* Returns: TRUE is loaded, FALSE otherwise
*/
gboolean
gst_plugin_is_loaded (GstPlugin *plugin)
{
g_return_val_if_fail (plugin != NULL, FALSE);
return (plugin->module != NULL);
}
/**
* gst_plugin_find:
* @name: name of plugin to find
*
* Search the list of registered plugins for one of the given name
*
* Returns: pointer to the #GstPlugin if found, NULL otherwise
*/
GstPlugin*
gst_plugin_find (const gchar *name)
{
GList *plugins = _gst_plugins;
g_return_val_if_fail (name != NULL, NULL);
while (plugins) {
GstPlugin *plugin = (GstPlugin *)plugins->data;
if (plugin->name) {
if (!strcmp (plugin->name, name)) {
return plugin;
}
}
plugins = g_list_next (plugins);
}
return NULL;
}
static GstPluginFeature*
gst_plugin_find_feature_func (GstPlugin *plugin, const gchar *name, GType type)
{
GList *features = plugin->features;
g_return_val_if_fail (name != NULL, NULL);
while (features) {
GstPluginFeature *feature = GST_PLUGIN_FEATURE (features->data);
if (!strcmp(GST_OBJECT_NAME (feature), name) && G_OBJECT_TYPE (feature) == type)
return GST_PLUGIN_FEATURE (feature);
features = g_list_next (features);
}
return NULL;
}
G_GNUC_UNUSED static GstPluginFeature*
gst_plugin_find_feature (const gchar *name, GType type)
{
GList *plugins;
plugins = _gst_plugins;
while (plugins) {
GstPlugin *plugin = (GstPlugin *)plugins->data;
GstPluginFeature *feature;
feature = gst_plugin_find_feature_func (plugin, name, type);
if (feature)
return feature;
plugins = g_list_next(plugins);
}
return NULL;
}
/**
* gst_plugin_add_feature:
* @plugin: plugin to add feature to
* @feature: feature to add
*
* Add feature to the list of those provided by the plugin.
* There is a separate namespace for each plugin feature type.
* See #gst_plugin_get_feature_list
*/
void
gst_plugin_add_feature (GstPlugin *plugin, GstPluginFeature *feature)
{
GstPluginFeature *oldfeature;
g_return_if_fail (plugin != NULL);
g_return_if_fail (GST_IS_PLUGIN_FEATURE (feature));
g_return_if_fail (feature != NULL);
oldfeature = gst_plugin_find_feature_func (plugin, GST_OBJECT_NAME (feature), G_OBJECT_TYPE (feature));
if (!oldfeature) {
feature->manager = plugin;
plugin->features = g_list_prepend (plugin->features, feature);
plugin->numfeatures++;
}
}
/**
* gst_plugin_get_list:
*
* get the currently loaded plugins
*
* Returns; a GList of GstPlugin elements
*/
GList*
gst_plugin_get_list (void)
{
return g_list_copy (_gst_plugins);
}
#ifndef GST_DISABLE_REGISTRY
/**
* gst_plugin_save_thyself:
* @parent: the parent node to save the plugin to
*
* saves the plugin into an XML representation
*
* Returns: the new XML node
*/
xmlNodePtr
gst_plugin_save_thyself (xmlNodePtr parent)
{
xmlNodePtr tree, subtree;
GList *l = NULL, *plugins = NULL;
plugins = _gst_plugins;
while (plugins) {
GstPlugin *plugin = (GstPlugin *)plugins->data;
GList *features;
plugins = g_list_next (plugins);
if (!plugin->name)
continue;
tree = xmlNewChild (parent, NULL, "plugin", NULL);
xmlNewChild (tree, NULL, "name", plugin->name);
xmlNewChild (tree, NULL, "longname", plugin->longname);
xmlNewChild (tree, NULL, "filename", plugin->filename);
features = plugin->features;
while (features) {
GstPluginFeature *feature = GST_PLUGIN_FEATURE (features->data);
subtree = xmlNewChild(tree, NULL, "feature", NULL);
xmlNewProp (subtree, "typename", g_type_name (G_OBJECT_TYPE (feature)));
gst_object_save_thyself (GST_OBJECT (feature), subtree);
features = g_list_next (features);
}
}
/* save the plugin search path in reverse order (because they are cons'd back on) */
l=g_list_last(_gst_plugin_paths);
while(l) {
xmlNewChild (parent, NULL, "plugin-path", l->data);
l = l->prev;
}
return parent;
}
/**
* gst_plugin_load_thyself:
* @parent: the parent node to load the plugin from
*
* load the plugin from an XML representation
*/
void
gst_plugin_load_thyself (xmlNodePtr parent)
{
xmlNodePtr kinderen;
gint featurecount = 0;
gchar *pluginname;
kinderen = parent->xmlChildrenNode; /* Dutch invasion :-) */
while (kinderen) {
if (!strcmp (kinderen->name, "plugin")) {
xmlNodePtr field = kinderen->xmlChildrenNode;
GstPlugin *plugin = g_new0 (GstPlugin, 1);
plugin->numfeatures = 0;
plugin->features = NULL;
plugin->module = NULL;
while (field) {
if (!strcmp (field->name, "name")) {
pluginname = xmlNodeGetContent (field);
if (gst_plugin_find (pluginname)) {
g_free (pluginname);
g_free (plugin);
plugin = NULL;
break;
} else {
plugin->name = pluginname;
}
}
else if (!strcmp (field->name, "longname")) {
plugin->longname = xmlNodeGetContent (field);
}
else if (!strcmp (field->name, "filename")) {
plugin->filename = xmlNodeGetContent (field);
}
else if (!strcmp (field->name, "feature")) {
GstPluginFeature *feature;
gchar *prop;
prop = xmlGetProp (field, "typename");
feature = GST_PLUGIN_FEATURE (g_object_new (g_type_from_name (prop), NULL));
if (feature) {
gst_object_restore_thyself (GST_OBJECT (feature), field);
gst_plugin_add_feature (plugin, feature);
featurecount++;
}
}
field = field->next;
}
if (plugin) {
_gst_plugins = g_list_prepend (_gst_plugins, plugin);
_gst_plugins_seqno++;
}
} else if (!strcmp (kinderen->name, "plugin-path")) {
_gst_plugin_paths = g_list_prepend (_gst_plugin_paths,
xmlNodeGetContent (kinderen));
}
kinderen = kinderen->next;
}
GST_INFO (GST_CAT_PLUGIN_LOADING, " added %d features ", featurecount);
}
#endif /* GST_DISABLE_REGISTRY */
/**
* gst_plugin_get_feature_list:
* @plugin: the plugin to get the features from
*
* get a list of all the features that this plugin provides
*
* Returns: a GList of features
*/
GList*
gst_plugin_get_feature_list (GstPlugin *plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->features;
}