Showing posts with label gstreamer. Show all posts
Showing posts with label gstreamer. Show all posts

Thursday, December 29, 2016

GStreamer でプログラミング 8 (Log)

1 はじめに

デバッグは、プログラムを書く上で避けては通れない作業の1つです。今回は、デバッグ時に役に立つ logger についてです。

2 すべてのログは gst_debug_log() に通ず

GStreamer のログは、どのマクロを使っても最終的には gst_debug_log() に展開されます。なので、この関数が使えるようになればどのマクロでも使いこなせます。

void
gst_debug_log (GstDebugCategory *category,
               GstDebugLevel level,
               const gchar *file,
               const gchar *function,
               gint line,
               GObject *object,
               const gchar *format,
               ...);
category

カテゴリーと呼ばれる、ログの種別を指定します。エレメントや、エレメントで使われるライブラリーなどは、小文字を使ったカテゴリー名を使います。例えば pipeline 、 bus 、 bin 、 basesink 、 basesrc 、 videotestsrc など、エレメントの名前がそのまま使われている事が多いです。

逆に GStreamer のコアは、 GST_ で始まるカテゴリー名を使っています。 GST_STATES 、 GST_CLOCK 、 GST_PADS 、 GST_BUS など抽象的な概念、 GST_INIT や GST_MEMORY 、GST_PLUGIN_LOADING 、 GST_REGISTRY などコア機能部分などです。

自分でアプリを作成する場合には、自分のアプリのカテゴリーを作るとログを分別するのに便利です。これについては後ほど

特に指定がなければ GST_CAT_DEFAULT を使います。こちらも後ほど詳しく説明します

level

severity と呼ばれる「ヤバイ度」を表すレベルを指定します。重度の方から

  • GST_LEVEL_ERROR
  • GST_LEVEL_WARNING
  • GST_LEVEL_FIXME
  • GST_LEVEL_INFO
  • GST_LEVEL_DEBUG
  • GST_LEVEL_LOG
  • GST_LEVEL_TRACE
  • GST_LEVEL_MEMDUMP

となっています。 ERROR から順に 1, 2, 3… と番号でも表わすことができます。 TRACE が 7 で、8 が バージョン 1.10.0 現在では定義されておらず、 MEMDUMP は 9 です

file / function / line
ファイル名、関数名、行番号を指定します。 gst_debug_log() を直接呼び出すことは、実際のコードではあまりなく、それぞれ __FILE__ __fun__ __LINE__ がマクロで指定されます。 gst_debug_log() を直接呼ぶなら嘘を書くこともできます
object
GObject から派生したオジェクトを渡すことができます。 gst_debug_print_object() を使ってオジェクトを綺麗に print してくれます
format / …
printf() と同じく format に続いて引数を列べます
#include <gst/gst.h>

int main(int argc, char *argv[])
{
        gst_init(&argc, &argv);

        gst_debug_log(GST_CAT_DEFAULT,
                      GST_LEVEL_ERROR,
                      "debuglog.c",
                      "main",
                      10,
                      NULL,
                      "hello from gst_debug_log!");

        GST_CAT_LEVEL_LOG(GST_CAT_DEFAULT,
                          GST_LEVEL_ERROR,
                          NULL,
                          "this should be similer to the line above");

        return 0;
}
$ gcc -Wall -Wextra -g $(pkg-config --cflags --libs gstreamer-1.0) debuglog.c
$ ./a.out
$ ./a.out --gst-debug 1
0:00:00.012686366 29952 0x56142d48fe90 ERROR                default debuglog.c:10:main: hello from gst_debug_log!
0:00:00.012716649 29952 0x56142d48fe90 ERROR                default debuglog.c:18:main: this should be similer to the line above
$ ./a.out --gst-debug ERROR
0:00:00.016021714 29979 0x55f65aae9e90 ERROR                default debuglog.c:10:main: hello from gst_debug_log!
0:00:00.016052525 29979 0x55f65aae9e90 ERROR                default debuglog.c:18:main: this should be similer to the line above
$ ./a.out --gst-debug default:6
0:00:00.000672566 30012 0x560b988048f0 DEBUG                default gstelement.c:194:gst_element_setup_thread_pool: creating element thread pool
0:00:00.022098288 30012 0x560b988048f0 DEBUG                default gsttracerutils.c:78:_priv_gst_tracing_init: Initializing GstTracer
0:00:00.022144095 30012 0x560b988048f0 ERROR                default debuglog.c:10:main: hello from gst_debug_log!
0:00:00.022150816 30012 0x560b988048f0 ERROR                default debuglog.c:18:main: this should be similer to the line above

gst-launch-1.0 ではなく、自作のアプリでも gst_init() をしていると、上記のように --gst-debug が使えます。説明のために GST_LEVEL_ERROR を使っています。 GST_LEVEL_ERROR は 1 と等価なので --gst-debug 1 と指定しても --gst-debug ERROR と指定しても同じ結果になります。

--gst-debug の代りに、環境変数 GST_DEBUG でも同じように指定できます。

--gst-debug のフォーマットは、 「カテゴリー : レベル」と指定します。サンプルコードで、カテゴリーが "default" の ERROR だけを表示したい場合は default:1 、または default:ERROR と指定します。 LOG (6) や DEBUG (5) まで表示したい場合は default:6 と指定します。 gstelement.c や gsttracerutils.c にも default カテゴリーを使っているものがあるのが分ります。

3 便利なマクロ達

実際にコードを書いている時には gst_debug_log() は面倒なので使いません。もっと簡単に使える以下のマクロを使います。

  • GST_ERROR()
  • GST_WARNING()
  • GST_INFO()
  • GST_DEBUG()
  • GST_LOG()
  • GST_FIXME()
  • GST_TRACE()

それぞれ、suffix になっているレベルでログを表示してくれます。カテゴリーは GST_CAT_DEFAULT です。

#include <gst/gst.h>

int main(int argc, char *argv[])
{
        gst_init(&argc, &argv);

        GST_ERROR("foo");   // 1
        GST_WARNING("foo"); // 2
        GST_INFO("foo");    // 3
        GST_DEBUG("foo");   // 4
        GST_LOG("foo");     // 5
        GST_FIXME("foo");   // 6
        GST_TRACE("foo");   // 7

        GST_CAT_ERROR(GST_CAT_DEFAULT, "foo");   // 1
        GST_CAT_WARNING(GST_CAT_DEFAULT, "foo"); // 2
        GST_CAT_INFO(GST_CAT_DEFAULT, "foo");    // 3
        GST_CAT_DEBUG(GST_CAT_DEFAULT, "foo");   // 4
        GST_CAT_LOG(GST_CAT_DEFAULT, "foo");     // 5
        GST_CAT_FIXME(GST_CAT_DEFAULT, "foo");   // 6
        GST_CAT_TRACE(GST_CAT_DEFAULT, "foo");   // 7

        return 0;
}

つまり、上と下のマクロは同じ意味になります。上の方がカテゴリーを省略して書けるので便利ですね。GStreamer の本体でも良く使われているマクロの1つです。

$ ./a.out --gst-debug default:6
0:00:00.000326411 31064 0x55e3cd8a38f0 DEBUG                default gstelement.c:194:gst_element_setup_thread_pool: creating element thread pool
0:00:00.012841797 31064 0x55e3cd8a38f0 DEBUG                default gsttracerutils.c:78:_priv_gst_tracing_init: Initializing GstTracer
0:00:00.012881297 31064 0x55e3cd8a38f0 ERROR                default log.c:12:main: foo
0:00:00.012886168 31064 0x55e3cd8a38f0 WARN                 default log.c:13:main: foo
0:00:00.012890248 31064 0x55e3cd8a38f0 INFO                 default log.c:14:main: foo
0:00:00.012893221 31064 0x55e3cd8a38f0 DEBUG                default log.c:15:main: foo
0:00:00.012898387 31064 0x55e3cd8a38f0 LOG                  default log.c:16:main: foo
0:00:00.012901336 31064 0x55e3cd8a38f0 FIXME                default log.c:17:main: foo
0:00:00.012904926 31064 0x55e3cd8a38f0 ERROR                default log.c:20:main: foo
0:00:00.012909571 31064 0x55e3cd8a38f0 WARN                 default log.c:21:main: foo
0:00:00.012913609 31064 0x55e3cd8a38f0 INFO                 default log.c:22:main: foo
0:00:00.012917039 31064 0x55e3cd8a38f0 DEBUG                default log.c:23:main: foo
0:00:00.012919887 31064 0x55e3cd8a38f0 LOG                  default log.c:24:main: foo
0:00:00.012923130 31064 0x55e3cd8a38f0 FIXME                default log.c:25:main: foo

:6 を :5 や :3 に変更すると表示されるものが変るので、試してみてください。

4 カテゴリーの作り方

さて、今回の本題である「自分用のカテゴリーの作り方」に入ります。

作り方は簡単で、 GstDebugCategory を1つ作るだけです。

#include <gst/gst.h>

