Gstreamerエレメントを自作するまで - Pythonでのプラグイン作成 -

## 目的

[Gstreamer](https://gstreamer.freedesktop.org/)を使ったアプリケーションの開発が始まってきたので、自分でエレメントを作成できるようになるまでにやったことをメモ書きする

## 目次

1. [Gstreamerとは](https://qiita.com/DaiGuard/items/6b66a45b545eaa9fbdcb)
1. [Pythonでのプラグイン作成](https://qiita.com/DaiGuard/items/05b0f233c4a3296d6677)
1. C++でのElement作成

## Pythonでのプラグイン作成

### 動作環境

- Ubuntu: 22.04
- Python: 3.10.12
- Gstreamer: 1.20.3
- Gstreamer Python: 1.20.1

### インストール

PythonでGstreamerプラグインを開発するまでにいつ様なツール、プラグインをインストールします

引用: [マルチメディアフレームワークGStreamer　～　入門編](https://qiita.com/kurun_pan/items/f7896d52c1a3fcc947b0)

##### 基本パッケージ

Gstreamerパイプラインを起動するためのコマンド

```bash
$ sudo apt install libgstreamer1.0-0 gstreamer1.0-tools
```

##### 必要なプラグイン

パイプラインを実行するのに必要なプライグイン郡をインストール

```bash
$ sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio 
```

##### Gstreamer Python バインド

Pythonで作られたGstreamerプラグインを読み込むためのライブラリ

```bash
$ sudo apt install gstreamer1.0-python3-plugin-loader
```

##### 動作確認

以下のコマンドを実行して`python`プラグインの詳細情報が表示されればインストール完了です

```bash
$ gst-inspect-1.0 python
Plugin Details:
  Name                     python
  Description              loader for plugins written in python
  Filename                 /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstpython.so
  Version                  1.16.2
  License                  LGPL
  Source module            gst-python
  Binary package           GStreamer Python
  Origin URL               http://gstreamer.freedesktop.org


  0 features:
```

> <span style="color: red;">※注意</span>
> 上記の手順でpythonプラグインが読み込めない場合、プラグインをインストールする前のキャッシュを使用してGstreamerが動作している場合があります
>
> 以下のコマンドを実行して、どこのキャッシュを読んでいるか確認しましょう
> ```bash
> GST_DEBUG=4 gst-inspect-1.0 python
> .
> 0:00:00.000481772 95684 0x56129c8cb000 INFO            GST_REGISTRY gstregistry.c:1733:ensure_current_registry: reading registry cache: /home/dai_guard/.cache/gstreamer-1.0/registry.x86_64.bin
> .
> ```
>
> このキャッシュを削除して再度、実行しましょう
> ```bash
> $ rm -rf ~/.cache/gstreamer-1.0
> $ gst-inspect-1.0 python
> Plugin Details:
>   Name                     python
>   Description              loader for plugins written in python
>   Filename                 /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstpython.so
>   Version                  1.16.2
>   License                  LGPL
>   Source module            gst-python
>   Binary package           GStreamer Python
>   Origin URL               http://gstreamer.freedesktop.org
> 
> 
>   0 features:
> ```

### プラグイン作成

[参考ソースコード](https://github.com/DaiGuard/gst_helloworld/blob/main/gst/python/gst_helloworld.py)

##### フォルダ構成

自分で作ったプラグインを格納するフォルダの構成

```bash
+ gst_my_plugin # なんでもOK
  └─ gst # なんでもOK
     └─ python # ここは"python"である必要がある
        └─ gst_my_plugin.py # 自作したプラグイン
```

##### Gstreamer Python プラグインの基本構造

最低限、Gstreamerプラグインを作成するための記述方法を記載します

```python
# gst_my_plugin.py

### 必要ライブラリのインポート ###
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstBase', '1.0')
gi.require_version('GstAudio', '1.0')

from gi.repository import Gst, GLib, GObject, GstBase, GstVideo, GstAudio

### 自作プラグインのクラスを宣言 ###
class GstMyPlugin(GstBase.BaseTransform):
    """
    作成するプラグインのタイプによって継承するクラスを変える

    BaseSrc: srcパッドだけが必要なプラグイン
    BaseSink: sinkパッドだけが必要なプラグイン
    BaseTransform: sink, srcが両方必要なプラグイン
    """

    ### Gstreamerプラグインの基本記述情報
    __gstmetadata__ = (
        "MyPlugin",  # Plugin name
        "Filter",    # Plugin Klass
                     #  - Source
                     #  - Sink
                     #  - Filter
                     #  - Effect
                     #  - Demuxer
                     #  - Muxer
                     #  - Decoder
                     #  - Encoder
                     #  - Mixer
                     #  - Converter
                     #  - Analyzer
                     #  - Control
                     #  - Extracter
                     #  - Formatter
                     #  - Connector
        "my plugin", # Plugin description
        "dai_guard <dai_guard@gmail.com>"  # Author information
    )

    ### パッドのCapabirity情報を記述する
    __gsttemplates__ = (
        Gst.PadTemplate.new(
            "src",
            Gst.PadDirection.SRC,
            Gst.PadPresence.ALWAYS,
            Gst.Caps.from_string(f"video/x-raw,format={FORMATS},width=[1,2147483647],height=[1,2147483647]")),
            .
            .
        )

    ### プラグインのproperty情報を記述する
    __gproperties__ = {
        "property_name": (
            GObject.TYPE_INT64,  # Data type
            "property_name",     # Nickname 
            "description",       # Description
            "1",                 # min value
            GLib.MAXINT,         # max value
            100,                 # default value
            GObject.ParamFlags.READWRITE, # parameter flags
        ),
        .
        .
    }

    ### 以下の関数は親クラスにオーバーライドされる
    ### コンストラクタ
    def __init__(self):
        ...
    ### プロパティの値を取得
    def do_get_property(self, prop):
        ...
    ### プロパティの値をセット
    def do_set_property(self, prop, value):
        ...
    ### 入力したバッファに対して出力バッファを作成する
    def do_transform(self, inbuf, outbuf):
        ...
    ### sinkが呼ばれた際に実行するバッファリングしょり
    def do_generate_output(self):
        ...
    ### capabilityのsink側とsrc側の情報を変化させる
    def do_transform_caps(self, direction, caps, filter_):
        ...
    ### capabilityのsink側とsrc側に固定の情報を流す
    def do_fixate_caps(self, direction, caps, othercaps):
        ...
    ### capabilityのsink側とsrc側を任意の値に設定する
    def do_set_caps(self, icaps, ocaps):
        ...

# 自作プラグインを登録する
GObject.type_register(GstMyPlugin)
__gstelementfactory__ = ("gst_my_plugin", Gst.Rank.NONE, GstMyPlugin)
```
##### オーバーライド関数のサンプル

- ##### def __init__(self)

```python
class GstMyPlugin(GstBase.BaseTransform):
    def __init__(self):
        # 親クラスの初期化を実施
        super(GstMyPlugin, self).__init__()
```

- ##### def do_get_property(self, prop)

プロパティ値を取得する際に実行され、その値を返す

```python
class GstMyPlugin(GstBase.BaseTransform):
    def do_get_property(self, prop: GObject.GparamSpec):
        if prop.name == 'param_name':
            return self.param_name
        .
        .
```

- ##### def do_set_property(self, prop, value)

プロパティ値をセットする際に実行され、値をクラス内に格納する

```python
class GstMyPlugin(GstBase.BaseTransform):
    def do_set_property(self, prop: GObject.GparamSpec, value):
        if prop.name == 'param_name':
            self.param_name = value
        .
        .
```


- ##### def do_transform(self, inbuf, outbuf)

> 作成中


- ##### def do_generate_output(self)

> 作成中


- ##### do_transform_caps(self, direction, caps, filter_)

> 作成中


- ##### do_fixate_caps(self, direction, caps, othercaps)

> 作成中


- ##### do_set_caps(self, icaps, ocaps)

> 作成中

## 参考リンク
- プラグインのクラスリスト: [Element Klass definition](https://gstreamer.freedesktop.org/documentation/additional/design/draft-klass.html?gi-language=c)
- パッドのタイプ: [Pads and capabilities](https://gstreamer.freedesktop.org/documentation/application-development/basics/pads.html?gi-language=c)
- エレメントのテンプレート: [Implementing an audio plotter](https://mathieuduponchelle.github.io/2018-02-15-Python-Elements-2.html)
- Pythonプラグインのサンプル: [gst-python-plugins](https://github.com/jackersson/gst-python-plugins)
- PyGObjectのクラス情報: [Gst 1.0](https://lazka.github.io/pgi-docs/index.html#Gst-1.0)