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 の主な構成要素と役割を簡単に説明しました。図示すると次のようになります。
一覧にまとめると次のようになります。
要素 | GStreamer の型 | 備考 |
---|---|---|
Element | GstElement | source, sink, decoder, encoder など、データの入出力や変換処理を行います。 |
Bin, Pipeline | GstBin, GstPipeline | Element 間の接続およびデータの流れを管理します。トップレベルの Bin を Pipeline といいます。 |
Pad | GstPad | Element 間の接点を表します。二種類の向き (source, sink) があります。 |
Bus | GstBus | Bin からの、エラー通知や EOS 通知といったメッセージを、アプリケーションが受け取る際に使用します。 |
クラスの継承関係は次のようになります。
メディア再生のシーケンス
ここまでは GStreamer の静的な側面に注目しました。ここからは動的な側面に注目していきます。
GStreamer を利用したマルチメディアファイル再生アプリケーションは、おおよそ以下のような流れで、指定されたファイルを再生します。
- GStreamer を初期化する (gst_init)
- メインループを作成する
- GLib の g_main_loop_newを利用できます
- Pipeline を構築する
- Pipeline を作成する (gst_pipeline_new)
- Element (ソース、デマルチプレクサ、デコーダ、シンクなど) を作成する (gst_element_factory)
- ソースに再生対象のファイルを指定する (g_object_set)
- Pipeline から Bus を取得する (gst_pipeline_get_bus)
- Bus にコールバック関数を登録する (gst_bus_add_watch)
- Pipeline に Element を追加する (gst_bin_add_many)
- Element 同士を接続する (gst_element_link)
- Pipeline の状態を PLAYING に変更する (gst_element_set_state)
- メインループを実行する (g_main_loop_run)
シーケンス図は次のようになります。
サンプルアプリケーションの作成
ここからは 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 では
のような 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_many も g_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 | 再生中 |
アプリケーションはこれらのどの二つの状態の遷移を指示することも可能ですが、必ず中間の状態を経由します。図示すると次の通りです。
一時停止状態を意味する GST_STATE_PAUSED は、再生中を意味する GST_STATE_PLAYING とほとんど同じ状態で、クロックが進行しない点が異なります。クロックが進行しないため、オーディオやビデオのレンダリングは行われません。
最後に
g_main_loop_run (loop);
を呼び出してメインループを実行します。 g_main_loop_run は g_main_loop_quit が呼び出されると終了します。このサンプルアプリケーションでは bus_call で GST_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 ファイルを再生するサンプルアプリケーションの動作を確認しました。
まとめ
この記事では
- GStreamer の構成要素
- GStreamer が Element, Bin, Pipeline, Pad, Bus といった要素から構成されること、および各要素の役割
- メディア再生のシーケンス
- GStreamer を初期化して、メディア再生を行い、終了するまでの流れ
- サンプルアプリケーションの作成
- 単純な ogg/vorbis ファイル再生アプリケーションの作成、およびソースコードの解説
について紹介しました。
※文中に記載されている各種名称、会社名、商品名などは各社の商標もしくは登録商標です。