int main(int argc, char *argv[])
{
        GstDebugCategory *cat;

        gst_init(&argc, &argv);

        cat = _gst_debug_category_new("my category", 0, "This is my category");

        GST_CAT_ERROR(cat, "foo");
        GST_CAT_WARNING(cat, "foo");
        GST_CAT_INFO(cat, "foo");
        GST_CAT_DEBUG(cat, "foo");
        GST_CAT_LOG(cat, "foo");
        GST_CAT_FIXME(cat, "foo");
        GST_CAT_TRACE(cat, "foo");

        return 0;
}

ただ、これだと main() の中でしか使えないので、大体はファイルグローバルの変数として宣言してしまいます。

#include <gst/gst.h>

static GstDebugCategory *cat;

int main(int argc, char *argv[])
{
        gst_init(&argc, &argv);

        cat = _gst_debug_category_new("my category", 0, "This is my category");

        GST_CAT_ERROR(cat, "foo");
        GST_CAT_WARNING(cat, "foo");
        GST_CAT_INFO(cat, "foo");
        GST_CAT_DEBUG(cat, "foo");
        GST_CAT_LOG(cat, "foo");
        GST_CAT_FIXME(cat, "foo");
        GST_CAT_TRACE(cat, "foo");

        return 0;
}

これを GST_DEBUG_CATEGORY_STATIC と GST_DEBUG_CATEGORY_INIT というマクロで書くと

#include <gst/gst.h>

GST_DEBUG_CATEGORY_STATIC(cat);

int main(int argc, char *argv[])
{
        gst_init(&argc, &argv);

        GST_DEBUG_CATEGORY_INIT (cat, "my category", 0, "This is my category");

        GST_CAT_ERROR(cat, "foo");
        GST_CAT_WARNING(cat, "foo");
        GST_CAT_INFO(cat, "foo");
        GST_CAT_DEBUG(cat, "foo");
        GST_CAT_LOG(cat, "foo");
        GST_CAT_FIXME(cat, "foo");
        GST_CAT_TRACE(cat, "foo");

        return 0;
}

となります。 _gst_debug_category_new() がアンダーバー _ で始まっていることからも、これはマクロから使われる為だけで公開関数ではないことが分ります。

さらにカテゴリーをわざわざ指定するの面倒なので、 自分のカテゴリーを GST_CAT_DEFAULT に define してしまいます。これで、やっと GstInfo の説明に書いてあるコード と同じになりました。

#include <gst/gst.h>

GST_DEBUG_CATEGORY_STATIC(cat);
#define GST_CAT_DEFAULT cat

int main(int argc, char *argv[])
{
        gst_init(&argc, &argv);

        GST_DEBUG_CATEGORY_INIT (cat, "my category", 0, "This is my category");

        GST_ERROR("foo");
        GST_WARNING("foo");
        GST_INFO("foo");
        GST_DEBUG("foo");
        GST_LOG("foo");
        GST_FIXME("foo");
        GST_TRACE("foo");

        return 0;
}

すると、こんな風に使えるようになります。

$ ./a.out --gst-debug "my category":6
0:00:00.012787531 30961 0x557846935e90 ERROR            my category mycat.c:12:main: foo
0:00:00.012817629 30961 0x557846935e90 WARN             my category mycat.c:13:main: foo
0:00:00.012826697 30961 0x557846935e90 INFO             my category mycat.c:14:main: foo
0:00:00.012833958 30961 0x557846935e90 DEBUG            my category mycat.c:15:main: foo
0:00:00.012839721 30961 0x557846935e90 LOG              my category mycat.c:16:main: foo
0:00:00.012846188 30961 0x557846935e90 FIXME            my category mycat.c:17:main: foo

"my category" にはスペースが入っているので、「"」で括るのを忘れずに。

5 おわりに

アプリを書く時も、エレメントを作成する時も、とりあえずボイラープレートとして自分のカテゴリーを登録してしまうと、後からのデバッグが楽になります。 --gst-debug には複数のカテゴリーを列べることもできますし、ワイルドカード「*」での指定も可能です。自分のプログラム内で、複数のカテゴリーを登録する時に決まった形の prefix を使えば

--gst-debug "myapp_*:6"

という感じでカテゴリーを選択できるので便利です。

ところで、 GST_CAT_DEFAULT の妙に気付いた人はいたでしょうか? 元々あった default のカテゴリーも GST_CAT_DEFAULT ですし、自分で define したのも GST_CAT_DEFAULT です。これ、考えてみると二重定義にならないのって変じゃないです?

答は、もちろんソースにあります。 gstinfo.h の 550行目あたりに

GST_EXPORT GstDebugCategory *   GST_CAT_DEFAULT;

って書いてあるはずです。そうです、自分で define していない GST_CAT_DEFAULT は、実は大文字の変数名なんですね。

C では、すでに宣言されている変数名と同じ名前で define することは許されています。 define すると、そちらの方がプリプロセッサーで先に処理されて 自分で作成した GstDebugCategory の変数に置き換わります。

define していない場合は、GStreamer が用意している変数がそのまま使われるという仕組みです。良く考えられてますねー。

分りました? もし分からなかっったらもう一度、 gstinfo.h と上のサンプルコードをじーっと眺めてみてください。

Wednesday, December 28, 2016

GStreamer でプログラミング 7 (Adapter)

1 はじめに

GstBuffer が完結している場合は良いのですが、Parser を通す前だと、必要以上のデーターが入っていたり、逆に全然データーが足りない場合があります。そんな時に活躍するのが GstAdapter です。今回は、このクラスを覗いてみましょう。

2 Adapt してくれる物

Adapter は、「Adapt する物」という意味です。 GstBuffer を Adapt する、つまり要求に「合わせて」くれるクラスです。ここで言う GstBuffer への要求とは、大きさ/サイズの変更です。

四の五の言わずに、さっそくサンプルコードを見てみましょう。

#include <gst/base/gstadapter.h>

int main(int argc, char *argv[])
{
        GstAdapter *a;
        GstBuffer *b;

        gst_init(&argc, &argv);

        a = gst_adapter_new();
        b = gst_buffer_new_allocate(NULL, 10, NULL);
        gst_adapter_push(a, b);
        b = gst_buffer_new_allocate(NULL, 5, NULL);
        gst_adapter_push(a, b);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));

        b = gst_adapter_take_buffer(a, 2);
        g_print("size: %" G_GSIZE_FORMAT ", buffer: %" G_GSIZE_FORMAT "\n",
                gst_adapter_available(a),
                gst_buffer_get_size(b));

        gst_adapter_flush(a, 3);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));

        gst_adapter_clear(a);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));

        return 0;
}
$ gcc -Wall -Wextra -g $(pkg-config --cflags --libs gstreamer-base-1.0) a.c
$ ./a.out
size: 15
size: 13, buffer: 2
size: 10
size: 0

GstAdapter には gst_adapter_push() することで、バッファーを入れることができます。 GstBuffer に入っているバッファーの総バイト数を gst_adapter_available() することで取得できます。

入れたバッファーを取り出すには、 gst_adapter_take_buffer() が便利です。 指定したバイト数入った GstBuffer が貰えます。

他にも、 GstAdapter の先頭から指定バイト数捨てる (flushする) gst_adapter_flush() や、中に入っているすべてのデーターを捨ててしまう、 gst_adapter_clear() があります。このあたりは、リファレンスマニュアルにも詳しく書いてあるので参考にしてください。

3 Take vs Get

GstAdapter には take 系の関数群と get 系の関数群があります。

  • take 系
    • gst_adapter_take()
    • gst_adapter_take_buffer()
    • gst_adapter_take_buffer_fast()
    • gst_adapter_take_list()
    • gst_adapter_take_buffer_list()
  • get 系
    • gst_adapter_get_buffer()
    • gst_adapter_get_buffer_fast()
    • gst_adapter_get_list()
    • gst_adapter_get_buffer_list()

take でも get でも同じような意味なので、英語に慣れていないと、どっちがどっちなのか分らなくなりそうです。 英語では take の方が若干「奪う」意味が入ります。でも、オブジェクト指向で書いている時に getter として良く使うのは名前の通り get じゃないでしょうか?

GstAdapter のメソッドでは、

take()
実際にデーターを Adapter から抜きとる
get()
実データーは Adapter に残したまま

になります。そうすると get() した時には、何が帰ってくるのかというと、 GstAdapter 内にあるデーターのコピーです。実装上は、 戻ってくる GstBuffer と GstAdapter 内にある GstMemory がシェアーされている状態になります。または、 take() の方は get() した後に flush() していると考えても良いかもしれません。

#include <gst/base/gstadapter.h>

