Some modifications to make the demuxers work, plus moving typefinding over from separ...
authorRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sun, 8 Jun 2003 13:31:53 +0000 (13:31 +0000)
committerRonald S. Bultje <rbultje@ronald.bitfreak.net>
Sun, 8 Jun 2003 13:31:53 +0000 (13:31 +0000)
Original commit message from CVS:
Some modifications to make the demuxers work, plus moving typefinding over from separate plugin to the demuxers themselves

ext/ffmpeg/gstffmpegdemux.c

index 986285b..d3fd7c8 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 
-#include <string.h>
+#ifdef HAVE_CONFIG_H
 #include "config.h"
+#endif
+
+#include <string.h>
 #ifdef HAVE_FFMPEG_UNINSTALLED
 #include <avformat.h>
 #include <avi.h>
 
 #include <gst/gst.h>
 
-extern GstCaps*        gst_ffmpegcodec_codec_context_to_caps (AVCodecContext *context, int codec_id);
-
-typedef enum {
-  STATE_OPEN,
-  STATE_DEMUX,
-} DemuxState;
+#include "gstffmpegcodecmap.h"
 
 typedef struct _GstFFMpegDemux GstFFMpegDemux;
 
@@ -45,17 +43,30 @@ struct _GstFFMpegDemux {
   GstPad               *sinkpad;
 
   AVFormatContext      *context;
-  DemuxState            state;
+  gboolean             opened;
 
   GstPad               *srcpads[MAX_STREAMS];
+  gint                 videopads, audiopads;
 };
 
+typedef struct _GstFFMpegDemuxClassParams {
+  AVInputFormat        *in_plugin;
+  GstPadTemplate       *sinktempl;
+  GstPadTemplate       *videosrctempl;
+  GstPadTemplate       *audiosrctempl;
+  GstPluginFeature     *typefind_feature;
+} GstFFMpegDemuxClassParams;
+
 typedef struct _GstFFMpegDemuxClass GstFFMpegDemuxClass;
 
 struct _GstFFMpegDemuxClass {
   GstElementClass       parent_class;
 
   AVInputFormat        *in_plugin;
+  GstPadTemplate       *sinktempl;
+  GstPadTemplate       *videosrctempl;
+  GstPadTemplate       *audiosrctempl;
+  GstPluginFeature     *typefind_feature;
 };
 
 #define GST_TYPE_FFMPEGDEC \
@@ -79,42 +90,17 @@ enum {
   /* FILL ME */
 };
 
-/* This factory is much simpler, and defines the source pad. */
-GST_PAD_TEMPLATE_FACTORY (gst_ffmpegdemux_sink_factory,
-  "sink",
-  GST_PAD_SINK,
-  GST_PAD_ALWAYS,
-  NULL
-)
-
-/* This factory is much simpler, and defines the source pad. */
-GST_PAD_TEMPLATE_FACTORY (gst_ffmpegdemux_audio_src_factory,
-  "audio_%02d",
-  GST_PAD_SRC,
-  GST_PAD_SOMETIMES,
-  NULL
-)
-
-/* This factory is much simpler, and defines the source pad. */
-GST_PAD_TEMPLATE_FACTORY (gst_ffmpegdemux_video_src_factory,
-  "video_%02d",
-  GST_PAD_SRC,
-  GST_PAD_SOMETIMES,
-  NULL
-)
-
-static GHashTable *global_plugins;
+static GHashTable *global_plugins, *typefind;
 
 /* A number of functon prototypes are given so we can refer to them later. */
 static void    gst_ffmpegdemux_class_init      (GstFFMpegDemuxClass *klass);
 static void    gst_ffmpegdemux_init            (GstFFMpegDemux *ffmpegdemux);
+static void    gst_ffmpegdemux_dispose         (GObject *object);
 
 static void    gst_ffmpegdemux_loop            (GstElement *element);
 
