Edited at

SkyWay初心者からステップアップしよう


はじめに



自己紹介


  • なかゆうすけ(@yusuke84

  • 仕事:



  • コミュニティ


    • WebRTC Meetup Tokyo 主催

    • WebRTC Beginners Tokyo 主催





この資料は何?


  • サポート等で問い合わせが多いSkyWayのティップスを厳選して紹介します



達成したいゴール


  • SkyWayを利用するエンジニアの困った:tired_face:を、1つでも多く解決し ハッピー:heart_eyes: になってもらう

  • SkyWay初心者からSkyWay中級者へステップアップしてもらう



今日お話する内容


  • SkyWaySDKの通信モデル

  • SkyWayの通信要件

  • SkyWayを利用したWebRTC通信のコネクティビティ(メディアコネクションの場合)を確認する

  • SkyWayを利用する際の通信帯域

  • SkyWayの動作確認済みブラウザとOS

  • SkyWayを配信に利用する場合のティップス



本題に入る前に…お知らせ



ダッシュボードで利用量の閲覧ができるようになりました


  • APIキー毎に直近2ヶ月分閲覧可能

  • 更新頻度は1日に数回程度

  • 利用量は1MB単位で切り上げ

  • Enterprise Edition利用のお客様は今まで通り「料金・請求」の明細画面からも確認可能



Enterprise EditonへのAPIキー移行ができるようになりました


  • Community EditionからEnterprise Editionへ移行可能


    • 逆はできない



  • サービス停止無しで移行可能

  • 利用量表示のデータはCommunity Editionでの実績が反映されますが、課金は移行完了した時点から開始



SkyWaySDKの通信モデル

SkyWayを利用するにあたり、知っておくべき概念です。



Peer


  • SkyWayを利用して何らかのサービスを作る場合、最初にPeer(ピア)というインスタンスを作成する

  • Peerは、SkyWayのシグナリングサーバや、他のクライアントとの接続を管理するエージェント



PeerID


  • Peerを作成(new Peer)すると、作成したPeerがシグナリングサーバへ接続する

  • 接続が成功すると、Peer.openイベントの発火と共に、SkyWayサーバ側がランダムに作成するPeer IDという値を取得できる

  • Peer IDはPeerを一意に識別するためのID

  • 接続が失敗した場合はPeer.errorイベントが発火する



通信モデルその1「電話モデル」


  • 電話で通話する場合と同様に1:1で通信するモデル

  • 通信を開始する場合には、相手のPeerIDを何らかの手段で知る必要がある



音声・映像(メディアチャンネル)


  1. 相PeerIDを指定して相手に発信(peer.call)

  2. callイベントが着信側で発火

  3. そのcall要求に対してanswer()を呼ぶことで通信確立

  4. 音声・映像はStreamイベントの発火とともに取得できる



データの場合(データチャンネル)


  1. peer.connect()で相手に接続要求

  2. 接続が確立するとconnectionイベントが発火


  • メディアチャンネルと異なりanswer()を明示的に呼ぶ必要がない



通信モデルその2「ルームモデル」


  • 同一ルームに存在するすべてのPeerで会話するモデル

  • ルームの名前空間はAPIキー毎に独立している

  • APIキーが違う場合は同一ルーム名でも別ルームと扱われて相互通信は出来ない

  • ルームの実現方式にはフルメッシュとSFUの2種類がある


    • 両者の違いはの詳細はこちら

    • 同一APIキー上にフルメッシュとSFUで方式で同時に同一ルーム名のルームを作ることは出来ない





  1. ルーム名を指定してルームに参加(peer.joinRoom)

  2. 他の参加者からメディアを受信した場合はRoom.streamイベントが発火する

  3. Room.Close()でルームからの退出する


  • ルームの削除は参加者が全員退出したら行われる


    • 明示的に消す方法はなし





メディアストリームの扱い


  • SkyWayのSDKではメディアストリームは入力時に渡して出力時に取得するのみが原則となる


    • iOS/Android SDKはメディア作成APIを搭載しているが入力はプログラム側から行う





SkyWayの通信要件



その1(管理系)


  • ディスパッチャ


    • TCP443(HTTPS Protocol)

    • dispatcher.webrtc.ecl.ntt.com



  • シグナリングサーバ


    • TCP443(プロトコルはSocket.io依存)

    • *.webrtc.ecl.ntt.com

    • シグナリングサーバは複数台構成のためワイルドカードで記載





その2(WebRTC関連)


  • P2P通信


    • ポート番号はUDPハイポートレンジを利用し毎回ランダムに決定

    • IPアドレスもクライアントアドレスは環境に合わせて変わる



  • STUN通信


    • UDP3478(STUN Protocol)

    • stun.webrtc.ecl.ntt.com

    • 役割


      • 自分自身のIPアドレスとポート番号を取得

      • STUNサーバへアクセスが出来ずP2P通信も出来ない場合は、TURNサーバで代用







  • TURN通信


    • TCP443,UDP443(低コストでつながる方を勝手に選択)

    • turn.webrtc.ecl.ntt.com

    • 役割


      • P2P通信できない場合の代替手段







TURN通信は3パターン


  • AさんのみTURN(プロトコル)を利用する場合



  • AさんとBさん両方が同一のTURN(プロトコル)を利用する場合



  • AさんとBさん両方が別々のTURN(プロトコル)を利用する場合



TURNサーバは極力使わないようにネットワーク設計する


  • UDPによるハイポートの通信を規制しない

  • STUNサーバへのアクセスを許可する

  • エンタープライズユースの場合はNATタイプに気を使う




絶対TURNサーバが必要な場合


  • UDP通信が禁止


    • TURNプロトコル(TCP)を利用した通信をするしかない



  • 通信相手をACL等で制限する必要がある


    • 通信相手を固定する場合はTURNサーバのアドレスで固定するしかない



  • IPv6アドレスを持ったPeerがSFUやIPv4アドレスのPeerと通信する場合





SkyWayを利用したWebRTC通信のコネクティビティ(メディアコネクションの場合)を確認する



予備知識〜SkyWay SDKのオブジェクト構造〜


全SDK共通


  • Peer Class/Object


    • シグナリングサーバとの接続を管理する



  • MediaConnection Class/Object


    • メディアコネクション(映像・音声のP2P通信)を管理する



  • DataConnection Class/Object


    • データコネクション(データのP2P通信)を管理する



  • SFURoom Class/Object


    • SFUのRoom接続を管理する



  • MeshRoom Class/Object


    • MeshのRoom接続を管理する





iOS/Android SDKのみ



  • iOS


    • SKWNavigator


      • ブラウザのgetUserMedia APIに相当



    • SKWMediaStream


      • メディアストリーム



    • SKWVideo


      • 映像を表示するレンダラービューオブジェクト






  • Android


    • Navigator


      • ブラウザのgetUserMedia APIに相当



    • MediaStream


      • メディアストリーム



    • Canvas


      • 映像を表示するレンダラービューオブジェクト







シグナリングサーバとの接続が確立できたことを把握


  • 全SDK共通


    • new Peer実行

    • Peer Class/Objectのopenイベントが発火する

    • 以降の処理はすべてこのイベントが発火してから実施する





(参考)new Peerを実行すると内部的には何が行われるのか?


  1. ディスパッチャーサーバから接続対象のシグナリングサーバを取得


    • シグナリングサーバは複数台で冗長構成を取っているため接続対象のサーバをディスパッチャーサーバに毎回問い合わせる


    • signaling-xxxxxxx.webrtc.ecl.ntt.com 等のURLを取得



  2. シグナリングサーバへの接続


    • 認証サーバ連携している場合は認証情報の検証

    • PeerIDの生成

    • TURNサーバ接続用の認証情報取得



  3. シグナリングサーバとは常時接続された状態を維持


    • 定期的にping/pongを実施




  4. openイベントが発火



メディア通信が開始されたことを把握



  • JS SDK


    • MediaConnectionオブジェクトまたはRoomオブジェクトのstreamイベント




  • iOS/Android SDK


    • P2Pの場合


      • SKWMediaConnection on:SKW_MEDIACONNECTION_EVENT_STREAM

      • MediaConnection.MediaEventEnum.STREAM



    • Roomの場合


      • SKWRoom on:SKW_ROOM_EVENT_STREAM

      • Room.RoomEventEnum.STEAM





これらのイベントが発火すると実際にメディアデータが送受信される



通信状況をモニターする


  • iOS/Android SDK


    • 機能としては用意されてないが、Webアプリと相互接続する場合、ブラウザ側で確認可能



  • JS SDK


    • ChromeまたはFirefoxのWebRTC開発者向けツールでモニタリング

    • 詳しくは次のページで





Chrome WebRTC Internalsの使い方


  • 情報量が多いのでChromeがオススメ

  • WebRTCで通話中に別のタブで開発者向けツールにアクセス



    • chrome://webrtc-internals/





WebRTC Internals その1


  • WebRTC通信を行っているブラウザのタブ毎にタブが作られるので、該当部分を選択


    • TimeとEventはWebRTCの接続シーケンスがログとして出ている

    • リアルタイムで更新される





  • ICE Connection Stateをチェック


  • ICEとはInteractive Connectivity Establishmentの略で、WebRTCによる通信経路定めるためのプロトコル



  • ICEのステータス遷移

ステータス
内容

connnected
通信可能な接続を発見した
でもまだ他の経路情報も探索中

completed
すべての経路を探査し終わり通信に利用する経路が確定した



WebRTC Internals その2


  • メディアの通信が行われている場合は、必ず一つだけ太字になっている部分がある


    • 無ければ接続がうまく行っていない





  • チェックする箇所は以下の通り

項目
意味

bytesReceived
受信バイト数
カウンタがリアルタイムに更新される

bytesSent
送信バイト数
カウンタがリアルタイムに更新される

googLocalAddress
自分自身の通信に利用しているIPアドレスとポート番号
TURNサーバを利用している場合はTURNサーバのアドレスとなる

googLocalCandidateType
通信経路の種類を識別
relayはTURNサーバ利用

googRemoteAddress
相手の通信に利用しているIPアドレスとポート番号
TURNサーバを利用している場合はTURNサーバのアドレスとなる

googRemoteCandidateType
通信経路の種類を識別
relayはTURNサーバ利用



WebRTC Internals その3


  • 通信するメディアのAnalyticsが表示される


    • 大まかな帯域等を把握することが出来る

    • 受信側、送信側でそれぞれ確認できる





SkyWayを利用する際の通信帯域



通信帯域はあくまで目安です

用途
帯域

1:1音声通話
40kbps

1:1ビデオ通話(SD)
500kbps

1:1ビデオ通話(HD)
1.5Mbps

4人ビデオ会議(SD)
1.5Mbps

出展: SkyWay利用モデル



 実際どうなの?


  • 計測条件


    • Chrome M66

    • コーデック:VP8

    • フレームレート:指定なし

    • MacBookPro 13inch 2016モデル

    • Logicool BRIO



  • 計測方法


    • カメラの前に座って会議をやっている前提(動き少なめ)

    • 同一PC内でP2Pビデオチャットを実施しwebrtc-internalsで確認




条件
帯域

320x240(QVGA)



450kbps前後

640x480(VGA)



1.6Mbps前後

720x480(SD)



1.6Mbps前後

1024x768(XGA)



1.6Mbps前後

1280x720(HD)



1.6Mbps前後

1920x1080(フルHD)



1.6Mbps前後

3840x2160(4K UHDTV)



2Mbps前後



  • 帯域を考える上でのポイント


    • アプリの作りである程度コントロール可能

    • 利用シーンによって変動幅が異なる


      • Web会議用は比較的変動が少ない

      • スマホやスマートグラス等動きがある場合はかなり変動幅が大きい







SkyWayの動作確認済みブラウザとOS



  • ChromeとFirefoxに正式対応


    • 安定版の最新2バージョンに対応


      • 例: 2018.04.19現在 最新の安定版は Version66 なので、動作確認済みブラウザは66と65



    • ブラウザは約6週間でアップデートされるため、JS SDKのアップデートへの追従はお早めに




  • Edge(一部機能のみ対応)


    • P2PとRoom(Meshモード)のみ利用可能

    • Room(SFUモード)への対応予定:無し


      • EdgeがMultiStream機能に対応していないから






  • Safari(一部機能のみ対応)


    • P2PとRoom(Meshモード)のみ利用可能

    • Room(SFUモード)への対応予定:有り


      • 現在のSFUはビデオコーデックがVP8固定のため利用不可

      • 今後コーデックの切り替え機能をリリース予定








  • iOS


    • iOS 8+




  • Android


    • Android 4.2+ (API Level 17+)




  • EPSON MOVERIO BT-300/350


    • Android SDKで動作





SkyWayを配信に利用する場合のティップス



配信に利用できる機能



受信のみモード



  • 自身のMediaStreamをオプションに渡さずに、Roomへjoinすることで受信のみモードになる

// 例

const room = peer.joinRoom("roomName", {
mode: 'mesh'
});


  • この時、MediaStreamをオプションに渡して参加したメンバがいる場合、そのメンバから受信のみモードメンバへ片方向通信が実現できる



videoReceiveEnabled/audiReceiveEnabled


  • 映像と音声を単独で受信のみにすることが出来る

// 例:音声だけ受信したいしたい

const room = peer.joinRoom("roomName", {
mode: 'mesh',
audioReceiveEnabled: true
});



  • 映像と音声を単独で受信のみにすることが出来る

// 例:音声は送受信、映像は受信だけしたい

const room = peer.joinRoom("roomName", {
mode: 'mesh',
stream: audioOnlyStream,
videoReceiveEnabled: true
});



おことわり…



  • replaceStreamで、受信のみモードから通常モード(MediaStreamを送信するモード)への切り替えは動作保障対象外(2018.04.19現在)


    • 送信していたMediaStreamを単純に差し替える用途では問題なく利用可能





それぞれの機能の利用可否まとめ

機能
JS SDK
iOS SDK
Android SDK

受信のみモード
:white_check_mark:
:white_check_mark:
:white_check_mark:

videoReceiveEnabled/audiReceiveEnabled
:white_check_mark:
:x:
:x:


機能
meshモード
sfuモード

受信のみモード
:white_check_mark:

:white_check_mark: ※1

videoReceiveEnabled/audiReceiveEnabled
:white_check_mark:
:x:


  • ※1:ワークアラウンドが必要(不要になりました)


配信等のユースケースで活用する場合、SFUモードが必要になることが多いと思います。以後、受信のみモード☓SFUモードreplaceStream☓SFUモードの組み合わせで利用する際のワークアラウンドにフォーカスします。



受信のみモード☓SFUモードのワークアラウンド

2019.04.09 追記

SFUの不具合が解消されたため、このワークアラウンドは不要となりました。


受信のみモードをSFUモードで利用する場合、送信される映像が受信のみモードのユーザ上で再生されない問題がある。この問題は、SkyWayのSFUサーバから映像の再生に必要なキーフレーム※参考情報(外部サイト)が、受信のみモードのユーザに届かないため発生している。

次に示す方法で回避可能。また、送信されるのが音声のみの場合は問題なく利用できる


SkyWayサポートコミュニティ:受信のみモードを併用した多人数でのSFUの利用で開発者の方が記載していただいている、ダミーのjoinRoomを実行する方法が一番良い。

const skyway = new Peer({key:'APIKEY'});

const room = skyway.joinRoom('roomName',{mode: 'sfu'});
room.on('open',async (peerId) => {
try {
await dummyRoomJoin();
} catch (err){
console.error(err);
}
});

function dummyRoomJoin(){
return new Promise((resolve, reject) => {
const dummyPeer = new Peer({key:'APIKEY'});
dummyPeer.on('open',() => {
const dummyRoom = dummyPeer.joinRoom('roomName',{mode: 'sfu'});
dummyRoom.on('open',() => {
dummyRoom.close();
});
dummyRoom.on('close',() => {
dummyPeer.destroy();
resolve();
});
dummyRoom.on('error',(err) => {
reject(err);
});
});
});
};

この他に、canvasで作成した1px X 1pxのダミーストリームを送信する方法もあるが、受信のみモードのユーザからSFUへのUpstreamのメディアコネクションが生成されるため、Roomに参加するユーザ全体の負荷が高くなる。おすすめしない。



replaceStream☓SFUモード

SFUモードでreplaceStreamを利用し、受信のみモードから通常モードへの切り替えを行うのは動作対象外。つまり、以下のようなユースケースについては、現状正常に動作しない。


  1. 全員が受信のみモードでRoom参加

  2. あるユーザがreplaceStreamを利用して映像・音声の送信者になる

  3. 他の参加者は受信のみモードで視聴する


  • この場合、3のタイミングで、他の参加者に対してstreamイベントが発火しない

  • JS SDKに関しては、誰かが一人でも通常モードで参加していれば、受信のみモードから通常モードへの切替時は可能



まとめ


  • SkyWaySDKの通信モデル

  • SkyWayの通信要件

  • SkyWayを利用したWebRTC通信のコネクティビティ(メディアコネクションの場合)を確認する

  • SkyWayを利用する際の通信帯域

  • SkyWayの動作確認済みブラウザとOS

  • SkyWayを配信に利用する場合のティップス