SkyWayを使ってVirtualとRealでビデオ通話

この記事は[リンク情報システム](https://www.lis.co.jp)の2018年アドベントカレンダーのリレー記事です。
engineer.hanzomon のグループメンバによってリレーされます。（リンク情報システムのFacebookは[こちら](https://www.facebook.com/lis.co.jp/)から）


#1.はじめに
さてアドベントカレンダー参加するとしてとりあえずVRいじりたいけどなにをしようか・・・と考えていたところでskywayなるものを使えば簡単にWebRTCができるよ！というのをどこかで見かけました。
（たぶんQiitaのトレンドかな・・・？）
WebRTCてなんじゃいってレベルだったんですが、ビデオ通話とかに使えるんですね・・・。
なるほど・・・では、たぶん誰かやってるだろうけどブラウザとVR空間でそういうのやってみようてな感じです。

で、やってみようはいいんですがWebRTCなんて触ったことないしUnityもよーわからんので3段階に分けてなんとかそれっぽいものが動けばと思います。

1.skyway使ってブラウザ同士でWebRTC
2.Unityでアバター動かしたり色々する
3.1と2をくっつける？

3までたどり着くことはできるのか・・・？

#2.ブラウザ同士でWebRTC
ということでまずはブラウザ同士でがんばるところです。
新しいことしたいということで興味はあったけど触ったことないNode.jsを触ってみたいと思います。
ついでに触ったことなかったHerokuも触ってみることにしました。
どんどんわからないことが増えていますが気にしない。
新しいものに触れるのは楽しいですからね。


##開発環境を整える
まずはNode.jsのインストールから。以下の記事を参考にさせていただきました。

[nodistでNode.jsをバージョン管理](https://qiita.com/satoyan419/items/56e0b5f35912b9374305)

バージョンは推奨のLTSである8.12.0、npmは4.0.5となりました。
ついでにフレームワークとしてExpressもいれておきます。

```zsh
npm init
npm install express --save
```

次にHerokuの登録、CLIのインストールを行います。
Git必要！と言われたのでGitもインストールします。
わからないことにGitが増えました。←New!
いいタイミングでまとめを上げてくださっている方がいたのでそれを見て少しお勉強します。
svnは使っているのですぐ覚えるでしょう・・・たぶん・・・

[Git/GitHubレベル別オススメ学習サイトまとめ完全保存版【2018.11】](https://qiita.com/think-a-lot/items/b3c2e9060f46f5d4ea46)

##作成
とりあえず新しいものに手を出すときはチュートリアルをやれと偉い人が言ってました。
というわけでまずはskywayのチュートリアルをやってみます。

[JavaScript SDK チュートリアル](https://webrtc.ecl.ntt.com/js-tutorial.html)

![skyway1.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/049f4bb6-443e-a58e-948d-f54ef6b48834.jpeg)

動きました。skyway完全に理解した。

skywayを完全に理解したところで色々作っていきます。
とりあえず必要そうなものは

・相手側のIDの検索画面
・ID検索するためにIDを保持するDB
・通話画面

でしょうか。

###DBの追加
ID検索の前にDBがないと検索ができないのでまずDBをHerokuのアプリケーションに追加します。
HerokuのアプリケーションページからResourcesタブを選択、Add-onsのあたりにあるFind more add-onsボタンを押します。
![skyway2.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/1200ddb6-57dd-09c2-a4c3-f86dba069ccd.jpeg)

次にHeroku Postgresを選択し、Install Heroku Postgresボタンを押します。
Add-on Planは今回あまり使う予定はないのでFreeのHobby Dev、App to provision toにアプリケーション名を入力するとProvision Add-onが活性化するので押します。
![skyway4.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/7b32e269-8e86-1ee3-9f24-d183ca6a6a64.jpeg)
これでとりあえず使えるようにはなりました。

ということでCLIからSQLを流します。
ResourcesのAdd-onにPostgresが追加されているのでクリックするとDATAページが開きます。
そこでSettingsタブのDatabase CredentialsのView Credentials...をクリックするとDBの情報がいろいろと書いてあります。
![skyway5.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/ed288ae5-dc79-9ab4-394d-769cce74ab75.jpeg)
ここのHeroku CLIの文字列をコマンドプロンプトにコピペするとDBに接続できます。
あとは普通にSQLを流すだけ・・・。

```zsh
webrtc-vr-test::DATABASE=> create table skyway_ids (
webrtc-vr-test::DATABASE(> id serial not null,
webrtc-vr-test::DATABASE(> skyway_id varchar(16) not null,
webrtc-vr-test::DATABASE(> primary key (id)
webrtc-vr-test::DATABASE(> );
CREATE TABLE
webrtc-vr-test::DATABASE=> select * from skyway_ids;
 id | skyway_id
----+-----------
(0 行)
```
###画面とか諸々の作成
さっそくExpressよくわからん・・・ってなったのでとりあえずexpress-generatorをインストールしてアプリケーションを作成します。テンプレートはPugを利用します。
Pugの書き方も勉強しておきましょう。

[Windows10 + Express & Pug（旧Jade）](https://qiita.com/zenno04/items/d16f881170170b567b16)


```zsh
npm install express-generator -g
express --view=pug app-name
cd app-name
npm install
```
画面構成は
　・/index：部屋立て or 部屋検索への入口
　・/room: 通話画面（idをDBに突っ込んで相手の接続を待つ）
　・/search: id検索画面（idをDBから検索して相手に接続する）
という感じにシンプルにします。

まずはindexから。

```pug:index.pug
extends layout

block content
  h1= title
  button(id="room")
    a(href="/room") 部屋立てる
  button(id="search")
    a(href="/search") 部屋検索
```
![skyway6.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/a1a6d086-0acf-07b9-aeb2-a433057c0d39.jpeg)

簡素だ・・・・・・。CSSもいじってないのですごいシンプル。
まぁ見た目は後から如何様にもなりましょう。

次にroomのルーターの設定を。

```javascript:routes/room.js
var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
    res.render('room', { title: 'app' });
});

module.exports = router;
```
```javascript:app.js
//以下追記
var roomRouter = require('./routes/room');
app.use('/room', roomRouter);
```
で、画面を作る前にDB接続があるのでpostgreSQL接続用にpgをインストールします。

```zsh
npm install pg --save
```
インストールが終わったら画面の作成へ。

```pug:room.pug
extends layout

block js
    script(type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js")
    script(type="text/javascript" src="https://cdn.webrtc.ecl.ntt.com/skyway-latest.js")
    script(type="text/javascript" src="../javascripts/roomScript.js")
block content
    div(id="video-container")
        video(id="their-video" autoplay)
        video(id="my-video" muted="true" autoplay)
    div(id="button-container")
        p id：
            span(id="my-id")
        button(id="end" type="button")
            a(href="/") end call
```
roomScript.jsはpeer idをDBに登録する部分以外はskywayチュートリアルとほとんど変わらないので登録部分だけ。

```javascript:javascripts/roomScript.js
peer.on('open', function(){
    $('#my-id').text(peer.id);
    $.ajax({
        url: '/add/' + peer.id,
        type: 'GET',
        dataType: 'json',
        timeout: 3000,
    })
    .done(function(data) {
    })
    .fail(function () {
        console.error('ajax error');
    })
});
```
で、addをルーターに追加します。

```javascript:routes/add.js
var express = require('express');
var router = express.Router();

router.get('/:skyway_id', function(req, res, next) {
    const { Client } = require('pg');
    const client = new Client({
        user: 'user',
        host: 'host',
        database: 'database',
        password: 'password',
        port: 5432,
        ssl: true
    });
    client.connect();
    client.query('INSERT INTO skyway_ids(skyway_id) VALUES($1)', [req.params['skyway_id']])
        .then(res => {
            client.end();
        })
        .catch(e => console.error(e.stack));
    res.send('success');
});

module.exports = router;
```
どこ書けばいいのかわからなかったのでとりあえずここに書きましたが、
ルーターにDB処理書くのはどうなんだろう・・・？

次は検索画面です。

```pug:search.pug
extends layout

block content
    table
        each id in ids
            -var url = '/room/' + id.skyway_id
            tr
                td= id.skyway_id
                td
                    button: a(href=url) 接続
```
```javascript:routes/search.js
var express = require('express');
var router = express.Router();


router.get('/', function(req, res, next) {
    const { Client } = require('pg');
    const client = new Client({
        user: 'user',
        host: 'host',
        database: 'database',
        password: 'password',
        port: 5432,
        ssl: true
    });
    client.connect();
    client.query('SELECT skyway_id FROM skyway_ids')
        .then(result => {
            res.render('search', { title: 'WebRTCTest', ids: result.rows });
            client.end();
        })
        .catch(e => console.error(e.stack));

});

module.exports = router;
```
これで全画面終わりですが、searchから接続するときに/room/:skyway_idで接続するようにしたのでその部分をroomに追加しておきます。

```pug:room.pug
//追記
    unless id == undefined
        script(type='text/javascript').
            peer.on('open', function () {
                const skyway_id = '#{id}';
                const call = peer.call(skyway_id, localStream);
                setupCallEventHandlers(call);
            });
```
```javascript:routes/room.js
//追記
router.get('/:skyway_id', function (req, res, next) {
    res.render('room', { title: 'WebRTCTest', id: req.params['skyway_id']});
});
```
これでとりあえずビデオ通話ができるようにはなりました！IDが削除されずに永遠に検索欄に表示されてますが・・・。
最終的に画面遷移は下のような感じに。
![skyway10.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/bdea32e9-6d87-b88c-7d05-68d49259e63e.jpeg)

#3.Unityでアバターを動かす
なんとかWeb側が動くようになったのでUnity側をいじっていきます。
ということでアバターのセットアップなんですが・・・全部書いてくれている方がいるので特に書くことがないです。
(~~そろそろバーチャルモーションキャプチャーも使ってみたい・・・~~)

[UniVRM+SteamVR+Final IKで始めるVTuber](https://qiita.com/sh_akira/items/81fca69d6f34a42d261c)

もちろん今回はVTuberになることが目的ではないので結構色々と省いていますが・・・。
（リップシンクもまばたきも入れてないし表情も設定してない）
そんなわけでさくっとセットアップはおわりました。
![20181212191217_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/282891/a00f4465-59d4-3961-cdba-c2826771a54c.jpeg)

#4.ブラウザとVRでWebRTC
やっと本題です。
本題までが長かった・・・・・・。
とりあえずブラウザでできればなんかVR内でもいけるんじゃない？くらいのかるーい気持ちでいたんですがどうやらそういうわけにもいかなさそう。
色々と調べてみると・・・ありました。

[SkyWay WebRTC GWを利用してUnityでWebRTC Videoをレンダリングする](https://qiita.com/nakakura/items/ac31956c2291b8d4a165)
[中身解説 -SkyWay WebRTC GWを利用してUnityでWebRTC Videoをレンダリングする-](https://qiita.com/nakakura/items/a99c468c56db2a92b32f)

やりたかったことそのまんまですやん。
とはいえ素人の私では読んでてさっぱりなので一歩ずつ追っていこうと思います。

##WebRTCGatewayを触ってみる
###・skyway-webrtc-gatewayのDL
これはそのままですね。

[skyway-webrtc-gateway](https://github.com/skyway/skyway-webrtc-gateway)

Windowsなのでgateway_windows.exeをDLします。
今のところこれが何なのかわかってませんが。

###・mrayGStreamerUnityの準備
とりあえず下記からDLしておきます。

[mrayGStreamerUnity](https://github.com/mrayy/mrayGStreamerUnity)

gstreamer 1.0 runtimes 64bit をDL&インストールしなさいとのことなのでします。
1.11.xと書いてあったのでとりあえず今回は1.11.91をインストールしました。
VS runtimes はもうあるのでとばして・・・
インストールしたgstreamerのbinフォルダにPATHを通しておきます。
これで準備はOK・・・かな？

###・サンプルを動かしてみる
せっかくサンプルがあるのでとりあえず動かしてみます。

[SkyWay-WebRTC-GW-Unity-Sample](https://github.com/nakakura/SkyWay-WebRTC-GW-Unity-Sample)

が、WebRTCGWがエラーを吐いて落ちてしまいました・・・・・・。

```zsh
#
# Fatal error in c:\dev\webrtc\src\webrtc\media\engine\webrtcvoiceengine.cc, line 264
# last system error: 0
# Check failed: 0 == voe_wrapper_->base()->Init(adm_.get(), nullptr, decoder_factory_) (0 vs. -1)
#
#
```
調べてみたところオーディオ系のなにかっぽいことまではわかりましたが詳しいことはよくわからず・・・。

・オーディオドライバ系の再インストール　→　ダメ
・既定のサウンドデバイスを色々と変えてみる　→　ダメ

わからんーーーーーとここで2時間くらい悩んでたところで原因らしきものを発見。
仮想オーディオデバイス君・・・？君か・・・？
CableInputとかVoiceMeeterBananaとかを無効にすると・・・
![skyway11.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/a339385f-9080-2628-cf77-e2d0108b800c.jpeg)
やったぜ。
壁を映してるのでなんかそういうテクスチャみたいになってますが無事に動きました。

##実際につかってみる
###・準備
Assets/PluginsフォルダにダウンロードしておいたmrayGStreamerUnityのUnity\UnityTests\Assetsフォルダ内にあるGStreamerUnityフォルダと、Plugins\x86_64フォルダを突っ込む。
次にAsset StoreからUniRxもインポート。
![skyway13.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/6cafe29a-489b-77fc-1517-581be4170d4a.jpeg)
Pluginsフォルダ内にMiniJSONフォルダを作成して下記からDLしたファイルをつっこむ。

[MiniJSON.cs](https://gist.github.com/darktable/1411710)
![skyway14.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/d49885f6-2cc5-99ab-7f22-534b60b272cc.jpeg)

見た感じ必要そうなのはこれくらいかな・・・？

###・ブラウザと通信する
とりあえず鏡の位置をちょっと横にずらしておきます。
（鏡があるとこに相手側の映像を映したいので）
ぐりぐり動かしてPosition(X:3.08,Y:1.09,Z:-0.6)、Rotation(X:0,Y:120,Z:0)としました。
![skyway15.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/9a6f3202-ada8-d316-8140-bf73f6d936b7.jpeg)

映像表示用のPlaneを作ります。
sceneない右クリック→3D Object→Planeを選択。
ぐりぐりと動かしていい感じの場所に持っていきます。
大体こんなとこで。
![skyway20.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/d1d1718f-af27-44ac-af3c-9e2830e2bd81.jpeg)

位置が決まったところでGstreamerのコンポーネントを突っ込みます。
GstreamerUnity\Components内にあるCustomPipelinePlayerをPlaneのInspector内にD&Dで突っ込みます。
Target Material入力欄横の◎をクリックしてDefault-Materialを選択、Pipelineはサンプル通りに以下のものを入力します。

```zsh
udpsrc port=7000 caps="application/x-rtp,paylocad=(int)100" ! rtpjitterbuffer ! rtph264depay ! avdec_h264 output-corrupt=false ! videoconvert ! appsink name=videoSink
```
次にskyway周りのスクリプトです。
Assets内にScriptsフォルダを作成し、サンプルのSkyWayRestApiをつっこみます。
今回は入力欄やボタンがない（VRでそのあたりの実装をどうするのかよくわからなかった）のですこしその辺をシンプルにします。

```c#
//Unityから直接叩かれるクラス
public class SkyWayRestApi : MonoBehaviour
{
	public string targetId;
	public string key;
	public string domain;
	public string peerId;
	public bool turn;

	void Start()
	{
		//SkyWay WebRTC Gateway操作用インスタンス生成
		var _restApi = new RestApi(key, domain, peerId, turn);
	
		//SkyWayサーバと接続されたときに発火させるイベント
		_restApi.OnOpen += () =>
		{
			_restApi.Call(targetId);
		};
	}

	void Update()
	{
	}
}
```
最初から相手のID指定しておいていきなり接続する形です。
で、これをまたPlaneにD&Dで突っ込み、IdやKeyを編集します。
![skyway22.JPG](https://qiita-image-store.s3.amazonaws.com/0/282891/ea6f3150-e6a8-e2f8-b253-a0227ff41349.jpeg)
これで実行してみます。
![20181216160942_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/282891/6abe4271-c1be-208f-91cf-e14f88eeace7.jpeg)
ブラウザ側の映像をVRに流し込むことはできました。

てことであとはVR側の映像の送信だけなんですが。
さっぱりわからず時間の関係もあり断念・・・。
gstreamerで流し込むのは何となくわかりましたがどこからカメラの映像とってくるのかいまいちわからず・・・。

#5.おわりに
色々触りましたが中途半端に全部よくわからんって感じで終わってしまった感じがありますね・・・。
heroku使う必要も特になかったし！
何もわからない状態から扱うには少しハードルが高すぎたかなという感じだったので、
Unityのお勉強ちゃんとしてからまたこのあたりリベンジしていきたいですね。


明日は@somu4さんです！