Friday, December 11, 2015

GStreamer でプログラミング 6 (Buffer と Memory)

GStreamer のパイプライン内を流れているのは GstEventGstBuffer ということでしたが、 実際にマルチメディアデーターを運んでいる方の GstBuffer を覗いてみましょう。

GstBuffer の構造体 は、

struct GstBuffer {
GstMiniObject mini_object;
GstBufferPool *pool;
/* timestamp */
GstClockTime pts;
GstClockTime dts;
GstClockTime duration;
/* media specific offset */
guint64 offset;
guint64 offset_end;
};
view raw gstbuffer.h hosted with ❤ by GitHub

と定義されています。

APIドキュメントによると

バッファーは、GStreamerのデーターを運ぶ基本ユニットであり、タイミング情報や、オフセット、バッファーが持つ GstMemory ブロックに関係するメタデーターなどが含まれている。

と言うことらしいです。メタデーターはとりあえず置いといて、 GstMemory が気になりますね。だって、 struct GstBuffer の中に GstMemory なんて入っていないし、どうなってんでしょう? 答は GstBufferImpl にありました。

typedef struct
{
GstBuffer buffer;
gsize slice_size;
/* the memory blocks */
guint len;
GstMemory *mem[GST_BUFFER_MEM_MAX];
/* memory of the buffer when allocated from 1 chunk */
GstMemory *bufmem;
/* FIXME, make metadata allocation more efficient by using part of the
* GstBufferImpl */
GstMetaItem *item;
} GstBufferImpl;
view raw gstbufferimpl.h hosted with ❤ by GitHub

つまり、

こんな風に、Bridge パターンを使って GstBufferImpl の方で GstMemory を持っているんですね。

さて、 GstMemory の方に行ってみましょう。 GstMemoryGstAllocator によって確保されるメモリです。 GstAllocator は、「いろいろな条件に合ったメモリをよしなに確保してくれるアロケーター」とでも今は覚えておいてください。 gst_allocator_alloc() でアロケーターを指定しない、つまり NULL を指定すると、システムのデフォルトアロケーターが使われます。

Sorry, your browser does not support SVG.

確保できたメモリは、 malloc() されたメモリのように、そのまま扱うことができません。どのようなメモリが確保できたかは、利用したアロケーターによって異なります。もしかすると、ぜんぜんメモリとは異なる状態かもしれません。このようなデーターを 「Opaque data type」と言います。

どのような構造のメモリなのか分らないので、そのままではアクセスすることができません。そのため CPUから利用する場合は、かならず「アクセスするよ (map)」「終ったよ (unmap)」と指示する必要があります。それぞれ、 gst_memory_map()gst_memory_unmap() です。 組み込みやカーネルのコードを知っている人であれば、「メモリ空間にマップしたので、触れる」と言えば、感覚として分るでしょうか? または、ファイルを mmap() してメモリとして触るような感じでしょうか。

malloc() されるようなシステムメモリであれば、このような仕組みは不要です。しかし GStreamer はマルチメディアを扱うフレームワークです。ビデオ処理や OpenGL 用のメモリを確保する場合もあります。その時 GPU 側のメモリの方が都合が良いかもしれません。 CUDA 6 の Unified MemoryAMD の HSA の様に CPU から直接触れる GPU メモリも増えそうですが、実際のハードウェアや OS によって状況が異なります。そのため、メモリアクセスの前後で、必要な処理をするタイミングを作ってあげる必要があったのです。

それでは、早速 GstBufferGstMemory を使って実験してみましょう。次の 4つを試してみたいと思います。

  1. GstBuffer を確保
  2. GstMemory を確保して、"abc" と書き、GstBuffer に追加
  3. もう1つ GstMemory を確保して、"12345" と書き、GstBuffer に追加
  4. GstBuffer を確認

まずは、 GstBuffer です。

