第 7章バス

バスとは、パイプラインスレッドからのメッセージを、独自のスレッドコンテキストで実行されているアプリケーションに転送するためのシンプルなシステムです。バスの利点は、GStreamer 自体はスレッドを多用するのに対し、アプリケーションがスレッド対応でなくても GStreamer を使用できる点にあります。

どのパイプラインにもデフォルトでバスが含まれているので、アプリケーションの側でバス (あるいはその他の何か) を作成する必要はありません。アプリケーション側で行う必要があるのは、バスに対してメッセージハンドラを設定することだけです。メッセージハンドラは、オブジェクトに対するシグナルハンドラに似ています。メインループを実行しているときに、新しいメッセージがないかどうかバスを定期的にチェックし、メッセージがあればコールバックが呼び出されるようにします。

7.1. バスの使い方

バスは以下に示す 2 つの異なる方法で使用できます。


#include <gst/gst.h>

static GMainLoop *loop;

static gboolean
my_bus_callback (GstBus     *bus,
		 GstMessage *message,
		 gpointer    data)
{
  g_print ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message));

  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:
      /* unhandled message */
      break;
  }

  /* we want to be notified again the next time there is a message
   * on the bus, so returning TRUE (FALSE means we want to stop watching
   * for messages on the bus and our callback should not be called again)
   */
  return TRUE;
}

gint
main (gint   argc,
      gchar *argv[])
{
  GstElement *pipeline;
  GstBus *bus;

  /* init */
  gst_init (&argc, &argv);

  /* create pipeline, add handler */
  pipeline = gst_pipeline_new ("my_pipeline");

  /* adds a watch for new message on our pipeline's message bus to
   * the default GLib main context, which is the main context that our
   * GLib main loop is attached to below
   */
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  gst_bus_add_watch (bus, my_bus_callback, NULL);
  gst_object_unref (bus);

[..]

  /* create a mainloop that runs/iterates the default GLib main context
   * (context NULL), in other words: makes the context check if anything
   * it watches for has happened. When a message has been posted on the
   * bus, the default main context will automatically call our
   * my_bus_callback() function to notify us of that message.
   * The main loop will be run until someone calls g_main_loop_quit()
   */
  loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (loop);

  /* clean up */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  g_main_loop_unref (loop);

  return 0;
}
    
    

メッセージハンドラはメインループのスレッドコンテキストの中で呼び出されるということを理解しておくことは重要です。これはすなわち、バスを介したパイプラインとアプリケーションのやり取りが非同期 (asynchronous) であり、したがって、音声トラックのクロスフェードをはじめ、ギャップレス再生やビデオエフェクトの (理論上の) 実現といった各種のリアルタイムの処理目的には適さないことを意味します。これらの処理はすべてパイプラインコンテキストで行う必要があり、そのための最も簡単な方法は GStreamer プラグインを記述することです。この方法は当初の目的、すなわちパイプラインからアプリケーションにメッセージを渡すという目的の面でも、実は非常に有益です。このアプローチの利点は、GStreamer が内部的に行うすべてのスレッド化がアプリケーションから隠蔽され、アプリケーション開発者はスレッドに関する問題をまったく意識しなくて済むことです。

デフォルトの GLib メインループへの組み込みを使う場合には、ウォッチをアタッチする代わりに、バス上の "メッセージ" シグナルに接続することができる点に注意してください。この方法では、すべての可能なメッセージの種類に対して switch() を使う必要はなく、 "message::<type>" という形式で、注目したいシグナルに接続するだけで済みます。ここで <type> は、特定のメッセージの種類を表します (メッセージの種類については、次のセクションを参照してください)。

このことをふまえると、上のコードは次のように書き直すことができます。


GstBus *bus;

[..]

bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline);
gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message::error", G_CALLBACK (cb_message_error), NULL);
g_signal_connect (bus, "message::eos", G_CALLBACK (cb_message_eos), NULL);

[..]
    

GLib メインループを使用しない場合、非同期メッセージシグナルはデフォルトで利用できなくなります。ただし、独自のメインループを起動して、gst_bus_async_signal_func () を使ってシグナルを送信するような独自の同期ハンドラをインストールすることはできます (詳細については、ドキュメントを参照してください)。