Tuesday, December 13, 2016

GStreamer でテキスト処理 2: なにもしない identity もどきのエレメント textnoop (Element)

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_TYPEG_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 クラスのヘッダーを include
gst_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 を取得し、引数で渡ってきた GstBuffersrcpad に 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