こんにちは!今回はGoogle I/O 2015の小セッションから、Justin UbertiさんとSam Duttonさんによる、”Video chat for Web, Android and iOS”のお話をお届けします。このセッションではWebRTCを使ってビデオチャットアプリを作る方法について、WebRTCの中の人であるお二人が説明してくれました。
WebRTCはWeb Realtime Communication の略で、Webブラウザ上でカメラやマイクの映像/音声を取得したり、他のブラウザと通信するための技術です。過去のHTML5Experts.jpの記事に解説がありますので、併せてそちらもご覧ください。
セッションの始めに
最初にJastinさんから、今日は Web/Android/iOS のアプリにリアルタイムコミュニケーションを組み込む話をする、との宣言がありました。従来は「Webブラウザで、JavaScriptでできる」という部分がクローズアップされることが多いのですが、今回はあえてWebブラウザ以外の「Androidネイティブアプリ」や「iOSネイティブアプリ」でも使えるということを強調した内容になっています。そう言えばセッションのタイトルも「WebRTC」という言葉は使っていませんでした。
セッションの冒頭では、関連する資料の公開先が案内されました。どちらもGoogle Docs上で共有されています。
- 関連する様々なリソースのまとめ g.co/webrtc
- 今回のプレゼンの資料 Video chat for Web, Android and iOS
余談ですが企業でGoogle Apps for work を使っていてdocs/driveが使えないように機能制限している場合、アカウントにログインした状態では見れません。私も最初は見れなくて、地味にはまりました。
Google I/Oの大きなセッションのビデオは、公式サイトで公開されています。残念ながら本セッションのような小規模セッションのビデオは公開されていませんが、Google I/O 2015のお土産にもらったCardboard用のアプリからGoogle I/Oの様子をVRで見ることが可能です。本セッションの映像もVRでみることができるので、今年のCardboardをお持ちの方はお試しください。
これまでを振り返る
まずはこれまでの経緯を振り返えっての説明がありました。「数年前、Webをリアルタイムコミュニケーションのプラットフォームにしようと考えた」と言います。
- あらゆるアプリでリアルタイム通信を可能にしたい
- オーディオ、ビデオ、独自のデータを扱えるようにたい
- エコシステムを構築するためにオープンなプロトコルを使いたい
そのために次の点について力を入れたとのことです。
- 実用になるための高品質
- 可能な限りPeer-to-Peerで通信
- End-to-Endの暗号化を提供
実に野心的なプランで、ここでSamさんが”Crazy Plan!”と突っ込みます。
- そんなプランに取り組み、WebRTCができあがった
- しかしその後、モバイルシフトがものすごい勢いで起こった
- モバイルでは、コミュニケーションアプリに非常に多くの時間を使っている
利用シーンの変化に合わせるため、AndoridとiOSのネイティブアプリでもWebブラウザと同じことができるようにした、との説明がありました。そしてここで、(WebRTC改め)”AndroidiOSWebRTC” と大きく書いたスライドが登場しました。
この通信技術は、Webブラウザだけリアルタイムコミュニケーションではなく、すべてのプラットフォームで使えるリアルタイムコミュニケーション技術だよ、との力強い声明です。
WebRTCの普及については、次のように説明しています。
- いまや 1.5 billion (15億)を超えるWebRTC対応ブラウザが使われている
- また 3 billion (30億)を超える回数、WebRTCのアプリがダウンロードされている
- 現在はWeb, Android, iOS の3つのプラットフォームで使える様になっている
30億回ダウンロードというのはにわかには信じがたいですが、アップデートも含めればそうなるのでしょう。 FacebookのメッセンジャーがWebRTCを使っているという話もあるので、それを含めれば30億回に到達するのかもしれません。
ソースコードの紹介
次に、実際のソースコードの概要について、3つのステップで説明がありました。Webブラウザ、Android、iOSのケースを順番に比較しています。
(1) カメラ、マイクへのアクセス
Webブラウザの場合
1 2 3 4 |
navigator.getUserMedia(constraints, onStream); function onStream(localStream) { // do something with stream } |
Webブラウザの場合、getUserMedia()を使って、リアルタイムの映像/音声データをセットでMediaStreamという形で取得できます。取得に成功するとコールバック関数(この例では onStream )に渡されるので、そこで処理します。映像/音声の取得については、こちらの記事もご覧ください。
Androidの場合
1 2 3 4 5 6 |
VideoCapturer c = VideoCapturer.create(device); PeerConnectionFactory factory = new PeerConnectionFactory(); MediaStream localStream = factory.createLocalMediaStream(streamName); VideoSource s = factory.createVideoSource(c, constraints); VideoTrack t = factory.createVideoTrack(trackName, s); localStream.addTrack(t); |
Androidの場合は、カメラの映像とマイクの音声は別々に取得するようです。
- まずVideoCaptureクラスを使ってVideoSource取得します
- 次にVideoSouceから、実際の映像であるVideoTrackを取得します
- VideoTrackが取得できたら、WebRTCで映像/音声を扱うためのMediaStreamクラスの配下に加えます
興味深いのは、MediaStreamのインスタンスはPeerConnectionFactoryから作る、というところでしょうか。Androidの通常の映像処理とは異なり、WebRTC専用クラスということだと思います。
コードの紹介では出てきませんでしたが、マイクからの音声もAudioTrackとして取得してからMediaStreamの配下に追加するのでしょう。
iOSの場合
iOSもAndroidと同じく、VideoCapture → VideoSource → VideoTrack → MediaStream という流れになります。
1 2 3 4 5 |
RTCVideoCapturer* c = [capturerWithDeviceName device]; RTCMediaStream* localStream = [factory mediaStreamWithLabel:streamName]; RTCVideoSource* s = [[factory videoSourceWithCapturer:c ...]; RTCVideoTrack* t = [[RTCVideoTrack alloc] source:s ...]; [localStream addVideoTrack:t]; |
(2) PeerConnectionを使った通信
次は通信です。通信で使うRTCPeerConnectionがWebRTCの肝で、裏で非常にたくさんのことをやっている、との説明がありました。
Webブラウザの場合
1 2 3 4 5 |
pc = new RTCPeerConnection(config); pc.onaddstream = onRemoteStream; pc.addStream(localStream); pc.createOffer(onCreateSuccess); function onCreateSuccess(offer) { sendMessage(offer); } |
Webブラウザの場合はPeerConnectionのオブジェクトを作るところから始まります。
- PeerConnecitonのオブジェクトのonaddstreamイベントハンドラに、相手側のメディア(映像/音声)を扱う処理を設定する(この例ではonRemoteStream)
- PeerConnecitonのオブジェクトにaddStream()で自分のメディア(映像/音声)を教える
- 通信を始めるための情報であるSDPを、createOffer()を呼び出して生成する
- SDPが生成されたらコールバックが呼ばれるので(この例ではonCreateSucess)、その中で相手側に送信する
Peer-to-Peer通信を始めるまでの手順をシグナリングと呼びます。シグナリングについてはこちらの記事もご覧ください。
Androidの場合
1 2 3 4 5 6 |
PeerConnection pc = factory.createPeerConnection(iceServers, constraints, observer); pc.addStream(localStream); pc.createOffer(this, offerConstraints); public void onCreateSuccess(final SessionDescription offer) { sendMessage(offer); } |
Androidも流れは同じです。FactoryでPeerConnectionを生成する際に、observerを指定するのがAndroid流のようですね。PeerConnectionにaddStream()して、createOffer()を呼び出し、生成されたSDPをobserver側のメソッド(この例ではonCreateSucess)で処理しているようです。
iOSの場合
1 2 3 4 5 6 7 8 |
RTCPeerConnection* pc = [factory peerConnectionWithICEServers:iceServers constraints:constraints delegate:self]; [pc addStream:localStream]; [pc createOfferWithDelegate:self constraints:offerConstraints]; - (void)peerConnection:(RTCPeerConnection *)peerConnection didCreateSessionDescription:(RTCSessionDescription *)offer error:(NSError *)error { sendMessage(offer); } |
流れはAndroidと同様ですが、delegateを使うのがiOS流ということでしょうか。createOffer()で作られたSDPを、delegateのdidCreateSessionDescrition()で処理しているようです。
(3) 表示
最後は動画を表示する部分です。
Webブラウザの場合
1 2 3 |
function onRemoteStream(remoteStream) { videoElement.src = window.URL.createObjectURL(remoteStream); } |
おなじみの video タグを使います。 URL.createObjectURL()を使って、MediaStreamをvideoタグで表示できる形に変換します。
Androidの場合
1 2 3 4 5 |
public void onAddStream(final MediaStream stream) { VideoTrack track = stream.getVideoTracks(0); YuvImageRenderer renderer = remoteRenderer; track.addRenderer(new VideoRenderer(renderer)); } |
MediaSteamからvideoTrackを取り出し、OpenGLベースのVideoRendererを使って描画する、という流れの様です。OpenGLベースなので描画にGPUを使っていて、CPU負荷が低いのが特徴とのこと。 ソースコードの中にYuvImageRendererとVideoRendererという2種類のRendererが登場していますが、私には役割の違いがよく分かっていません。すみません…。
iOSの場合
1 2 3 4 5 6 |
- (void)peerConnection:(RTCPeerConnection *)peerConnection addedStream:(RTCMediaStream *)stream { RTCVideoTrack* track = stream.videoTracks[0]; RTCEAGLVideoView* view = _remoteVideoView; [track addRenderer:view]; } |
OpenGLベースのRTCEAGLVideoViewというものを使うそうです。VideoTrack.addRenderer()を使うところは、Androidと同じです。
詳細は
Web上に公開されている2つの記事を参考にしてい欲しいとのことでした。(どちらも、WebRTCを使ったビデオチャットサービスを提供している、apper.inのブログ記事です)
AppRTCを使ったデモ
実際に動くデモを用意していますとのことで、AppRTCについての紹介がありました。Hight performance, fast call setup, easy to understand and extend の3つを目指しているとのことです。
実際にChromebookとNexus6 を使ったデモが行われました。Nexus6ではエンコード側でもハードウェア(GPU)を使えて、720pのHD動画が快適に送れます。
フロントカメラからバックカメラに切り替えて、観客も映して見せました。
apprtc関連として、次の情報も紹介されていました。
- apprtcの短縮URLとして、appr.tc が使える
- ソースコードはgithubで github.com/webrtc
- ブラウザ側で[i]キーを押すと、統計情報(stats)が表示される
自分でも試してみましたが、Chromeで表示できる統計情報(stats)はどのような通信が行われているかを知る手掛かりとして、とても興味深いです。
つながるためのサーバー側の仕組み
デモの後は、実際の通信がつながるまでの裏側についての解説です。
シグナリングサーバー
Peer-to-Peer通信を始める前にはシグナリングと呼ばれる処理が必要ですが、AppRTCではそのサーバーとデバイスの間の通信にはWebSocketを使っているそうです。(シグナリングについてはこちらをご覧ください)
AppRTCならではの工夫(Smart signaling)として、最初のユーザがシグナリングサーバーに接続した際に、ユーザの接続情報(おそらくSDPのことだと思われます)をサーバー側にキャッシュしているとのこと。2番目のユーザが接続してきたら、サーバーから直ちにキャッシュされた情報を渡すことで、接続までの時間を短縮しているそうです。
リレーサーバー(TURN)
Peer-to-Peerで通信できないケースでは、リレーサーバー(TURNサーバー)が必要です。AppRTCでは次の仕組みを用意しているそうです。
TURNサーバーの役割については、こちらの記事をご覧いただくと参考になると思います。
- Tailbone … オープンソースのGoogle App Engineモジュール(動的にGoogle Compute Engineのインスタンスを起動する仕組みのようです。そのなかのmeshというものが、webrtc関連のように見えます)
- Coturn server + (Tailboneで起動される)オンデマンドのGoogle Compute Engineインスタンス
これによってすべてのGCEのゾーンでTURNサーバーが動き、世界中で快適に接続できるとのことでした。 ユーザになるべく近いゾーンのTURNサーバーを経由することで、遅延を小さく抑えるようにしているのだと思います。
さまざまな工夫によって「通信が確立するまで500ミリ秒しかかからないよ」と、その短さを強調していました。
最後に、改めて「情報はg.co/webrtcを見てほしい」との紹介のあと、「フィードバックを歓迎します」と締めくくられました。
おまけ
セッション終了後、Justinさんに質問してみました。
- Q. iOSのSDKでTURN over TCPがProxy設定を見てくれないようですが?
- A. 先週(5/21)その修正が反映されました。最新のソースで試してください
- Q. ChromeのBandwidth Estimationで、帯域の上限を指定するにどうすればよいですか?
- A. SDPのb=ASで指定すれば、その上限は超えません
- Q. 指定しているのですが効いていないみたいです。2Mbit/secぐらいです
- A. それはデフォルトの値ですね。指定が間違っていると思うので確認してください
どちらもちょっと間の抜けた質問だったようです。失礼しました…。