-static void    gst_ffmpegdemux_set_property    (GObject *object, guint prop_id, const GValue *value, 
-                                                GParamSpec *pspec);
-static void    gst_ffmpegdemux_get_property    (GObject *object, guint prop_id, GValue *value, 
-                                                GParamSpec *pspec);
+static GstElementStateReturn
+               gst_ffmpegdemux_change_state    (GstElement *element);
 
 static GstElementClass *parent_class = NULL;
 
@@ -125,179 +111,251 @@ gst_ffmpegdemux_class_init (GstFFMpegDemuxClass *klass)
 {
   GObjectClass *gobject_class;
   GstElementClass *gstelement_class;
+  GstFFMpegDemuxClassParams *params;
 
   gobject_class = (GObjectClass*)klass;
   gstelement_class = (GstElementClass*)klass;
 
   parent_class = g_type_class_ref(GST_TYPE_ELEMENT);
 
-  klass->in_plugin = g_hash_table_lookup (global_plugins,
-                 GINT_TO_POINTER (G_OBJECT_CLASS_TYPE (gobject_class)));
+  params = g_hash_table_lookup (global_plugins,
+               GINT_TO_POINTER (G_OBJECT_CLASS_TYPE (gobject_class)));
 
-  gobject_class->set_property = gst_ffmpegdemux_set_property;
-  gobject_class->get_property = gst_ffmpegdemux_get_property;
+  klass->in_plugin = params->in_plugin;
+  klass->typefind_feature = params->typefind_feature;
+  klass->videosrctempl = params->videosrctempl;
+  klass->audiosrctempl = params->audiosrctempl;
+  klass->sinktempl = params->sinktempl;
+
+  gstelement_class->change_state = gst_ffmpegdemux_change_state;
+  gobject_class->dispose = gst_ffmpegdemux_dispose;
 }
 
 static void
 gst_ffmpegdemux_init(GstFFMpegDemux *ffmpegdemux)
 {
-  //GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
+  GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
 
-  ffmpegdemux->sinkpad = gst_pad_new_from_template (
-                 GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_sink_factory), "sink");
+  ffmpegdemux->sinkpad = gst_pad_new_from_template (oclass->sinktempl,
+                                                   "sink");
+  gst_element_add_pad (GST_ELEMENT (ffmpegdemux),
+                      ffmpegdemux->sinkpad);
+  gst_element_set_loop_function (GST_ELEMENT (ffmpegdemux),
+                                gst_ffmpegdemux_loop);
 
-  gst_element_add_pad (GST_ELEMENT (ffmpegdemux), ffmpegdemux->sinkpad);
-  gst_element_set_loop_function (GST_ELEMENT (ffmpegdemux), gst_ffmpegdemux_loop);
+  ffmpegdemux->opened = FALSE;
 
-  ffmpegdemux->state = STATE_OPEN;
+  ffmpegdemux->videopads = 0;
+  ffmpegdemux->audiopads = 0;
 }
 
 static void
