SkyWayでデスクトップの映像とカメラの映像をクロマキー合成して配信する

# はじめに
この記事は[SkyWay Advent Calendar 2018](https://qiita.com/advent-calendar/2018/skyway)の21日目(大遅刻)の記事です。
20日目は[ShirataHikaru](https://qiita.com/ShirataHikaru)さんの[「SkyWayとVue.jsでリアルタイムに共同編集可能なマークダウンエディタっぽいもの」](https://qiita.com/ShirataHikaru/items/be83c42304cab912bc86)でした。

# WebRTCの"W"の字もわからない
まず「WebRTC」という言葉を知っているかどうかも怪しい状態からのスタートです。
全くどうしたらいいかわからなかったので、SkyWayのアドベントカレンダーを書いている方達の記事を読みながら使い方を学ぼうと思いました。
まずはアドベントカレンダー8日目の、[「【爆速！】5分でビデオチャットを構築する」](https://qiita.com/kolife/items/6b771676d7558254b3a6)by [kolife](https://qiita.com/kolife)さんの記事を参考にWebRTCとSkyWayに触れてみます。
はい、**これでWebRTCとSkyWayは完全に理解しました。**

# 今回作るもの
完全に理解したので、早速サンプルコードなどを改造して自分でも何か作ってみようと思いました。
そこで参考にさせていただいたのがアドベントカレンダー16日目の[「SkyWay のビデオチャットに雑なクロマキー合成してみた」](https://qiita.com/horihiro/items/dcc21bb496c6112aef65)by[horihiro](https://qiita.com/horihiro)さんの記事です。
カメラ映像の背景を任意の画像とクロマキー合成するというものですが、このアイデアはそのまま、置き換える背景画像をデスクトップキャプチャーの映像にすれば**即席顔出しゲーム実況配信**みたいなのが実現できるのではないかと思ったので作ってみました。horihiroさん良記事ありがとうございます。

# 下準備
SkyWayのスクリーンシェアサンプルコードを改造して作っていきます。
> https://github.com/skyway/skyway-screenshare

サンプルのREADMEにしたがって、画面共有ができることを確認します。


# 作る
以下の記事が大変参考になりました。
>[canvasでクロマキー合成っぽいことしてみる](https://qiita.com/keiskimu/items/a4fc9ebb338906b9813e)

この記事では、カメラから取り込んだvideo要素をクロマキー加工して、結果をCanvasに描画しているようです。
これを使えばカメラの背景透過はできそう。
で、スクリーンキャプチャと合成して送信しなきゃいけないんですが、スクリーンキャプチャもCanvasに描画して、`カメラCanvas`と`スクリーンキャプチャCanvas`をいい感じに合成して相手に送れば実現できそう。
概要図で表すとこんな感じ。
<img width="90%" src="https://qiita-image-store.s3.amazonaws.com/0/259617/397780c8-1a92-803e-0139-86428845f443.jpeg">

## キャンバスを作る
まずは**「いい感じに合成」**する手前までを実装して、自分で確認できるようにしましょう。
クロマキー調節のInputと、Canvas2つを作ります。

```HTML:index.html
      <p>My Video</p>

      <div>
        <input id="color" type="color" value="#00ff00" />
        <input id="distance" type="number" value="30" />
      </div>

      <div id="canvasArea">
        <canvas id="myCanvas1" width="320" height="240"></canvas>
        <canvas id="myCanvas0" width="320" height="240"></canvas>
      </div>
```
CSSで以下のように書くと、`<div id="canvasArea">`に囲まれたキャンバスが重なります。

```CSS:style.css
#canvasArea {
 position: relative;
 width: 320px;
 height: 240px;
 margin: 0 auto;
 border: 1px black solid;
}

canvas{
 position: absolute;
}
```

## クロマキーして描画
[canvasでクロマキー合成っぽいことしてみる](https://qiita.com/keiskimu/items/a4fc9ebb338906b9813e)のコードを使ってWebカメラをクロマキー処理します。
クロマキー処理は`app.js`の`Cameraボタン`を押された時の動作が記述されているところに追記します。

```JS:app.js
  // Camera
  $('#start-camera').on('click', () => {
    navigator.mediaDevices.getUserMedia({audio: true, video: true})
      .then(stream => {
        $('#my-video')[0].srcObject = stream;
        localStream = stream;

        if (existingCall !== null) {
          const peerid = existingCall.peer;
          existingCall.close();
          const call = peer.call(peerid, localStream);
          step3(call);
        }
      })
      .catch(err => {
        $('#step1-error').show();
      });

      $('#my-video')[0].addEventListener("loadedmetadata",function(e) {
              ctx.translate(canvas.width,0);
              ctx.scale(-1,1);

              setInterval(function(e) {
                  //videoタグの描画をコンテキストに描画
                  ctx.drawImage($('#my-video')[0],0,0,canvas.width,canvas.height);
                  chromaKey();
              },33);
      });
  });
```
ポイントは、

```JS
$('#my-video')[0].addEventListener("loadedmetadata",function(e) {});
```
で、streamが取得されるのを待っていること。
これがないとタイミングが合わず、Canvasにうまく描画されないことがあります。
あと、Webカメラの映像はデフォルトで左右反転するらしいので、以下のコードでキャンバスを反転させて映像を正しい方向に直しています。

```JS
ctx.translate(canvas.width,0);
              ctx.scale(-1,1);
```

## スクリーンキャプチャも描画
スクリーンキャプチャの映像も同じ要領でCanvasに描画します。
スクリーンキャプチャで得られる映像は左右反転しないですし、クロマキー処理もいらないので、`Screenボタン`が押された時の動作に以下を追記するだけでいいです。

```JS:app.js
      $('#my-video2')[0].addEventListener("loadedmetadata",function(e) {
              setInterval(function(e) {
                  ctx1.drawImage($('#my-video2')[0],0,0,canvas1.width,canvas1.height);
              },33);
      });
```

この段階でWebページの表示テストをしてみて、`Cameraボタン` `Screenボタン`をそれぞれ押してから、カラークロマキーを調節してうまく合成されればひとまず**「いい感じに合成」の手前**までの処理は完成です。

<img width="694" alt="かいめつ" src="https://qiita-image-store.s3.amazonaws.com/0/259617/44148e2f-ea20-ab06-ecbb-b7994d27f43e.png">
・・・怖い。

## canvasをいい感じに合成
ここから2つのキャンバスをいい感じに１つにまとめて、相手に送ります。
別に一つにまとめなくても、SkyWayのMediaStreamはTrackを追加することで複数のメディア(Webカメラとスクリーンキャプチャ同時とか)を送信することができるようです。
>Qiita記事
>[WebRTCで快適な画面共有を実現する](https://qiita.com/yusuke84/items/3f781909e3b975dd9db5)

しかし、これがまたなかなか理解できず今回のアドベントカレンダー大遅刻の原因となりました。

### 力技で解決
しょうがないので今回は、もう一つキャンバスを作って、そこに必ず`スクリーンキャプチャ`→`クロマキー処理されたカメラ映像`の順番に描画を上書きすることで合成する方法で解決しました。

3つ目のキャンバスを作ります。

```JS
    var canvas2 = document.createElement("canvas");
    var ctx2 = canvas2.getContext("2d");
    canvas2.width = 320;
    canvas2.height = 240;
```
3つ目のキャンバスは結合して送信するようで、別にWebページに表示する必要はないのでHTMLには記載せず`.createElement("canvas")`で作ってJS内だけで使います。

で、色々と悩んだ結果クロマキー処理の末尾にcanvas結合の処理を書きました。

```JS
  // クロマキー処理
  var chromaKey = function () {

      //...省略

      ctx.putImageData(imageData, 0, 0);
      //ここでcanvas3を上書きしていく
      ctx2.drawImage(canvas1,0,0,canvas.width,canvas.height);
      ctx2.drawImage(canvas,0,0,canvas.width,canvas.height);
  };
```

## 相手に送信する
合成できた`canvas3`を相手に送信するようにします。
ここもまた悩みましたが、アドベントカレンダー 9日目[「カメラ映像を分割してSkyWayで配信するアプリケーションを作ってみた」](https://qiita.com/sublimer/items/12bec46cfce78086c29c)by[sublimer](https://qiita.com/sublimer)さんの記事を参考にしたら解決しました。

```
canvas.captureStream(フレームレート);
```
で、canvasを`MediaStream`として取得できるようです。
**Camera処理**、**ScreenShare処理**それぞれの、
`localStream = stream;`
となっているところを、
`localStream = canvas2.captureStream($('#FrameRate').val());`
に書き換えます。
また、その下のif文内にある
`const call = peer.call(peerid, stream);`
の.call( )の引数も、
`const call = peer.call(peerid, localStream);`
と書き換えます。
これで通信相手にはcanvas3の`MediaStream`のみが送信されるようになりました。

# 完成！
### 送信側
映像を送信する側はこんな感じに表示確認できます。
下段左側が`Webカメラ`の映像。
下段右側が`スクリーンキャプチャ`の映像。
上段がクロマキー合成された映像です。
![SkyWay.gif](https://qiita-image-store.s3.amazonaws.com/0/259617/1f090b7a-3ad8-16da-2d4b-91742f03dff9.gif)

### 受信側
受信側もきちんとクロマキー合成された映像が表示されます。
![SkyWay2.gif](https://qiita-image-store.s3.amazonaws.com/0/259617/8729ae63-d770-f69d-9487-0781c23fd377.gif)

# まとめ
これがあれば、即席で**顔出しのゲーム実況配信**や**動画講座の配信**ができそうですね。
今回作成したコードを以下のリポジトリにまとめました。
> GitHub
> [Video-and-Screen_ChromakeySharing](https://github.com/TakedaHiromasa/Video-and-Screen_ChromakeySharing)

最初はわからないことだらけでしたが、SkyWayアドベントカレンダーの投稿記事のおかげでだいぶ助けられました。先人たちに感謝です。