HTML5Experts.jp

ネイティブでもWebRTCを使おう!―Android/iOSでWebRTC

こんにちは!今回は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上で共有されています。

余談ですが企業でGoogle Apps for work を使っていてdocs/driveが使えないように機能制限している場合、アカウントにログインした状態では見れません。私も最初は見れなくて、地味にはまりました。

Google I/Oの大きなセッションのビデオは、公式サイトで公開されています。残念ながら本セッションのような小規模セッションのビデオは公開されていませんが、Google I/O 2015のお土産にもらったCardboard用のアプリからGoogle I/Oの様子をVRで見ることが可能です。本セッションの映像もVRでみることができるので、今年のCardboardをお持ちの方はお試しください。

これまでを振り返る

まずはこれまでの経緯を振り返えっての説明がありました。「数年前、Webをリアルタイムコミュニケーションのプラットフォームにしようと考えた」と言います。


そのために次の点について力を入れたとのことです。


実に野心的なプランで、ここでSamさんが”Crazy Plan!”と突っ込みます。


利用シーンの変化に合わせるため、AndoridとiOSのネイティブアプリでもWebブラウザと同じことができるようにした、との説明がありました。そしてここで、(WebRTC改め)”AndroidiOSWebRTC” と大きく書いたスライドが登場しました。


この通信技術は、Webブラウザだけリアルタイムコミュニケーションではなく、すべてのプラットフォームで使えるリアルタイムコミュニケーション技術だよ、との力強い声明です。

WebRTCの普及については、次のように説明しています。

30億回ダウンロードというのはにわかには信じがたいですが、アップデートも含めればそうなるのでしょう。 FacebookのメッセンジャーがWebRTCを使っているという話もあるので、それを含めれば30億回に到達するのかもしれません。

ソースコードの紹介

次に、実際のソースコードの概要について、3つのステップで説明がありました。Webブラウザ、Android、iOSのケースを順番に比較しています。

(1) カメラ、マイクへのアクセス

Webブラウザの場合


navigator.getUserMedia(constraints, onStream);
function onStream(localStream) {
  // do something with stream
}
Webブラウザの場合、getUserMedia()を使って、リアルタイムの映像/音声データをセットでMediaStreamという形で取得できます。取得に成功するとコールバック関数(この例では onStream )に渡されるので、そこで処理します。映像/音声の取得については、こちらの記事もご覧ください。

Androidの場合


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の場合は、カメラの映像とマイクの音声は別々に取得するようです。

興味深いのは、MediaStreamのインスタンスはPeerConnectionFactoryから作る、というところでしょうか。Androidの通常の映像処理とは異なり、WebRTC専用クラスということだと思います。
コードの紹介では出てきませんでしたが、マイクからの音声もAudioTrackとして取得してからMediaStreamの配下に追加するのでしょう。

iOSの場合

iOSもAndroidと同じく、VideoCapture → VideoSource → VideoTrack → MediaStream という流れになります。

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ブラウザの場合


pc = new RTCPeerConnection(config);
pc.onaddstream = onRemoteStream;
pc.addStream(localStream);
pc.createOffer(onCreateSuccess);
function onCreateSuccess(offer) { sendMessage(offer); }

Webブラウザの場合はPeerConnectionのオブジェクトを作るところから始まります。

Peer-to-Peer通信を始めるまでの手順をシグナリングと呼びます。シグナリングについてはこちらの記事もご覧ください。

Androidの場合


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の場合


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ブラウザの場合


function onRemoteStream(remoteStream) {
  videoElement.src = window.URL.createObjectURL(remoteStream);
}
おなじみの video タグを使います。 URL.createObjectURL()を使って、MediaStreamをvideoタグで表示できる形に変換します。

Androidの場合


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の場合


- (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関連として、次の情報も紹介されていました。

統計情報(stats)を表示しているところ

自分でも試してみましたが、Chromeで表示できる統計情報(stats)はどのような通信が行われているかを知る手掛かりとして、とても興味深いです。


つながるためのサーバー側の仕組み

デモの後は、実際の通信がつながるまでの裏側についての解説です。

シグナリングサーバー

Peer-to-Peer通信を始める前にはシグナリングと呼ばれる処理が必要ですが、AppRTCではそのサーバーとデバイスの間の通信にはWebSocketを使っているそうです。(シグナリングについてはこちらをご覧ください)


AppRTCならではの工夫(Smart signaling)として、最初のユーザがシグナリングサーバーに接続した際に、ユーザの接続情報(おそらくSDPのことだと思われます)をサーバー側にキャッシュしているとのこと。2番目のユーザが接続してきたら、サーバーから直ちにキャッシュされた情報を渡すことで、接続までの時間を短縮しているそうです。

リレーサーバー(TURN)

Peer-to-Peerで通信できないケースでは、リレーサーバー(TURNサーバー)が必要です。AppRTCでは次の仕組みを用意しているそうです。
TURNサーバーの役割については、こちらの記事をご覧いただくと参考になると思います。

これによってすべてのGCEのゾーンでTURNサーバーが動き、世界中で快適に接続できるとのことでした。 ユーザになるべく近いゾーンのTURNサーバーを経由することで、遅延を小さく抑えるようにしているのだと思います。

さまざまな工夫によって「通信が確立するまで500ミリ秒しかかからないよ」と、その短さを強調していました。


最後に、改めて「情報はg.co/webrtcを見てほしい」との紹介のあと、「フィードバックを歓迎します」と締めくくられました。

おまけ

セッション終了後、Justinさんに質問してみました。

どちらもちょっと間の抜けた質問だったようです。失礼しました…。