-gst_ffmpegdemux_loop (GstElement *element)
+gst_ffmpegdemux_dispose (GObject *object)
 {
-  GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *)(element);
-  GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
+  GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *) object;
+
+  if (ffmpegdemux->opened) {
+    av_close_input_file (ffmpegdemux->context);
+    ffmpegdemux->opened = FALSE;
+  }
+}
+
+static GstCaps*
+gst_ffmpegdemux_typefind (GstBuffer *buffer,
+                         gpointer   priv)
+{
+  GstFFMpegDemuxClassParams *params;
+  AVInputFormat *in_plugin;
   gint res = 0;
+  gint required = AVPROBE_SCORE_MAX * 0.8; /* 80% certainty enough? */
+  
+  params = g_hash_table_lookup (typefind, priv);
 
-  switch (ffmpegdemux->state) {
-    case STATE_OPEN:
-    {
-       res = av_open_input_file (&ffmpegdemux->context, 
-                           g_strdup_printf ("gstreamer://%p", ffmpegdemux->sinkpad),
-                           oclass->in_plugin,
-                           0,
-                           NULL);
+  in_plugin = params->in_plugin;
 
-      ffmpegdemux->state = STATE_DEMUX;
-      break;
-    }
-    case STATE_DEMUX:
-    {
-      gint res;
-      AVPacket pkt;
-      AVFormatContext *ct = ffmpegdemux->context;
-      AVStream *st;
-      GstPad *pad;
-      
-      res = av_read_packet(ct, &pkt);
-      if (res < 0) {
-       if (url_feof (&ct->pb)) {
-         gint i;
-
-         for (i = 0; i < ct->nb_streams; i++) {
-            GstPad *pad;
-
-           pad = ffmpegdemux->srcpads[i];
-
-           if (GST_PAD_IS_USABLE (pad)) {
-             gst_pad_push (pad, GST_BUFFER (gst_event_new (GST_EVENT_EOS)));
-           }
-         }
-         gst_element_set_eos (element);
-       }
-       return;
-      }
+  if (in_plugin->read_probe) {
+    AVProbeData probe_data;
 
-      st = ct->streams[pkt.stream_index];
+    probe_data.filename = "";
+    probe_data.buf = GST_BUFFER_DATA (buffer);
+    probe_data.buf_size = GST_BUFFER_SIZE (buffer);
 
-      if (st->codec_info_state == 0) {
-       gchar *templname = NULL;
+    res = in_plugin->read_probe (&probe_data);
+    if (res >= required) {
+      GstCaps *caps;
+      caps = GST_PAD_TEMPLATE_CAPS (params->sinktempl);
+      /* make sure we still hold a refcount to this caps */
+      gst_caps_ref (caps);
+      return caps;
+    }
+  }
        
-        st->codec_info_state = 1;
-
-       if (st->codec.codec_type == CODEC_TYPE_VIDEO) {
-         templname = "video_%02d";
-       }
-       else if (st->codec.codec_type == CODEC_TYPE_AUDIO) {
-         templname = "audio_%02d";
-       }
-
-       if (templname != NULL) {
-         gchar *padname;
-          GstCaps *caps;
-         GstPadTemplate *templ;
-
-         caps = gst_ffmpegcodec_codec_context_to_caps (&st->codec, st->codec.codec_id);
-         templ = gst_pad_template_new (templname,
-                                       GST_PAD_SRC,
-                                       GST_PAD_SOMETIMES,
-                                       caps, NULL);
-
-         padname = g_strdup_printf (templname, pkt.stream_index);
-          pad = gst_pad_new_from_template (templ, padname);
-
-         ffmpegdemux->srcpads[pkt.stream_index] = pad;
-          gst_element_add_pad (GST_ELEMENT (ffmpegdemux), pad);
-       }
-       else {
-          g_warning ("unknown pad type %d", st->codec.codec_type);
-         return;
-       }
-      }
-      else {
-       pad = ffmpegdemux->srcpads[pkt.stream_index];
-      }
+  return NULL;
+}
 
-      if (GST_PAD_IS_USABLE (pad)) {
-        GstBuffer *outbuf;
+static void
+gst_ffmpegdemux_loop (GstElement *element)
+{
+  GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *)(element);
+  GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
 
-        outbuf = gst_buffer_new ();
-        GST_BUFFER_DATA (outbuf) = pkt.data;
-        GST_BUFFER_SIZE (outbuf) = pkt.size;
+  gint res;
+  AVPacket pkt;
+  AVFormatContext *ct;
+  AVStream *st;
+  GstPad *pad;
+
+  /* open file if we didn't so already */
+  if (!ffmpegdemux->opened) {
+    res = av_open_input_file (&ffmpegdemux->context, 
+                             g_strdup_printf ("gstreamer://i/%p",
+                                              ffmpegdemux->sinkpad),
+                             oclass->in_plugin, 0, NULL);
+    if (res < 0) {
+      gst_element_error (GST_ELEMENT (ffmpegdemux),
+                        "Failed to open demuxer/file context");
+      return;
+    }
 
-       if (pkt.pts != AV_NOPTS_VALUE && ct->pts_den) {
-          GST_BUFFER_TIMESTAMP (outbuf) = pkt.pts * GST_SECOND * ct->pts_num / ct->pts_den;
-       }
-       else {
-          GST_BUFFER_TIMESTAMP (outbuf) = -1;
-       }
+    ffmpegdemux->opened = TRUE;
+  }
 
-        gst_pad_push (pad, outbuf);
+  /* shortcut to context */
+  ct = ffmpegdemux->context;
+
+  /* read a package */
+  res = av_read_packet (ct, &pkt);
+  if (res < 0) {
+    if (url_feof (&ct->pb)) {
+      int i;
+
+      /* we're at the end of file - send an EOS to
+       * each stream that we opened so far */
+      for (i = 0; i < ct->nb_streams; i++) {
+        GstPad *pad;
+        GstEvent *event = gst_event_new (GST_EVENT_EOS);
+
+        pad = ffmpegdemux->srcpads[i];
+        if (GST_PAD_IS_USABLE (pad)) {
+          gst_data_ref (GST_DATA (event));
+          gst_pad_push (pad, GST_BUFFER (event));
+        }
+        gst_data_unref (GST_DATA (event));
       }
-      break;
-    }
-    default:
       gst_element_set_eos (element);
-      break;
+
+      /* FIXME: should we go into
+       * should we close the context here?
+       * either way, a new media stream needs an
+       * event too */
+    }
+    return;
   }
-}
 
-static void
-gst_ffmpegdemux_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-  GstFFMpegDemux *ffmpegdemux;
+  /* shortcut to stream */
+  st = ct->streams[pkt.stream_index];
+
+  /* create the pad/stream if we didn't do so already */
+  if (st->codec_info_state == 0) {
+    GstPadTemplate *templ = NULL;
+    GstCaps *caps;
+    gchar *padname;
+    gint num;
+
+    /* mark as handled */      
+    st->codec_info_state = 1;
+
+    /* find template */
+    switch (st->codec.codec_type) {
+      case CODEC_TYPE_VIDEO:
+        templ = oclass->videosrctempl;
+        num = ffmpegdemux->videopads++;
+        break;
+      case CODEC_TYPE_AUDIO:
+        templ = oclass->audiosrctempl;
+        num = ffmpegdemux->audiopads++;
+        break;
+      default:
+        g_warning ("Unknown pad type %d",
+                  st->codec.codec_type);
+        return;
+    }
+
+    /* create new pad for this stream */
+    padname = g_strdup_printf (GST_PAD_TEMPLATE_NAME_TEMPLATE(templ),
+                              num);
+    pad = gst_pad_new_from_template (templ, padname);
+    g_free (padname);
+
+    /* FIXME: convert() and query() functions for pad */
+
+    /* store pad internally */
+    ffmpegdemux->srcpads[pkt.stream_index] = pad;
+    gst_element_add_pad (GST_ELEMENT (ffmpegdemux), pad);
+
+    /* get caps that belongs to this stream */
+    caps = gst_ffmpeg_codecid_to_caps (st->codec.codec_id,
+                                      &st->codec);
+    if (gst_pad_try_set_caps (pad, caps) <= 0) {
+      GST_DEBUG (GST_CAT_PLUGIN_ERRORS,
+                "Failed to set caps from ffdemuxer on next element");
+      /* we continue here, in the next pad-is-usable check,
+       * we'll return nonetheless */
+    }
+  }
 
-  /* Get a pointer of the right type. */
-  ffmpegdemux = (GstFFMpegDemux *)(object);
+  /* shortcut to pad belonging to this stream */
+  pad = ffmpegdemux->srcpads[pkt.stream_index];
 
-  /* Check the argument id to see which argument we're setting. */
-  switch (prop_id) {
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
+  /* and handle the data by pushing it forward... */
+  if (GST_PAD_IS_USABLE (pad)) {
+    GstBuffer *outbuf;
+
+    outbuf = gst_buffer_new_and_alloc (pkt.size);
+    memcpy (GST_BUFFER_DATA (outbuf), pkt.data, pkt.size);
+    GST_BUFFER_SIZE (outbuf) = pkt.size;
+
+    if (pkt.pts != AV_NOPTS_VALUE && ct->pts_den) {
+      GST_BUFFER_TIMESTAMP (outbuf) = pkt.pts * GST_SECOND *
+                                       ct->pts_num / ct->pts_den;
+    }
+
+    if (pkt.flags & PKT_FLAG_KEY) {
+      GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_KEY_UNIT);
+    }
+
+    gst_pad_push (pad, outbuf);
+    pkt.destruct (&pkt);
   }
 }
 
