97
Node学園祭 2014 Nodeで操るKurentoメディアサーバ 2014/11/15 発表:がねこまさし 全面協力:ばばあつし

Nodeで操るKurentoメディアサーバー ( Kurento + WebRTC + Node.js )

  • Upload
    mganeko

  • View
    9.518

  • Download
    30

Embed Size (px)

DESCRIPTION

Node学園祭2014での発表資料です。 Kurento + WebRTC + Node.js の話をします。

Citation preview

Node学園祭 2014Nodeで操るKurentoメディアサーバ

2014/11/15発表:がねこまさし

全面協力:ばばあつし

はじめに

• 本日の内容は、こちらの内容を全面的に参考にしています

– 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

今日の話

Node.js

WebRTCメディア

サーバー

今日の話

Node.js

WebRTCメディア

サーバー

ここ!(とっても狭い)

今日の話

Node.js

WebRTCメディア

サーバー

ここ!(とっても狭い)

みなさん、大丈夫ですか?部屋間違ってませんか?

WebRTC & Node.js おさらい

質問タイム

• (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

DEMO

• 通信してみます

– インターネットにつながったら

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の機能に加えて

– メディア変換

– 録画、再生

などの機能を持つことが多い(みたい)

What’s Kurento?

http://www.kurento.org/

Kurentoとはエスペラント語で「Stream」の意味

カレントと発音するようです。

2013年頃から開発が始まり、現在は Ver5.0

が発表されています。

・2014 WebRTC EXPO IV Pioneer Awards

most innovative technologies受賞

・GStreamer Conferences 2014 Speaker

KurentoとはWebRTCをサポートしたメディアサーバ

基本的なMCU/トランスコードの他 Mixing

AR等のメディア加工がリアルタイムに可能

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

システムの構成

Kurento提供の部品

部品を組合せて利用

Why Kurento?

なぜ選んだか• 変換不要でWebRTCと直結できる。

– 映像を合成、通信を1本化できる

• フリー(OSS) GNU LGPL 2.1ライセンス

• Node対応のAPIが提供されている– 既存のシグナリングサーバと同じ環境にしたい

• TURNにも対応– NAT超えの環境に対応

• GStreamerがベース– プラグイン豊富で開発が活発

Use Kurento

> 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 Media Server自分の映像/音声を送信

メディアサーバ側でループバック

シグナリングサーバー側:準備// -- 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クライアント

対応生成

ストリームループバックの流れシグナリングサーバー Kurento Media Server

kurentoClient

ブラウザ

ブラウザ側:ビデオ開始、通信要求

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>

対応生成

ストリームループバックの流れシグナリングサーバー Kurento Media Server

webrtcPeer

kurentoClient

ブラウザ

ループバックの流れブラウザ シグナリングサーバー Kurento Media Server

connect

kurentClient

Offer SDP

シグナリングサーバー側:映像処理開始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

PipelinekurentoClient

ブラウザ

シグナリングサーバー側:映像処理開始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

ブラウザ側:停止function stopVideo() {

if (webRtcPeer) {webRtcPeer.dispose();webRtcpeer = null;}

}

ループバックデモ 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

多人数会議のデモ

• N対Nの通信

– N人分の映像を合成

– N-1人分の音声を合成

• 自分は含まない

– 下りのストリームを1本化

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

録画

再生

Kurentoを使った録画、再生の基本形

複雑な録画、再生のデモ

• N対Nの通信で

– N人分の映像を合成

– N-1人分の音声を合成

– 下りのストリームを1本化

• それを録画、再生

Kurento Media Server自分の映像/音声を送信

合成された映像/音声を送信

録画

Kurento Media Server デモ 複雑な録画、再生

再生

対応生成

ストリーム多人数録画シグナリングサーバー 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 ...");

});});

});});

});});

}

Kurento Media Server

Kurentoで多人数配信の基本形

複雑な多人数配信のデモ

• N対Nの通信で

– N人分の映像を合成

– N-1人分の音声を合成

– 下りのストリームを1本化

• それを多人数に配信

Kurento Media Server

Kurento Media Server デモ 複雑な多人数配信

対応生成

ストリーム

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

N対N会議の場合10~12人ぐらいならOK

たくさんの会議をホストしたい場合、複数のKurentoサーバーが必要

スケールアウトの可能性

多人数会議×n

会議室ごとに、別のメディアサーバーに接続させる

Kurento Media Server

多人数配信(Multi-Server)

PlumberEndPoint

Kurento Media Server

複数のメディアサーバーを連携して動作させる

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のプラグインが書けるレベルになれば、独自の部品が作成できそう。

大規模に使おうとすると、複数台のサーバーが必要。やっぱお金がかかる….

Thank you!

97