WebRTC利用でありがちな不満点とベストプラクティスの模索

# 1. はじめに

WebRTCは人類の英知の結集と言っても過言ではない、とても洗練されたプロトコルです。

- NATやFirewallを超えてP2Pで通信できる
    - 通信に失敗したらサーバリレーモデルにFallbackまでしてくれる
- 映像・音声・その他のデータを転送できる

辺りが主な特徴です。
これは非常に強力で、既に幅広く利用されており、
[市場に関するレポート](https://www.gii.co.jp/report/mama355219-web-real-time-communication-market-by-product-type.html)には以下のように記載されています。

>2017年の10億4000万米ドルから、2022年までに64億9000万米ドルまで拡大すると見られています。

こうして広く活用が進むに連れ、元々考慮されていたメインのユースケースであるビデオチャットから離れた用途での活用も増えてきました。
例えばゲームやIoTといった分野での活用が進みつつあります。

かく言う私もニッチな使い方をしている者の一人です。Telexistenceという分野の研究チームの一人として、WebRTCを用いてこのビデオにあるようなロボットを動かしています。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/UD15I7SKjv">https://t.co/UD15I7SKjv</a></p>&mdash; さぎにゃん (@arukakan) <a href="https://twitter.com/arukakan/status/1083257168256614400?ref_src=twsrc%5Etfw">2019年1月10日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


このように人の動きに合わせて遠隔地で動作し、周囲の映像や触覚情報をVRで提示してくれるロボットがいれば、
その場にいるかのように自由に活動することができるようになります。

![748624bc-5a7b-023d-3411-38a2a7baf9e6.png](https://qiita-image-store.s3.amazonaws.com/0/104451/e449a1fa-a12f-4047-1d08-319a17a86c5a.png)


これを実現するためには、人の動きを認識して操縦信号としてロボットに送り、同じようにロボットを動かす必要があります。
操縦者の声も送ってロボットから再生します。
一方で、ロボットのカメラで撮影した映像、マイクで取得した音声、触覚センサで取得した触覚情報を操縦者に送り返し、VRとして提示するということもやっています。


![1d859464-4b56-9e60-4faa-1663d9535e0c.png](https://qiita-image-store.s3.amazonaws.com/0/104451/9cb10242-fb83-6061-5b81-bdc4f473de92.png)


こういう使い方をしていると、当然WebRTC設計者の想定外の利用法のため、現在のWebRTCの仕様では対応できないケースが出てきます。
W3Cもこのことは認識しており、WebRTC NVとしてより発展的な仕様の策定が進められています。私もそれに協力しつつ、まずは現状可能な範囲でニッチなケースをサポートする方法を考えています。
この記事は今までに確認している問題のうちよくあるものと、その対応策について紹介するものです。

# 2. 課題

以下に課題を書いていきます。原因だけリストアップすると大体以下の通りです

- 既存実装の制約によるもの
    - ブラウザ
    - OSS
- 適応対象の制約によるもの
- WebRTCの仕様によるもの

## 2.1 既存実装の制約によるもの

### 2.1.1 ブラウザ利用の場合

ブラウザ上のJavaScriptはOSに直接アクセスできず、ブラウザの提供するAPIしか利用できません。
従って以下のようなケースで困ります。

#### ファイルの読み書き

ファイルが操作できないので、例えば録画や受け取ったメッセージの保存が困難です。MediaRecorderやBlobでなんとかできなくはないのですが、対応状況がいまいちなのと、使い勝手を考えると非常に辛いところです。

#### デバイスとの連携

ブラウザ上のJavaScriptからアクセスできるデバイスは限定されています。
WebMidi APIでMIDIデバイスにアクセスしたり、WebVRでHMDにアクセスしたりはできますが、APIの提供されていないその他のデバイスにはアクセスできません。

私の場合は加速度センサや力覚センサからの値の取得ができず、ロボットのサーボの操作もできなかったので困りました。

#### 他の既存ソフトウェアとの連携

Shared Memoryやソケット通信が行えないので既存ソフトウェアとの連携に苦労します。
現実的にはWebSocket一択です。

#### 動作環境

ブラウザが動作する環境でしか動きません(当たり前)。
つまり基本的にはGUIの入っている環境でしか動きません。
ヘッドレスブラウザも存在しますが現実的ではありませんし、WebRTCで取得した映像が再生できないので片手落ちです。

### 2.1.2 OSS利用の場合

世の中にはいくつかWebRTCのOSSが存在します。ここではJanusやOpenWebRTCを前提にして話を進めます。

#### 動作環境

基本的にはLinuxのサーバ用アプリケーションです。インストーラーを利用してWindowsやMacに入れられるようなものではなく、自力で依存性を解決した後make installすることになり導入コストが高いです。

#### 利用の困難さ

利用はブラウザでWebRTCを動かすのと比較して大分複雑です。そもそもブラウザでWebRTCアプリケーションを実装するのですら困難なため、世の中には沢山のWebRTCプラットフォームが多数存在するような状況です。
一般的にこのようなWebRTCプラットフォームのサポートは受けられず、自分で必要なサーバを立てて動作させる必要があります。
上級者＆インフラをメンテできる人向けです。

## 2.2 WebRTCの仕様によるもの

### Mediaフォーマット

Mediaのフォーマットには制限があり、

- 動画はH.264, VP8, VP9。あともうそろそろAV1が利用できるようになる
- 音声はOpus, G.711, DTMF

その他のフォーマットは利用できません。

### Adaptive Bitrate Streaming

これはデメリットとは言い切れないのですが、WebRTCは通信経路のネットワークの品質に応じて動画の品質を変更します。つまり、実装が勝手に動画品質を落としてしまいます。
FPSは落としていいけど解像度は落としたくないとか、その逆だとか、動画配信に拘りだすとキリがない調整項目がありますが自動的に変換されるのに委ねることになります。

### Media, Dataの非対称性

MediaはSRTP, DataはSCTPで転送されており扱いが明確に異なります。
これはビデオチャットしながらテキストチャットもするというようなケースでは問題ないのですが、発展的なケースでは困る場合があります。

例えば私の場合、ロボット操作回りでいくつかの問題が有りました。

まず、ロボットの操縦信号はSCTPで転送されるためジッタが吸収されません。従ってジッタによって応答速度が早くなったり遅くなったりしてしまいます。

次に、ロボットが取得する触覚データはMediaStreamで送れない(触覚用のペイロードフォーマットが規定されていない)ため、DataChannelで転送することになります。
触覚は視覚情報と同期されなければ意味が無いものになってしまいます。
たとえばハイタッチする場合。相手と手が合わさったように見えてから数秒後に触覚が再生されると非常に奇妙な印象を受けます。

![cb819aec-1728-0b2c-e9b6-a5ebab29613c.png](https://qiita-image-store.s3.amazonaws.com/0/104451/ab67e601-d7ae-96b8-838e-49f1847bcde4.png)


そんなに大きくズレが発生しなくても、視覚と触覚のズレによってバネの硬さの認識が変わってしまったという実験結果が報告されています([論文リンク](https://ieeexplore.ieee.org/document/7148129))。
これはつまり、遠隔診療で触診するようなケースにおいて、患部の硬さがちゃんとわからないということなので困ってしまいます。

# 3 対応策

## 3.1 ブラウザのExtensionとしての開発

例えばGoogle Appsとして開発すれば、ブラウザのWebRTC Engineを使いつつ、ある程度OSへアクセスすることが可能です。
例えば[Serial API](https://developer.chrome.com/apps/serial)を利用すればシリアルデバイスへアクセスできます。
これで加速度センサや力覚センサ、ロボットのサーボのコントロールが行なえます。
またRaw Socket([TCP](https://developer.chrome.com/apps/sockets_tcpServer), [UDP](https://developer.chrome.com/apps/sockets_udp))も利用できるため、既存アプリケーションと通信経由で連携することができます。
これらの機能でデバイスや既存アプリケーションとデータのやりとりをし、WebView上でWebRTCセッションを確立してDataChannelに流し込むような実装が可能です。

### 課題

ここでの主な課題は動作環境です。ブラウザが動く必要があるのでOSがGUI Installされていることが前提になります。
例えばロボットや監視カメラのようなデバイスのOSがGUIであることは考えづらいため、この方法では対応できません。
またブラウザの一機能に過ぎないので、ブラウザ自体が無くなったり、この機能が削除になる可能性がありますし、性能や長時間運用での耐久性にも不安が残ります。

その他の問題として、開発はJavaScriptで行う必要があるので、シリアルデバイスから取得したバイナリデータの編集や、UDPで送信するパケットのフォーマットもJavaScriptで行う必要が有ります。
これは好みの問題も絡んできますが、「先頭16bitはBigEndianの整数が格納されている。これは後ろに続く文字列の長さを示している。このlengthフィールドの直後から可変長のバイト列で文字列が格納されている」というようなペイロードフォーマットはよくありますが、そんなデータをJavaScriptで扱いたくないと個人的には思います。

これはWindowsやMacのPCからちょっとデバイスを操作してデータを送りたいというような状況でのみ役に立つ方法だと言えるでしょう。

## 3.2 OSS利用

OpenWebRTCやJanusといったOSSをインストールして、それと連携するアプリケーションを構築すれば、割と何でも書くことができます。
またlibWebRTCのようなWebRTC機能を提供するライブラリもあるので、それを利用してアプリケーションを開発することもできます。

### 課題

これらのOSSは基本的にLinux上の動作が前提で、WindowsやMac用にコンパイルするには深い知識が必要です。
またWebRTCプラットフォームの支援も受けられないため、WebRTCサービス運用に必要なサーバは自前でメンテナンスする必要が有ります。

## 3.3 WebRTCプラットフォームが提供しているSDKを利用する

WebRTCプラットフォームが提供しているSDKを利用すれば、その環境で動作するアプリケーションを開発することができます。
例えば[OpenTokのWindows SDK](https://tokbox.com/developer/sdks/windows/)、[SkyWayのiOS SDK](https://webrtc.ecl.ntt.com/ios-sdk.html)のようなものを利用すればその環境で動作するアプリケーションが開発可能です。

### 課題

まず最初に思いつくのは、WebRTCプラットフォームに囲い込まれることになるという点ですが、これは現実的には大きな課題にはならないでしょう。
スクラッチからWebRTCの開発することは現実的にはレアケースであり、基本的には何らかのプラットフォームを選択することになるからです。

課題となりうるのは、好きなWebRTCプラットフォームが対象の環境用のSDKを提供していない場合やSDKの機能が足りない場合が考えられます。
新規開発ではなく過去の資産や買ってきた機材を利用する場合も障害になり得ます。
例えば今自社ビルに設置済みの監視カメラをそのまま利用したい場合、WebRTC用アプリケーションを書いて監視カメラ一台一台のソフトウェアを書き換えるというのは現実的ではありません。
書き換えが困難だったり、WebRTCエンジンを動作させるのにはスペック不足だったりすることが懸念されます。

加えて書くとすると、ここまでに記載した3つの手法は、当然WebRTCの仕様に沿うように設計されています。従ってWebRTCの標準仕様を外れた使い方はできません。
これは何ら落ち度のない話なので、課題と言ってしまうと問題がありますが、単に希望として書くと、私のような使い方をするユーザが特殊な実験をしようと思ったときに活用できるものが世の中に一つくらいあってもいいなと思っています。

## 3.4 第四の方法

### 考え方

以上に記載した方法が当てはまらない私のようなケースはどうしたらいいのでしょうか。
要するに以下の不満点があるわけです。

- デバイスとの連携
- 外部アプリケーションとの連携
- 貧弱なマシンへの対応
- アプリケーションの書き換えが行えない環境への対応
- WebRTCの標準仕様から外れた機能の検証

書き換えが行えない環境をサポートする一般的な方法は、Adapter Patternの適用です。
どういうことか、抽象的に説明すると以下のような感じです。
既存ソフトウェア間の通信は、決められた通信相手と決められた通信をするように設計されています。

![c99335c0-0129-5c95-d6b6-4b2a48b4734b.png](https://qiita-image-store.s3.amazonaws.com/0/104451/bdaf4a03-5a17-930a-18b8-9bbd278654bd.png)



凸と凹がはまると通信が成立するイメージです。
WebRTCを利用できるようには設計されていないので、当然WebRTCとは噛み合いません。


![bbb771bf-0abb-e2ab-3137-2c810913d62d.png](https://qiita-image-store.s3.amazonaws.com/0/104451/8dbf20ed-1f2b-be47-63c6-1e8a0041ae88.png)

SkyWayとも噛み合いません。

![30eb7064-1241-13d4-ed08-715cf2ecb5a2.png](https://qiita-image-store.s3.amazonaws.com/0/104451/adcfa0ab-c427-f774-17fc-2d08231878c9.png)


一番素朴に考えると、アプリケーション自体を作り変える方法が考えられます。図で言うと以下の赤い部分を修正すればはまるわけです。
ですがまず開発が大変だし、当事の設計者から引き継ぎを受けていなかったり開発費用がなかったり、そう簡単に修正できないアプリケーションもあります。アプリケーションの修正ができても、デバイスを全て回収して組み込まれたソフトウェアを書き換えるのは現実的ではありません。

![d59ca119-2888-8c4c-3e46-ade6a1be7738.png](https://qiita-image-store.s3.amazonaws.com/0/104451/bca34e2c-ff59-33d9-b202-5e86a2d49394.png)


ここで役に立つのがアダプタの概念です。要するにこういう形のモジュールを用意してあげれば、

![74782878-4551-a71d-626e-ab9356356ddc.png](https://qiita-image-store.s3.amazonaws.com/0/104451/ecb1c910-5712-e076-8534-9857d2f704aa.png)


既存のアプリケーションを書き換えなくてもはまるわけです。

![355ca7de-beb5-b2e3-5eb5-ea28436a515d.png](https://qiita-image-store.s3.amazonaws.com/0/104451/9e67117c-98f5-1860-3fed-4ef9db900422.png)

抽象度を通信のレイヤまで落とすとどうすればいいでしょうか？
例えば先程の私の例で言うと、こういう通信が走っているわけです。

![80e46b87-159e-5127-d06e-f39d123cc210.png](https://qiita-image-store.s3.amazonaws.com/0/104451/1e2e3bc2-860f-e39d-6276-d292d7e7775d.png)

ロボットの操縦というリアルタイムアプリケーションなので、操縦信号はUDPベースの独自メッセージ、メディアはRTPでやり取りされています。
であればローカルで飛び交うUDP通信をそのまま受信して、WebRTCで相手側へ転送してあげればよいわけです。
WebRTCセッション確立の際にWebRTCプラットフォームの機能を利用するようにすれば、自分でサーバ管理する必要もありません。

![2bd6437e-497b-a7d7-1f17-e6bf4e4c6223.png](https://qiita-image-store.s3.amazonaws.com/0/104451/e95c53b2-0d1a-75c7-4281-d735b8d5896c.png)


### 実装方針

実装方針レベルまで落とし込むと、ユーザとのインタフェース部分に置いて考えるべきことは以下の通りです。

- アダプタアプリケーションの動作環境
- アダプタアプリケーションの操作方法
- マネージド化のレベル
- WebRTCの枠を外れた使い方を否定しない

既存アプリケーションによって事情はさまざまなので、動作環境については自由に選べるのがよいです。
アプリケーションが書き換えられなかったり、端末が非力でWebRTC Stackを動かす余裕が無い場合は別端末で、
強力なマシンの場合は同一端末で動かせるのが便利です。
従ってLinuxサーバだけではなく、Windowsのようなユーザが利用する環境上でも動作するとよいでしょう。

![d54cf1e6-392c-db06-4965-9ea4795f792e.png](https://qiita-image-store.s3.amazonaws.com/0/104451/88ea079e-0430-690f-56fe-8ac67463c876.png)


操作方法については、単体のアプリケーションなので、通信ベースで行うのが妥当でしょう。
またローカルネットワークで通信するので、別段Firewallを超えることを考える必要はないです。
但し既存アプリケーションの書き換えを必要としない方法がよいです。

ひとまずは対応環境を最大限広げることを考えて、REST APIで実装することにします。
REST APIであれば任意の言語で実装可能ですし、最悪htmlとJavaScriptのファイルを置いておくだけで操作できます。

簡単に利用してもらうためにはマネージドサービスとして設計していくのがよいでしょう。
機能だけを抜き出したものより、綺麗にラッピングしてあげたもののほうが使いやすいです。

![42abe767-77fd-1185-c2eb-b847a6df8ce7.png](https://qiita-image-store.s3.amazonaws.com/0/104451/84850600-83f1-980f-92d0-2766693f420b.png)

但しラッピングも良し悪しで、生の機能にアクセスできなくなることを意味します。
自分にとって必要な部分が隠蔽されてしまったら困ってしまいます。高度に抽象化された言語よりもC言語を好む人が居るようなものだと考えてもらうと分かりやすいかも知れません。
そもそも現状のWebRTCに満足できないニッチな使い方をしている層向けなので、ある程度の技術力がある方向けであり、
変に隠蔽するよりも全ての機能にアクセスできるようにすることが重要だと考えています。
ここである程度マスの使い方が出てきたときに、改めてそこに向けてきちんと取捨選択した上でマネージド化していくことにしました。

要するに何をやったのか整理すると、
「カメラやデータの生成部がWebRTC Stackと同じ端末にあり、getUserMedia等でアクセスする」
という前提を外して1段抽象化したということです。もちろん同じ端末にあるカメラを使っても構わないので、自由度が一つ上がったわけです。

最後に、WebRTCの枠を外れた使い方を否定しないようにしたいと考えています。
これはWebRTCの仕様に異議を唱えるということではなく、今後の拡張に向けた議論のための検証を行えるようにするという考えです。
実際に私はMediaStreamで触覚を転送するために、独自定義したペイロードフォーマットのRTPを流して使っています。
RTPで映像と触覚情報を転送してあげればタイムスタンプを利用して同期が行えるため、↑に書いたような同期ズレを防ぐことができます。
ここで得られた検証結果は適宜W3Cへフィードバックしており、将来的にMediaStreamが五感情報全てをサポートしてくれるようになるために貢献していきたいと考えています。

### 完成品の公開

以上の考えの元に実装された実装は[SkyWay WebRTC Gateway](https://github.com/skyway/skyway-webrtc-gateway)として提供を開始しています。
先程のセクションで記載したように、マネージドサービスではなく、上手く使いこなさないと手に刺さる状態であり、既存のSDKとは区別してLabs版としての提供です。
まずは用途を最大限に広げていくための拡張を行い、将来的に選択と集中の元、用途を限定した使いやすいマネージドSDKやAPIとして提供していければいいなと考えています。

# 4. おわりに

以上長々と書いてきましたが、こういうモチベーションのもと開発を行っています。
自分が使うことを考えて作ったので、自分が一番のヘビーユーザであり、気づいた点を適宜修正していければと思っていますが、一方で全然違う使い方もサポートしたいと思っていますので、WebRTCをこんなニッチな使い方をしていてこういうところで困ってます！という声を頂けると非常にありがたいです。
私もハマった経験のある問題であれば解決策をお伝えできますし、私も気づいていない問題であればまず一時的な対応と、将来的な仕様変更に向けたW3Cでの議論にご協力するなど、色々方法が考えられると思うので、まずは皆で不満を持ち寄る場を作りたいと考えています。