現在位置 : ホーム > PIXELA Technical Expertise > GStreamer > GStreamer を使ったアプリケーションの作成

GStreamer を使ったアプリケーションの作成

公開日:2023年1月5日

GStreamer の紹介ではコマンドラインアプリケーション gst-launch-1.0 を利用して ogg/vorbis ファイルの再生を行いました。この記事では、類似の機能を持つアプリケーションを C 言語で作成しながら、 GStreamer の動作の仕組みを解説します。

GStreamer は GObject で提供されるオブジェクトシステムを利用していますが、この記事では GObject の詳細は扱いません。

GStreamer の構成要素

GStreamer で最も重要な構成要素の一つとして Element が挙げられます。Element はソースやデマルチプレクサやデコーダやシンクといった、マルチメディア処理を担う主体です。Element 間の接続の接点となる要素を Pad といいます。Pad には二つの向きがあり、 Pad の所属する Element の観点で、データを受信する Pad を sink といい、データを生成する Pad を source といいます。いくつかの Element を接続したグラフを Bin といいます。Bin は Element と同様にふるまうため Bin を Element とみなすことができます。つまり、複雑な処理を行う Bin を一つの Element とみなし、その詳細を忘れることができます。トップレベルの Bin を、特別に Pipeline と呼びます。アプリケーションは Pipeline を作成して制御することにより、マルチメディアの処理を実現します。この時、アプリケーションは Pipeline のストリーミングスレッドからのメッセージ (エラーやストリーム終端への到達 (EOS = End of Stream) など) を受信することができ、そのメッセージ受信部分を Bus と呼びます。

ここまでで GStreamer の主な構成要素と役割を簡単に説明しました。図示すると次のようになります。

objects in gstreamer

objects in gstreamer(クリックで拡大)

一覧にまとめると次のようになります。

要素 GStreamer の型 備考
Element GstElement source, sink, decoder, encoder など、データの入出力や変換処理を行います。
Bin, Pipeline GstBin, GstPipeline Element 間の接続およびデータの流れを管理します。トップレベルの Bin を Pipeline といいます。
Pad GstPad Element 間の接点を表します。二種類の向き (source, sink) があります。
Bus GstBus Bin からの、エラー通知や EOS 通知といったメッセージを、アプリケーションが受け取る際に使用します。

クラスの継承関係は次のようになります。

GStreamer class diagram

メディア再生のシーケンス

ここまでは GStreamer の静的な側面に注目しました。ここからは動的な側面に注目していきます。

GStreamer を利用したマルチメディアファイル再生アプリケーションは、おおよそ以下のような流れで、指定されたファイルを再生します。

  1. GStreamer を初期化する (gst_init)
  2. メインループを作成する
    1. GLib の g_main_loop_newを利用できます
  3. Pipeline を構築する
    1. Pipeline を作成する (gst_pipeline_new)
    2. Element (ソース、デマルチプレクサ、デコーダ、シンクなど) を作成する (gst_element_factory)
      1. ソースに再生対象のファイルを指定する (g_object_set)
    3. Pipeline から Bus を取得する (gst_pipeline_get_bus)
    4. Bus にコールバック関数を登録する (gst_bus_add_watch)
    5. Pipeline に Element を追加する (gst_bin_add_many)
    6. Element 同士を接続する (gst_element_link)
  4. Pipeline の状態を PLAYING に変更する (gst_element_set_state)
  5. メインループを実行する (g_main_loop_run)

シーケンス図は次のようになります。

playback sequence

playback sequence(クリックで拡大)

サンプルアプリケーションの作成

ここからは ogg/vorbis のオーディオファイルを再生するコマンドラインアプリケーションを題材にして GStreamer の動作する仕組みを見ていきます。

まず、必要なアプリケーションやライブラリなどをインストールします。

# 必要なツールやプラグインのインストール (再掲)
$ sudo apt install libgstreamer1.0-0 gstreamer1.0-tools
$ sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good

# コンパイラなどのインストール
$ sudo apt install build-essential

