Kurentoの独自Moduleを作る

## 概要

[前回](https://qiita.com/Otama75/items/9124ad2be642eddb2a38)、KurentoでMCU接続のGroup Callを行うサンプルを作成しましたが、Kurento標準のCompositeMixerエレメントでは、レイアウトのパターンが決まっていて自由に配置する事ができませんでした。このレイアウトを変更するためには、独自のKurentoエレメントを作成する必要があります。

そこで今回は新規にKuretno Moduleを作成し、CompositeMixerエレメントと同じ機能を持つCustomMixerという名前のエレメントを追加して動作させてみます。CustomMixerを変更して、オリジナルのレイアウトに変更する作業は次回に行います。



独自Kurento Moduleとエレメント追加＆動作確認は、以下の手順で行っていきます。

1. Module作成ツールのインストール

2. Module作成ツールを使って雛形を作る

3. 既存のコードをベースにElement本体を作る

4. ビルド環境を整える

5. ビルドをする

6. debパッケージの作成とインストール

7. JAVA Client用ライブラリの作成とインストール

8. 動作確認



## Module作成ツールのインストール

Kurento Moduleの作成作業は、kurento-media-serverを動作させるUbuntuマシンにログインして行います。

まずは、作業用ディレクトリを作っておきます。

```shell
cd
mkdir work
cd work
```

自作エレメントの作成の際に参照するので、オリジナルのkurento−elementsのソースを取得しておきます。

```shell
git clone https://github.com/Kurento/kms-elements.git
cd kms-elements
git checkout 6.16.0
cd ..
```

Kurento Moduleを自作するときには、kurento-module-scaffoldというツールを使います。kurento-module-scaffoldはkurento-media-server-devというパッケージに入っているのでインストールします。

```
sudo apt update && apt install -y kurento-media-server-dev
```



## Module作成ツールを使って独自のModuleを作る

kurento-module-scaffoldを使って新しいmodule("MyModule")を作成します。一番目の引数にmodule名（Module名はCamelCaseで指定する必要あり）２番目の引数に出力ディレクトリを指定します。３番目の引数を与えるとOpenCV用のフィルタモジュールを作成しますが、ここではGstermer用のモジュールを作成するので何も指定せずに実行します。

```shell
kurento-module-scaffold MyModule .
```

このコマンドにより、my-moduleフォルダが作成されて、モジュールのソースコード一式がそのフォルダの中に作成されます。

```
cd my-module
```

MyModuleのソースコードは、以下のようなフォルダ構成になっています。

```shell
my-module (ルート)
|-- src
|   |-- gst-plugins  ... Kurento Media Server内に生成されるCustomMixerの本体のソースコード
|   |-- server
|       |-- implementation ... Kurento ClientからCustomMixerを呼び出すAPIのコード
|       |-- interface ... CustomMixerモジュールの記述ファイル(.kmf)
|-- debianパッケージを生成するためのフォルダ
```

ソースコードの雛形が作成されました。



## 既存のコードをベースにElementを作る

ここからMyModuleの中にCustomMixer Elementを作成していきます。エレメントのコードを０から作るのは大変なので、標準のKurento-elementsモジュールに含まれるCompositeMixerをコピーして作成します。

src/server/inferfaceフォルダ内のモジュール記述ファイル(xxx.kmd.json)に作成するelementの情報を記述します。雛形を作成した段階で、src/server/interfaceフォルダの中に、mymodule.MyModule.kmd.jsonというファイルがあるので、ファイル名をmymodule.CustomMixer.kmd.jsonに変更します。

```shell
mv src/server/interface/mymodule.MyModule.kmd.json src/server/interface/mymodule.CustomMixer.kmd.json
```

mymodule.CustomMixer.kmd.jsonの内容をオリジナルのCompositeMixerの記述ファイル(kms-elements/src/server/interface/elements.Composite.kmd.json)を参考に以下の通りに書き換えます。

```src/server/interface/mymodule.CustomMixer.kmd.json
{
  "remoteClasses": [
    {
      "name": "CustomMixer",
      "extends": "Hub",
      "doc": "CustomMixer interface. Documentation about the module",
      "constructor": {
        "doc": "Create an element",
        "params": [
          {
            "name": "mediaPipeline",
            "doc": "the parent :rom:cls:`MediaPipeline`",
            "type": "MediaPipeline",
            "final": true
          }
        ]
      }
    }
  ]
}
```

このファイルでは作成するエレメントの名前、親クラス、説明、パラメータなどを記述します。記述ファイルを作成したら、cmakeを実行するとAPIを作成するコード(C++)を src/server/implementation/objectフォルダの中に自動生成します。ここでは、プロジェクトのルートフォルダの下に"build"フォルダを作成して`cmake ..`を実行します。

```shell
mkdir build
cd build
cmake ..
cd ..
```

これで、src/server/implementation/objectの中に`CustomMixerImpl.cpp` と `CustomMixerImpl.hpp`ができました。

```shell
ls src/server/implementation/objects/
> CustomMixerImpl.cpp  CustomMixerImpl.hpp
```

APIを生成するコードが自動生成されたのですが、一部不完全なので追記が必要な箇所があります。親クラスのコンストラクタを呼び出すコードをCompositeMixerのコードに従って追記します。`CustomMixerImpl.cpp`のコード21行目から25行目までのCustomMixerImplコンストラクタのコードを以下のコードに置き換えて、親クラスのコンストラクタが呼ばれるように書き換えます。

```src/server/implementation/objects/CustomMixerImpl.cpp
#define FACTORY_NAME "custommixer"

CustomMixerImpl::CustomMixerImpl (const boost::property_tree::ptree &conf, std::shared_ptr<MediaPipeline> mediaPipeline) 
    : HubImpl (conf, std::dynamic_pointer_cast<MediaObjectImpl> (mediaPipeline), FACTORY_NAME) 
{
}

```

以上で新しいエレメントのためのKurento APIを作成するコードができました。

次にCustomMixerの処理の内容を記述します。オリジナルのCompositeMixerをベースに、kmscompositemixer.cの中の"COMPOSITE"の文字列を"CUSTOM"に置換します。全部大文字、先頭大文字、全部小文字の３パターンとも書き換えて、src/gst-plugin/gstcustommixer.cとして書き込みます。以下のコマンドをプロジェクトのルートディレクトリで実行します。

```
sed -e 's/COMPOSITE/CUSTOM/g' -e 's/Composite/Custom/g' -e 's/composite/custom/g' ../kms-elements/src/gst-plugins/kmscompositemixer.c > src/gst-plugins/gstcustommixer.c
```

ヘッダーファイルも同様に作成します。

```
sed -e 's/COMPOSITE/CUSTOM/g' -e 's/Composite/Custom/g' -e 's/composite/custom/g' ../kms-elements/src/gst-plugins/kmscompositemixer.h > src/gst-plugins/gstcustommixer.h
```

gstcustommixer.cppファイルの先頭部分を編集してgstcustommixer.hをincludeするように修正します。

```src/gst-plugins/gstcustommixer.c
// kmscustommixer.h から gstcustommixer.hに変更する
#include "gstcustommixer.h"
```

同様にmymodule.cファイルの先頭部分を編集してgstcustommixer.hをincludeするように修正します。

```src/gst-plugins/mymodule.c
// gstmymodule.h から gstcustommixer.hに変更する
#include "gstcustommixer.h"
```

さらにmymodule.cファイルのinit()関数から呼び出されるCustomMixer エレメントの初期化関数を書き換えます。

```src/gst-plugins/mymodule.c
static gboolean
init (GstPlugin *plugin)
{
    // gst_my_module_plugin_initからkms_custom_mixer_plugin_initに変更
    if (!kms_custom_mixer_plugin_init (plugin))  
    return FALSE;

  return TRUE;
}
```

src/gst-pluginsフォルダの中に、gstmymodule.cppとgstmymodule.hのファイルがありますが、これは自動生成された中身のないフィルタエレメントのコードで不要なので削除しても構いません。

エレメントのコードの作成は以上です。



## ビルド環境を整える

kurentoのヘッダーファイルと、ライブラリがリンクできるようにプロジェクトのルートフォルダにある`.CMakeLists.txt`の51行目の`# Generate file "config.h"`の直前に以下の2行を追記します。

```.CMakeLists.txt
include(GenericFind)
generic_find(LIBNAME KmsGstCommons REQUIRED)

```

src/gst-plugins/CMakeLists.txt を修正して、kurentoのヘッダーファイルとライブラリの設定を追記します。またソースコードとしてgstcustommixer.cとgstcustommixer.hを指定します。

```src/gst-plugins/CMakeLists.txt
include_directories(
  ${GSTREAMER_INCLUDE_DIRS}
  ${GSTREAMER_VIDEO_INCLUDE_DIRS}
  ${CMAKE_CURRENT_SOURCE_DIR}
  ${OPENCV_INCLUDE_DIRS}
  ${KmsGstCommons_INCLUDE_DIRS}      # 行追加
)

set(MY_MODULE_SOURCES
  mymodule.c
  gstcustommixer.h                   # gstcustommixer.hを指定
  gstcustommixer.c                   # gstcustommixer.cを指定
)

add_library(mymodule MODULE ${MY_MODULE_SOURCES})
if(SANITIZERS_ENABLED)
  add_sanitizers(mymodule)
endif()

target_link_libraries(mymodule
  ${GSTREAMER_LIBRARIES}
  ${GSTREAMER_VIDEO_LIBRARIES}
  ${OPENCV_LIBRARIES}
  ${KmsGstCommons_LIBRARIES}         # 行追加
)

install(
  TARGETS mymodule
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  LIBRARY DESTINATION ${CMAKE_INSTALL_GST_PLUGINS_DIR}
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

```



## ビルドをする

ここで、ビルドディレクトリを作って`cmake ..`を実行すると、コードが自動生成されてmakeできるようになり、makeします。

```
cd build
cmake ..
make
```

ビルドしてできたモジュールのライブラリ(libmymodule.so)がbuild/srcディレクトリの中に作成されました。



## debパッケージの作成とインストール

モジュールのライブラリをkurento-media-serverが起動時に読み込んで実行できるようにするためにdebianパッケージを作成します。

プロジェクトのルートフォルダに戻って、debパッケージのビルドツールがインストールされていなければインストールします。

```shell
cd .. 
sudo apt update && sudo apt install --no-install-recommends --yes dpkg-dev
```

ルートディレクトリでパッケージを作成します。

```shell
dpkg-buildpackage -us -uc
```

これで、作成したライブラリのdebパッケージができました。生成されたdebパッケージはルートフォルダの親フォルダにあります。

```sh
ls ../*.deb
../my-module-dev_0.0.1~rc1_amd64.deb  ../my-module_0.0.1~rc1_amd64.deb
```

パッケージをkurento-media-serverが動作するUbuntuにインストールします。

```shell
sudo dpkg -i ../my-module_0.0.1~rc1_amd64.deb
```

インストールできたかどうかは `kurento-media-server` を `-v`オプション付きで起動するとわかります。

```shell
$ kurento-media-server -v
Kurento Media Server version: 6.16.0
Found modules:
	'core' version 6.16.0
	'elements' version 6.16.0
	'filters' version 6.16.0
	'mymodule' version 0.0.1~1.g13fd0d5
```

作成したモジュールが無事にkurento-media-serverに読み込まれるようになりました。



## JAVA Client用ライブラリの作成とインストール

Java Client用のライブラリを作成します。このライブラリを読み込ませるとKurento Client ApplicationからMyModule内のCustomMixerを呼び出して使用することができるようになります。

```shell
cd build
cmake .. -DGENERATE_JAVA_CLIENT_PROJECT=TRUE
make java
```

ビルド終了後、`build/java/target`ディレクトリの下に`custommixer-0.0.1-SNAPSHOT.jar`ファイルが作成されました。

このファイルは、kurento-clientから、kurento-media-serverのCustomMixerを作成・制御するためのAPIを含むライブラリです。kurento-clientを動作させるマシンにインストールする必要がありますので、このファイルをMacにコピーしてローカルのmvnリポジトリにインストールします。

Macの任意のディレクトリで以下を実行します。

```shell
scp <username>@<kuernto host IP>:~/work/my-module/build/java/target/mymodule-0.0.1-SNAPSHOT.jar .

mvn install:install-file \
-Dfile=./mymodule-0.0.1-SNAPSHOT.jar \
-DgroupId=org.kurento.module \
-DartifactId=mymodule \
-Dversion=0.0.1-SNAPSHOT \
-Dpackaging=jar \
-DgeneratePom=true

```



## 動作確認

これを前回作ったkurento-group-call-mcuで動かして試してみましょう。

kurento-group-call-mcuプロジェクトのルートディレクトリにあるpom.xmlの`<dependencies>`の項目に以下を追記します。

```pom.xml
		<dependency>
			<groupId>org.kurento.module</groupId>
			<artifactId>mymodule</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
```

javaのコードを今回作成したCustomMixerを使うように修正します。

room.javaのソースコードでImportするモージュール名を以下の様にComposteからCustomMixerに変更し、

```room.java
-import org.kurento.client.Composite;
+import org.kurento.module.mymodule.CustomMixer;
```

以下room.java中のComposteクラスをCustomMixerクラスに置き換えます。(3箇所くらい)



Ubuntu 側でkurento-media-serverを起動してからKurento Clientを起動します。

```shell
# Ubuntuマシンで実行
GST_DEBUG="1,Kurento*:5,custommixer*:5" kurento-media-server
```

環境変数`GST_DEBUG`に上記の様にデバッグオプションを指定してから起動すると、kurento-media-serverとKurentoClient間のメッセージのやり取りを確認することができます。ログを解析すると、CustomMixerが呼び出されていることを確認することができます。

Mac側

```shell
# Macで実行
cd kurento-group-call-mcu
./start.sh
```

macのブラウザから`localhost:8443`にアクセスして前回作った標準のComposite による画面合成と全く同様に動くとこを確認できればOKです。



## まとめ

新規にKuretno Moduleを作成し、CompositeMixerエレメントと同じ機能を持つCustomMixerという名前のエレメントを追加して動作させてみました。次回はCustomMixerを変更して、独自のレイアウトに変更する作業を行います。

ここまでのソースコードは[こちら](https://github.com/kozokomiya/kurento-my-module/tree/1_add_custommixer)でダウンロードできます。