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

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

逆に GStreamer のコアは、 GST_ で始まるカテゴリー名を使っています。 GST_STATESGST_CLOCKGST_PADSGST_BUS など抽象的な概念、 GST_INITGST_MEMORY 、GST_PLUGIN_LOADINGGST_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.cgsttracerutils.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_STATICGST_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.h550行目あたり

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 内にあるデーターのコピーです。実装上は、 戻ってくる GstBufferGstAdapter 内にある 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

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

さて、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()
指定したバイト数分の GstBufferGList に繋がれた状態で貰えます。 take_buffer_fast() のように GstBuffer 内に GstMemory が連なっているのではなく、 GstMemoryGList に連なっているわけですね
take_buffer_list() / get_buffer_list()
GList ではなく、~GstBufferList~ に繋がれた状態で貰えます
take()
生の takeメソッドです。戻り値はメモリーアドレスなので、読んだり書いたりできます。このタイプだけ生の get() はありません。理由は以下で。

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

4 map / unmap

GstAdapterGstBufferGstMemory と同じように 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 に変更しています。 GstBaseParsegstreamer-base-1.0 に含まれている為です。

3 必ず実装するもの

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

  • srcsink と名前を付けた、パットテンプレートを持つ
  • 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() は、 GstBaseParseGstBaseParseFrame 、そして 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! ;-)

Wednesday, December 14, 2016

Glib Object System: 派生クラスの作成

「そもそも、Glib / GObject を使ってクラスを作る方法が分らない」という話があったので、書いてみようかと思います。

1 まずは C で書く

40行程度なので、いきなりコードから

/* Declaration Begin */

#include <glib-object.h>

#define MY_TYPE_FOO (my_foo_get_type())
G_DECLARE_FINAL_TYPE (MyFoo, my_foo, MY, FOO, GObject)

/* Declaration End */
/* Definition Begin */

struct _MyFoo
{
        GObject parent_class;
};

G_DEFINE_TYPE(MyFoo, my_foo, G_TYPE_OBJECT)

static void
my_foo_class_init (MyFooClass * klass)
{
        g_print("class init\n");
}

static void
my_foo_init (MyFoo * noop)
{
        g_print("instance init\n");
}

MyFoo * my_foo_new(void)
{
        return (MyFoo*) g_type_create_instance(MY_TYPE_FOO);
}

/* Definition End */

int main()
{
        g_print("main\n");
        my_foo_new();
        my_foo_new();
        return 0;
}

これで全部です。

では、最初の行から説明していきます。

#include <glib-object.h>
GObject を使う場合に include するヘッダーです
#define MY_TYPE_FOO (my_foo_get_type())
これだけは、 G_DECLARE_FINAL_TYPE が生成できないので手で作る必要があります。 my_foo_get_type() と書けば事足りるので、「そもそも、必要あるのか?」と自分は思ってしまうのですが、 GObject の流儀なので作成しておきましょう。 G_DECLARE_FINAL_TYPE がこのマクロを作成できないのは、C 言語の制限で、C言語のマクロ( #define )の中では #define できない為です
G_DECLARE_FINAL_TYPE
MyFoo クラスの宣言をしてくれます。 my_foo という「モジュール_クラス」の名前を宣言します。 MY というモジュールの下で FOO というクラスを作成することになります。こんな感じに 2種類の宣言を書かなければいけない所が C に無理矢理オブジェクトシステムを入れたイマイチ感を匂わせます。最後の引数が親クラスです。GObject から派生させたクラスにしたいので、 GObject を指定します。GStreamer のエレメントを作る場合は、 GstElemet を指定したりします。

ここまでが宣言になります。もし他のファイルからこのクラスを使う場合は、ここまでをヘッダーファイル .h に記載します。ここからは実際のクラスの定義に入って行きます。他のコードと分けるには、ここからのコードは .c に記載してください。

struct _MyFoo
ここからがクラス本体です。メソッドがないので、親クラスである GObject を内包しているだけの struct です
G_DEFINE_TYPE
GObject タイプシステムとして必要なコードを生成してくれるマクロです。 _get_type() の生成がメインです。作成するタイプのキャメルケース を第1引数に、スネークケース を第2引数に指定します。最後は親のタイプ名、ここでは GObject のタイプ名、を指定します
my_foo_class_init()
クラスの初期化関数です。 GObject Type System が MyFoo クラスを始めて使う場合に呼ばれます。大抵の場合は最初のインスタンスを作成する時でしょうか。
my_foo_init()
インスタンスの初期化関数です。インスタンスが生成される度に呼び出されます
my_foo_new()
MyFoo クラスのコンストラクターです。シンプルな場合は、直接 Type System のジェネリックなコンストラクタ g_type_create_instance() を呼んでも良いですが、ラップしておいた方が無難です。

main関数で、インスタンスを2つ作成して、どのタイミングでクラスの初期化関数とインスタンスの初期化関数が呼ばれるか確認してみます。

$ meson . build
$ ninja -C build
$ build/foo
main
class init
instance init
instance init

main関数が始まって、「main」と表示されます。次に my_foo_new() が始めて呼び出された時に GObject Type System は MyFoo タイプを初期化します。これが結果的に my_foo_class_init() を呼び出します。クラスが初期化されると実際にインスタンスが生成され my_foo_init() が呼び出されます。

2回目に my_foo_init() が呼び出された時には Type System は MyFoo クラスを知っているのでクラスが初期化されることはありません。すぐにインスタンスの初期化関数 my_foo_init() が呼び出されます。

こんな感じに、シンプルなクラスであれば、それなりの行数で作成することができます。もちろん GObject を継承しているので MyFoo のオブジェクトも プロパティー を持ったり シグナル を扱うことができます。

とは言え Ruby や C# のように class 生成をサポートしている言語には敵いませんね。

2 次は Vala でも書いてみる

GObject Type System を使った言語で Vala という言語があります。 Hello World はこんな感じです。

class Demo.HelloWorld : GLib.Object {

    public static int main(string[] args) {

    stdout.printf("Hello, World\n");

    return 0;
    }
}

この Vala、「Vala → C → ネイティブコード」という順番にコンパイルしています。つまり、実行ファイルを生成する前に C へのコンパイルで止めてあげれば、コードジェネレーターとして使うことができます。

先程の C のコードと同じ動きをする Vala のコードは、こんな感じです。

class My.Foo {
        static construct {
                stdout.printf("class init\n");
        }
        public Foo() {
                stdout.printf("instance init\n");
        }
}

public static int main(string[] args) {
    stdout.printf("main\n");
        new My.Foo();
        new My.Foo();

    return 0;
}
$ valac myfoo.vala
$ ./myfoo
main
class init
instance init
instance init

この Vala のコードから C のコードを生成するには

$ valac -h myfoo.h -C myfoo.vala

とします。 myfoo.hmyfoo.c が生成されます。生成されたコードは、数百行あるので掲載は割愛します。内容を確認してみると勉強になりますよ。

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_TYPEG_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 を取得し、引数で渡ってきた GstBuffersrcpad に 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 おわりに

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