int main(int argc, char *argv[])
{
        GstAdapter *a;
        GstBuffer *b, *b1, *b2;

        gst_init(&argc, &argv);

        a = gst_adapter_new();

        b1 = gst_buffer_new_allocate(NULL, 10, NULL);
        g_print("b1 %p\n", b1);
        b2 = gst_buffer_new_allocate(NULL, 20, NULL);
        gst_adapter_push(a, b1);
        gst_adapter_push(a, b2);

        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));

        b = gst_adapter_get_buffer(a, 5);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));
        g_print("b %p\n", b);

        b = gst_adapter_take_buffer(a, 5);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));
        g_print("b %p\n", b);

        return 0;
}
$ gcc -Wall -Wextra -g $(pkg-config --cflags --libs gstreamer-base-1.0) take_vs_get.c
$ ./a.out
b1 0x55eb735c20b0
size: 30
size: 30
b 0x55eb735c22d0
size: 25
b 0x55eb735c23e0

GstBuffer と GstMemory の関係については、こちら を参照してください。

さて、take/get の関数ですが、リファレンスマニュアルにも記載があるので簡単にだけ。

take_buffer() / get_buffer()
GstBuffer を返します。 take_buffer() の場合は、 GstAdapter 内からデーターを取り出してしまいます。
take_buffer_fast() / get_buffer_fast()
戻ってきた GstBuffer が複数の GstMemory で構成されている可能性があります。つまり GstMemory が merge されていません。大きなデーターになると merge する時間、つまりデーターコピーの時間が問題になることがあります。そのため _fast() を使って merge されない状態で GstBuffer を貰います。逆に言うと take_buffer() や get_buffer() では merge されていることが保証されています。
take_list() / get_list()
指定したバイト数分の GstBuffer が GList に繋がれた状態で貰えます。 take_buffer_fast() のように GstBuffer 内に GstMemory が連なっているのではなく、 GstMemory が GList に連なっているわけですね
take_buffer_list() / get_buffer_list()
GList ではなく、~GstBufferList~ に繋がれた状態で貰えます
take()
生の takeメソッドです。戻り値はメモリーアドレスなので、読んだり書いたりできます。このタイプだけ生の get() はありません。理由は以下で。

用途に合わせて、どの getter を使うのか決めることができます。

4 map / unmap

GstAdapter も GstBuffer や GstMemory と同じように map() / unmap() メソッドを持っています。しかし、 map() メソッドの使い方は GstBuffer とは異なります。 map() / unmap() は get() の汎用型のメソッドになっています。

gconstpointer
gst_adapter_map (GstAdapter *adapter,
                 gsize size);

関数のシグネチャーから分る通り map() は、指定した size 分のデーターが書込まれたメモリーアドレスを返してくれます。メモリアドレスは gconstpointer で読み専用で、書き込む事はできません。

gst_adapter_take() があるのに、 gst_adapter_get() がない理由がこれです。 gst_adapter_map() は gst_adapter_get() に alias されていても良いですね。

メモリーを使い終ったら unmap() してください。

#include <stdio.h>
#include <gst/base/gstadapter.h>

int main(int argc, char *argv[])
{
        GstAdapter *a;
        GstBuffer *b;
        char *buf;
        char const * p;
        GstMapInfo i;

        gst_init(&argc, &argv);

        a = gst_adapter_new();
        buf = g_malloc0(10);
        sprintf(buf, "abc");
        b = gst_buffer_new_wrapped(buf, 10);
        gst_adapter_push(a, b);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));

        p = gst_adapter_map(a, 4);
        g_print("%s\n", p);
        gst_adapter_unmap(a);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));

        b = gst_adapter_take_buffer(a, 4);
        gst_buffer_map(b, &i, GST_MAP_READ);
        g_print("%s\n", i.data);
        gst_buffer_unmap(b, &i);
        g_print("size: %" G_GSIZE_FORMAT "\n", gst_adapter_available(a));

        return 0;
}
$ gcc -Wall -Wextra -g $(pkg-config --cflags --libs gstreamer-base-1.0) map.c
$ ./a.out
size: 10
abc
size: 10
abc
size: 6

5 おわりに

Parser を作る時 に重宝する GstAdapter を紹介しました。 GstBaseParse でも実際に内部で使われています。 GstBaseParse の子クラスで実装しなければいけない handle_frame() メソッド に渡ってくる frame->buffer は、実は gst_adapter_get_buffer() したバッファーです。そう take_buffer() じゃないんですね。 finish_frame() した時にはじめて flush() されるようになっています。

Sunday, December 25, 2016

GStreamer でテキスト処理 3: 行に分割する textparse (Parse)

1 はじめに

「GStreamer でテキスト処理 1: Core機能だけで cat, cp, dd, & tee」のおわりにで触れたように、GStreamer には「行」という概念がありません。しかし、多くの Unix ツールは、「行」を基本データーとして扱います。 grep しかり、 sed しかり。そこで、ストリーム内に流れるデーターを行毎に区切ってからダウンストリームに渡すプラグインを作ってみたいと思います。

GStreamer では、ある条件に従ってデーターを区切るエレメントを Parser と呼びます。今回は「改行があれば、そこで区切る」という条件を持った Parser を作成します。 Upstream からは、行を無視して GstBuffer にテキストデーターが詰められてやってきます。これから作成するエレメントは、一行分のデーターだけが入った GstBuffer を Downstream に流すように作ります。

GstElement から派生して作成しても良いのですが、せっかくなので GstBaseParse クラスを使ってみましょう。 GstBaseParse は、パーサーエレメントを作るときに便利なベースクラスです。パーサーとしてやらなければいけない多くの事をやってくれるので、子クラスでは「どのようにデーターをパースするか」というパーサー固有の部分を実装すれば良いようになっています。

GStreamer は、すでに多くのパーサーエレメントを持っています。パーサーエレメントは、だいたい "xxxparse" という名前になっているので、以下のコマンドでリストすることができます。多くのパーサーはすでに GstBaseParse を使って実装されているので自作パーサーを作るときにも参考になります。

$ gst-inspect-1.0 | grep parse

2 決り文句

それでは、いつものように、class のボイラープレート(決まり文句)から始めます。

前回 と違うのは、 GstElement ではなく GstBaseParse を継承するところです。

#ifndef __GST_TEXT_PARSE_H__
#define __GST_TEXT_PARSE_H__

#include <gst/base/gstbaseparse.h>

G_BEGIN_DECLS

G_DECLARE_FINAL_TYPE (GstTextParse, gst_text_parse, GST_TEXT, PARSE, GstBaseParse)

G_END_DECLS

#endif /* __GST_TEXT_PARSE_H__ */
#include <gst/base/gstbaseparse.h>
GstBaseParse クラスを使うので、そのヘッダーファイルを include
G_DECLARE_FINAL_TYPE
GstElement ではなく GstBaseParse を親クラスに指定します。
#include "gsttextparse.h"

struct _GstTextParse
{
        GstBaseParse parent;

};

G_DEFINE_TYPE(GstTextParse, gst_text_parse, GST_TYPE_BASE_PARSE)

