85
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Amazon Kinesis Video Streams WebRTC を動かしてみた

Last updated at Posted at 2020-01-11

はじめに

2019年のre:Inventで、Amazon Kinesis Video Streams (以後KVSと表記) に WebRTCを使ったリアルタイム通信が加わりました。

ブラウザ(JavaScript)向けのSDKだけでなく、組み込み用途のC言語SDKや、iOS/Androidといったモバイルアプリ向けのSDKがあるのが特徴です。

今回はWeb用のSDKを使って動作を確認してみました。オマケとしてC用のSDKを覗いて分かったことを記載します。

※続編:[Amazon Kinesis Video Streams WebRTC で無理やり複数人のビデオチャットを作る]
(https://qiita.com/massie_g/items/4cdf475ab623757a2630)

KVS WebRTCがサポートすること、しないこと

サーバー機能

KVS WebRTC のサーバーが提供する機能は、次の通りです。

  • シグナリング ... Offer/Answer SDP の交換、ICE candidateの交換
  • STUN/TURN ... P2P通信でNAT越えを行ったり、UDPが通らない場合でも通信を行うための仕組み

対してサポートしない機能もあります。

  • メディアの送受信
  • メディアの変換

もともとあったKVSの方では、サーバー側でメディアを受信して保存したり、HLSで配信する仕組みを持っていました。KVS WebRTCでは同様なことは現状行えないので、注意が必要です。

※TURNによる接続について
私が試せる環境(企業のFirewall内と外部)では、TURN経由の接続が成功していません。私の使い方が悪いのか、環境が厳しすぎる(UDPはブロック、80/TCP, 443/TCPしか通らない)のかは不明です。

クライアント機能(SDK)

WebSocketを使ったシグナリングがKVS WebRTCの基本機能になります。

Web (JavaScript)向けのSDK

  • シグナリング ... WebSocketによるOffer/Answer SDP の交換、ICE candidateの交換

Web(JavaScript)向けのSDKでは、メディアの扱いやWebRTC通信そのものはブラウザがもっている機能を使うのが前提です。

C言語用

こちらはシグナリングに加えて(S)RTP通信もサポートしているようです。

  • シグナリング ... WebSocketによるOffer/Answer SDP の交換、ICE candidateの交換
  • (S)RTP通信 ... RTCPeerConnection相当の通信

メディアの取得や再生は別のライブラリ(GStreamer等)を使う必要があります。

KVS WebRTCの通信形態

  • シグナリングチャネル(ルーム)ごとのシグナリング
    • 1つのMasterが参加
    • 1つ以上複数のViewerが参加可能(現在、最大10まで)
  • P2P通信
    • メディアの通信
    • データの通信(DataChannel)
  • 双方向、片方向はどちらも可能
    • Master --> Viewer
    • Master <-- Viewer
    • Master <--> Viewer

kvs_master_viewer.png

MasterとViewerは1対1で使うことが自然だと思いますが、複数のViewerを1つのMasterに接続することも可能です。一方Viewer間の通信はできないため、3クライアント以上のフルメッシュ構造は取れません。

kvs_topology.png

シグナリングチャネル

KVS WebRTCでシグナリングをするためには、あらかじめ「シグナリングチャネル」を作成しておく必要があります。これは、WebRTCのアプリケーションで良くある「ルーム」や「チャネル」に相当するものです。
同じ「シグナリングチャネル」に参加しているクライアント同士で通信を行うことができます。

シグナリングチャネルを作成するには、次の方法があります。

  • AWSコンソールから作成
  • AWSのコマンドラインツール(CLI)から作成
  • AWSのSDKからAPIを利用して作成
  • REST APIを利用して作成

シグナリングチャネルには「チャネルタイプ」がありますが、現在は"SINGLE_MASTER"のみ指定できます。(もしかしたら将来的には MULTI_MASTER や SFU_MASTER みたいな種類も提供される、かも知れません)

シグナリングチャネルを利用するには、あらかじめIAMでアプリ用のユーザを作っておく必要があります。(後述)

AWSコンソールからの作成

AWSコンソールにログインし、希望リージョンのAmazon Kinesis Video Streamsのシグナリングチャネルのページから、新規にシグナリングチャネルを作成します。

kvs_channel_list.png

チャネル名と、必要に応じてTTL(シグナリングメッセージを保持する秒数)、タグを指定して作成します。

kvs_new_channel.png

AWSのコマンドラインツール(CLI)からの作成

こちらの手順に従ってCLIツールをインストールします。

macOSの場合は次の手順です。

$ curl "https://d1vvhvl2y92vvt.cloudfront.net/awscli-exe-macos.zip" -o "awscliv2.zip"
$ unzip awscliv2.zip
$sudo ./aws/install

/usr/local/bin/aws2 としてインストールされました。

AWSで利用するアクセスキーとシークレットアクセスキーを設定します。ファイルで指定する方法と、環境変数で指定する方法があります。

macOSやLinuxでファイルを利用する場合は、~/.aws/credentials に下記のように指定します。

$ cat ~/.aws/credentials
[default]
aws_access_key_id=xxxxxx
aws_secret_access_key=xxxxxxxxx/xxxxxxx

CLIツールでシグナリングチャネル "channel02"を作るには、次のようにターミナルから操作します。

$ aws2 --region "us-west-2" kinesisvideo create-signaling-channel --channel-name channel02 --channel-type SINGLE_MASTER
{
    "ChannelARN": "arn:aws:kinesisvideo:us-west-2:xxxxxxxxxx:channel/channel02/xxxxxxxxx"
}

成功すると、上記のようにJSON形式でARNが返ってきます。

AWSのSDKからAPIを利用した作成

AWSのSDKを用いて、プログラムからシグナリングチャネルを作成することも可能です。例えばNode.jsでは次のようなコードで作成できます。(SDKのリファレンスはこちら)

create_channel.js
const AWS = require("aws-sdk");
AWS.config.update({ region: 'us-west-2' });
 
AWS.config.getCredentials(function (err) {
 if (err) {
   // credentials not loaded
   console.log(err.stack);
   process.exit(1);
 }
 else {
   console.log("Access key:", AWS.config.credentials.accessKeyId);
   console.log("Secret access key:", AWS.config.credentials.secretAccessKey);
 }
});
 
const kinesisvideo = new AWS.KinesisVideo();
const channel = 'channel03'; 
const params = {
 ChannelName: channel, /* required */
 ChannelType: 'SINGLE_MASTER',
 SingleMasterConfiguration: {
   MessageTtlSeconds: 30
 }
};
 
kinesisvideo.createSignalingChannel(params, function (err, data) {
 if (err) console.log(err, err.stack); // an error occurred
 else console.log(data);           // successful response
});

実行には、CLIツールと同様に認証情報を予め設定しておく必要があります。作成に成功すると、次のようにARNが返ってきます。

$ node create_channel.js
{ ChannelARN:
   'arn:aws:kinesisvideo:us-west-2:xxxxxx:channel/channel03/xxxxx' }

AWS REST APIによる作成

CLIやSDKを使わず、直接REST APIを呼び出すこともできます。

us-west-2 リージョンに "channel04"を作るには、curlを使って次のような操作でできるハズです。が、Authenticationヘッダーに指定する文字列の生成の方法がまだ理解できおらず、成功していません。

$ curl -X POST https://kinesisvideo.us-west-2.amazonaws.com/createSignalingChannel \
 -H "Content-Type: application/json" \
 -d '{"ChannelName": "channel04", "ChannelType": "SINGLE_MASTER"}' \
 -H "Authorization: AWS AWSAccessKeyId:Signature"

IAMによるユーザー作成

シグナリングチャネルを利用するには、あらかじめIAM(Identity and Access Managemenent)でユーザを作り、
AmazonKinesisVideoStreamsFullAccess の権限を与えておく必要があります。その際のアクセスキーおよびアクセスシークレットキーを記録しておき、次の利用時に指定します。

  • アプリからシグナリングチャネルに接続するとき
  • CLIツールや、API経由でシグナリングチャネルを作成するとき

AWSコンソールのIAMで、ユーザーを追加
IAM_1.png

ユーザー名を指定し、「プログラムによるアクセス」を指定
IAM_2.png

権限として「AmazonKinesisVideoStreamsFullAccess」を指定
IAM_3.png

アクセスキー、シークレットアクセスキーを記録
IAM_4.png

Master/Viewerとシグナリング

現状はViewerがSDP Offer側、MasterがSDP Answer側になることが前提になっています。
シグナリングチャネル経由でSDPを送る際に、Offerは必ずMasterに届けられます。ViewerにOfferを送信することはできません。

また、シグナリングチャネルで送信できるのはSDPとICE Candidateのみになっています。アプリケーション独自のメッセージを送ることはできず、よく行われる切断の通知には利用できません。

KVS WebRTC (for JavaScript)のサンプル

公式のサンプルがあるのでそれを読めば良いのですが、ちょっと複雑です。なるべく最小限で動くようにしたものをこちらに上げておきます。

共通の処理

該当部分のコードはこちら
https://github.com/mganeko/kvs_webrtc_example/blob/master/master.html#L45

  • AWS.KinesisVideoインスタンスを生成
    • リージョン、アクセスキー、シークレットアクセスキーを指定
  • シグナリングチャネル用のエンドポイントを取得
    • WSS, HTTPSがある
  • STUN/TURNサーバー(iceServers)の情報を取得
  • KVSWebRTC.SignalingClient インスタンスを生成
    • シグナリングチャネルを利用するためのオブジェクト
    • 生成するときにロールを指定
      • KVSWebRTC.Role.MASTER または KVSWebRTC.Role.VIEWER
    • イベントに対応するハンドラを設定
      • open, close
      • sdpOffer, sdpAnswer
      • iceCandidate
KinesisVideo,SignalingClientインタンス生成部分
    const kinesisVideoClient = new AWS.KinesisVideo({
      region,
      accessKeyId,
      secretAccessKey,
    });

    const getSignalingChannelEndpointResponse = await kinesisVideoClient
      .getSignalingChannelEndpoint({
        ChannelARN: channelARN,
        SingleMasterChannelEndpointConfiguration: {
          Protocols: ['WSS', 'HTTPS'],
          Role: KVSWebRTC.Role.VIEWER, // MASTER or VIEWER
        },
      })
      .promise();
    const endpointsByProtocol = getSignalingChannelEndpointResponse.ResourceEndpointList.reduce((endpoints, endpoint) => {
      endpoints[endpoint.Protocol] = endpoint.ResourceEndpoint;
      return endpoints;
    }, {});

    const kinesisVideoSignalingChannelsClient = new AWS.KinesisVideoSignalingChannels({
      region,
      accessKeyId,
      secretAccessKey,
      endpoint: endpointsByProtocol.HTTPS,
    });

    const getIceServerConfigResponse = await kinesisVideoSignalingChannelsClient
      .getIceServerConfig({
        ChannelARN: channelARN,
      })
      .promise();
    const iceServers = [
      { urls: `stun:stun.kinesisvideo.${region}.amazonaws.com:443` }
    ];
    getIceServerConfigResponse.IceServerList.forEach(iceServer =>
      iceServers.push({
        urls: iceServer.Uris,
        username: iceServer.Username,
        credential: iceServer.Password,
      }),
    );

    const signalingClient = new KVSWebRTC.SignalingClient({
      channelARN,
      channelEndpoint: endpointsByProtocol.WSS,
      clientId,
      role: KVSWebRTC.Role.VIEWER,
      region,
      credentials: {
        accessKeyId,
        secretAccessKey,
      },
    });

Viewerの処理

コードはこちら
https://github.com/mganeko/kvs_webrtc_example/blob/master/viewer.html

  • 自分のClientIDを決定する
    • シグナリングチャネル内で一意になるように
  • シグナリングチャネルに接続
    • KVSWebRTC.SignalingClient.open()を呼ぶ
    • openイベントが発生したら、処理を続行する
  • カメラ/マイク等のメディアを取得
  • RTCPeerConnectionのインスタンスを生成
    • addTrack()でメディアを追加
  • RTCPeerConnection.createOffer()でoffer SDPを生成
  • KVSWebRTC.SignalingClient.sendSdpOffer()でoffer SDPを(Masterに)送信
    • あて先は指定できない。自動的にMasterに送信される
  • Answer SDPを受け取ったら、RTCPeerConnection.setRemoteDescription()で設定
    • KVSWebRTC.SignalingClien の sdpAnswer イベント
  • RTCPeerConnection で iceCandidate が生成されたら、Masterに送る
    • KVSWebRTC.SignalingClient.sendIceCandidate() で送信
    • あて先を省略すると、Masterに送信される
  • iceCandidate を受け取ったら、RTCPeerConnection.addIceCandidate()で追加
    • KVSWebRTC.SignalingClien の iceCandidate イベント
Offerを生成、送信する部分の抜粋
      // Create an SDP offer and send it to the master
      const offer = await peer.createOffer({
        offerToReceiveAudio: true,
        offerToReceiveVideo: true,
      });
      await peer.setLocalDescription(offer);
      console.log('send offer');
      signalingClient.sendSdpOffer(peer.localDescription);
Answerを受け取った時の処理の抜粋
    // When the SDP answer is received back from the master, add it to the peer connection.
    signalingClient.on('sdpAnswer', async answer => {
      console.log('got answer');
      await globalKVS.peerConnection.setRemoteDescription(answer);
    });

Masterの処理

コードはこちら
https://github.com/mganeko/kvs_webrtc_example/blob/master/master.html

  • カメラ/マイク等のメディアを取得
  • シグナリングチャネルに接続
    • KVSWebRTC.SignalingClient.open()を呼ぶ
    • ※Viewerよりも先に接続しておくと、処理がスムーズ
  • Offer SDPとclientIDを受け取ったら、対応するRTCPeerConnectionを生成
    • KVSWebRTC.SignalingClien の sdpOffer イベント
    • Viewer側のclientIDを保持
    • 複数Viewerの場合は、clientIDと対応付けてRTCPeerConnectionのインスタンスを保持
    • RTCPeerConnection.addTrack()でメディアを追加
    • RTCPeerConnection.setRemoteDescription()でOfferを設定
  • RTCPeerConnection.createAnswer()でanswer SDPを生成
  • KVSWebRTC.SignalingClient.sendSdpAnswer()でanswer SDPをViewerに送信
    • あて先として、通信相手となるViewerのclientIDを指定
  • RTCPeerConnection で iceCandidate が生成されたら、Viewerに送る
    • KVSWebRTC.SignalingClient.sendIceCandidate() で送信
    • あて先として、ViewerのclientIDを指定
  • iceCandidate を受け取ったら、RTCPeerConnection.addIceCandidate()で追加
    • KVSWebRTC.SignalingClien の iceCandidate イベント
Offer受信→Answer送信まで
    signalingClient.on('sdpOffer', async (offer, remoteClientId) => {
      // --- prepare new peer ---
      let peer = prepareNewPeer(globalKVS.iceServers, globalKVS.signalingClient, remoteClientId); // 関数内でRTCPeerConnetionを生成
      if (localStream) {
        localStream.getTracks().forEach(track => peer.addTrack(track, localStream));
      }
      else {
        // --- recvonly ---
        if (peer.addTransceiver) {
          peer.addTransceiver('video', { direction: 'recvonly' });
          peer.addTransceiver('audio', { direction: 'recvonly' });
        }
      }

      await peer.setRemoteDescription(offer);
      await peer.setLocalDescription(
        await peer.createAnswer({
          offerToReceiveAudio: true,
          offerToReceiveVideo: true,
        }),
      );

      // send back answer
      signalingClient.sendSdpAnswer(peer.localDescription, remoteClientId);
    });

KVS WebRTC の利用料金

こちらのモデルケースが参考になります。

米国東部における WebRTC 料金は、アクティブなシグナリングチャネルが月額で 0.03 USD、シグナリングメッセージ 100 万件あたりで 2.25 USD、1,000 TURN ストリーミング時間 (分) あたりで 0.12 USD となっています。

アクティブなシグナリングチャネル = 100 x (0.03 USD/月) = 3.0 USD
シグナリングメッセージ = 100 ユーザー x 1,500 シグナリングメッセージ/1,000,000 x (2.25 USD/100 万シグナリングメッセージ) = 0.34 USD

TURN ストリーミング時間 (分) = 100 ユーザー x 400 TURN ストリーミング時間 (分) x (0.12 USD/1,000 TURN ストリーミング時間 (分)) = 4.8 USD

合計 = 8.14 USD

注意: インターネット経由で TURN ストリーミングから AWS 外の送信先にデータを送信する場合は、AWS の標準のデータ転送料金がかかります。

AWSのデータ転送料金は次の概算になりました。

  • メディア通信が1Mbpsと仮定
  • 40000 TURNストリーミング時間/月 → 300GB/月
  • 転送料金 27 USD/月 (299GB分、us-west-2の場合)

KVS WebRTC の感想

少し触っただけですが、個人的な感想をあげてみます。

  • 現状はビデオチャットのようなコミュニケーションアプリを作るためのものではない
    • 1対1なら可能だが、Master(主催者)とViewer(参加者)がはっきり区別される
    • n対nは不可能
    • ただし今後SFUのようなサーバー側の仕組みが登場する可能性はある
  • 1対少数の配信は可能だが、大規模配信はできない
    • それは(WebRTCではない)Kinesis Video Streams を使うのが筋が良さそう

個人的にはビデオチャットのプラットフォームとして利用するなら、他のクラウドサービスを利用する方がスムーズと感じました。(登場したばかりなので、今後どう発展するかは未知数ですが)
どちらかというと、C言語のSDKが提供されていることからも分かる通り、組み込みデバイスからの映像やセンサーデーターをモニターするような、IoT的な使い方がマッチしそうです。

85
58
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
85
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?