GStreamer のパイプライン内を流れているのは GstEvent と GstBuffer ということでしたが、 実際にマルチメディアデーターを運んでいる方の GstBuffer を覗いてみましょう。
と定義されています。
APIドキュメントによると
バッファーは、GStreamerのデーターを運ぶ基本ユニットであり、タイミング情報や、オフセット、バッファーが持つ GstMemory ブロックに関係するメタデーターなどが含まれている。
と言うことらしいです。メタデーターはとりあえず置いといて、 GstMemory が気になりますね。だって、 struct GstBuffer の中に GstMemory なんて入っていないし、どうなってんでしょう? 答は GstBufferImpl にありました。
つまり、
こんな風に、Bridge パターンを使って GstBufferImpl の方で GstMemory を持っているんですね。
さて、 GstMemory の方に行ってみましょう。 GstMemory は GstAllocator によって確保されるメモリです。 GstAllocator は、「いろいろな条件に合ったメモリをよしなに確保してくれるアロケーター」とでも今は覚えておいてください。 gst_allocator_alloc() でアロケーターを指定しない、つまり NULL を指定すると、システムのデフォルトアロケーターが使われます。
確保できたメモリは、 malloc() されたメモリのように、そのまま扱うことができません。どのようなメモリが確保できたかは、利用したアロケーターによって異なります。もしかすると、ぜんぜんメモリとは異なる状態かもしれません。このようなデーターを 「Opaque data type」と言います。
どのような構造のメモリなのか分らないので、そのままではアクセスすることができません。そのため CPUから利用する場合は、かならず「アクセスするよ (map)」「終ったよ (unmap)」と指示する必要があります。それぞれ、 gst_memory_map() と gst_memory_unmap() です。 組み込みやカーネルのコードを知っている人であれば、「メモリ空間にマップしたので、触れる」と言えば、感覚として分るでしょうか? または、ファイルを mmap() してメモリとして触るような感じでしょうか。
malloc() されるようなシステムメモリであれば、このような仕組みは不要です。しかし GStreamer はマルチメディアを扱うフレームワークです。ビデオ処理や OpenGL 用のメモリを確保する場合もあります。その時 GPU 側のメモリの方が都合が良いかもしれません。 CUDA 6 の Unified Memory や AMD の HSA の様に CPU から直接触れる GPU メモリも増えそうですが、実際のハードウェアや OS によって状況が異なります。そのため、メモリアクセスの前後で、必要な処理をするタイミングを作ってあげる必要があったのです。
それでは、早速 GstBuffer と GstMemory を使って実験してみましょう。次の 4つを試してみたいと思います。
- GstBuffer を確保
- GstMemory を確保して、"abc" と書き、GstBuffer に追加
- もう1つ GstMemory を確保して、"12345" と書き、GstBuffer に追加
- GstBuffer を確認
まずは、 GstBuffer です。
GstBuffer は、 gst_buffer_new() な ど で取得します。 gst_buffer_new() で取得したときは、サイズは 0、持っている GstMemory ( nr ) も 0 です。
buf: size: 0 maxsize: 0 nr: 0
次は、 GstMemory を作成してみます。
GstMemory は、 gst_allocator_alloc() で取得します。この実験ではアロケーターとアロケーターへのパラメーターは気にしないので、NULL を指定します。確保するメモリは、3 byte にしてみましょう。これで確保できたメモリは、ちゃんと size が 3 になっています。 maxsize が 10 なのは、後で少し追加可能なように、ちょっと多めにメモリが確保されるようになっているからです。
このメモリに、3文字 "abc" と書き込みます1。 gst_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 を確保します。
今度は、確保する領域を 6 byte にし、"12345" と書き込みます。今度は、NUL 文字も含め 6文字です。 今回の GstMemory の maxsize は、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 のように Luma と Chrominance が空間として分れている場合、ハードウェアがバラバラのメモリ空間にデーターがあることを期待しているかもしれません。しかし CPU から触るときは、まとまったデーターとして触りたい。なので map すると内部にあるメモリが合わさって (mergeされて) 見える作りになっています。GStreamer は、さまざまなデーターを扱うため、どうしてもこの柔軟な機能が必要だったようです。
GStreamer を使っていないくても、不連続なメモリを使いたいことは良くあります。そんなとき使ってみたくなりませんか?
ぜひ、お試しあれ。
Footnotes:
実験のため、NUL文字を省いて書き込んでいます。そのため、文字列として扱うのは危険ですので注意してください。