-/* The set function is simply the inverse of the get fuction. */
-static void
-gst_ffmpegdemux_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+static GstElementStateReturn
+gst_ffmpegdemux_change_state (GstElement *element)
 {
-  GstFFMpegDemux *ffmpegdemux;
-
-  /* It's not null if we got it, but it might not be ours */
-  ffmpegdemux = (GstFFMpegDemux *)(object);
+  GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *)(element);
+  gint transition = GST_STATE_TRANSITION (element);
 
-  switch (prop_id) {
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  switch (transition) {
+    case GST_STATE_PAUSED_TO_READY:
+      if (ffmpegdemux->opened) {
+        av_close_input_file (ffmpegdemux->context);
+        ffmpegdemux->opened = FALSE;
+      }
       break;
   }
+
+  if (GST_ELEMENT_CLASS (parent_class)->change_state)
+    return GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+  return GST_STATE_SUCCESS;
 }
 
 gboolean
 gst_ffmpegdemux_register (GstPlugin *plugin)
 {
   GstElementFactory *factory;
+  GstTypeFactory *type_factory;
+  GstTypeDefinition *type_definition;
   GTypeInfo typeinfo = {
     sizeof(GstFFMpegDemuxClass),      
     NULL,
@@ -312,14 +370,47 @@ gst_ffmpegdemux_register (GstPlugin *plugin)
   GType type;
   GstElementDetails *details;
   AVInputFormat *in_plugin;
+  GstFFMpegDemuxClassParams *params;
+  AVCodec *in_codec;
   
   in_plugin = first_iformat;
 
   global_plugins = g_hash_table_new (NULL, NULL);
+  typefind = g_hash_table_new (NULL, NULL);
 
   while (in_plugin) {
     gchar *type_name;
     gchar *p;
+    GstCaps *sinkcaps, *audiosrccaps, *videosrccaps;
+
+    /* Try to find the caps that belongs here */
+    sinkcaps = gst_ffmpeg_formatid_to_caps (in_plugin->name);
+    if (!sinkcaps) {
+      goto next;
+    }
+    /* This is a bit ugly, but we just take all formats
+     * for the pad template. We'll get an exact match
+     * when we open the stream */
+    audiosrccaps = NULL;
+    videosrccaps = NULL;
+    for (in_codec = first_avcodec; in_codec != NULL;
+        in_codec = in_codec->next) {
+      GstCaps *temp = gst_ffmpeg_codecid_to_caps (in_codec->id, NULL);
+      if (!temp) {
+        continue;
+      }
+      switch (in_codec->type) {
+        case CODEC_TYPE_VIDEO:
+          videosrccaps = gst_caps_append (videosrccaps, temp);
+          break;
+        case CODEC_TYPE_AUDIO:
+          audiosrccaps = gst_caps_append (audiosrccaps, temp);
+          break;
+        default:
+          gst_caps_unref (temp);
+          break;
+      }
+    }
 
     /* construct the type */
     type_name = g_strdup_printf("ffdemux_%s", in_plugin->name);
@@ -337,39 +428,71 @@ gst_ffmpegdemux_register (GstPlugin *plugin)
       goto next;
     }
 
-    /* create the gtk type now */
+    /* create the type now */
     type = g_type_register_static(GST_TYPE_ELEMENT, type_name , &typeinfo, 0);
 
     /* construct the element details struct */
-    details = g_new0 (GstElementDetails,1);
-    details->longname = g_strdup (in_plugin->name);
-    details->klass = "Codec/Demuxer/FFMpeg";
-    details->license = "LGPL";
-    details->description = g_strdup (in_plugin->name);
-    details->version = g_strdup("1.0.0");
-    details->author = g_strdup("The FFMPEG crew, GStreamer plugin by Wim Taymans <wim.taymans@chello.be>");
-    details->copyright = g_strdup("(c) 2002");
-
-    g_hash_table_insert (global_plugins, 
-                        GINT_TO_POINTER (type), 
-                        (gpointer) in_plugin);
+    details = g_new0 (GstElementDetails, 1);
+    details->longname = g_strdup (in_plugin->long_name);
+    details->klass = g_strdup ("Codec/Demuxer");
+    details->license = g_strdup ("LGPL");
+    details->description = g_strdup_printf ("FFMPEG %s demuxer",
+                                           in_plugin->name);
+    details->version = g_strdup (VERSION);
+    details->author = g_strdup ("The FFMPEG crew\n"
+                               "Wim Taymans <wim.taymans@chello.be>\n"
+                               "Ronald Bultje <rbultje@ronald.bitfreak.net>");
+    details->copyright = g_strdup ("(c) 2002-2003");
 
     /* register the plugin with gstreamer */
     factory = gst_element_factory_new(type_name,type,details);
     g_return_val_if_fail(factory != NULL, FALSE);
 
-    gst_element_factory_set_rank (factory, GST_ELEMENT_RANK_NONE);
+    /* typefind info */
+    type_definition = g_new0 (GstTypeDefinition, 1);
+    type_definition->name = g_strdup_printf ("fftype_%s",
+                                            in_plugin->name);
+    type_definition->mime = g_strdup (gst_caps_get_mime (sinkcaps));
+    type_definition->exts = g_strdup (in_plugin->extensions);
+    type_definition->typefindfunc = gst_ffmpegdemux_typefind;
+
+    type_factory = gst_type_factory_new (type_definition);
+
+    /* create a cache for these properties */
+    params = g_new0 (GstFFMpegDemuxClassParams, 1);
+    params->in_plugin = in_plugin;
+    params->typefind_feature = GST_PLUGIN_FEATURE (type_factory);
+    params->sinktempl = gst_pad_template_new ("sink", GST_PAD_SINK,
+                                             GST_PAD_ALWAYS,
+                                             sinkcaps, NULL);
+    gst_element_factory_add_pad_template (factory,
+                                         params->sinktempl);
+    params->audiosrctempl = gst_pad_template_new ("audio_%02d",
+                                                 GST_PAD_SRC,
+                                                 GST_PAD_SOMETIMES,
+                                                 audiosrccaps, NULL);
+    gst_element_factory_add_pad_template (factory,
+                                         params->audiosrctempl);
+    params->videosrctempl = gst_pad_template_new ("video_%02d",
+                                                 GST_PAD_SRC,
+                                                 GST_PAD_SOMETIMES,
+                                                 videosrccaps, NULL);
+    gst_element_factory_add_pad_template (factory,
+                                         params->videosrctempl);
+
+    g_hash_table_insert (global_plugins, 
+                        GINT_TO_POINTER (type), 
+                        (gpointer) params);
 
-    gst_element_factory_add_pad_template (factory, 
-                   GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_sink_factory));
+    g_hash_table_insert (typefind,
+                        (gpointer) type_factory,
+                        (gpointer) params);
 
-    gst_element_factory_add_pad_template (factory, 
-                   GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_video_src_factory));
-    gst_element_factory_add_pad_template (factory, 
-                   GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_audio_src_factory));
+    gst_element_factory_set_rank (factory, GST_ELEMENT_RANK_MARGINAL);
 
     /* The very last thing is to register the elementfactory with the plugin. */
     gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
+    gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (type_factory));
 
 next:
     in_plugin = in_plugin->next;