buf = gst_buffer_new();
size = gst_buffer_get_sizes(buf, NULL, &maxsize);
g_print("buf:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
g_print(" nr: %u\n", gst_buffer_n_memory(buf));
view raw mem0.c hosted with ❤ by GitHub

GstBuffer は、 gst_buffer_new() で取得します。 gst_buffer_new() で取得したときは、サイズは 0、持っている GstMemory ( nr ) も 0 です。

buf:
 size: 0
 maxsize: 0
 nr: 0

次は、 GstMemory を作成してみます。

mem = gst_allocator_alloc(NULL, 3, NULL);
size = gst_memory_get_sizes(mem, NULL, &maxsize);
g_print("mem 1:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
gst_memory_map(mem, &info, GST_MAP_WRITE);
memcpy(info.data, "abc", 3);
gst_memory_unmap(mem, &info);
gst_buffer_insert_memory(buf, -1, mem);
size = gst_buffer_get_sizes(buf, NULL, &maxsize);
g_print("buf:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
g_print(" nr: %u\n", gst_buffer_n_memory(buf));
view raw mem1.c hosted with ❤ by GitHub

GstMemory は、 gst_allocator_alloc() で取得します。この実験ではアロケーターとアロケーターへのパラメーターは気にしないので、NULL を指定します。確保するメモリは、3 byte にしてみましょう。これで確保できたメモリは、ちゃんと size が 3 になっています。 maxsize が 10 なのは、後で少し追加可能なように、ちょっと多めにメモリが確保されるようになっているからです。

このメモリに、3文字 "abc" と書き込みます1gst_memory_map()gst_memory_unmap() で括るのを忘れずに。これで最初に確保した GstMemory には "abc" と書かれました。では、このメモリを、先程確保した GstBuffer に追加してみましょう。

mem 1:
 size: 3
 maxsize: 10
buf:
 size: 3
 maxsize: 10
 nr: 1

ちゃんと、メモリブロックの数が 1 になりました。 GstBuffer が表示するサイズは、内包するメモリブロックのままのようです。

もう1つ、 GstMemory を確保します。

mem = gst_allocator_alloc(NULL, 6, NULL);
size = gst_memory_get_sizes(mem, NULL, &maxsize);
g_print("mem 2:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
gst_memory_map(mem, &info, GST_MAP_WRITE);
memcpy(info.data, "12345", 6);
gst_memory_unmap(mem, &info);
gst_buffer_insert_memory(buf, -1, mem);
size = gst_buffer_get_sizes(buf, NULL, &maxsize);
g_print("buf:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
g_print(" nr: %u\n", gst_buffer_n_memory(buf));
view raw mem2.c hosted with ❤ by GitHub

今度は、確保する領域を 6 byte にし、"12345" と書き込みます。今度は、NUL 文字も含め 6文字です。 今回の GstMemorymaxsize は、13 になるようです。

mem 2:
 size: 6
 maxsize: 13
buf:
 size: 9
 maxsize: 16
 nr: 2
abc12345

2つ目のメモリブロックが追加された GstBuffer の方は、どうでしょう? size は前回の 3 と 今回の 6 で 9 になり、 nr は 2 になっています。でも、 maxsize は 10 + 13 = 23 を期待していたのに、16となりました。なぜでしょう?

GstBuffer は、内部に持っているメモリブロックを列べ、あたかも連続したメモリ空間のように見せてくれます。その時 maxsize として見えるのは最後のメモリブロックの空き領域分だけのようです。これは、そういう仕様だと理解するしかないようです。

メモリバッファーと言えば、「リニアなメモリ空間」というシンプルな物を想像しますが、GStreamer では、とても複雑なことをしています。この GstMemory の機能は、長年続いた Gstreamer 0.x 系から、1.x系にするときに追加された機能です。たとえば YUV 4:2:0 のように LumaChrominance が空間として分れている場合、ハードウェアがバラバラのメモリ空間にデーターがあることを期待しているかもしれません。しかし CPU から触るときは、まとまったデーターとして触りたい。なので map すると内部にあるメモリが合わさって (mergeされて) 見える作りになっています。GStreamer は、さまざまなデーターを扱うため、どうしてもこの柔軟な機能が必要だったようです。

GStreamer を使っていないくても、不連続なメモリを使いたいことは良くあります。そんなとき使ってみたくなりませんか?

ぜひ、お試しあれ。

#include <gst/gst.h>
#include <string.h>
int main(int argc, char *argv[])
{
GstBuffer *buf;
GstMemory *mem;
GstMapInfo info;
gsize size;
gsize maxsize;
gst_init(&argc, &argv);
buf = gst_buffer_new();
size = gst_buffer_get_sizes(buf, NULL, &maxsize);
g_print("buf:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
g_print(" nr: %u\n", gst_buffer_n_memory(buf));
mem = gst_allocator_alloc(NULL, 3, NULL);
size = gst_memory_get_sizes(mem, NULL, &maxsize);
g_print("mem 1:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
gst_memory_map(mem, &info, GST_MAP_WRITE);
memcpy(info.data, "abc", 3);
gst_memory_unmap(mem, &info);
gst_buffer_insert_memory(buf, -1, mem);
size = gst_buffer_get_sizes(buf, NULL, &maxsize);
g_print("buf:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
g_print(" nr: %u\n", gst_buffer_n_memory(buf));
mem = gst_allocator_alloc(NULL, 6, NULL);
size = gst_memory_get_sizes(mem, NULL, &maxsize);
g_print("mem 2:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
gst_memory_map(mem, &info, GST_MAP_WRITE);
memcpy(info.data, "12345", 6);
gst_memory_unmap(mem, &info);
gst_buffer_insert_memory(buf, -1, mem);
size = gst_buffer_get_sizes(buf, NULL, &maxsize);
g_print("buf:\n");
g_print(" size: %" G_GSIZE_FORMAT "\n", size);
g_print(" maxsize: %" G_GSIZE_FORMAT "\n", maxsize);
g_print(" nr: %u\n", gst_buffer_n_memory(buf));
gst_buffer_map(buf, &info, GST_MAP_READ);
g_print("%s\n", info.data);
gst_buffer_unmap(buf, &info);
return 0;
}
view raw mem.c hosted with ❤ by GitHub

Footnotes:

1

実験のため、NUL文字を省いて書き込んでいます。そのため、文字列として扱うのは危険ですので注意してください。

No comments:

Post a Comment