static GstStaticPadTemplate sinktemplate =
        GST_STATIC_PAD_TEMPLATE ("sink",
                                 GST_PAD_SINK,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate srctemplate =
        GST_STATIC_PAD_TEMPLATE ("src",
                                 GST_PAD_SRC,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static void
gst_text_parse_class_init (GstTextParseClass * klass)
{
        GstElementClass *element_class = GST_ELEMENT_CLASS(klass);

        gst_element_class_set_static_metadata (element_class,
                                               "Text Parser element",
                                               "Filter",
                                               "Parse text stream",
                                               "Yasushi SHOJI <yasushi.shoji@gmail.com");

        gst_element_class_add_static_pad_template (element_class, &srctemplate);
        gst_element_class_add_static_pad_template (element_class, &sinktemplate);
}

static void
gst_text_parse_init (G_GNUC_UNUSED GstTextParse * parse)
{
}
#include "gsttextparse.h"
先程作ったヘッダーを include
G_DEFINE_TYPE
GstTextParse クラスを作るので、その名前で作成します
GST_STATIC_PAD_TEMPLATE
パッドテンプレートは必要なので、入れてしまいます。今回もとりあえず ANY で
gst_element_class_set_static_metadata()
メタデーターの情報も、Text Parser に合せて更新しておきます。
gst_element_class_add_static_pad_template()
作成したテンプレートも追加しておきます
gst_text_parse_init()
インスタンスの初期化関数は、とりあえず空っぽにしておきましょう
#include <gst/gst.h>
#include "config.h"
#include "gsttextnoop.h"
#include "gsttextparse.h"

static gboolean
plugin_init (GstPlugin * plugin)
{
        gst_element_register(plugin, "textnoop", GST_RANK_NONE, gst_text_noop_get_type());
        gst_element_register(plugin, "textparse", GST_RANK_NONE, gst_text_parse_get_type());
        return TRUE;
}


GST_PLUGIN_DEFINE (
        GST_VERSION_MAJOR,
        GST_VERSION_MINOR,
        gsttext,
        "Text Plugins for GStreamer",
        plugin_init,
        PACKAGE_VERSION,
        "LGPL",
        "GStreamer Text Package",
        "https://github/yashi/gst-plugins-text")
gst_element_register()
textparse を追加しました
project('gst-plugins-text', 'c', version : '0.1.0')

src = ['src/gsttext.c', 'src/gsttextnoop.c', 'src/gsttextparse.c']

gst_dep = dependency('gstreamer-base-1.0', version : '>1.0')

cdata = configuration_data()
cdata.set_quoted('PACKAGE', meson.project_name())
cdata.set_quoted('PACKAGE_VERSION', meson.project_version())

configure_file(output : 'config.h', configuration : cdata)

gsttext = library('gsttext', src, dependencies : gst_dep)
src
作成した 'src/gsttextparse.c' を追加しています。左にあるソースコードから順番にビルドされるようです
dependency()
gstreamer-1.0 ではなく、 gstreamer-base-1.0 に変更しています。 GstBaseParse が gstreamer-base-1.0 に含まれている為です。

3 必ず実装するもの

GstBaseParse から派生させた子クラスでは、以下の要件を必ず実装しなければなりません。

  • src と sink と名前を付けた、パットテンプレートを持つ
  • handle_frame メソッドを実装する
  • ソースパットの Caps を確定(Fixate)する

3.1 handle_frame

handle_frame() を実装していないと、 Segmentation Fault が発生します。

$ GST_PLUGIN_PATH=. gst-launch-1.0 filesrc location=/tmp/a.txt ! textparse ! filesink location=/tmp/b.txt
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Caught SIGSEGV

理由は、 GstBaseParse が Pure Virtual Method の handle_frame() を実装されているか確認せずに呼び出すためです。

static GstFlowReturn
gst_base_parse_handle_buffer (GstBaseParse * parse, GstBuffer * buffer,
    gint * skip, gint * flushed)
{
        :
        :
  frame = gst_base_parse_prepare_frame (parse, buffer);
  ret = klass->handle_frame (parse, frame, skip);
        :
        :
}

以下のように handle_frame() を実装しました。

static void
gst_text_parse_class_init (GstTextParseClass * klass)
{
        GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
        GstBaseParseClass *base_class = GST_BASE_PARSE_CLASS(klass);

        gst_element_class_set_static_metadata (element_class,
                                               "Text Parser element",
                                               "Filter",
                                               "Parse text stream ",
                                               "Yasushi SHOJI <yasushi.shoji@gmail.com");

        gst_element_class_add_static_pad_template (element_class, &srctemplate);
        gst_element_class_add_static_pad_template (element_class, &sinktemplate);

        base_class->handle_frame = gst_text_parse_handle_frame;
}

static GstFlowReturn
gst_text_parse_handle_frame (GstBaseParse * parse,
                             GstBaseParseFrame * frame,
                             G_GNUC_UNUSED gint * skipsize)
{
        gint size;

        size = gst_buffer_get_size(frame->buffer);

        return gst_base_parse_finish_frame(parse, frame, size);
}
base_class->handle_frame =
class_init() の中で、 base_class->handle_frame を設定します
gst_text_parse_handle_frame()
handle_frame() は、 GstBaseParse と GstBaseParseFrame 、そして int を貰い、 GstFlowRetrun を返す関数です
gst_base_parse_finish_frame()
finish_frame() を使って、ダウンストリームにデーターを流します。 finish_frame() は、 frame->buffer のうち「何バイトダウンストリームに流すか」という情報を第3引数の size で受けとります。そのために、 gst_buffer_get_size() を使ってバッファーのサイズを取得しています。とりあえず frame->buffer すべてをダウンストリームに流したいので、 get_size() で得たサイズをそのまま finish_frame() に渡します

3.2 Src Pad の Caps を決定

ソースパッドの Caps が Fix されていないと、実行時に Baseクラスに怒られます。

$ GST_PLUGIN_PATH=. gst-launch-1.0 filesrc location=/tmp/a.txt ! textparse ! filesink location=/tmp/b.txt
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
ERROR: from element /GstPipeline:pipeline0/GstTextParse:textparse0: No caps set
Additional debug info:
gstbaseparse.c(2606): gst_base_parse_push_frame (): /GstPipeline:pipeline0/GstTextParse:textparse0
ERROR: pipeline doesn't want to preroll.
Setting pipeline to NULL ...
Freeing pipeline ...

GstBaseParse には、「 finish_frame() を呼ばれる前に、 Sink pad の Caps が Fixed されていなければならない」というルールがあるようです。これは 「GstBaseParse がそのように実装されている」というだけで GStreamer の制限ではありません。でも考えてみれば、 Parser なのだからダウンストリームに流すデーターは固定されているべきですよね。

ドキュメントには「Fixate the source pad caps when appropriate」と書いてあります。「when appropriate (適切な時に)」って、いつ(?)、どこ(?)なんでしょう? 作っているパーサーによって異なるのでドキュメントでは明言されていません。 gst_base_parse_finish_frame() の前には決定していなければいけないので、 handle_frame() の中でとりあえずやってしまいます。

static GstFlowReturn
gst_text_parse_handle_frame (GstBaseParse * parse,
                             GstBaseParseFrame * frame,
                             G_GNUC_UNUSED gint * skipsize)
{
        gint size;
        GstPad *srcpad = gst_element_get_static_pad(GST_ELEMENT(parse), "src");
        if (!gst_pad_has_current_caps(srcpad))
                gst_pad_set_caps(srcpad, gst_caps_new_empty_simple ("text/x-raw"));

        size = gst_buffer_get_size(frame->buffer);

        return gst_base_parse_finish_frame(parse, frame, size);
}

やっと、パイプラインが動作するようになりました。この状態で cp と同じように filesrc で指定したファイルを filesink に指定したファイルにコピーすることができます。

$ GST_DEBUG_NO_COLOR=1 GST_PLUGIN_PATH=. gst-launch-1.0 filesrc location=/tmp/a.txt ! textparse ! filesink location=/tmp/b.txt
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
Got EOS from element "pipeline0".
Execution ended after 0:00:00.000257185
Setting pipeline to PAUSED ...
Setting pipeline to READY ...
Setting pipeline to NULL ...
Freeing pipeline ...
$ diff -u /tmp/a.txt /tmp/b.txt
$

4 Finding a new line

さて、次は本題の「改行毎に区切ってダウンストリームに流す」部分を作ります。 handle_frame() に渡ってきた frame には、バッファーが付いてきています。 frame->buffer の中から、改行文字列を探しだし、一行分のデーター長を gst_base_parse_finish_frame() に教えてあげれば、完成です。難しいことは GstBaseParse クラスがやってくれるのでとても簡単です。

GstBuffer の中を触るには、 gst_buffer_map() と gst_buffer_unmap() を使うというのは、前回 やった通りです。

GstBuffer が持っているデーターの中から '\n' を探し出します。文字列の中からある文字を探し出す C の関数は strstr() です。今回もこの関数を使うことができますが、GstBuffer には文字の終端を表わす NUL 文字 (\0)も入っているかもしれません。今回は、NUL 文字の事も考えて memchr() を使います。

static GstFlowReturn
gst_text_parse_handle_frame (GstBaseParse * parse,
                             GstBaseParseFrame * frame,
                             G_GNUC_UNUSED gint * skipsize)
{
        GstPad *srcpad;
        ptrdiff_t len;
        guint8 *pos;
        GstMapInfo info;

        srcpad = gst_element_get_static_pad(GST_ELEMENT(parse), "src");
        if (!gst_pad_has_current_caps(srcpad))
                gst_pad_set_caps(srcpad, gst_caps_new_empty_simple ("text/x-raw"));

        gst_buffer_map(frame->buffer, &info, GST_MAP_READ);

        pos = memchr(info.data, '\n', info.size);
        if (!pos)
                return GST_FLOW_OK;
        len = pos - info.data;

        gst_buffer_unmap(frame->buffer, &info);

        return gst_base_parse_finish_frame(parse, frame, len+1);
}
gst_buffer_map()
GstBuffer にアクセスするための情報を &info で貰う
memchr()
info.data の中に改行文字 ('\n') が含まれているか検索。含まれていたら、改行文字のアドレスを戻り値で返す
if (!pos)
貰った frame->buffer の中に改行文字を見付けられなかった場合は、 NULL が帰る。その時は GST_FLOW_OK を返せば、次のデーターと一緒にまた handle_frame() が呼ばれる仕組みになっている
len = pos - info.data;
見つかった改行文字の場所から、データーの先頭アドレスを引く事で、一行のバイト数が分る
gst_buffer_unmap()
GstBuffer へのアクセスが終ったことを伝える
gst_base_parse_finish_frame()
ダウンストリームに、一行分のデーターが入った GstBuffer を流す。

gst_base_parse_finish_frame() の第3引数で len+1 としているのは、改行も含めてダウンストリームに流したいからです。

これで、動作するはずです。実際に一行づつに分れて流れているか identity を使ってデーターを dump してみます。

$ GST_PLUGIN_PATH=. gst-launch-1.0 filesrc location=/tmp/sonnet.txt ! textparse ! identity dump=true ! filesink location=/tmp/b.txt
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
00000000 (0x7f7d40006530): 46 52 4f 4d 20 66 61 69 72 65 73 74 20 63 72 65  FROM fairest cre
00000010 (0x7f7d40006540): 61 74 75 72 65 73 20 77 65 20 64 65 73 69 72 65  atures we desire
00000020 (0x7f7d40006550): 20 69 6e 63 72 65 61 73 65 2c 0a                  increase,.
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
00000000 (0x7f7d4000a3f0): 54 68 61 74 20 74 68 65 72 65 62 79 20 62 65 61  That thereby bea
00000010 (0x7f7d4000a400): 75 74 79 27 73 20 72 6f 73 65 20 6d 69 67 68 74  uty's rose might
00000020 (0x7f7d4000a410): 20 6e 65 76 65 72 20 64 69 65 2c 0a               never die,.

00000000 (0x7f7d4000c3e0): 42 75 74 20 61 73 20 74 68 65 20 72 69 70 65 72  But as the riper
00000010 (0x7f7d4000c3f0): 20 73 68 6f 75 6c 64 20 62 79 20 74 69 6d 65 20   should by time
00000020 (0x7f7d4000c400): 64 65 63 65 61 73 65 2c 0a                       decease,.

00000000 (0x7f7d4000e350): 48 69 73 20 74 65 6e 64 65 72 20 68 65 69 72 20  His tender heir
00000010 (0x7f7d4000e360): 6d 69 67 68 74 20 62 65 61 72 20 68 69 73 20 6d  might bear his m
00000020 (0x7f7d4000e370): 65 6d 6f 72 79 3a 0a                             emory:.

00000000 (0x7f7d400102f0): 42 75 74 20 74 68 6f 75 2c 20 63 6f 6e 74 72 61  But thou, contra
00000010 (0x7f7d40010300): 63 74 65 64 20 74 6f 20 74 68 69 6e 65 20 6f 77  cted to thine ow
00000020 (0x7f7d40010310): 6e 20 62 72 69 67 68 74 20 65 79 65 73 2c 0a     n bright eyes,.

00000000 (0x7f7d400123a0): 46 65 65 64 27 73 74 20 74 68 79 20 6c 69 67 68  Feed'st thy ligh
00000010 (0x7f7d400123b0): 74 27 73 74 20 66 6c 61 6d 65 20 77 69 74 68 20  t'st flame with
00000020 (0x7f7d400123c0): 73 65 6c 66 2d 73 75 62 73 74 61 6e 74 69 61 6c  self-substantial
00000030 (0x7f7d400123d0): 20 66 75 65 6c 2c 0a                              fuel,.

00000000 (0x7f7d400142a0): 4d 61 6b 69 6e 67 20 61 20 66 61 6d 69 6e 65 20  Making a famine
00000010 (0x7f7d400142b0): 77 68 65 72 65 20 61 62 75 6e 64 61 6e 63 65 20  where abundance
00000020 (0x7f7d400142c0): 6c 69 65 73 2c 0a                                lies,.

00000000 (0x7f7d40015350): 54 68 79 73 65 6c 66 20 74 68 79 20 66 6f 65 2c  Thyself thy foe,
00000010 (0x7f7d40015360): 20 74 6f 20 74 68 79 20 73 77 65 65 74 20 73 65   to thy sweet se
00000020 (0x7f7d40015370): 6c 66 20 74 6f 6f 20 63 72 75 65 6c 2e 0a        lf too cruel..

00000000 (0x7f7d40016340): 54 68 6f 75 20 74 68 61 74 20 61 72 74 20 6e 6f  Thou that art no
00000010 (0x7f7d40016350): 77 20 74 68 65 20 77 6f 72 6c 64 27 73 20 66 72  w the world's fr
00000020 (0x7f7d40016360): 65 73 68 20 6f 72 6e 61 6d 65 6e 74 0a           esh ornament.

00000000 (0x7f7d40017200): 41 6e 64 20 6f 6e 6c 79 20 68 65 72 61 6c 64 20  And only herald
00000010 (0x7f7d40017210): 74 6f 20 74 68 65 20 67 61 75 64 79 20 73 70 72  to the gaudy spr
00000020 (0x7f7d40017220): 69 6e 67 2c 0a                                   ing,.

00000000 (0x7f7d400181e0): 57 69 74 68 69 6e 20 74 68 69 6e 65 20 6f 77 6e  Within thine own
00000010 (0x7f7d400181f0): 20 62 75 64 20 62 75 72 69 65 73 74 20 74 68 79   bud buriest thy
00000020 (0x7f7d40018200): 20 63 6f 6e 74 65 6e 74 0a                        content.

00000000 (0x563a714e0750): 41 6e 64 2c 20 74 65 6e 64 65 72 20 63 68 75 72  And, tender chur
00000010 (0x563a714e0760): 6c 2c 20 6d 61 6b 65 73 74 20 77 61 73 74 65 20  l, makest waste
00000020 (0x563a714e0770): 69 6e 20 6e 69 67 67 61 72 64 69 6e 67 2e 0a     in niggarding..

00000000 (0x7f7d40019190): 50 69 74 79 20 74 68 65 20 77 6f 72 6c 64 2c 20  Pity the world,
00000010 (0x7f7d400191a0): 6f 72 20 65 6c 73 65 20 74 68 69 73 20 67 6c 75  or else this glu
00000020 (0x7f7d400191b0): 74 74 6f 6e 20 62 65 2c 0a                       tton be,.

00000000 (0x563a71423760): 54 6f 20 65 61 74 20 74 68 65 20 77 6f 72 6c 64  To eat the world
00000010 (0x563a71423770): 27 73 20 64 75 65 2c 20 62 79 20 74 68 65 20 67  's due, by the g
00000020 (0x563a71423780): 72 61 76 65 20 61 6e 64 20 74 68 65 65 2e 0a     rave and thee..
Got EOS from element "pipeline0".
Execution ended after 0:00:00.000613045
Setting pipeline to PAUSED ...
Setting pipeline to READY ...
Setting pipeline to NULL ...
Freeing pipeline ...

分りやすいように、オフセット 0 の所で改行を入れてみました。ちゃんと GstBuffer には一行づつのデーターが入っているようです。

5 さいごに

どうだったでしょうか? 結局新しく実装したのは、 memchr() まわりだけです。これだけのコード量で簡単な Parser なら実装できてしまいます。

pos = memchr(info.data, '\n', info.size);
if (!pos)
        return GST_FLOW_OK;
len = pos - info.data;

しかし、残念なことにこのコードには1つだけバグがあります。入力ファイルが改行で終っていない場合には、最後の行だけ処理されません。ファイルの最後の行が改行で終っていない場合にも、最後の行を down stream に流すように改造するのは、みなさんへの宿題としたいと思います。

それでは、 Merry Christmas & Happy Coding! ;-)

Tuesday, December 13, 2016

GStreamer でテキスト処理 2: なにもしない identity もどきのエレメント textnoop (Element)

1 はじめに

一番簡単なエレメントは、アップストリームから受け取ったデーターをなにもせずにダウンストリームに流す identity の様なエレメントです。本物の identity エレメントは、 GstBaseTransform を継承し、10以上のプロパティーを持ったエレメントで、コードの行数も 1000 行近くあります。

ここでは、GStreamer の基本クラスである GstElement から派生させ、簡単なエレメント GstTextNoop を作成してみます。まだ一度もプラグインをビルドしたことが無い人は、空っぽのプラグインをビルドする「The Meson Build System」を読んでみてください。

詳しい情報は、 GStreamer の Plugin Writer's Guide を参照してください。

2 決まり文句

GStreamer は Glib が提供している GObject (と GType) を使って オブジェクト指向なプログラミング環境をユーザーに提供しています。C言語は、元々 オブジェクト指向ではない言語なので、GObject のような Object System を導入したとしても、クラスを1つ作成する為にも、以前は面倒な記述が必要でした。しかし Glib バージョン 2.44 から G_DECLARE_FINAL_TYPE() が導入され、一行でクラスの宣言が可能になりました。

G_DECLARE_FINAL_TYPE (GstTextNoop, gst_text_noop, GST_TEXT_NOOP, TEXT_NOOP, GstElement)

このように、 GstElement を継承した GstTextNoop クラスの宣言ができるようになりました。それでは早速 GstTextNoop クラスを見ていきましょう。最初はヘッダーファイルからです。

#ifndef __GST_TEXT_NOOP_H__
#define __GST_TEXT_NOOP_H__

#include <gst/gst.h>

G_BEGIN_DECLS

G_DECLARE_FINAL_TYPE (GstTextNoop, gst_text_noop, GST_TEXT, NOOP, GstElement)

G_END_DECLS

#endif /* __GST_TEXT_NOOP_H__ */
__GST_TEXT_NOOP_H__
ヘッダーが複数回 include されないようにガード
#include <gst/gst.h>
GStreamer のヘッダーを include
G_BEGIN_DECLS
C++ からでも使えるようにするためのマクロ。この辺りまでは GStreamer の作法的に毎回一緒です
G_DECLARE_FINAL_TYPE
拡張を禁止したクラス GstTextNoop を作成します。Java の final class や C# の sealed class と同じです
G_END_DECLS
G_DECLARE_FINAL_TYPE で始めてので、=G_END_DECLS= で終ります

続いて、Cファイル。こちらは、あたりまえですが、宣言(declaration)だけではなく、実体/定義 (definition) が入ります。

#include "gsttextnoop.h"

struct _GstTextNoop
{
        GstElement parent_class;
};

G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT)

