Table of Contents
1 はじめに
一番簡単なエレメントは、アップストリームから受け取ったデーターをなにもせずにダウンストリームに流す identity
の様なエレメントです。本物の identity
エレメントは、 GstBaseTransform
を継承し、10以上のプロパティーを持ったエレメントで、コードの行数も 1000 行近くあります。
ここでは、GStreamer の基本クラスである GstElement
から派生させ、簡単なエレメント GstTextNoop
を作成してみます。まだ一度もプラグインをビルドしたことが無い人は、空っぽのプラグインをビルドする「The Meson Build System」を読んでみてください。
詳しい情報は、 GStreamer の Plugin Writer's Guide を参照してください。
2 決まり文句
GStreamer は Glib が提供している GObject (と GType) を使って オブジェクト指向なプログラミング環境をユーザーに提供しています。C言語は、元々 オブジェクト指向ではない言語なので、GObject のような Object System を導入したとしても、クラスを1つ作成する為にも、以前は面倒な記述が必要でした。しかし Glib バージョン 2.44 から G_DECLARE_FINAL_TYPE()
が導入され、一行でクラスの宣言が可能になりました。
G_DECLARE_FINAL_TYPE (GstTextNoop, gst_text_noop, GST_TEXT_NOOP, TEXT_NOOP, GstElement)
このように、 GstElement
を継承した GstTextNoop
クラスの宣言ができるようになりました。それでは早速 GstTextNoop
クラスを見ていきましょう。最初はヘッダーファイルからです。
#ifndef __GST_TEXT_NOOP_H__ #define __GST_TEXT_NOOP_H__ #include <gst/gst.h> G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (GstTextNoop, gst_text_noop, GST_TEXT, NOOP, GstElement) G_END_DECLS #endif /* __GST_TEXT_NOOP_H__ */
__GST_TEXT_NOOP_H__
- ヘッダーが複数回 include されないようにガード
#include <gst/gst.h>
- GStreamer のヘッダーを include
G_BEGIN_DECLS
- C++ からでも使えるようにするためのマクロ。この辺りまでは GStreamer の作法的に毎回一緒です
G_DECLARE_FINAL_TYPE
- 拡張を禁止したクラス
GstTextNoop
を作成します。Java の final class や C# の sealed class と同じです G_END_DECLS
G_DECLARE_FINAL_TYPE
で始めてので、=G_END_DECLS= で終ります
続いて、Cファイル。こちらは、あたりまえですが、宣言(declaration)だけではなく、実体/定義 (definition) が入ります。
#include "gsttextnoop.h" struct _GstTextNoop { GstElement parent_class; }; G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT) static void gst_text_noop_class_init (GstTextNoopClass * klass) { GstElementClass *gstelement_class; gstelement_class = (GstElementClass *) (klass); gst_element_class_set_static_metadata (gstelement_class, "Noop Element", "Filter", "Simple element like Identity", "Me"); } static void gst_text_noop_init (GstTextNoop * noop) { }
#include "gsttextnoop.h"
- 上で作成したヘッダーファイルを include
struct _GstTextNoop
GstTextNoop
の実体です。マクロでGstTextNoop
に typedef されています。実体を .c に隠すのは opaque な struct にするためです。こうすることで外部からGstTextNoop
の中が見えなくなり、カプセル化することができます。G_DEFINE_TYPE
G_DECLARE_FINAL_TYPE
で宣言した型の定型部分を実装します。これも決り文句の1つですgst_text_noop_class_init()
- クラスの初期化関数です。
G_DECLARE_FINAL_TYPE
やG_DEFINE_TYPE
で使ったgst_text_noop
に_class_init
を付けた関数名で作成する必要があります。クラスが初期化された時に呼ばれます。 gst_element_class_set_static_metadata()
- GStreamer のクラスではメタデーターが設定されていないとエラーになります。このクラスの名前や種類、説明、作者を記載します
gst_text_noop_init()
- インスタンスの初期化関数です。インスタンスが生成された時に呼ばれます。まだなにも必要ないので、空っぽで良いです
これでクラスは作成できました。名前を置き換えれば、自分のクラスを作ることができます。
gst-template にはテンプレートから クラスのコードを生成するスクリプト もあり、Plugin Writer's Guide でもこれを使っています。しかし、最初から生成されるコードが多いと嫌になるので、今回はあえて手で作成しました。 また、Vala コンパイラーを使うことででも簡単にクラスのボイラープレートコードを生成することができます。
それでは、作成したクラスを、ここ で作成した空っぽのプラグインに追加してみましょう。
#include <gst/gst.h> #include "config.h" #include "gsttextnoop.h" static gboolean plugin_init (GstPlugin * plugin) { gst_element_register(plugin, "textnoop", GST_RANK_NONE, gst_text_noop_get_type()); return TRUE; } GST_PLUGIN_DEFINE ( GST_VERSION_MAJOR, GST_VERSION_MINOR, gsttext, "Text Plugins for GStreamer", plugin_init, PACKAGE_VERSION, "LGPL", "GStreamer Text Package", "https://github/yashi/gst-plugins-text")
#include "gsttextnoop.h"
GstTextNoop
クラスのヘッダーを includegst_element_register()
- エレメントをプラグインに追加します。第1引数は受け取ったプラグイン、第2引数はエレメントの名前「
textnoop
」、第3引数はランクです。ランク は4つから選べます。とりあえずは、一番優先度が低いNONE
にしておきましょう。最後の引数はGstTextNoop
のタイプを取得するための関数を指定します。G_DEFINE_TYPE
が生成してくれているので、自分で作った覚えがなくても大丈夫です
ソースコードは以上です。ビルドするためには、ビルドシステムにソースコードを教えてあげる必要があります。
project('gst-plugins-text', 'c', version : '0.1.0') src = ['src/gsttextnoop.c', 'src/gsttext.c'] gst_dep = dependency('gstreamer-1.0', version : '>1.0') cdata = configuration_data() cdata.set_quoted('PACKAGE', meson.project_name()) cdata.set_quoted('PACKAGE_VERSION', meson.project_version()) configure_file(output : 'config.h', configuration : cdata) gsttext = library('gsttext', src, dependencies : gst_dep)
- src
- 作成した
'src/gsttextnoop.c'
を追加します。他は変更しなくても大丈夫です
$ GST_PLUGIN_PATH=`pwd`/build gst-inspect-1.0 gsttext Plugin Details: Name gsttext Description Text Plugins for GStreamer Filename ./libgsttext.so Version 0.1.0 License LGPL Source module gst-plugins-text Binary package GStreamer Text Package Origin URL https://github/yashi/gst-plugins-text textnoop: Noop Element 1 features: +-- 1 elements
エレメントの情報も、取ることができます。
$ GST_PLUGIN_PATH=`pwd`/build gst-inspect-1.0 textnoop Factory Details: Rank none (0) Long-name Noop Element Klass Filter Description Simple element like Identity Author Me Plugin Details: Name gsttext Description Text Plugins for GStreamer Filename ./libgsttext.so Version 0.1.0 License LGPL Source module gst-plugins-text Binary package GStreamer Text Package Origin URL https://github/yashi/gst-plugins-text GObject +----GInitiallyUnowned +----GstObject +----GstElement +----GstTextNoop Pad Templates: none Element Flags: no flags set Element Implementation: Has change_state() function: gst_element_change_state_func Element has no clocking capabilities. Element has no URI handling capabilities. Pads: none Element Properties: name : The name of the object flags: readable, writable String. Default: "textnoop0" parent : The parent of the object flags: readable, writable Object of type "GstObject"
3 Pad
インスペクトは正しく認識するようになりましたが、まだパイプラインを形成できません。
GST_PLUGIN_PATH=build gst-launch-1.0 -q fakesrc num-buffers=0 ! textnoop ! fakesink WARNING: erroneous pipeline: could not link fakesrc0 to textnoop0
fakesrc
から textnoop
エレメントに link できないと言われました。エレメントを繋ぐのは Pad なので、pad がないと繋がりません。
Pad を作成するのに良く使われる方法は、Pad Template からの作成です。
#include "gsttextnoop.h" struct _GstTextNoop { GstElement parent_class; }; G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT) static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static void gst_text_noop_class_init (GstTextNoopClass * klass) { GstElementClass *gstelement_class; gstelement_class = (GstElementClass *) (klass); gst_element_class_set_static_metadata (gstelement_class, "Noop Element", "Filter", "Simple element like Identity", "Me"); } static void gst_text_noop_init (GstTextNoop * noop) { GstPad *srcpad; GstPad *sinkpad; srcpad = gst_pad_new_from_static_template(&srctemplate, "src"); gst_element_add_pad(GST_ELEMENT(noop), srcpad); sinkpad = gst_pad_new_from_static_template(&sinktemplate, "sink"); gst_element_add_pad(GST_ELEMENT(noop), sinkpad); }
- GST_STATIC_PAD_TEMPLATE
- static の Pad Template を生成するマクロ。テンプレートの名前、Padの方向 (SRC / SINK)、Pad がいつ存在するか、static の Caps (ANY / NONE) を指定します。
textnoop
エレメントは、source 側にも sink 側にも繋がるエレメントなので 2つパッドを作ります。テンプレートもそれぞれ作成します。なにが繋がっても良いのでGST_STATIC_CAPS_ANY
を指定します。 gst_pad_new_from_static_template()
- テンプレートから Padを作成します。第1引数にテンプレートを、第2引数に作成する Pad の名前を指定します。こちらも、 source 側と sink 側の両方作成します
gst_element_add_pad
- noop エレメントに Pad を追加します。
GstElement
クラスのメソッドなので noop をGST_ELEMENT
に変換し、第1引数に渡します。第2引数にはgst_pad_new_from_static_template()
で作成した Pad を指定します
これでエラーせずに繋がりました。
GST_PLUGIN_PATH=. gst-launch-1.0 -q fakesrc num-buffers=0 ! textnoop ! fakesink
4 Pad: Template
パッドを作成して繋がるところまではできましたが、Pad の情報が inspect で出てきません。
GST_PLUGIN_PATH=. gst-inspect-1.0 noop Factory Details: Rank none (0) Long-name Noop Element Klass Filter Description Simple element like Identity Author Me Plugin Details: Name gsttext Description Text Plugins for GStreamer Filename ./libgsttext.so Version 0.1.0 License LGPL Source module gst-plugins-text Binary package GStreamer Text Package Origin URL https://github/yashi/gst-plugins-text GObject +----GInitiallyUnowned +----GstObject +----GstElement +----GstTextNoop Pad Templates: none ← ここね Element Flags: no flags set Element Implementation: Has change_state() function: gst_element_change_state_func Element has no clocking capabilities. Element has no URI handling capabilities. Pads: SRC: 'src' Pad Template: 'src' SINK: 'sink' Pad Template: 'sink' Element Properties: name : The name of the object flags: readable, writable String. Default: "textnoop0" parent : The parent of the object flags: readable, writable Object of type "GstObject"
これは、クラスに Pad Tempalte を追加していないからです。追加してしまいましょう。
#include "gsttextnoop.h" struct _GstTextNoop { GstElement parent_class; }; G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT) static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static void gst_text_noop_class_init (GstTextNoopClass * klass) { GstElementClass *gstelement_class; gstelement_class = (GstElementClass *) (klass); gst_element_class_set_static_metadata (gstelement_class, "Noop Element", "Filter", "Simple element like Identity", "Me"); gst_element_class_add_static_pad_template (gstelement_class, &srctemplate); gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate); } static void gst_text_noop_init (GstTextNoop * noop) { GstPad *srcpad; GstPad *sinkpad; srcpad = gst_pad_new_from_static_template(&srctemplate, "src"); gst_element_add_pad(GST_ELEMENT(noop), srcpad); sinkpad = gst_pad_new_from_static_template(&sinktemplate, "sink"); gst_element_add_pad(GST_ELEMENT(noop), sinkpad); }
gst_element_class_add_static_pad_template()
- noop エレメントのクラスに、Pad Template を追加します
Factory Details: Rank none (0) Long-name Noop Element Klass Filter Description Simple element like Identity Author Me Plugin Details: Name gsttext Description Text Plugins for GStreamer Filename ./libgsttext.so Version 0.1.0 License LGPL Source module gst-plugins-text Binary package GStreamer Text Package Origin URL https://github/yashi/gst-plugins-text GObject +----GInitiallyUnowned +----GstObject +----GstElement +----GstTextNoop Pad Templates: SINK template: 'sink' Availability: Always Capabilities: ANY SRC template: 'src' Availability: Always Capabilities: ANY Element Flags: no flags set Element Implementation: Has change_state() function: gst_element_change_state_func Element has no clocking capabilities. Element has no URI handling capabilities. Pads: SRC: 'src' Pad Template: 'src' SINK: 'sink' Pad Template: 'sink' Element Properties: name : The name of the object flags: readable, writable String. Default: "textnoop0" parent : The parent of the object flags: readable, writable Object of type "GstObject"
5 Pad: chain
ところが、データーを流そうとすると怒られます。いろいろ起りますね…。もうちょいです。
GST_PLUGIN_PATH=. gst-launch-1.0 -q fakesrc num-buffers=1 ! textnoop ! fakesink (gst-launch-1.0:20837): GStreamer-CRITICAL **: chain on pad textnoop0:sink but it has no chainfunction ERROR: from element /GstPipeline:pipeline0/GstFakeSrc:fakesrc0: Internal data stream error. Additional debug info: gstbasesrc.c(2950): gst_base_src_loop (): /GstPipeline:pipeline0/GstFakeSrc:fakesrc0: streaming stopped, reason not-supported (-6) ERROR: pipeline doesn't want to preroll.
chain on pad textnoop0:sink but it has no chainfunction (textnoop の sink pad で chain しようとしたけど、チェイン関数ないよ) って言われています。まぁ、たしかに追加していないので、無いわけですが..。 chain / チェイン / チェーン 関数とは、 GStreamer のパイプラインに GstBuffer
を流している張本人の関数です。ソースエレメントから始まってシンクエレメントに辿りつくまでに、各エレメント内の chain 関数を数珠繋ぎに呼び出すことから chain 関数と呼ばれています。
各エレメントは Pad で繋ると言う話をしたと思いますが、これには変更がありません。つまり「エレメント内の chain 関数」とは、 「Pad に登録されている chain 用の関数」ということなのです。 Muxer のようなエレメントは、Pad によって音声を流したり映像を流したりします。なので、chain 関数はエレメント単位ではなく、 Pad 単位になっています。
関数名も gst_pad_set_chain_function()
となっています。
パイプラインの中は、上流から下流に、つまりsourceエレメントから sink エレメントに流れます。 source エレメントの source pad に繋っているのは、あるエレメントの sink Pad です。今回例であれば noop エレメントの sink Pad です。上流のエレメントは下流のエレメントの sink pad を探し、その Pad に登録されている chain 関数を呼びだすようになっています。なので noop でも sink パッドに chain 関数を登録します。
#include "gsttextnoop.h" struct _GstTextNoop { GstElement parent_class; }; G_DEFINE_TYPE(GstTextNoop, gst_text_noop, GST_TYPE_ELEMENT) static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static GstFlowReturn gst_text_noop_chain(GstPad * pad, GstObject * parent, GstBuffer * buffer); static void gst_text_noop_class_init (GstTextNoopClass * klass) { GstElementClass *gstelement_class; gstelement_class = (GstElementClass *) (klass); gst_element_class_set_static_metadata (gstelement_class, "Noop Element", "Filter", "Simple element like Identity", "Me"); gst_element_class_add_static_pad_template (gstelement_class, &srctemplate); gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate); } static void gst_text_noop_init (GstTextNoop * noop) { GstPad *srcpad; GstPad *sinkpad; srcpad = gst_pad_new_from_static_template(&srctemplate, "src"); gst_element_add_pad(GST_ELEMENT(noop), srcpad); sinkpad = gst_pad_new_from_static_template(&sinktemplate, "sink"); gst_element_add_pad(GST_ELEMENT(noop), sinkpad); gst_pad_set_chain_function(sinkpad, gst_text_noop_chain); } static GstFlowReturn gst_text_noop_chain(GstPad * sinkpad, GstObject * parent, GstBuffer * buffer) { GstPad *srcpad; GstFlowRetrun ret; srcpad = gst_element_get_static_pad(GST_ELEMENT(parent), "src"); ret = gst_pad_push(srcpad, buffer); gst_object_ref(srcpad); return ret; }
- static GstFlowReturn gst_text_noop_chain();
- forward declaration です。関数は先に宣言しておいて、下に実体を書くのが GObject 流の作法のようです。
gst_pad_set_chain_function()
- 第1引数に対象の Pad を、第2引数に関数を指定します
- gst_text_noop_chain()
- chain 関数の実体です。
srcpad
を取得し、引数で渡ってきたGstBuffer
をsrcpad
に push します。get()
したsrcpad
はリファレンスが増えているので忘れずにgst_object_ref()
で減らしてください。
GST_PLUGIN_PATH=. gst-launch-1.0 -q filesrc location=/tmp/a.txt ! textnoop ! filesink location=/tmp/b.txt cmp /tmp/a.txt /tmp/b.txt
これで、正しくデーターが流れるようになりました。 a.txt
をコピーした b.txt
が同じ内容になっています。
6 おわりに
他にもエレメントでは、イベントの処理や、クエリーに反応したり、プロパティーの設定など、いろいろな事をやらなければなりません。それらの話は、また今度。
No comments:
Post a Comment