公式WebRTC plugin for Unityのサンプルを公開しました(Android向け)
前回 ↓ からだいぶ間あいてしまいましたが、続編です。
前回WebRTCのソースからビルドしたプラグインですが、前回以降修正が入って、より最新に近いソースがビルド可能になりました。それを動かすためのサンプルコードを今回作成しました。一応ちゃんとUnityでWebRTCが動きます!ぱちぱちぱち。
プラグイン本体はこちらからダウンロードしてください。(以前より更新しました)
https://github.com/mhama/webrtc-dev-env/releases/tag/master_20180321_085858
プロジェクトはサンプルとして公開しました。
https://github.com/mhama/WebRtcUnityPluginSample
やれること
- Socket.IOでの簡易なシグナリングを用いて、Unityアプリとブラウザ上のWebRTCクライアントとでリアルタイム動画を送信・受信できます。
- 簡易なサーバープログラムを作成しました。サーバーホスティング等を用意しなくてもテストができます。
Unity用Socket.IOのライブラリはこちらを使わせてもらいました。
https://github.com/floatinghotpot/socket.io-unity
できないこと
- シグナリング部分がサンプルレベルなので、エラー処理、状態管理などはザルです。
まともにするには、サーバーコード、クライアントコードともに調整する必要があると思います。 - ルームの管理を行っていません。
同じサーバーに3人以上接続できますが、何が起こるかわかりません。 - Unityエディタ上でWebRTC機能は動きません。WebRTCプラグインのWindows/MacOSビルドができていないためです。今の所Androidビルド(実機)でのみ動作します。
サンプルの使い方
概要
サーバー(node.js)を起動し、ブラウザとUnityアプリの2つがサーバーに接続する形で通信(シグナリング)を行います。この通信によってWebRTCでの動画通信の下準備を整えることができ、準備ができたらP2Pで直接の通信が行われます。
動作環境
通信料金についての注意
同じLANに両方の端末がつながっていれば、映像・音声のデータはLAN内でやりとりされる可能性が高いです。ただ、場合によってはインターネットを経由する可能性もあるので、従量課金制のネットワークをお使いの場合はご注意ください。
セットアップ
Unity 2017.3以降でプロジェクトを開いてください。 node.js をインストールしてください。
サーバー起動
Windowsの場合、serverディレクトリ内の server.bat を起動してください。
cd server
npm install
node index.js
成功すると、以下のようなログになります。
listening on *:3000 forwarding to global domain... ngrok server: https://12345abcde.ngrok.io
最後に https://12345abcde.ngrok.io のようなURL が出てきますが、これがサーバーのURLですので、控えておいてください。ウィンドウはそのまま開いておいてください。閉じるとサーバーが止まります。このURLは、外部のインターネットからアクセス可能です。
外部インターネットからのアクセスは、ngrokというサービスで実現しています。無料で利用している関係で、24時間経過するとURLが無効になるので、その場合は再度 server.bat を起動してください。
ブラウザでサンプルページを開く
インターネットが使えるLANに接続し、 (カメラがついた)PCのブラウザで上記のURLを開いてください。カメラの利用を許可してください。 Start CameraしてStart Peerしてください。 カメラのリアルタイム映像が1つ表示されます。
Unityプロジェクト
Androidプラットフォームで WebRTCSample/mainシーンを Build and Runしてください。
Andoidアプリ
インターネットが使えるLANに接続しておいてください。
アプリが起動したら、URL入力ボックスのところに先程のサーバーURLを指定してください。ただし、https:// はアクセスできないので、 http:// に変更してください。
ConnectしてからOffer with Camera または Offer without Cameraを選んでください。
Offer with Cameraの場合
PCのカメラ映像がスマホに表示され、スマホのカメラ映像がPCに表示されたら成功です。Offer without Cameraの場合
PCのカメラ映像がスマホに表示されたら成功です。
切断などは未実装です!
組み込み方法
正直、もろもろ組み込める品質に達しているとは思えませんが、一応組み込めるはずです。
- Empty Objectを作成して、WebRtcNativeCallSample コンポーネントを追加してください。
- ローカル側テクスチャを貼りたいGameObjectにWebRtcVideoPlayerコンポーネントを追加してください。このGameObjectを上記WebRtcNativeCallSample のLocal Playerにセットしてください。
- リモート側テクスチャを貼りたいGameObjectにWebRtcVideoPlayerコンポーネントを追加してください。このGameObjectを上記WebRtcNativeCallSample のRemote Playerにセットしてください。
- 上記2つのGameObjectのマテリアルに、VideoMaterialをセットしてください。これをしないと、YUVをそのままRGBとして解釈した色になってしまいます。
- WebRtcNativeCallSample コンポーネントのServer UrlパラメータにサーバーのURLをセットしてください。
- 相手側クライアント(このサンプルではブラウザ)のStartCamera/Start Peerを呼んでください。
- スクリプトなどから、WebRtcNativeCallSample コンポーネントのInitWebRTC()を呼んで、OfferWithCameraまたはOfferWithoutCamera()を呼ぶようにしてください。
うまくいけばテクスチャ上で動画の再生がはじまると思います。
解説
シグナリングって結局何なのか
WebRTCではシグナリングを実装する必要がある、というように言われますが、シグナリングって何なのか、モヤッとしてよくわからない時期があったので、そのあたりを明確にする感じで書いてみます。
WebRTCでは、見ず知らずのAというクライアントとBというクライアントが最終的に動画を交換するわけですが、そのAとBを「お見合い」させて、お互いの連絡先を交換させる、みたいな相談所みたいな役割をシグナリングという仕組みでやります。
とか、WebRTCはネゴシエーションの通信手段が決められていなくて、そこは自分で実装する必要があります。
・・・みたいな雑な説明はよく聞くと思うのですが、プログラマ的にはあまりピンとこないと思います。もうちょっと具体的にしましょう。
まず前提として、以下を把握しておく必要があります。
- シグナリングとは、動画・音声通信の通信方法についてのネゴシエーション、つまり準備のことです。動画、音声自体の通信は、準備がうまくいったらWebRTCライブラリ同士が勝手にやります。
- シグナリングでは、通信部分以外はほとんどWebRTCのライブラリがやります。プログラムで最初のトリガーを与えたら、あとはWebRTCライブラリが「データ用意したからこれ相手に送れ」のように指示してくる感じです。送るデータ内容の変更は通常は行いません。
- シグナリングに必要な、「相手と相互にメッセージを交換」する、といういわばチャットのような通信機能を自分で実装する必要があります。たとえば今回のサンプルでは、Socket.IOを利用して実装しました。
流れでみてみましょう。JSのAPIの例です。ネイティブでは微妙に異なりますが、似たような流れです。ここで書かれているような流れを実装する必要があります。
- A,B: 初期化する。
- オファー(提案)
- アンサー(回答)
- ICE(=Intaractive Connectivity Establishment: 相互通信路構築)
- A: WebRTCから「
ICE Candidate
(通信路候補)ができた(ので相手に送れ)」と言われる - A: なんらかの通信手段でAからBに
ICE Candidate
を送る- A ===> B
- B: 受け取った
ICE Candidate
を AddIceCandidate API につっこむ。 - 同様に、A <=== B の流れもあります。
- 上記を繰り返していると、ICE Candidateのどれかで相互通信が確認され、映像・音声のチャネルが接続され、動画通信が開始される。
- A: WebRTCから「
- 動画ストリームの追加
- B: Aから新たな動画ストリームがきたら、 OnStreamAddedのようなコールバックが呼ばれるので、動画表示モジュール等にわたす。
- A: Bから新たな動画ストリームがきたら、 OnStreamAddedのようなコールバックが呼ばれるので、動画表示モジュール等にわたす。
おおよそ以上です。上記に加え、オファーを送る側がBである逆パターンもあります。あと通信を閉じるところなどは除外していますが、なんとなく全体像がみえてきたら幸いです。
実装
Unity のクライアントコード
このOfferWithCamera()のあたり でOffer with Cameraボタン押下時の処理をしていますが、初期化して、AddStreamして、CreateOfferしています。
そうすると、OnLocalSdpReadyToSendが呼ばれるので、そこで得られたSDPを相手側に投げます。ここで、通常は、というかJavaScript APIの場合はSetLocalDescriptionを呼んでこのSDPをセットしますが、Unity Pluginの場合は、プラグイン内部でやっているようで、自分で呼ぶ必要はありません。
しばらくすると相手側からAnswerが送られてくるので、OnAnswerのあたりで受けて、もらったSDPをSetRemoteDescriptionでセットします。
あとはICEを送るだけです。 ちなみに、JS側のコードは、ICEはすべて初回のSDPに含めていることを前提にしているため、Unity C#側ではICE Candidateを受けるコードを書いていません。
このあたりはがねこまさしさんのVanilla ICEとTrickle ICEの説明などを参考にしてください。 html5experts.jp
※このあたりが適当なので、Unityアプリ対Unityアプリの通信は失敗する気がします。
うまくいけば、動画通信が開始され、OnI420RemoteFrameReadyがフレームごとに呼ばれます。
シグナリングサーバーコード
超適当実装です。
https://github.com/mhama/WebRtcUnityPluginSample/blob/master/server/index.js
ブラウザー用JSコード
html埋め込み状態です。Vanilla ICE式にしています。ICEが全部揃ってからSDPを送っています。
https://github.com/mhama/WebRtcUnityPluginSample/blob/master/server/index.html
ngrokで楽にhttps通信を実現する
ブラウザでWebRTCを動作させるには、 https 通信がほぼ必須ですが、ローカルマシンでhttpsサーバーをたてるには、ドメイン証明書の取得、ドメイン設定など面倒な部分があります。この点をクリアするため ngrok を利用しています。ngrokは、クラウドサービスですが、ローカルのポートで待ち受けるhttpサーバーをグローバルIPに公開することができ、さらに https 接続も可能になります。このためWebRTCのサンプルページを公開するのに非常に便利です。無料で24時間までは起動しておくことができます。
WebSocketを使わない理由について
Socket.IOには、ロングポーリング(http/https)での通信と、WebSocketでの通信のモードがあり、Socket.IO側で勝手に切り替えて通信を行ったりします。今回は、ngrokの設定が面倒にならないように、Socket.IOの初期化時にpollingを指定して、ロングポーリングに限定して通信をしています。
WebSocketにもhttpsのようなセキュア版(wss)と非セキュア版(ws)がありますが、WebRTCを使う場合は、セキュリティの関係でセキュア版を使う必要があります。これもngrokで公開できる可能性がありますが、もろもろ面倒なので今回は利用しませんでした。
ピクセルフォーマットの変換について
WebRTCでは、最終的にYUV420形式でフレーム情報が落ちてきます。今回のサンプルでは、YUV420形式のデータを、ピクセルごとにYUVそれぞれ8ビットをパックした形式に変換してUnityのテクスチャに格納し、最終的にshaderでRGBに変換して表示しています。 ただしこの方式は無駄が多いです。YUV420を、YUVそれぞれの3つのテクスチャに分けて扱うほうが処理が少なくて済みそうです。さらに、Unityのスレッドに渡さず、バックグラウンドスレッドでOpenGLテクスチャにセットするなどすればもっと高速になるでしょう。