static void
gst_text_noop_class_init (GstTextNoopClass * klass)
{
        GstElementClass *gstelement_class;
        gstelement_class = (GstElementClass *) (klass);

        gst_element_class_set_static_metadata (gstelement_class,
                                               "Noop Element",
                                               "Filter",
                                               "Simple element like Identity",
                                               "Me");
}

static void
gst_text_noop_init (GstTextNoop * noop)
{
}
#include "gsttextnoop.h"
上で作成したヘッダーファイルを include
struct _GstTextNoop
GstTextNoop の実体です。マクロで GstTextNoop に typedef されています。実体を .c に隠すのは opaque な struct にするためです。こうすることで外部から GstTextNoop の中が見えなくなり、カプセル化することができます。
G_DEFINE_TYPE
G_DECLARE_FINAL_TYPE で宣言した型の定型部分を実装します。これも決り文句の1つです
gst_text_noop_class_init()
クラスの初期化関数です。 G_DECLARE_FINAL_TYPE や G_DEFINE_TYPE で使った gst_text_noop に _class_init を付けた関数名で作成する必要があります。クラスが初期化された時に呼ばれます。
gst_element_class_set_static_metadata()
GStreamer のクラスではメタデーターが設定されていないとエラーになります。このクラスの名前や種類、説明、作者を記載します
gst_text_noop_init()
インスタンスの初期化関数です。インスタンスが生成された時に呼ばれます。まだなにも必要ないので、空っぽで良いです

