第 18章パイプラインの操作

この章では、アプリケーションからパイプラインを操作するさまざまな方法について説明します。部分的にかなりハッキング的な要素を持つところもあるので、この章を読むにはある程度のプログラミングの素養が必要です。

この章で取り上げるテーマは、アプリケーションからパイプラインにデータを挿入する方法、パイプラインからデータを読み取る方法、パイプラインの速度、長さ、スタート箇所を操作する方法、およびパイプラインのデータ処理を監視する方法です。

18.1. データのプローブ

プローブは、パッドリスナーと考えるのが最も適切です。技術的には、プローブとは、パッドにアタッチできるシグナルコールバックにすぎません。シグナルの送信はデフォルトではまったく行われないようになっていますが (パフォーマンスを損なう可能性があるからです)、gst_pad_add_buffer_probe ()gst_pad_add_event_probe ()、または gst_pad_add_data_probe () を使ってプローブをアタッチすることで、シグナル送信が可能になります。これらの関数はシグナルハンドラをアタッチし、実際のシグナルの送信を可能にします。同様に、gst_pad_remove_buffer_probe ()gst_pad_remove_event_probe ()、または gst_pad_remove_data_probe () を使えば、シグナルハンドラを元どおりに削除することができます。

プローブはパイプラインのスレッドコンテキストの中で実行されるので、コールバックでは、ブロックするような動作をしないようにすることはもちろん、一般的観点から不適切な動作をしないようにする必要があります。なぜなら、不適切な動作はパイプラインのパフォーマンスに悪影響を及ぼす可能性があり、もしコールバックにバグがあれば、デッドロックやクラッシュを引こ起こすおそれがあるからです。もっと具体的に言うと、一般に、プローブコールバックの中から GUI 関連の関数を呼び出したり、パイプラインの状態の変更を試みたりしてはいけません。その代わり、アプリケーションはパイプラインのバスに独自のメッセージを送信することができるので、この仕組みを使ってメインアプリケーションスレッドと通信し、パイプラインを停止するといった動作をさせることができます。

いずれにしても、エレメントが _chain () 関数の中で行うことができるほとんどの一般的なバッファ操作は、プローブコールバックでも行うことができます。次に示す例は、プローブコールバックの使い方を簡単に示したものです (実際には、この例のような使い方は正しくないのですが、それについてはあとで説明します)。


#include <gst/gst.h>

static gboolean
cb_have_data (GstPad    *pad,
	      GstBuffer *buffer,
	      gpointer   u_data)
{
  gint x, y;
  guint16 *data = (guint16 *) GST_BUFFER_DATA (buffer), t;

  /* invert data */
  for (y = 0; y < 288; y++) {
    for (x = 0; x < 384 / 2; x++) {
      t = data[384 - 1 - x];
      data[384 - 1 - x] = data[x];
      data[x] = t;
    }
    data += 384;
  }

  return TRUE;
}

gint
main (gint   argc,
      gchar *argv[])
{
  GMainLoop *loop;
  GstElement *pipeline, *src, *sink, *filter, *csp;
  GstCaps *filtercaps;
  GstPad *pad;

  /* init GStreamer */
  gst_init (&argc, &argv);
  loop = g_main_loop_new (NULL, FALSE);

  /* build */
  pipeline = gst_pipeline_new ("my-pipeline");
  src = gst_element_factory_make ("videotestsrc", "src");
  if (src == NULL)
    g_error ("Could not create 'videotestsrc' element");

  filter = gst_element_factory_make ("capsfilter", "filter");
  g_assert (filter != NULL); /* should always exist */

  csp = gst_element_factory_make ("ffmpegcolorspace", "csp");
  if (csp == NULL)
    g_error ("Could not create 'ffmpegcolorspace' element");

  sink = gst_element_factory_make ("xvimagesink", "sink");
  if (sink == NULL) {
    sink = gst_element_factory_make ("ximagesink", "sink");
    if (sink == NULL)
      g_error ("Could not create neither 'xvimagesink' nor 'ximagesink' element");
  }

  gst_bin_add_many (GST_BIN (pipeline), src, filter, csp, sink, NULL);
  gst_element_link_many (src, filter, csp, sink, NULL);
  filtercaps = gst_caps_new_simple ("video/x-raw-rgb",
			   "width", G_TYPE_INT, 384,
			   "height", G_TYPE_INT, 288,
			   "framerate", GST_TYPE_FRACTION, 25, 1,
			   "bpp", G_TYPE_INT, 16,
			   "depth", G_TYPE_INT, 16,
			   "endianness", G_TYPE_INT, G_BYTE_ORDER,
			   NULL);
  g_object_set (G_OBJECT (filter), "caps", filtercaps, NULL);
  gst_caps_unref (filtercaps);

  pad = gst_element_get_pad (src, "src");
  gst_pad_add_buffer_probe (pad, G_CALLBACK (cb_have_data), NULL);
  gst_object_unref (pad);

  /* run */
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  /* wait until it's up and running or failed */
  if (gst_element_get_state (pipeline, NULL, NULL, -1) == GST_STATE_CHANGE_FAILURE) {
    g_error ("Failed to go into PLAYING state");
  }

  g_print ("Running ...\n");
  g_main_loop_run (loop);

  /* exit */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);

  return 0;
}
    

上のコードを実行したときの出力と、"gst-launch-0.10 videotestsrc ! xvimagesink" の出力とを比べ、何をしようとしているのか把握してみてください。

上に示した例は、実際には正しいコードではありません。厳密に言うと、パッドプローブコールバックでは、バッファが書き込み可能である場合にのみ、バッファの内容に変更を加えることができ、バッファのメタデータが書き込み可能である場合にのみ、タイムスタンプやケイパビリティなどのバッファメタデータに変更を加えることができます。このようなことができるかどうかは、パイプライン、および関連するエレメントによって大部分が左右されます。しばしばそうしたことができる場合もある一方で、時としてできない場合もあります。できない場合にデータやメタデータに対して不用意に変更を加えると、デバッグで見つけ出すことがきわめて困難なバグが混入してしまうことがあります。バッファとそのメタデータが書き込み可能かどうかは、gst_buffer_is_writable () および gst_buffer_is_metadata_writable () で調べることができます。受け取ったバッファとは異なるバッファを送り返すことはできないので、コールバック関数の中でバッファを書き込み可能にすることはできません。

パッドプローブは、パッドがパイプラインを通すデータを監視するのに最も適しています。データに変更を加える必要がある場合は、プログラマが自身で独自の GStreamer エレメントを記述する必要があります。この作業は、GstAudioFilter や GstVideoFilter、GstBaseTransform といったベースクラスを使えば、かなり容易に行うことができます。

パイプラインを通るバッファを調べるだけなら、わざわざパッドプローブをセットアップする必要はありません。パイプラインに identity エレメントを挿入し、このエレメントの "handoff" シグナルに接続しても、目的を実現できるからです。identity エレメントは "dump" プロパティや "last-message" プロパティといった便利なデバッグツールもいくつか提供しています ("last-message" プロパティは、gst-launch に '-v' スイッチを指定すると有効になります)。