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() されるようになっています。

No comments:

Post a Comment