これでクラスは作成できました。名前を置き換えれば、自分のクラスを作ることができます。

gst-template にはテンプレートから クラスのコードを生成するスクリプト もあり、Plugin Writer's Guide でもこれを使っています。しかし、最初から生成されるコードが多いと嫌になるので、今回はあえて手で作成しました。 また、Vala コンパイラーを使うことででも簡単にクラスのボイラープレートコードを生成することができます。

それでは、作成したクラスを、ここ で作成した空っぽのプラグインに追加してみましょう。

#include <gst/gst.h>
#include "config.h"
#include "gsttextnoop.h"

static gboolean
plugin_init (GstPlugin * plugin)
{
        gst_element_register(plugin, "textnoop", GST_RANK_NONE, gst_text_noop_get_type());
        return TRUE;
}


GST_PLUGIN_DEFINE (
        GST_VERSION_MAJOR,
        GST_VERSION_MINOR,
        gsttext,
        "Text Plugins for GStreamer",
        plugin_init,
        PACKAGE_VERSION,
        "LGPL",
        "GStreamer Text Package",
        "https://github/yashi/gst-plugins-text")
#include "gsttextnoop.h"
GstTextNoop クラスのヘッダーを include
gst_element_register()
エレメントをプラグインに追加します。第1引数は受け取ったプラグイン、第2引数はエレメントの名前「 textnoop 」、第3引数はランクです。ランク は4つから選べます。とりあえずは、一番優先度が低い NONE にしておきましょう。最後の引数は GstTextNoop のタイプを取得するための関数を指定します。 G_DEFINE_TYPE が生成してくれているので、自分で作った覚えがなくても大丈夫です

ソースコードは以上です。ビルドするためには、ビルドシステムにソースコードを教えてあげる必要があります。

project('gst-plugins-text', 'c', version : '0.1.0')

src = ['src/gsttextnoop.c', 'src/gsttext.c']

gst_dep = dependency('gstreamer-1.0', version : '>1.0')

cdata = configuration_data()
cdata.set_quoted('PACKAGE', meson.project_name())
cdata.set_quoted('PACKAGE_VERSION', meson.project_version())

configure_file(output : 'config.h', configuration : cdata)

gsttext = library('gsttext', src, dependencies : gst_dep)
src
作成した 'src/gsttextnoop.c' を追加します。他は変更しなくても大丈夫です
$ GST_PLUGIN_PATH=`pwd`/build gst-inspect-1.0 gsttext
  Plugin Details:
    Name                     gsttext
    Description              Text Plugins for GStreamer
    Filename                 ./libgsttext.so
    Version                  0.1.0
    License                  LGPL
    Source module            gst-plugins-text
    Binary package           GStreamer Text Package
    Origin URL               https://github/yashi/gst-plugins-text

    textnoop: Noop Element

    1 features:
    +-- 1 elements

エレメントの情報も、取ることができます。

$ GST_PLUGIN_PATH=`pwd`/build gst-inspect-1.0 textnoop
  Factory Details:
    Rank                     none (0)
    Long-name                Noop Element
    Klass                    Filter
    Description              Simple element like Identity
    Author                   Me

  Plugin Details:
    Name                     gsttext
    Description              Text Plugins for GStreamer
    Filename                 ./libgsttext.so
    Version                  0.1.0
    License                  LGPL
    Source module            gst-plugins-text
    Binary package           GStreamer Text Package
    Origin URL               https://github/yashi/gst-plugins-text

  GObject
   +----GInitiallyUnowned
  +----GstObject
               +----GstElement
                     +----GstTextNoop

  Pad Templates:
    none

  Element Flags:
    no flags set

  Element Implementation:
    Has change_state() function: gst_element_change_state_func

  Element has no clocking capabilities.
  Element has no URI handling capabilities.

  Pads:
    none

  Element Properties:
    name                : The name of the object
                          flags: readable, writable
                          String. Default: "textnoop0"
    parent              : The parent of the object
                          flags: readable, writable
                          Object of type "GstObject"

3 Pad

インスペクトは正しく認識するようになりましたが、まだパイプラインを形成できません。

GST_PLUGIN_PATH=build gst-launch-1.0 -q fakesrc num-buffers=0 ! textnoop ! fakesink
WARNING: erroneous pipeline: could not link fakesrc0 to textnoop0

fakesrc から textnoop エレメントに link できないと言われました。エレメントを繋ぐのは Pad なので、pad がないと繋がりません。

Pad を作成するのに良く使われる方法は、Pad Template からの作成です。

#include "gsttextnoop.h"


struct _GstTextNoop
{
        GstElement parent_class;

};

G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT)