# 開発用のライブラリのインストール
$ sudo apt install libgstreamer1.0-dev

Your first application に記載されているソースコードをコピーして helloworld.c などのファイル名で保存します。

# ビルド
$ gcc -Wall helloworld.c -o helloworld $(pkg-config --cflags --libs gstreamer-1.0)

# 実行
$ ./helloworld song.ogg

song.ogg の終端まで再生されると helloworld は終了します。

このソースコード helloworld.c では

ogg vorbis playback pipeline

ogg vorbis playback pipeline(クリックで拡大)

のような pipeline を構築して song.ogg を再生しています。

gst_init (&argc, &argv);

では GStreamer の初期化を行っています。

loop = g_main_loop_new (NULL, FALSE);

で GLib を利用してメインループを作成しています。

pipeline = gst_pipeline_new ("audio-player");

で audio-player という名前の pipeline を作成しています。

source   = gst_element_factory_make ("filesrc",       "file-source");
demuxer  = gst_element_factory_make ("oggdemux",      "ogg-demuxer");
decoder  = gst_element_factory_make ("vorbisdec",     "vorbis-decoder");
conv     = gst_element_factory_make ("audioconvert",  "converter");
sink     = gst_element_factory_make ("autoaudiosink", "audio-output");

gst_element_factory_makeの第一引数で指定されるファクトリを用いて、第二引数で指定される名前を持った Element を作成しています。例えば source は filesrc という、ファイル読み出しを行う Element のファクトリを利用して、名前が file-source である新しい Element を作成しています。このサンプルアプリケーションには出てきませんが

GstElement *element;
gchar *name;
/** ここで element 作成 **/
g_object_get (G_OBJECT (element), "name", &name, NULL);
g_print ("The name of the element is '%s'.\n", name);
g_free (name);

のようにして Element に与えられた名前 (gst_element_factory_make の第二引数で指定したもの) を確認できます。

g_object_set (G_OBJECT (source), "location", argv[1], NULL);

で再生するファイルの名前を指定しています。前述の実行例であれば song.ogg になります。

余談ですが、ここに出てきた g_object_get, g_object_setは GStreamer ではなく GLib の API になります。これらの API の引数は可変長で、一度に複数のプロパティを取得および設定することができます。引数の末尾で与えている NULL は可変長引数の終端を意味しています。二つ目以降のプロパティを指定する場合も、一つ目と同様に、名前と値の対を与えて、最後は NULL を与えます。

Element ごとに指定可能なプロパティは決まっており、例えば filesrc では、名前が location で型が文字列 (gchararray) であると調べることができます。

閑話休題してソースコードの解説に戻ります。

bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);
gst_object_unref (bus);

で pipeline の Bus を取得して GStreamer から通知されたメッセージを受け取るコールバック関数 (bus_call) を指定しています。

コールバック関数 (bus_call) では次のように

