Upload
mganeko
View
9.518
Download
30
Embed Size (px)
DESCRIPTION
Node学園祭2014での発表資料です。 Kurento + WebRTC + Node.js の話をします。
Citation preview
はじめに
• 本日の内容は、こちらの内容を全面的に参考にしています
– http://www.kurento.org/docs/current/
• 本利用に含まれる製品名、商標、ロゴは、各権利者に帰属します。
– ®、TM 等の表記は省略します
自己紹介
• がねこまさし @massie_g– インフォコム株式会社 所属
• Node学園祭2013に初登場– WebRTCの話をさせていただきました– Node.jsの話はちょっとだけ– http://www.slideshare.net/mganeko/2013-web-rtcnode
• HTML5Experts.jp– WebRTCの話を書かせていただいてます– http://html5experts.jp/mganeko/
• WebRTC Meetup Japan– いまのところ毎回しゃべらせていただいてます– http://www.slideshare.net/mganeko/meetup1-webrtc– ※次回は 2014/11/26(水) https://atnd.org/events/58755
質問タイム
• (1) WebRTC という言葉を聞いたことがある人は?
• (2) WebRTCのサンプルに触ったことがある人は?
• (3) WebRTCを使ったコードを書いたことがある人は?
8
WebRTCの構成要素
• ユーザーメディア getUserMedia()– カメラ– マイク– 画面キャプチャ
• ストリーム (MediaStream)
• P2P通信 (RTCPeerConnection)• データ通信 (DataChannel)
• 関連するAPI、HTML要素– JavaScript (大前提)– Video, Audio– WebScoket– WebAudio API– Canvas– WebGL
9
通信について
• RTCPeerConnection
– 動画、音声などのMediaStreamを転送する経路
– P2P (Peer to Peer) → ブラウザとブラウザ
– UDP/IP を使用
10
RTCPeerConnection RTCPeerConnection
UDP/IP
P2P通信を始めるには• お互い、相手のIPアドレスを知る必要がある• 使用するポート番号を知る必要がある
– 利用するUDPのポートはダイナミックに割り振られる
• RTCPeerConnectionの通信を始める前に、何らかの手段でネゴシエーション、合意が必要– この手順を”シグナリング:Signaling”と呼びます
11
RTCPeerConnection RTCPeerConnection
UDP/IP
お互いのIPアドレス利用するUDPポート
P2P開始前のシグナリング
• どちらのブラウザからもわかる、中継役が必要– →普通はシグナリングサーバーを立てる
• シグナリングのプロトコルは標準化されていない– 独自の方式
• WebSocket利用(TCP/IP)• Ajax利用(HTTP, HTTPS)
– 既存のプロトコル• SIP(VoIP用) with WebSocket(TCP/IP)• XMPP(IM用)with WebSocket(TCP/IP)
12
(1) 情報交換(シグナリング)
(2) P2P
P2P開始前のシグナリング
• どちらのブラウザからもわかる、中継役が必要– →普通はシグナリングサーバーを立てる
• シグナリングのプロトコルは標準化されていない– 独自の方式
• WebSocket利用(TCP/IP)• Ajax利用(HTTP, HTTPS)
– 既存のプロトコル• SIP(VoIP用) with WebSocket(TCP/IP)• XMPP(IM用)with WebSocket(TCP/IP)
13
Node.js+ socket.io
Peer-to-Peer
• 良いところ
– 通信のオーバーヘッドが少ない
• 悪いところ
– 通信相手が多いと、マシンの負荷が高くなる
– ネットワーク負荷
– 動画エンコード負荷
• 相手ごとに圧縮している
なぜメディアサーバを使うのか
• なしの場合、同時接続数に限界あり(≒5端末)
• メディアサーバを使うと、負荷がサーバ側に移動する
• → (それなりのサーバを使えば)より多人数での接続が可能
Media Server
メディアサーバーありの構成
H/W・N/W負荷小
P2Pの構成
H/W・N/W負荷大
MCUとメディアサーバー
• MCU
– 多地点接続装置 / Multi point Control Unit
• メディアサーバー
MCUの機能に加えて
– メディア変換
– 録画、再生
などの機能を持つことが多い(みたい)
MCUとメディアサーバー
• MCU
– 多地点接続装置 / Multi point Control Unit
• メディアサーバー
MCUの機能に加えて
– メディア変換
– 録画、再生
などの機能を持つことが多い(みたい)
Kurentoとはエスペラント語で「Stream」の意味
カレントと発音するようです。
2013年頃から開発が始まり、現在は Ver5.0
が発表されています。
・2014 WebRTC EXPO IV Pioneer Awards
most innovative technologies受賞
・GStreamer Conferences 2014 Speaker
Kurentoの構造
GStreamerhttp://gstreamer.freedesktop.org/
C言語で作られた、メディアエンジン
Kurento Media Server http://www.kurento.org/
C++で作られた、メディアサーバー
Kurento Client for Java
Kurento Client for JS
Kurento Client for Node.js
JSON RPC over ws
なぜ選んだか• 変換不要でWebRTCと直結できる。
– 映像を合成、通信を1本化できる
• フリー(OSS) GNU LGPL 2.1ライセンス
• Node対応のAPIが提供されている– 既存のシグナリングサーバと同じ環境にしたい
• TURNにも対応– NAT超えの環境に対応
• GStreamerがベース– プラグイン豊富で開発が活発
> sudo service kurento-media-server start
> sudo add-apt-repository ppa:kurento/kurento> wget -O - http://ubuntu.kurento.org/kurento.gpg.key | sudo apt-key add –> sudo apt-get update> sudo apt-get install kurento-media-server
セットアップhttp://www.kurento.org/docs/current/installation_guide.htmlサーバはUbuntu 14.04 LTS (64 bits)奨励
サーバインストール
設定ファイル/etc/kurento/kurento.conf.json ⇒必要に応じてTURN/STUNの設定を追加/etc/kurento/sdp_pattern.txt ⇒必要ならSDPのひな形を編集
サーバ起動
kurento.conf.json 例{
"mediaServer" : {"net" : {
"websocket": {"port": 8888,"path": "kurento","threads": 10
}}
},"modules": {
"kurento": {"SdpEndpoint" : {
"sdpPattern" : "sdp_pattern.txt"},"WebRtcEndpoint" : {
"stunServerAddress" : "111.112.113.114", // Only IP address are supported"stunServerPort" : 80,"turnURL" : "user:[email protected]:80?transport=tcp"
},
JSON-RPC over ws の待ち受けポート、URL等
SDPのひな形
STUN/TURNサーバーの指定
同時に接続できる数
sdp_pattern.txt 例
v=0o=- 0 0 IN IP4 0.0.0.0s=TestSessionc=IN IP4 0.0.0.0t=0 0m=audio 0 RTP/AVP 98 99 0a=rtpmap:98 OPUS/48000/1a=rtpmap:99 AMR/8000/1a=rtpmap:0 PCMU/8000m=video 0 RTP/AVP 95 96 100a=rtpmap:95 VP8/90000a=rtpmap:96 H263-1998/90000a=rtpmap:100 MP4V-ES/90000
SDPって何?
• Session Description Protocol
– VoIP等で利用されている
• ストリームの情報、Peerの情報を含む
– メディアの種類(音声、映像)、コーデック
– IPアドレス、ポート番号
– P2Pのデータ転送プロトコル → Secure RTP
– 通信で使用する帯域
など
> sudo npm install -g bower> bower install kurento-client> bower install kurento-util
> sudo add-apt-repository ppa:chris-lea/node.js> sudo npm install express> sudo npm install kurento-client> sudo npm install kurento-util
Nodeで使うには (A)Webサーバ&シグナリングサーバー
シグナリングサーバにAPIを追加
package.jsonを使って npm install でもOK
bower.jsonを使って bower installでもOK
以上で準備できました。
> sudo npm install -g bower> bower install kurento-util> cp bower_components/kurento-utils/js/kurento-utils.js 適切なWebサーバー
> sudo add-apt-repository ppa:chris-lea/node.js> sudo npm install socket.io> sudo npm install kurento-client
Nodeで使うには (B)シグナリングサーバーのみ (Webサーバ別)
シグナリングサーバにAPIを追加
以上で準備できました。
Webサーバーにブラウザ用JavaScriptを追加
シグナリングサーバー側:準備// -- kurento client ---var kurento = require('kurento-client');var kurentoClient = null;const ws_uri = 'ws://kurento.sample.com:8888/kurento';
function prepareKurento() {kurento(ws_uri, function(error, client) {
if (error) { return onError(error); }
kurentoClient = client;});
}
prepareKurent();
// -- create the socket server on the port ---var srv = require('http').Server();var io = require('socket.io')(srv);var port = 8000;srv.listen(port);io.on('connection', function(socket) {// …
}
Socket.IO サーバー
Kurentoクライアント
ブラウザ側:ビデオ開始、通信要求
var pc_config = {“iceServers”:[ // ← TURN設定{"url":"turn:turn.xxxx.xxxx:80?transport=udp", "username":“xxxxx", "credential":“xxxxx"},{"url":"turn:turn.xxxx.xxxx:80?transport=tcp", "username":“xxxxx", "credential":“xxxxx"}
]};kurentoUtils.WebRtcPeer.prototype.server = pc_config;var webRtcPeer = null;var localVideo = document.getElementById('local_video');var remoteVideo = document.getElementById('remote_video');var socket = io.connect(‘http://シグナリングサーバー:ポート/');function startVideo() {
webRtcPeer = kurentoUtils.WebRtcPeer.startSendRecv( // ← クライアント側WebRTC Peer作成localVideo, remoteVideo, onOfferCreated,onError
);}
function onOfferCreated(sdpOffer) {var msg = "echoback_request";socket.emit(msg, sdpOffer); // ← サーバへOffer SDP送信
}
<script src=“js/adapter.js”></script> <!-- ブラウザ互換スクリプト --><script src="js/kurento-utils.js"></script>
シグナリングサーバー側:映像処理開始io.on('connection', function(socket) {// …socket.on('echoback_request', function(sdp) {
handleEchobackOffer(sdp, socket);});
}
var media_pipeline = null;function handleEchobackOffer(sdp, socket) { //← ブラウザのWebRTCのSDPをWebSocketで受信kurentoClient.create(‘MediaPipeline’, function(error, pipeline) { ← パイプライン作成if (error) { onError(error); return null;}media_pipeline = pipeline;
pipeline.create(‘WebRtcEndpoint’, function(error, webRtcEndpoint) { //← サーバ側のWebRTC作成webRtcEndpoint.connect(webRtcEndpoint, function(error) { // ← Loopするように繋ぐwebRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {
//← サーバ側のWebRTC Answer用SDP作成socket.emit(‘echoback_response’, { sdp: sdpAnswer }); //Answer用SDPをWebSocketでクライアントへ送信
});});
});}
シグナリングサーバー側:映像処理開始io.on('connection', function(socket) {// …socket.on('echoback_request', function(sdp) {
handleEchobackOffer(sdp, socket);});
}
var media_pipeline = null;function handleEchobackOffer(sdp, socket) { //← ブラウザのWebRTCのSDPをWebSocketで受信kurentoClient.create(‘MediaPipeline’, function(error, pipeline) { ← パイプライン作成if (error) { onError(error); return;}media_pipeline = pipeline;
pipeline.create(‘WebRtcEndpoint’, function(error, webRtcEndpoint) { //← サーバ側のWebRTC作成webRtcEndpoint.connect(webRtcEndpoint, function(error) { // ← Loopするように繋ぐwebRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {
//← サーバ側のWebRTC Answer用SDP作成socket.emit(‘echoback_response’, { sdp: sdpAnswer }); //Answer用SDPをWebSocketでクライアントへ送信
});});
});}
対応生成
ストリームループバックの流れシグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
WebRtcEndpointwebrtcEndpoint
kurentoClient
ブラウザ
シグナリングサーバー側:映像処理開始io.on('connection', function(socket) {// …socket.on('echoback_request', function(sdp) {
handleEchobackOffer(sdp, socket);});
}
var media_pipeline = null;function handleEchobackOffer(sdp, socket) { //← ブラウザのWebRTCのSDPをWebSocketで受信kurentoClient.create(‘MediaPipeline’, function(error, pipeline) { ← パイプライン作成if (error) { onError(error); return null;}media_pipeline = pipeline;
pipeline.create(‘WebRtcEndpoint’, function(error, webRtcEndpoint) { //← サーバ側のWebRTC作成webRtcEndpoint.connect(webRtcEndpoint, function(error) { // ← Loopするように繋ぐwebRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {
//← サーバ側のWebRTC Answer用SDP作成socket.emit(‘echoback_response’, { sdp: sdpAnswer }); //Answer用SDPをWebSocketでクライアントへ送信
});});
});}
対応生成
ストリームループバックの流れシグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
WebRtcEndpointwebrtcEndpoint
kurentoClient
ブラウザ
シグナリングサーバー側:映像処理開始io.on('connection', function(socket) {// …socket.on('echoback_request', function(sdp) {
handleEchobackOffer(sdp, socket);});
}
var media_pipeline = null;function handleEchobackOffer(sdp, socket) { //← ブラウザのWebRTCのSDPをWebSocketで受信kurentoClient.create(‘MediaPipeline’, function(error, pipeline) { ← パイプライン作成if (error) { onError(error); return null;}media_pipeline = pipeline;
pipeline.create(‘WebRtcEndpoint’, function(error, webRtcEndpoint) { //← サーバ側のWebRTC作成webRtcEndpoint.connect(webRtcEndpoint, function(error) { // ← Loopするように繋ぐwebRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {
//← サーバ側のWebRTC Answer用SDP作成socket.emit(‘echoback_response’, { sdp: sdpAnswer }); //Answer用SDPをWebSocketでクライアントへ送信
});});
});}
シグナリングサーバー側:応答を返すio.on('connection', function(socket) {// …socket.on('echoback_request', function(sdp) {
handleEchobackOffer(sdp, socket);});
}
var media_pipeline = null;function handleEchobackOffer(sdp, socket) { //← ブラウザのWebRTCのSDPをWebSocketで受信kurentoClient.create(‘MediaPipeline’, function(error, pipeline) { ← パイプライン作成if (error) { onError(error); return null;}media_pipeline = pipeline;
pipeline.create(‘WebRtcEndpoint’, function(error, webRtcEndpoint) { //← サーバ側のWebRTC作成webRtcEndpoint.connect(webRtcEndpoint, function(error) { // ← Loopするように繋ぐwebRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {
//← サーバ側のWebRTC Answer用SDP作成socket.emit(‘echoback_response’, { sdp: sdpAnswer }); //Answer用SDPをWebSocketでクライアントへ送信
});});
});}
ループバックの流れブラウザ シグナリングサーバー Kurento Media Server
connect
kurentClient
Offer SDPceate “Pipeline”
pipeline
ceate “WebRtcEndpoint”
WebRtcEndpoint
set Offer
Answer SDPAnswer SDP
ブラウザ側:応答(Answer)を受け取る
var socket = io.connect(‘シグナリングサーバーURL’);socket.on('connect', onChannelOpened).on('echoback_response', onEchobackResponse)
function onEchobackResponse(evt) {if (evt.sdp) {webRtcPeer.processSdpAnswer(evt.sdp); // Answer SDP を受け付ける}
}
ループバックの流れ:全体シグナリングサーバー
pipeline
webrtcPeer
Pipeline
WebRtcEndpointwebrtcEndpoint
kurentoClient
ブラウザ
対応生成
ストリーム
Kurento Media Server
ループバックの流れブラウザ シグナリングサーバー Kurento Media Server
connect
kurentClient
Offer SDPceate “Pipeline”
pipeline
ceate “WebRtcEndpoint”
WebRtcEndpoint
set Offer
Answer SDPAnswer SDP
Peer to Peer 通信開始
補足:ICE Candidateについて
• シグナリングの過程では、2種類の情報をやりとりします
– SDP (Peerの情報)
– ICE Candidate (通信経路の情報)
• Kurento では、 SDP+ICEをまとめて、一度に送っています
TURNサーバーが有る場合:全体シグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
WebRtcEndpointwebrtcEndpoint
kurentoClient
ブラウザ
TURNサーバー
firewall
ループバックデモ 2• メディアサーバで映像を加工
– ブラウザからの映像を、加工してから送り返す
Kurento Media Server自分の映像/音声を送信
メディアサーバ側で映像加工(Filter)してループバック
ブラウザ側:映像開始、フィルター指定
var webRtcPeer = null; var localVideo = document.getElementById('local_video');var remoteVideo = document.getElementById('remote_video');
var webRtcPeer = null;function startVideo() {
webRtcPeer = kurentoUtils.WebRtcPeer.startSendRecv( //← クライアント側WebRTC Peer作成localVideo, remoteVideo, onOfferCreated,onError);
}
function onOfferCreated(sdpOffer) {var msg = "echoback_request";var effect = getEffect();socket.emit(msg, sdpOffer, effect); // ← サーバへSDP送信
}
サーバー側:フィルター指定Var media_pipeline = null;function handleEchobackOffer(sdp, socket, effect) { // ← ブラウザのWebRTCのSDPをWebSocketで受信kurentoClient.create(‘MediaPipeline’, function(error, pipeline) { ← パイプライン作成if (error) { onError(error); return null;}media_pipeline = pipeline;
pipeline.create(‘WebRtcEndpoint’, function(error, webRtcEndpoint) { // ← サーバ側のWebRTC作成// 加工フィルター作成pipeline.create(‘GStreamerFilter’,{command : effect,filterType:“VIDEO”}, function(error, filter) {webRtcEndpoint.connect(filter, function(error) { // WebRtcEndpoint → フィルタに繋ぐfilter.connect(webRtcEndpoint, function(error) { // フィルター → WebRtcEndpointに繋ぐ// サーバ側のWebRTC Answer用SDP作成webRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {
// サーバ側のWebRTC Answer用SDP作成socket.emit(‘echoback_response’, { sdp: sdpAnswer });
});});});});
});}
対応生成
ストリームフィルター有りの流れ:全体シグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
WebRtcEndpointwebrtcEndpoint
kurentoClient
ブラウザ
GStreamerFiltergstreamerFilter
Kurento Media Server自分の映像/音声を送信
合成された映像/音声を送信
入力ソース3つを1つに合成
映像×3➡1音声×2 ➡1
Kurento Media Server デモ N対N 映像/音声
対応生成
ストリーム多人数合成の流れ:全体シグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
Composite
kurentoClient
ブラウザ
Composite
webrtcEndPoint
webrtcEndPoint
webrtcEndPoint
hubporthubport
hubport
ブラウザ側:入室、映像開始var socket = io.connect(‘シグナリングサーバーのURL');socket.on('connect', onChannelOpened).on('multi_response', onPlaybackResponse)
function onChannelOpened(evt) {var roomname = getRoomName();socket.emit(‘enter’, roomname); // 会議室に入る
}
var webRtcPeer = null;function startVideo() { // 映像開始
webRtcPeer = kurentoUtils.WebRtcPeer.startSendRecv(localVideo, remoteVideo,onOfferCreated,onError
);}
function onOfferCreated(sdpOffer) {var room = getRoomName();var msg = "multi_start";socket.emit(msg, room, sdpOffer); // Offer SDP を送信
}
シグナリングサーバ側:入室
socket.on('enter', function(roomname) {socket.join(roomname);setRoomname(roomname);
setupRoom(roomname, socket);});
function setRoomname(room) {//// for v0.9//socket.set('roomname', room);
// for v1.0socket.roomname = room;
}
シグナリングサーバ側:部屋の準備// 部屋ごとvar media_pipelines = [];var composites = [];
function setupRoom(roomname, socket) {if (media_pipelines[roomname]) {
// 部屋の準備は完了notifyRoomReady(roomname, socket);
}
// まだ部屋に対応した要素が無ければ、作成kurentoClient.create('MediaPipeline', function(error, pipeline) {
media_pipelines[roomname] = pipeline;pipeline.create("Composite", function(error, comp, socket) {composites[roomname] = comp;notifyRoomReady(roomname, socket);
});});
}
function notifyRoomReady(roomname, socket) {socket.emit('room_ready', {roomname: roomname} );
}
対応生成
ストリーム多人数合成の流れ:部屋の準備
シグナリングサーバー Kurento Media Server
pipeline
Pipeline
Composite
kurentoClient
ブラウザ
Composite
webrtcPeer
シグナリングサーバ側:接続開始// --- クライアントごと ---var webrtcs = [];var hubs = [];
socket.on('multi_start', function(room, sdp) {handleOffer(sdp, room, socket);
});
function handleOffer(sdp, roomname, socket) {var pipeline = media_pipelines[roomname];var composite = composites[roomname];pipeline.create('WebRtcEndpoint', function(error, webRtcEndpoint) {
webrtcs[socket.id] = webRtcEndpoint;composite.createHubPort(function(error, hubpoint) {hubs[socket.id] = hubpoint;hubpoint.connect(webRtcEndpoint);webRtcEndpoint.connect(hubpoint);webRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {
socket.emit('multi_response', { sdp: sdpAnswer, endpoit: webRtcEndpoint.id });});});
});}
多人数合成の流れ:1人目シグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
Composite
kurentoClient
ブラウザ
Composite
webrtcEndPoint
hubport
ブラウザ側:映像受信
var socket = io.connect(‘シグナリングサーバーURL’);socket.on('connect', onChannelOpened).on(‘multi_response', onMultiResponse)
function onMultiResponse(evt) {if (evt.sdp) {webRtcPeer.processSdpAnswer(evt.sdp); // Answer SDP を受け付ける}
}
対応生成
ストリーム多人数合成の流れ:全体シグナリングサーバー Kurento Media Server
pipeline
Pipeline
Composite
kurentoClient
ブラウザ
Composite
webrtcEndPoint
webrtcEndPoint
webrtcEndPoint
hubporthubport
hubport
ブラウザブラウザ
webrtcPeer
シグナリングサーバ側:おまけ
// 部屋に接続しているクライアントの数function getClientCount(room) {
//// for v0.9//var clients = io.sockets.clients(room);//var count = clients.length;
// for v1.0var namespace = '/';var count = 0;var roominfo = io.nsps[namespace].adapter.rooms[room];if (roominfo) {
count = Object.keys(roominfo).length;}return count;
}
もっと良い方法があったら、教えてください!
対応生成
ストリーム多人数録画シグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
Composite
kurentoClient
ブラウザ
Composite
webrtcEndPoint
hubport
recorderEndPoint
シグナリングサーバ側:録画開始、停止var recorders = [];function startRecord(room, socket_id) {var pipeline = media_pipelines[room];var file_uri = 'file:///tmp/' + room + '_' + socket_id + '_mix.webm';var hub = hubs[id];pipeline.create('RecorderEndpoint', { uri: file_uri }, function(error, recorder) {
if (error) { onError(error); return; }
recorders[socket_id] = recorder;hub.connect(recorder);recorder.record();
});}
function stopRecord(socket_id) {var recorder = recorders[socket_id];if (recorder) {
recorder.stop();return;
}}
対応生成
ストリーム
Pipeline
再生シグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
kurentoClient
ブラウザ
webrtcEndPoint
playerEndPoint
webrtcPeerwebrtcEndPoint
pipeline
シグナリングサーバ側:再生開始function playbackStart(room, sdpOffer, socket) {var file_uri = 'file:///tmp/' + room + '_' + socket_id + '_mix.webm';kurentoClient.create('MediaPipeline', function(error, pipeline) {
pipeline.create('WebRtcEndpoint', function(error, webRtc) {webRtc.processOffer(sdpOffer, function(error, sdpAnswer) {
pipeline.create("PlayerEndpoint", { uri: file_uri }, function(error, player) {socket.emit('playback_response', { id: socket.id, sdp: sdpAnswer, playerpoint: player.id });player.on('EndOfStream', function(event) {pipeline.release();console.log('End Playing');
});player.connect(webRtc, function(error) {
player.play(function(error) {console.log("Playing ...");
});});
});});
});});
}
対応生成
ストリーム
webrtcEndPoint
多人数合成→配信シグナリングサーバー Kurento Media Server
pipeline
webrtcPeer
Pipeline
Composite
kurentoClient
ブラウザ
Composite
webrtcEndPointwebrtcEndPoint
hubporthubport
hubport
webrtcPeerwebrtcEndPoint
話す人
見る人
webrtcPeer
見る人
webrtcEndPoint
ブラウザ側:配信開始の要求
function startPlayback() {webRtcPeerPlayback = kurentoUtils.WebRtcPeer.startRecvOnly(playbackVideo,onPlaybackOfferCreated,onError
);}
function onPlaybackOfferCreated(offer) {var room = getRoomName();var msg = 'playback_start';socket.emit(msg, room, offer);
}
シグナリングサーバ側:配信開始
socket.on('viewer_request', function(room, sdp) {masterid = findFirstSocketIdByRoomId(room); handleViewerOffer(sdp, room, socket, masterid);
});
function handleViewerOffer(sdp, roomname, socket, masterid) {var pipeline = media_pipelines[roomname];var composite = composites[roomname];
pipeline.create('WebRtcEndpoint', function(error, webRtcEndpoint) {if (error) { onError(error); return; }webrtcs[socket.id] = webRtcEndpoint;hubs[masterid].connect(webRtcEndpoint);
webRtcEndpoint.processOffer(sdp, function(error, sdpAnswer) {if (error) { onError(error); return; }socket.emit('viewer_response', { sdp: sdpAnswer, endpoit: webRtcEndpoint.id });
});});
}
シグナリングサーバ側:おまけ
// 部屋につながっている最初のソケットIDを取得する
function findFirstSocketIdByRoomId(room) {var res = null;var namespace = '/';roominfo = io.nsps[namespace].adapter.rooms[room];if (roominfo) {
for (var id in roominfo) {res = id;break;
}}return res;
}
もっと良い方法があったら、教えてください!
他人数配信(基本形)での測定
0
5
10
15
20
25
30
35
40
45
50
1 5 10 15 20 25
CPU使用率
CPU使用率
680
690
700
710
720
730
740
750
760
1 5 10 15 20 25
Memory
Memory
0
500000
1000000
1500000
2000000
2500000
3000000
3500000
1 5 10 15 20 25
recv
send
Network
(人) (人)
(人)
(%)
(Byte/sec)
(MByte)
サーバースペック(ゲストVM)・CPU: 4コア・メモリ: 4GB
・社内ネットワーク・40人まで配信を確認・50人ぐらい行けそうな感触
サーバースペック(ホストマシン)・CPU: 6コア、12スレッド、3.3GHz・メモリ: 64GB
PlumberEndpoint
公式ドキュメントには無いですが、
メディアサーバで処理したストリームを別のメディアサーバと
繋ぐことができる部品が実装されています。
Plumber:配管工
Gstreamer Conference 2014の発表では
輻輳制御で使おうと試みようとしているようでした。
http://gstconf.ubicast.tv/videos/implementing-webrtc-capabilities-for-gstreamer-the-kurento-webrtc-endpoint/
RabbitMQ Cluster
公式ドキュメントには無いですが、サーバ設定ファイル
にRabbitMQの指定が可能になっています。
Kurento Media Controllerというサーバと組合せて
クラスタリング動作すると思われます。
感想
シンプルなAPIでLEGOブロックのように組合せて機能を実現できるのは面白い。
今後部品も増えそうなのでアイディア次第で面白い使い方ができる。
メジャーバージョンアップ(Ver 5.0)でかなりNodeで使いやすくなった。
(それまでは、JBossが必要でした。うへぇ)
使っている人が少ないのか検索してもあまり情報が拾えない。
(ソース解析が主になってます。)
ベースとなっているGStreamerのプラグインが書けるレベルになれば、独自の部品が作成できそう。
大規模に使おうとすると、複数台のサーバーが必要。やっぱお金がかかる….