Thursday, December 10, 2015

GStreamer でプログラミング 5 (Pad と Event)

パイプライン構造をしている GStreamer で、各エレメントを繋ぐ大事な役割をしているのが GstPad です。

cat の例 では、 filesrc エレメントと fdsink エレメントが繋がっています。 gst-launch の記法では、「 ! 」を使って、「左側のエレメントと右側のエレメントを繋ぐ」を表現しています。 Unix パイプの「 | 」と同じ使い方です。

linked-elements.png

Figure 1: GStreamer Application Development Manual の Linking elementsから抜粋

パイプラインの図を見ても分るように、各エレメントはいくつかのパッド(Pad)を持っていて、これらのパッドが繋がることでエレメントが繋がることができます。ある瞬間にひとつのパッドが繋がることができるのは、1つだけです。かならず 1対1の関係になります。

マニュアル では、 gst_element_link() を使って2つのエレメントを繋いで いますが、実はこの関数 gst_element_link_pads() のラッパー関数です。本来なら繋ぐのはパッド同士なので、「エレメントA のパッド1 を エレメント B のパッド2に繋げ」となるところですが、 gst_element_link() では、「Pad の名前は指定しないので、よしなに繋いでおいて」という感じでしょうか。

各エレメントがどのようなパッドを持つのかというのは、しっかりとドキュメント化されています。たとえば filesrc であれば、

name
src
direction
source
presence
always
details
ANY

という感じに。 filesrc は、方向(direction) が "source" のパッド1つしか持っていません。source 方向のパッドは下位 (downstream) にデーターを出すためのパッドです。 filesrc が「Source Element」と言われる所以です。名前(name)はそのまま "src"。Presence が "always" となっています。"always" なパッドは、エレメントが生成された後であれば、いつでも存在するという意味です。この様なパッドは、「Static pad」と呼ばれます。逆に必要にならないと存在しないパッドは、 「Dyanmic Pad」と呼ばれます。

次に、フィルターエレメントである tee も見てみましょう。

name
sink
direction
sink
presence
always
details
ANY
name
src_%u
direction
source
presence
request
details
ANY

フィルターエレメントは、上流と下流の両方で他のエレメントと繋がります。そのため、方向(direction)が "sink" と "source" の 2種類のパッドを持ちます。 tee は、上流から来たデーターを複製するエレメントです。そのため、下流に流す Sourceパッドを複数持ちます。それぞれのパッドには、0始まりのインデックス番号が振られます。"src_%u" と書かれているのはそのためです。また、"request" された時、つまり繋がるエレメントの数に合わせてパッドが生成されるので presence には "request" と記載されています。

その他パッドについては、マニュアルの「Pads and capabilities」が詳しいです。

さて、Pad 同士が繋っているなら、辿って行けば対になっているパッドが分るはずです。さらにパッドは自分を保持しているエレメントの事も知っています。それなら、端のエレメントから辿って行くことで、隣りのエレメントまで行けるはずですよね?

filesrc0 → src pad → sink pad → fdsink0

と、たどってみましょう。

#include <gst/gst.h>
gboolean on_message(GstBus *bus, GstMessage *message, gpointer p)
{
GMainLoop *mainloop = p;
(void)bus;
if ((GST_MESSAGE_TYPE(message) & GST_MESSAGE_EOS))
g_main_loop_quit(mainloop);
return TRUE;
}
int main(int argc, char *argv[])
{
GMainLoop *mainloop;
GstElement *pipeline;
GError *error = NULL;
GstBus *bus;
GstElement *src;
GstElement *sink;
GstPad *pad1;
GstPad *pad2;
gst_init(&argc, &argv);
mainloop = g_main_loop_new(NULL, FALSE);
pipeline = gst_parse_launch("filesrc location=a.txt ! fdsink", &error);
bus = gst_element_get_bus(pipeline);
gst_bus_add_watch(bus, on_message, mainloop);
src = gst_bin_get_by_name(GST_BIN(pipeline), "filesrc0");
pad1 = gst_element_get_static_pad(src, "src");
pad2 = gst_pad_get_peer(pad1);
sink = gst_pad_get_parent_element(pad2);
g_print("name: %s\n", gst_element_get_name(sink));
gst_element_set_state(pipeline, GST_STATE_PLAYING);
g_main_loop_run(mainloop);
return 0;
}
view raw pad-peer.c hosted with ❤ by GitHub

