Kurento CompositeMixerのレイアウト変更


## 概要

[前回](https://qiita.com/Otama75/items/2fb87895e1a9166d2e01)、Kurento用の独自モジュールであるMyModuleを作成し、Kurento標準のCompositeMixerをベースとするCustomMixerエレメントを追加して動作させました。

今回は、このCustomMixerの中身を変更して合成レイアウトの変更を行ってみます。

### 作成するCustomMixerエレメント

KurentoオリジナルのComposteMixerでは、複数の入力ストリームをタイル状に合成するレイアウトが実装されていますが、今回はPNGファイルとして用意した固定の壁紙の上に２つの映像ストリームを重ね合わせて合成するビデオミキサーを作成します。（３つ以上のストリームが入力された場合は、３つ目以降のストリームは破棄されて合成されないものとします。）

![スクリーンショット 2021-03-19 11.22.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189951/eaecb208-0b6e-9027-9d8d-1f121f46a152.png)

## Kurentoエレメントの構成

KurentoのエレメントはGstreamerのエレメントがベースとなっているので、その作成にはGstreamer Element作成の知識が必要となります。また、Gstreamer Elementは、GObjectライブラリを使ってオブジェクト指向化されたC言語で記述されたクラスとして実装されているので、GObjectライブラリの理解も必要となります。

Kurento Elementの全体の説明は大変なので、ここではレイアウトの変更に必要な部分のみピンポイントで説明します。

### ストリーム画像のレイアウトの変更

gstcustommixer.c のなかで、ストリーム画像のレイアウト決めを行っている部分は、kms_custom_mixer_recalculate_size()関数です。

各映像ストリームが入力されるPad毎に、合成後の位置("xpos", "ypos")とサイズ("width", "height")と重ね合わせの順番("zorder" 大きいほうが手前)をパラメータとして与えているところがあるので、その部分を以下のように変更します。

 ```gstcustommixer.c
static void
kms_custom_mixer_recalculate_sizes (gpointer data)
{
  KmsCustomMixer *self = KMS_CUSTOM_MIXER (data);
  GstCaps *filtercaps;
  gint width, height, top, left, zorder, counter;
  GList *l;
  GList *values = g_hash_table_get_values (self->priv->ports);

  if (self->priv->n_elems <= 0) {
    return;
  }

  counter = 0;
  values = g_list_sort (values, compare_port_data);

  for (l = values; l != NULL; l = l->next) {
    KmsCustomMixerData *port_data = l->data;
    if (port_data->input == FALSE) {
      continue;
    }
    if (counter == 0) {		        // ストリーム1の位置 
      width = 400;  height = 300;
      left = 100;   top = 100;
      zorder = 2;
    } else if (counter == 1) {    // ストリーム2の位置
      width = 200;  height = 150;
      left = 400;   top = 350;
      zorder = 3;
    } else {											// その他のストリームは表示しない
      width = 0;    height = 0;
      left = 0;     top = 0;
      zorder = 0;
    }
 
    filtercaps =
        gst_caps_new_simple ("video/x-raw",
        "width", G_TYPE_INT, width, "height", G_TYPE_INT, height,
        "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL);
    g_object_set (port_data->capsfilter, "caps", filtercaps, NULL);
    gst_caps_unref (filtercaps);

    // 合成後の位置の設定
    g_object_set (port_data->video_mixer_pad, "xpos", left, "ypos", top,
         "alpha", 1.0, "zorder", zorder, NULL);
    counter++;
    GST_DEBUG_OBJECT (self, "counter %d id_port %d ", counter, port_data->id);
    GST_DEBUG_OBJECT (self, "top %d left %d width %d height %d", top, left,
        width, height);
  }
  g_list_free (values);
}
 ```

### 背景画像の追加

CompositeMixer エレメントは、複数のGstreamer Elementの組み合わせでできています。壁紙画像を読み込んで合成画面に重ね合わせるには、さらに"filesrc"、"pngdec"、"imagefreeze"という3つのエレメント追加します。

kms_custom_mixer_handle_port() 関数の`if (self->priv->videotestsrc == NULL) {`から始まるif文の後半に、背景画像用エレメントの追加と設定する部分を追記していきます。

壁紙の画像ファイルは /usr/share/kurento/modules/ ディレクトリに `wallpaper.png`というファイル名で置かれていることを想定しています。

```src/gst-plugins/gstcustommixer.c
kms_custom_mixer_handle_port (KmsBaseHub * mixer,
    GstElement * mixer_end_point)
{
...
    if (self->priv->videotestsrc == NULL) {
      GstElement *capsfilter;
      GstCaps *filtercaps;
      GstPad *pad;
      GstPadTemplate *sink_pad_template;
      GstElement *filesrc;
      GstElement *pngdec;
      GstElement *imagefreeze;

      sink_pad_template =
          gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS
          (self->priv->videomixer), "sink_%u");

      if (G_UNLIKELY (sink_pad_template == NULL)) {
        GST_ERROR_OBJECT (self, "Error taking a new pad from videomixer");
      }

      self->priv->videotestsrc =
          gst_element_factory_make ("videotestsrc", NULL);
      g_object_set (self->priv->videotestsrc, "is-live", TRUE, "pattern",
          /*black */ 2, NULL);

      capsfilter = gst_element_factory_make ("capsfilter", NULL);
      g_object_set (G_OBJECT (capsfilter), "caps-change-mode", 1, NULL);

      filtercaps =
          gst_caps_new_simple ("video/x-raw",
          "width", G_TYPE_INT, self->priv->output_width,
          "height", G_TYPE_INT, self->priv->output_height,
          "framerate", GST_TYPE_FRACTION, 15, 1, NULL);
      g_object_set (G_OBJECT (capsfilter), "caps", filtercaps, NULL);
      gst_caps_unref (filtercaps);

      gst_bin_add_many (GST_BIN (self), self->priv->videotestsrc,
          capsfilter, NULL);

      gst_element_link (self->priv->videotestsrc, capsfilter);

      /*link capsfilter -> videomixer */
      pad = gst_element_request_pad (self->priv->videomixer, sink_pad_template,
          NULL, NULL);

      gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
          (GstPadProbeCallback) cb_latency, NULL, NULL);

      gst_element_link_pads (capsfilter, NULL,
          self->priv->videomixer, GST_OBJECT_NAME (pad));
      g_object_set (pad, "xpos", 0, "ypos", 0, "alpha", 0.0, "zorder", 0, NULL);
      g_object_unref (pad);

      gst_element_sync_state_with_parent (capsfilter);
      gst_element_sync_state_with_parent (self->priv->videotestsrc);

			// ここから背景画像用エレメントの追加と設定
      sink_pad_template =
          gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS
          (self->priv->videomixer), "sink_%u");

      if (G_UNLIKELY (sink_pad_template == NULL)) {
        GST_ERROR_OBJECT (self, "Error taking a new pad from videomixer");
      }
      filesrc =
          gst_element_factory_make("filesrc", NULL);
      g_object_set(filesrc, "location", "/usr/share/kurento/modules/wallpaper.png", NULL);
      g_object_set(filesrc, "is-live", TRUE, NULL);
      pngdec = gst_element_factory_make ("pngdec", NULL);
      imagefreeze = gst_element_factory_make ("imagefreeze", NULL);

      gst_bin_add_many (GST_BIN (self), filesrc, pngdec, imagefreeze, NULL);

      gst_element_link (filesrc, pngdec);
      gst_element_link (pngdec, imagefreeze);

      pad = gst_element_request_pad (self->priv->videomixer, sink_pad_template,
          NULL, NULL);
      gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
          (GstPadProbeCallback) cb_latency, NULL, NULL);
      gst_element_link_pads (imagefreeze, NULL, self->priv->videomixer, GST_OBJECT_NAME (pad));
      g_object_set (pad, "xpos", 0, "ypos", 0, "alpha", 1.0, "zorder", 1, NULL);
      g_object_unref (pad);

      gst_element_sync_state_with_parent (filesrc);
      gst_element_sync_state_with_parent (pngdec);
      gst_element_sync_state_with_parent (imagefreeze);
    }
  ...
```



## 画像ファイルを準備

背景画像としてwallpaper.png を準備します。画サイズは800x600にします。ルートフォルダの下にresouceフォルダを作ってそこに置きます。



### ./CMakeLists.txtの編集

./CMakeLists.txt の最後に以下の1行を追加します。この設定により resouceフォルダにある画像ファイルを最終的に /usr/share/kurento/module の中にインストールするdebパッケージが作成されます。

```./CMakeLists.txt
install(FILES resource/wallpaper.png DESTINATION /usr/share/kurento/modules)
```



### debian/my-module.install の編集

debian/my-module.installの最後に以下の1行を追加します。

```debian/my-module.install
usr/share/kurento/modules/*.png
```



変更は以上です。今回はAPIの変更は無いのでJavaコードとKurento Clinentの変更は必要なりません。



## 動作確認

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

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

Mac側

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

macのブラウザから`localhost:8443`にアクセスしてUserとRoom名を入れてログインします。

レイアウトを変更した合成画面がブラウザに表示されます。

![スクリーンショット 2021-03-19 16.52.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189951/dcff35ad-4c4a-de8e-c4c4-e51c4c5167b6.png)




## まとめ

KurentoオリジナルのCompositeMixerエレメントをコピーして作ったCustomMixerエレメントを改造して、合成のレイアウトを変更しました。PNG画像を読み込むエレメントを追加して、合成画面の背景に表示するようにできました。

今回作成したMyModuleのソースコード一式は[こちら](https://github.com/kozokomiya/kurento-my-module)。