はじめに
2019年のre:Inventで、Amazon Kinesis Video Streams (以後KVSと表記) に WebRTCを使ったリアルタイム通信が加わりました。
ブラウザ(JavaScript)向けのSDKだけでなく、組み込み用途のC言語SDKや、iOS/Androidといったモバイルアプリ向けのSDKがあるのが特徴です。
- Web用SDK ... https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-js
- 組み込みC用SDK ... https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c
- iOS用SDK ... https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-ios
- Android用SDK ... https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-android
今回は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
MasterとViewerは1対1で使うことが自然だと思いますが、複数のViewerを1つのMasterに接続することも可能です。一方Viewer間の通信はできないため、3クライアント以上のフルメッシュ構造は取れません。
シグナリングチャネル
KVS WebRTCでシグナリングをするためには、あらかじめ「シグナリングチャネル」を作成しておく必要があります。これは、WebRTCのアプリケーションで良くある「ルーム」や「チャネル」に相当するものです。
同じ「シグナリングチャネル」に参加しているクライアント同士で通信を行うことができます。
シグナリングチャネルを作成するには、次の方法があります。
- AWSコンソールから作成
- AWSのコマンドラインツール(CLI)から作成
- AWSのSDKからAPIを利用して作成
- REST APIを利用して作成
シグナリングチャネルには「チャネルタイプ」がありますが、現在は"SINGLE_MASTER"のみ指定できます。(もしかしたら将来的には MULTI_MASTER や SFU_MASTER みたいな種類も提供される、かも知れません)
シグナリングチャネルを利用するには、あらかじめIAMでアプリ用のユーザを作っておく必要があります。(後述)
AWSコンソールからの作成
AWSコンソールにログインし、希望リージョンのAmazon Kinesis Video Streamsのシグナリングチャネルのページから、新規にシグナリングチャネルを作成します。
チャネル名と、必要に応じてTTL(シグナリングメッセージを保持する秒数)、タグを指定して作成します。
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のリファレンスはこちら)
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経由でシグナリングチャネルを作成するとき
権限として「AmazonKinesisVideoStreamsFullAccess」を指定
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
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 イベント
// 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);
// 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 イベント
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的な使い方がマッチしそうです。