Presense が "always" のパッドは、 gst_element_get_static_pad() で取り出します。名前は "src" でした。gst_pad_get_peer() という関数は、指定したパッドに繋っているパッドを返してくれます。取り出したパッドは fdsink0 の sink pad なはず。このパッドを持っているエレメント、つまり親のエレメントを gst_pad_get_parent_element() で取り出せば、ちゃんと名前が "fdsink0" となっているエレメントになりました。

./a.out
name: fdsink0
hello

もう1つパッドには重要な仕事があります。隣のエレメントにデーターを渡すという大事な仕事です。 GStreamer がパイプライン構造をしているのは、何らかの情報を流すためですもんね。パイプラインの中を流れるデーターは GstEventGstBuffer というオジェクトです。

パッドを眺めていると、流れているデーターを見ることができます。医療や検査で、ある場所を調査する道具を プローブ と言いますが、パッドにもプローブ(Probe)を付けて覗き見ることができます。その名も gst_pad_add_probe() 。必要に応じてプローブの種類を選べます。指定は、enum GstPadProbeType で行います。

ここでは例として、以前紹介した End of Stream を拾ってみましょう。End of Stream は Event Type なので、 GST_PAD_PROBE_TYPE_BUFFER_EVENT_DOWNSTREAM を指定します。

#include <gst/gst.h>
gboolean on_message(GstBus *bus, GstMessage *message, gpointer p)
{
GMainLoop *mainloop = p;
(void)bus;
if ((GST_MESSAGE_TYPE(message) & GST_MESSAGE_EOS)) {
g_print("message: eos\n");
g_main_loop_quit(mainloop);
}
return TRUE;
}
GstPadProbeReturn on_probe(GstPad *pad, GstPadProbeInfo *info, gpointer unused)
{
GstEvent *event;
(void)pad;
(void)unused;
event = gst_pad_probe_info_get_event(info);
g_print("event: %s\n", GST_EVENT_TYPE_NAME(event));
return GST_PAD_PROBE_OK;
}
int main(int argc, char *argv[])
{
GMainLoop *mainloop;
GstElement *pipeline;
GError *error = NULL;
GstBus *bus;
GstElement *src;
GstElement *sink;
GstPad *pad1;
GstPad *pad2;
gst_init(&argc, &argv);
mainloop = g_main_loop_new(NULL, FALSE);
pipeline = gst_parse_launch("filesrc location=a.txt ! fdsink", &error);
bus = gst_element_get_bus(pipeline);
gst_bus_add_watch(bus, on_message, mainloop);
src = gst_bin_get_by_name(GST_BIN(pipeline), "filesrc0");
pad1 = gst_element_get_static_pad(src, "src");
pad2 = gst_pad_get_peer(pad1);
sink = gst_pad_get_parent_element(pad2);
g_print("name: %s\n", gst_element_get_name(sink));
gst_pad_add_probe(pad1, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, on_probe, NULL, NULL);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
g_main_loop_run(mainloop);
return 0;
}
view raw pad.c hosted with ❤ by GitHub

イベントが流れたときに、 on_probe() を呼び出してもらいます。 on_probe() には、 GstPadProbeInfo が渡ってくるので、この情報から event を取り出し、 GST_EVENT_TYPE_NAME() で種類を表示します。

ついでに on_message() も修正して、End of Stream のメッセージが来たら表示してみます。これで、パッド上をイベントが流れる方が先か、バスにメッセージが投げられるのが先か分りますね。実行すると、 stream-startsegment というイベントの後に、 eos が流れています。どうやらバスにメッセージが流れる方が後のようです。 on_message() の方が後なのは、 sink エレメントにまで EOS が届いてから、バスにメッセージが投げられるからです。

./a.out
name: fdsink0
event: stream-start
event: segment
hello
event: eos
message: eos

お試しあれ。

No comments:

Post a Comment