app plugin でより深いコードを書く


[Day 1 のスライド](http://www.slideshare.net/shocrunch/about-gstreamer-10-application-development-for-beginners-55594448)の45枚目のネタです。

GStreamer には、app plugin という、プラグインを作成せずともエレメント内部の処理をユーザーが実装できる汎用的なエレメントがあります。
app plugin を使うことで、v4l2を直接叩いてカメラを制御したり、処理したデータを別のプログラムに渡すなどが行えるようになるので「Element を書くほどじゃないけど特殊なことをしたい」そんな時に便利です。

app plugin には `appsrc` と `appsink` の2つがあります。
詳しくは `inspect` で確認してください。

```shell-session
$ gst-inspect-1.0 appsrc
```

```shell-session
$ gst-inspect-1.0 appsink
```

---

今回は例として、appsink が受け取ったフレームを数えるプログラムを作ります。

```text
videotestsrc num-buffers=30 ! appsink
```

### inspect で callback のインターフェースを確認

```shell-session
$ gst-inspect-1.0 appsink

[..]

Element Signals:
  "eos" :  void user_function (GstElement* object,
                               gpointer user_data);
  "new-preroll" :  GstFlowReturn user_function (GstElement* object,
                                                gpointer user_data);
  "new-sample" :  GstFlowReturn user_function (GstElement* object,
                                               gpointer user_data);
```

appsink がフレームを受け取った時の処理には`new-sample`というコールバックを使います。

ここで抑えておくのは、戻り(GstFlowReturn)と引数(object, data)です。

### ベースとなるコードを実装

```bash:pipeline
videotestsrc ! autovideosink
```

この pipeline を C で実装すると以下のようになります。

```c:code
#incude <gst/gst.h>

int main(int argc, char *argv[])
{
	GString *launch_str;
	GstElement *pipeline;
	GstBus *bus;
	GstMessage *msg;

	gst_init(&argc, &argv);

	launch_str = g_string_new(NULL);
	g_string_printf(launch_str,
		"videotestsrc is-live=true num-buffers=30 ! autovideosink", NULL);

	pipeline = gst_parse_launch(launch_str->str, NULL);

	if (pipeline == NULL) {
		g_print("parse_launch fail.\n");
		return 1;
	}
	
	bus = gst_element_get_bus(pipeline);

	gst_element_set_state(pipeline, GST_STATE_PLAYING);

	gint handle_msg;
	handle_msg = (GST_MESSAGE_EOS | GST_MESSAGE_ERROR);
	msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, (GstMessageType)handle_msg);

	GError *err;
	gchar *debug_info;
	if (msg != NULL)
		switch (GST_MESSAGE_TYPE(msg)) {
		case GST_MESSAGE_ERROR:
			gst_message_parse_error(msg, &err, &debug_info);
			g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
			g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
			g_clear_error(&err);
			g_free(debug_info);
			break;
		case GST_MESSAGE_EOS:
			g_print("End-Of-Stream reached.\n");
			break;
		default:
			/* We should not reach here because we only asked for ERRORs and EOS */
			g_printerr("Unexpected message received.\n");
			break;
		}
	gst_object_unref(bus);
	gst_element_set_state(pipeline, GST_STATE_NULL);
	gst_object_unref(pipeline);

	return 0;
}
```

### コードに修正を加える

#### パイプラインに Appsink を追加

appsink を追加するほかに抑えておかなければならないのは、`appsink`のプロパティに`emit-signals=true`を設定すること。
これを行わなければ、コールバック関数を実装・登録しても実行されません。

```diff
@@ -11,7 +11,7 @@ int main(int argc, char *argv[])

        launch_str = g_string_new(NULL);
        g_string_printf(launch_str,
-               "videotestsrc is-live=true num-buffers=30 ! autovideosink", NULL);
+               "videotestsrc is-live=true num-buffers=30 ! appsink name=appsink emit-signals=true", NULL);

        pipeline = gst_parse_launch(launch_str->str, NULL);

```

#### パイプラインから Appsink を取得する

次にパイプラインから appsink を取得します。

```diff
@@ -4,6 +4,7 @@ int main(int argc, char *argv[])
 {
        GString *launch_str;
        GstElement *pipeline;
+       GstElement *appsink;
        GstBus *bus;
        GstMessage *msg;

@@ -20,6 +21,13 @@ int main(int argc, char *argv[])
                return 1;
        }

+       appsink = gst_bin_get_by_name(GST_BIN(pipeline), "appsink");
+       if (appsink == NULL)
+       {
+               g_print("appsink is NULL\n");
+       }
+       gst_object_unref(appsink);
+
        bus = gst_element_get_bus(pipeline);

        gst_element_set_state(pipeline, GST_STATE_PLAYING);
```

#### コールバックの実装と登録

最後にコールバック関数の`cb_new_sample()`を実装し、`g_signal_connect()`で登録します。

```diff
@@ -1,4 +1,19 @@
 #include <gst/gst.h>
+#include <gst/app/gstappsink.h>
+
+GstFlowReturn cb_new_sample(GstAppSink *sink, gpointer data)
+{
+       static int counter = 0;
+       GstSample *sample;
+       g_print("frame: %d\n", counter++);
+
+       sample = gst_app_sink_pull_sample(sink);
+       if (sample == NULL) {
+               return GST_FLOW_EOS;
+       } else {
+               return GST_FLOW_OK;
+       }
+}

 int main(int argc, char *argv[])
 {
@@ -26,6 +41,7 @@ int main(int argc, char *argv[])
        {
                g_print("appsink is NULL\n");
        }
+       g_signal_connect(appsink, "new-sample", G_CALLBACK(cb_new_sample), NULL);
        gst_object_unref(appsink);

        bus = gst_element_get_bus(pipeline);
```

### ビルド・実行

VisualStudio で appsink (または appsrc) を使う場合は、プロパティシートを新たに追加する必要があります。追加するプロパティシートは `gstreamer-app-1.0.props`です。

Linux の場合も同様に `gstreamer-app-1.0.pc` を使います。

```shell-session
$ gcc main.c `pkg-config --libs --cflags gstreamer-app-1.0`
```

実行すると以下のような表示が出ます。

```shell-session
$ a.out
frame: 0
frame: 1
frame: 2
frame: 3
frame: 4
frame: 5

:

frame: 27
frame: 28
frame: 29
End-Of-Stream reached.
```

もちろん、`videotestsrc` の `num-buffers` の値を変えると、その分表示が変わります。

以上が、appsink を使ったプログラミングの一例でした。これを使えば、mp4 のフレーム数を数えるなどもできますね。

使ってみてください :)