17.3. 複数の動的パイプラインのプラッギング

警告

この項のコードは、今となっては古くて無効で、必要以上に複雑です。また、デコーダをオートプラッギングするには、decodebin、playbin、または uridecodebin を使う必要があります。

この章では、動的なパイプラインを作成する方法について説明します。動的なパイプラインとは、データがその中を流れている間に、更新または作成されるパイプラインのことです。ここでは、最初に部分的なパイプラインを作成し、このパイプラインの再生中にエレメントを付け加えていきます。以下に示すプレーヤーのベースとなっているのは、前の項 (項17.2) で未知のメディアストリームを識別するために作成したアプリケーションです。

メディアの種類が認識されたら、該当するストリームの種類をデコードできるエレメントをレジストリの中から探します。そのために、すべてのエレメントファクトリを取得し (エレメントファクトリについては、項5.2 で説明しました)、与えられた MIME 型とケイパビリティをシンクパッド上に持つエレメントを見つけます。このとき、パーサ、デマルチプレクサ、およびデコーダだけを使うことに注意してください。これら以外の種類のエレメントファクトリは使用しません。そうしないと、エンコーダとデコーダのループにはまってしまう可能性があるからです。具体的には、GStreamer の初期化直後に、"許可された" ファクトリのリストを作成します。


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_klass (GST_ELEMENT_FACTORY (feature));
  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_default (),
      (GstPluginFeatureFilter) cb_feature_filter, FALSE, NULL);

  /* sort them according to their ranks */
  factories = g_list_sort (factories, (GCompareFunc) cb_compare_ranks);
}
    

このエレメントファクトリのリストから、与えられた出力の種類に応じてメディアストリームをデコードするのに最も役立つと思われるものを選びます。そして、新しく作成される各エレメントについて、そのソースパッドに対し、新しいエレメントのオートプラッギングを試みます。また、エレメントが動的パッドを持っている場合には (動的パッドについては項8.1.1 で説明しました)、新しく作成されたソースパッドを調べ、これらのソースパッドについても処理します。次に示すコードは、前の項の cb_typefound を、上に説明したアプローチで処理を続行するオートプラッギングを行う関数で置き換えたものです。


static void try_to_plug (GstPad *pad, const GstCaps *caps);

static GstElement *audiosink;

static void
cb_newpad (GstElement *element,
	   GstPad     *pad,
	   gpointer    data)
{
  GstCaps *caps;

  caps = gst_pad_get_caps (pad);
  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_get_caps (pad);

        /* 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,
	     const GstCaps *caps)
{
  GstObject *parent = GST_OBJECT (GST_OBJECT_PARENT (pad));
  const gchar *mime;
  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 */
  mime = gst_structure_get_name (gst_caps_get_structure (caps, 0));
  if (g_strrstr (mime, "video")) {
    g_print ("Omitting link for pad %s:%s because mimetype %s is non-audio\n",
	     GST_OBJECT_NAME (parent), GST_OBJECT_NAME (pad), mime);
    return;
  }

  /* can it link to the audiopad? */
  audiocaps = gst_pad_get_caps (gst_element_get_static_pad (audiosink, "sink"));
  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",
	   mime, 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));
}
    

こうした処理を行うことによって、任意のメディアの種類に対応するパイプラインの自動的なセットアップが可能な簡単なオートプラッガを作成することができます。以下に示す例では、この処理を音声に対してのみ行います。しかし、動画に対しても同じ要領で処理を行うことで、音声と動画の両方を再生するプレーヤーを作成することもできます。[訳注:「以下に示す例」は、原文では削除されています]

上のサンプルは、オートプラッガを作成する試みとしてはちょうどよい例です[訳注:「上のサンプル」は、原文では削除されています]。次のステップとして考えられるのは、たとえば、"パッドが削除" (pad-removed) されたことを示すシグナルを受け取るようにして、ストリームが変更されたときに (DVB や Ogg ラジオではこのようなことが起こります)、パイプラインを動的に変更できるようにすることです。また、入力が既知のコンテンツとなる (DVD やオーディオ CD の) ケースや、その他多数の特別なケースに対応するコードを記述したい場合もあります。さらに、オートプラッギング中の無限ループを避けるためにさまざまなチェックを行ったり、最も最適なパイプラインが選ばれるように最短経路探索を実装したりするといったことも考えられます。根本的には、プログラムがオートプラッガで実装する機能は、プログラムがオートプラッガで何をしたいかによって変わってきます。本格的な実装については、"playbin" エレメントと "decodebin" エレメントを参照してください。