static GstStaticPadTemplate sinktemplate =
        GST_STATIC_PAD_TEMPLATE ("sink",
                                 GST_PAD_SINK,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate srctemplate =
        GST_STATIC_PAD_TEMPLATE ("src",
                                 GST_PAD_SRC,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static void
gst_text_noop_class_init (GstTextNoopClass * klass)
{
        GstElementClass *gstelement_class;
        gstelement_class = (GstElementClass *) (klass);

        gst_element_class_set_static_metadata (gstelement_class,
                                               "Noop Element",
                                               "Filter",
                                               "Simple element like Identity",
                                               "Me");
}

static void
gst_text_noop_init (GstTextNoop * noop)
{
        GstPad *srcpad;
        GstPad *sinkpad;

        srcpad = gst_pad_new_from_static_template(&srctemplate, "src");
        gst_element_add_pad(GST_ELEMENT(noop), srcpad);

        sinkpad = gst_pad_new_from_static_template(&sinktemplate, "sink");
        gst_element_add_pad(GST_ELEMENT(noop), sinkpad);
}
GST_STATIC_PAD_TEMPLATE
static の Pad Template を生成するマクロ。テンプレートの名前、Padの方向 (SRC / SINK)、Pad がいつ存在するか、static の Caps (ANY / NONE) を指定します。 textnoop エレメントは、source 側にも sink 側にも繋がるエレメントなので 2つパッドを作ります。テンプレートもそれぞれ作成します。なにが繋がっても良いので GST_STATIC_CAPS_ANY を指定します。
gst_pad_new_from_static_template()
テンプレートから Padを作成します。第1引数にテンプレートを、第2引数に作成する Pad の名前を指定します。こちらも、 source 側と sink 側の両方作成します
gst_element_add_pad
noop エレメントに Pad を追加します。 GstElement クラスのメソッドなので noop を GST_ELEMENT に変換し、第1引数に渡します。第2引数には gst_pad_new_from_static_template() で作成した Pad を指定します

これでエラーせずに繋がりました。

GST_PLUGIN_PATH=. gst-launch-1.0 -q fakesrc num-buffers=0 ! textnoop ! fakesink

4 Pad: Template

パッドを作成して繋がるところまではできましたが、Pad の情報が inspect で出てきません。

GST_PLUGIN_PATH=. gst-inspect-1.0 noop
Factory Details:
  Rank                     none (0)
  Long-name                Noop Element
  Klass                    Filter
  Description              Simple element like Identity
  Author                   Me

Plugin Details:
  Name                     gsttext
  Description              Text Plugins for GStreamer
  Filename                 ./libgsttext.so
  Version                  0.1.0
  License                  LGPL
  Source module            gst-plugins-text
  Binary package           GStreamer Text Package
  Origin URL               https://github/yashi/gst-plugins-text

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstTextNoop

Pad Templates:
  none                  ← ここね

Element Flags:
  no flags set

Element Implementation:
  Has change_state() function: gst_element_change_state_func

Element has no clocking capabilities.
Element has no URI handling capabilities.

Pads:
  SRC: 'src'
    Pad Template: 'src'
  SINK: 'sink'
    Pad Template: 'sink'

Element Properties:
  name                : The name of the object
                        flags: readable, writable
                        String. Default: "textnoop0"
  parent              : The parent of the object
                        flags: readable, writable
                        Object of type "GstObject"

これは、クラスに Pad Tempalte を追加していないからです。追加してしまいましょう。

#include "gsttextnoop.h"


struct _GstTextNoop
{
        GstElement parent_class;

};

G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT)

static GstStaticPadTemplate sinktemplate =
        GST_STATIC_PAD_TEMPLATE ("sink",
                                 GST_PAD_SINK,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate srctemplate =
        GST_STATIC_PAD_TEMPLATE ("src",
                                 GST_PAD_SRC,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static void
gst_text_noop_class_init (GstTextNoopClass * klass)
{
        GstElementClass *gstelement_class;
        gstelement_class = (GstElementClass *) (klass);

        gst_element_class_set_static_metadata (gstelement_class,
                                               "Noop Element",
                                               "Filter",
                                               "Simple element like Identity",
                                               "Me");

        gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
        gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
}

static void
gst_text_noop_init (GstTextNoop * noop)
{
        GstPad *srcpad;
        GstPad *sinkpad;

        srcpad = gst_pad_new_from_static_template(&srctemplate, "src");
        gst_element_add_pad(GST_ELEMENT(noop), srcpad);

        sinkpad = gst_pad_new_from_static_template(&sinktemplate, "sink");
        gst_element_add_pad(GST_ELEMENT(noop), sinkpad);
}
gst_element_class_add_static_pad_template()
noop エレメントのクラスに、Pad Template を追加します
Factory Details:
  Rank                     none (0)
  Long-name                Noop Element
  Klass                    Filter
  Description              Simple element like Identity
  Author                   Me

Plugin Details:
  Name                     gsttext
  Description              Text Plugins for GStreamer
  Filename                 ./libgsttext.so
  Version                  0.1.0
  License                  LGPL
  Source module            gst-plugins-text
  Binary package           GStreamer Text Package
  Origin URL               https://github/yashi/gst-plugins-text

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstTextNoop

Pad Templates:
  SINK template: 'sink'
    Availability: Always
    Capabilities:
      ANY

  SRC template: 'src'
    Availability: Always
    Capabilities:
      ANY


Element Flags:
  no flags set

Element Implementation:
  Has change_state() function: gst_element_change_state_func

Element has no clocking capabilities.
Element has no URI handling capabilities.

Pads:
  SRC: 'src'
    Pad Template: 'src'
  SINK: 'sink'
    Pad Template: 'sink'

Element Properties:
  name                : The name of the object
                        flags: readable, writable
                        String. Default: "textnoop0"
  parent              : The parent of the object
                        flags: readable, writable
                        Object of type "GstObject"

5 Pad: chain

ところが、データーを流そうとすると怒られます。いろいろ起りますね…。もうちょいです。

GST_PLUGIN_PATH=. gst-launch-1.0 -q fakesrc num-buffers=1 ! textnoop ! fakesink

(gst-launch-1.0:20837): GStreamer-CRITICAL **: chain on pad textnoop0:sink but it has no chainfunction
ERROR: from element /GstPipeline:pipeline0/GstFakeSrc:fakesrc0: Internal data stream error.
Additional debug info:
gstbasesrc.c(2950): gst_base_src_loop (): /GstPipeline:pipeline0/GstFakeSrc:fakesrc0:
streaming stopped, reason not-supported (-6)
ERROR: pipeline doesn't want to preroll.

chain on pad textnoop0:sink but it has no chainfunction (textnoop の sink pad で chain しようとしたけど、チェイン関数ないよ) って言われています。まぁ、たしかに追加していないので、無いわけですが..。 chain / チェイン / チェーン 関数とは、 GStreamer のパイプラインに GstBuffer を流している張本人の関数です。ソースエレメントから始まってシンクエレメントに辿りつくまでに、各エレメント内の chain 関数を数珠繋ぎに呼び出すことから chain 関数と呼ばれています。

各エレメントは Pad で繋ると言う話をしたと思いますが、これには変更がありません。つまり「エレメント内の chain 関数」とは、 「Pad に登録されている chain 用の関数」ということなのです。 Muxer のようなエレメントは、Pad によって音声を流したり映像を流したりします。なので、chain 関数はエレメント単位ではなく、 Pad 単位になっています。

関数名も gst_pad_set_chain_function() となっています。

パイプラインの中は、上流から下流に、つまりsourceエレメントから sink エレメントに流れます。 source エレメントの source pad に繋っているのは、あるエレメントの sink Pad です。今回例であれば noop エレメントの sink Pad です。上流のエレメントは下流のエレメントの sink pad を探し、その Pad に登録されている chain 関数を呼びだすようになっています。なので noop でも sink パッドに chain 関数を登録します。

#include "gsttextnoop.h"

struct _GstTextNoop
{
        GstElement parent_class;

};

G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT)

static GstStaticPadTemplate sinktemplate =
        GST_STATIC_PAD_TEMPLATE ("sink",
                                 GST_PAD_SINK,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate srctemplate =
        GST_STATIC_PAD_TEMPLATE ("src",
                                 GST_PAD_SRC,
                                 GST_PAD_ALWAYS,
                                 GST_STATIC_CAPS_ANY);

static GstFlowReturn gst_text_noop_chain(GstPad * pad, GstObject * parent,
                                           GstBuffer * buffer);

static void
gst_text_noop_class_init (GstTextNoopClass * klass)
{
        GstElementClass *gstelement_class;
        gstelement_class = (GstElementClass *) (klass);

        gst_element_class_set_static_metadata (gstelement_class,
                                               "Noop Element",
                                               "Filter",
                                               "Simple element like Identity",
                                               "Me");

        gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
        gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
}

static void
gst_text_noop_init (GstTextNoop * noop)
{
        GstPad *srcpad;
        GstPad *sinkpad;

        srcpad = gst_pad_new_from_static_template(&srctemplate, "src");
        gst_element_add_pad(GST_ELEMENT(noop), srcpad);

        sinkpad = gst_pad_new_from_static_template(&sinktemplate, "sink");
        gst_element_add_pad(GST_ELEMENT(noop), sinkpad);

        gst_pad_set_chain_function(sinkpad, gst_text_noop_chain);
}

static GstFlowReturn
gst_text_noop_chain(GstPad * sinkpad,
                      GstObject * parent,
                      GstBuffer * buffer)
{
        GstPad *srcpad;
        GstFlowRetrun ret;

        srcpad = gst_element_get_static_pad(GST_ELEMENT(parent), "src");
        ret = gst_pad_push(srcpad, buffer);
        gst_object_ref(srcpad);
        return ret;
}
static GstFlowReturn gst_text_noop_chain();
forward declaration です。関数は先に宣言しておいて、下に実体を書くのが GObject 流の作法のようです。
gst_pad_set_chain_function()
第1引数に対象の Pad を、第2引数に関数を指定します
gst_text_noop_chain()
chain 関数の実体です。 srcpad を取得し、引数で渡ってきた GstBuffer を srcpad に push します。 get() した srcpad はリファレンスが増えているので忘れずに gst_object_ref() で減らしてください。
GST_PLUGIN_PATH=. gst-launch-1.0 -q filesrc location=/tmp/a.txt ! textnoop ! filesink location=/tmp/b.txt
cmp /tmp/a.txt /tmp/b.txt

これで、正しくデーターが流れるようになりました。 a.txt をコピーした b.txt が同じ内容になっています。

6 おわりに

他にもエレメントでは、イベントの処理や、クエリーに反応したり、プロパティーの設定など、いろいろな事をやらなければなりません。それらの話は、また今度。

Monday, December 12, 2016

The Meson Build System

1 はじめに

Meson というビルドシステムをご存知でしょうか? Jussi Pakkanen が作成したビルドシステムで、最近 GNOME 界隈で人気です。 Meson 自体は、 GNU Make の様に実際に build せず、 ビルド自体は Chrome Project で作られた Ninja を使います。 つまり Meson は GYP や CMake と同じ立ち位置です。

GYP → Make から GYP → Ninja 、Meson → Ninja と感じでしょうか。

Ninja は、 build system の高速 assembler を目指して作られただけあって、いろいろな Generator が生まれました。 その1つが Meson です。

2 Hello World

まず最初は、C の Hello World でどのように Mesonを記述するのか見てみましょう。

C のコードはいたってふつうの Hello World です。

#include <stdio.h>

int main()
{
        printf("Hello, World!\n");
        return 0;
}

このコードをコンパイルできるようにする Meson のコードは、次の2行です。これを meson.build というファイルに記載します。

project('helloworld', 'c')
executable('hello', 'hello.c')

project() でプロジェクト名と言語を、 executable() で実行ファイル名とソースファイル名を記述します。

Meson は In-Tree ビルドに対応していません。かならず Out-of-Tree ビルドになります。 Meson を実行するには、

meson [source dir] [build dir]

と、します。hello.c が置いてあるディレクトリで実行するなら、次のようになります。

$ ls
hello.c  meson.build
$ meson . build
The Meson build system
Version: 0.36.0
Source dir: /tmp/meson
Build dir: /tmp/meson/build
Build type: native build
Project name: helloworld
Native c compiler: ccache cc (gcc 6.2.1)
Build machine cpu family: x86_64
Build machine cpu: x86_64
Build targets in project: 1

Meson がファイルを生成し終ったら Ninja を使ってビルドします。

$ ninja -C build
ninja: Entering directory `build'
[2/2] Linking target hello

無事、 build/hello が生成されました。

$ build/hello
Hello, World!

3 Build Application against GStreamer with Meson

ということで、次は GStreamer を使ったアプリを Meson でビルドしてみます。こちらもとてもシンプルなコード で試してみます。

#include <gst/gst.h>

int main(int argc, char *argv[])
{
        GMainLoop *mainloop;
        GstElement *pipeline;
        GError *error = NULL;

        gst_init(&argc, &argv);

        mainloop = g_main_loop_new(NULL, FALSE);
        pipeline = gst_parse_launch("videotestsrc ! autovideosink", &error);

        gst_element_set_state(pipeline, GST_STATE_PLAYING);
        g_main_loop_run(mainloop);

        return 0;
}

このコードを、先程と同じ meson.build を使ってビルドすると

[1/2] Compiling c object 'hellogst@exe/hellogst.c.o'
FAILED: hellogst@exe/hellogst.c.o
ccache cc  '-Ihellogst@exe' '-fdiagnostics-color=always' '-I.' '-I..' '-pipe' '-Wall' '-Winvalid-pch' '-O0' '-g' '-MMD' '-MQ' 'hellogst@exe/hellogst.c.o' '-MF' 'hellogst@exe/hellogst.c.o.d' -o 'hellogst@exe/hellogst.c.o' -c ../hellogst.c
../hellogst.c:1:21: fatal error: gst/gst.h: No such file or directory
 #include <gst/gst.h>
                     ^
compilation terminated.
ninja: build stopped: subcommand failed.

と、 gst/gst.h が見つからないと怒られてしまいます。それもそのはずで、ビルドシステムは GStreamer のヘッダーに関してなにも知りません。そこで、このアプリは GStreamer 1.0 に依存していることを教えてあげます。

project('hello-gst', 'c')

gst_dep = dependency('gstreamer-1.0', version : '>=1.0')

executable('hellogst', 'hellogst.c', dependencies : gst_dep)

dependency() が、依存関係の記述です。 gstreamer-1.0 に依存し、バージョンは 1.0以上 という風に指定しました。この記述、実は pkg-config のモジュールの書き方と一緒です。 Meson は実行されているシステムでどのように依存関係を解決すべきか知っています。 Linux 上では pkg-config を使って解決してくれます。 もちろん Meson は Multi Platform 対応なので Windows 上でも MacOS 上でも、同じ書き方で GStreamer の依存関係を解決してくれます。

依存関係を追記したら、 build ディレクトリを消して試してみましょう。 (正確には Meson は一度実行された後は、自動で設定が変った後に自分自身を再度実行します。そのため削除する必要はありません。ここでは、あえて GStreamer 1.0 を見つけるログを表示するために削除しています。)

$ rm -r build
$ meson . build
The Meson build system
Version: 0.36.0
Source dir: /tmp/hello-gst
Build dir: /tmp/hello-gst/build
Build type: native build
Project name: hello-gst
Native c compiler: ccache cc (gcc 6.2.1)
Build machine cpu family: x86_64
Build machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (0.29)
Native dependency gstreamer-1.0 found: YES 1.10.2  ← GStreamer を見つけました
Build targets in project: 1

続いて ninja でビルドします。

$ ninja -C build
[0/1] Regenerating build files
The Meson build system
Version: 0.36.0
Source dir: /tmp/hello-gst
Build dir: /tmp/hello-gst/build
Build type: native build
Project name: hello-gst
Native c compiler: ccache cc (gcc 6.2.1)
Build machine cpu family: x86_64
Build machine cpu: x86_64
Build targets in project: 1
[2/2] Linking target hellogst
$ cd build
$ ls
build.ninja            hellogst      meson-logs
compile_commands.json  hellogst@exe  meson-private

hellogst が生成されました。

他にも、いろいろな中間ファイルが生成されています。

$ tree
.
├── build.ninja
├── compile_commands.json
├── hellogst
├── hellogst@exe
│   └── hellogst.c.o
├── meson-logs
│   └── meson-log.txt
└── meson-private
    ├── build.dat
    ├── coredata.dat
    ├── install.dat
    ├── meson_benchmark_setup.dat
    ├── meson_test_setup.dat
    ├── sanitycheckc.c
    └── sanitycheckc.exe

3 directories, 12 files

4 Build GStreamer Plugin with Meson

さて、やっと本題です。 GStreamer の Plugin を Meson でビルドしてみます。次の空っぽの Plugin をビルドします。

#include <gst/gst.h>
#include "config.h"

static gboolean
plugin_init (GstPlugin * plugin)
{
        return TRUE;
}


GST_PLUGIN_DEFINE (
        GST_VERSION_MAJOR,
        GST_VERSION_MINOR,
        gsttext,
        "Text Plugins for GStreamer",
        plugin_init,
        PACKAGE_VERSION,
        "LGPL",
        "GStreamer Text Package",
        "https://github/yashi/gst-plugins-text")

Meson.build は次のようになります。

project('gst-plugins-text', 'c', version : '0.1.0')

src = ['src/gsttext.c']

gst_dep = dependency('gstreamer-1.0', version : '>1.0')

cdata = configuration_data()
cdata.set_quoted('PACKAGE', meson.project_name())
cdata.set_quoted('PACKAGE_VERSION', meson.project_version())

configure_file(output : 'config.h', configuration : cdata)

gsttext = library('gsttext', src, dependencies : gst_dep)
project()
新しく version を追加しています。それ以外は、名前が異なるだけで同じ内容です
src
executable() に直接指定していたソースコードを変数に外出ししました。ファイルのリストなので [ ] で括っています
configuration_data()

設定を記載するためのデーターを作成します。最初はからっぽ。言語で C を選択していると #define が記述されたファイルが生成されます

/*
 * Autogenerated by the Meson build system.
 * Do not edit, your changes will be lost.
 */

#pragma once

#define PACKAGE "gst-plugins-text"

#define PACKAGE_VERSION "0.1.0"
cdata.set_quoted()
設定を追加します。設定値を " " で括りたいので _quoted を使っています。括らないなら set() が使えます
configure_file()
設定ができあがったら、ファイルに書き出します。ここでは autotools の名残りで config.h にしてみました
library()
実行ファイルではなくライブラリを作成したいので、 executable() ではなく library() を使います。ファイル名と、ソースコード、そして依存を記載します
$ meson . build
The Meson build system
Version: 0.36.0
Source dir: /home/yashi/src/gst/gst-plugins-text
Build dir: /home/yashi/src/gst/gst-plugins-text/build
Build type: native build
Project name: gst-plugins-text
Native c compiler: ccache cc (gcc 6.2.1)
Build machine cpu family: x86_64
Build machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (0.29)
Native dependency gstreamer-1.0 found: YES 1.10.2
Build targets in project: 1
ninja -C build
ninja: Entering directory `build'
[2/2] Linking target libgsttext.so
GST_PLUGIN_PATH=`pwd`/build gst-inspect-1.0 gsttext
Plugin Details:
  Name                     gsttext
  Description              Text Plugins for GStreamer
  Filename                 /home/yashi/src/gst/gst-plugins-text/build/libgsttext.so
  Version                  0.1.0
  License                  LGPL
  Source module            gst-plugins-text
  Binary package           GStreamer Text Package
  Origin URL               https://github/yashi/gst-plugins-text


  0 features:

5 おわりに

いかがだったでしょうか? ビルドスクリプトは頻繁に更新するものではないので、Autotools の書き方を毎回忘れてしまうという人でも Meson だったら簡単じゃないでしょうか? Linux でも Windows でも MacOS でも使えるという利点や、依存関係が Python と ninja だけになるという点も嬉しいです。しかし、プログラマーにとって一番嬉しいのは、ビルド時間が劇的に早くなることによって、コンパイルによって集中が途切れず、開発効率が改善されることじゃないでしょうか。え? コンパイル中に休憩 できなくなって困るなんてことはないですよね?