static gboolean
bus_call (GstBus     *bus,
          GstMessage *msg,
          gpointer    data)
{
  GMainLoop *loop = (GMainLoop *) data;

  switch (GST_MESSAGE_TYPE (msg)) {

    case GST_MESSAGE_EOS:
      g_print ("End of stream\n");
      g_main_loop_quit (loop);
      break;

    case GST_MESSAGE_ERROR: {
      gchar  *debug;
      GError *error;

      gst_message_parse_error (msg, &error, &debug);
      g_free (debug);

      g_printerr ("Error: %s\n", error->message);
      g_error_free (error);

      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }

  return TRUE;
}

EOS (End of Stream) やエラーが通知された場合に、コールバック関数内で検知して対処することができます。他のメッセージについては GstMesage#GstMessageType に一覧が記載されています。

次に、作成した Element をまとめて pipeline に追加します。

/* we add all elements into the pipeline */
/* file-source | ogg-demuxer | vorbis-decoder | converter | alsa-output */
gst_bin_add_many (GST_BIN (pipeline),
                source, demuxer, decoder, conv, sink, NULL);

Element を Bin (Pipeline) に追加する API gst_bin_add_manyg_object_get などと同様に複数の Element をまとめて追加することができ、最後の引数に NULL を与えて終端します。追加する Element が一つの場合は gst_bin_add を利用することができます。

次に Element を接続します。

/* we link the elements together */
/* file-source -> ogg-demuxer ~> vorbis-decoder -> converter -> alsa-output */
gst_element_link (source, demuxer);
gst_element_link_many (decoder, conv, sink, NULL);

ストリーミング開始前には、まだ demuxer の source Pad が作成されていないため、この時点で demuxer と decoder を接続することはできません。このことは oggdemux の Pad Templates に src が Presence: sometimes と記載されていることより、常に source Pad が存在するわけではないとわかります。ストリーミング開始後に ogg の解析が行われて、どのようなコンポーネントがコンテナに含まれるかがわかってから、対応する Pad が出現する、という動作になります。

以上のような、動的な Pad の生成に対応するには

g_signal_connect (demuxer, "pad-added", G_CALLBACK (on_pad_added), decoder);

のように demuxer に対して pad-added のイベントが発生した際のコールバックを登録することで対処します。また pad-added のイベントを受信した際に demuxer の source Pad と decoder の sink Pad を接続したいので decoder も渡します。

static void
on_pad_added (GstElement *element,
              GstPad     *pad,
              gpointer    data)
{
  GstPad *sinkpad;
  GstElement *decoder = (GstElement *) data;

  /* We can now link this pad with the vorbis-decoder sink pad */
  g_print ("Dynamic pad created, linking demuxer/decoder\n");

  sinkpad = gst_element_get_static_pad (decoder, "sink");

  gst_pad_link (pad, sinkpad);

  gst_object_unref (sinkpad);
}

は pad-added のイベントが発生した際に呼び出されて、最後の引数の data には g_signal_connect の最後に引数で渡した decoder が与えられます。この関数内で demuxer と decoder の接続を行っています。

再生の準備が整ったので pipeline の状態を PLAYING に遷移させます。

gst_element_set_state (pipeline, GST_STATE_PLAYING);

GStreamer の Element, Bin, Pipeline は以下の四つの状態のどれかをとります。

状態 説明
GST_STATE_NULL 初期状態
GST_STATE_READY PAUSED に遷移可能
GST_STATE_PAUSED 一時停止状態
GST_STATE_PLAYING 再生中

アプリケーションはこれらのどの二つの状態の遷移を指示することも可能ですが、必ず中間の状態を経由します。図示すると次の通りです。

GStreamer state

一時停止状態を意味する GST_STATE_PAUSED は、再生中を意味する GST_STATE_PLAYING とほとんど同じ状態で、クロックが進行しない点が異なります。クロックが進行しないため、オーディオやビデオのレンダリングは行われません。

最後に

g_main_loop_run (loop);

を呼び出してメインループを実行します。 g_main_loop_rung_main_loop_quit が呼び出されると終了します。このサンプルアプリケーションでは bus_callGST_MESSAGE_EOS または GST_MESSAGE_ERROR メッセージが通知された際に g_main_loop_quit が呼び出されて g_main_loop_run が終了します。

再生の終了 (またはエラー発生) 後は

/* Out of the main loop, clean up nicely */
g_print ("Returned, stopping playback\n");
gst_element_set_state (pipeline, GST_STATE_NULL);

g_print ("Deleting pipeline\n");
gst_object_unref (GST_OBJECT (pipeline));
g_source_remove (bus_watch_id);
g_main_loop_unref (loop);

return 0;

pipeline の状態を NULL に戻して、確保したリソースを解放してからアプリケーションを終了します。

以上で ogg/vorbis ファイルを再生するサンプルアプリケーションの動作を確認しました。

まとめ

この記事では

について紹介しました。

文中に記載されている各種名称、会社名、商品名などは各社の商標もしくは登録商標です。

PIXELA Technical Expertise

当社が技術学習するための情報を体系的に整理したものです。多くのエンジニアの一助になればと考え公開しています。

GStreamer 記事一覧