前回で uv4l-webrtcの環境ができたのでfirebase等を使ってルーターを超えて接続できるようにしていきます。
uv4l-server (uv4l-webrct) 設定を調整する。
ここで uv4l-uvc.conf の webtct に関する設定を変更します。
-
uv4l-uvc.conf をエディタで開いて変更する。
sudo vi /etc/uv4l/uv4l-uvc.conf ### WebRTC options: # server-option = --enable-webrtc=yes ... server-option = --webrtc-receive-video=no <- 受信ストリームのOverlayを無効にする ### video rendering window size on display ... server-option = --webrtc-preferred-vcodec=2 <- 優先ビデオコーディックを VP9 にする
-
リブートして設定を有効にする。
sudo reboot
webrtc-receive-video を no にしておかないとxwindowを立ち上げている場合、受信した映像が前面に表示されてしまいます。
uv4l-webrtc の WebSocket コマンドについて
uv4l-webrtcとシグナリングする場合に WebSocketでローカルのサーバーへコマンドを送る必要があります。
接続先
WebSocketで接続する場合の接続先です。
wss://localhost:8090/stream/webrtc
コマンド
WebSocketローカルサーバーへ送信する必要のあるコマンド
-
offer
answervar command = { command_id: "offer", options: { force_hw_vcodec: true, vformat: "60" /* 1280x720 30 fps */ } }; ws.send(JSON.stringify(command));
-
answer
answervar command = { command_id: "answer", data: JSON.stringify(sessionDescription) }; ws.send(JSON.stringify(command));
-
addicecandidate
addicecandidateconst date = JSON.parse(evt.data.icecandidate); const candidate = { sdpMLineIndex: date.sdpMLineIndex, sdpMid: date.sdpMid, candidate: date.candidate }; const command = {command_id: "addicecandidate",data:JSON.stringify(candidate)}; ws.send(JSON.stringify(command));
Firebase Realtime Databaseを使ってシグナリングして通話してみる
RasPi側でnode.jsアプリを作りFirebase Realtime Databaseを使ってWebRTCシグナリングをするコードを書いてみます。
node.js で Firebase,WebSocket,wrtcを利用できるようにする
npm install firebase --save
npm install websocket --save
sudo apt-get update
sudo apt-get install python2.7 git-all pkg-config libncurses5-dev libssl-dev libnss3-dev libexpat-dev
sudo apt-get install w3m
git clone https://github.com/ssaroha/node-webrtc.git
cd node-webrtc
gunzip third_party/webrtc/lib/libwebrtc.a.gz
npm install -g
node.jsアプリのコード
/** 各種設定
*
*/
class Config{
constructor(){
//このデバイスの固有の識別子(ユニークな文字列)
this.IDENTIFIER = "Rb6LFUGK";
// Initialize Firebase
this.FIREBASE_CONFIG = {
apiKey: "APIKEY",
authDomain: "0000000.firebaseapp.com",
databaseURL: "https://0000000.firebaseio.com",
storageBucket: "00000.appspot.com",
messagingSenderId: "00000"
};
//uv4l-server (uv4l-webrtc)のWebSocket接続先
this.UV4L_WEB_RTC_WEB_SOCKET = "ws://localhost:8090/stream/webrtc";
//シグナリングの為のデータベース名
this.FIREBASE_DB_WEBRTC_SIGNALING = "/webrtc/signaling";
}
}
module.exports = new Config();
/** Firebase Realtime Database でシグナリングする際に利用
*
*/
class WebRtcSignaling {
constructor(identifier,type,data){
this.identifier = identifier;
this.type = type;
this.data = data;
}
toData(){
return {
"identifier":this.identifier,
"eventType":this.eventType,
"data":this.data
};
}
}
WebRtcSignaling.TYPE_OFFER = "offer";
WebRtcSignaling.TYPE_ANSWER = "answer";
WebRtcSignaling.TYPE_CANDI_DATES = "candidates";
WebRtcSignaling.TYPE_CANDI_DATE = "candidate";
WebRtcSignaling.TYPE_REMOTE_CALL = "remote_call";
WebRtcSignaling.TYPE_REMOTE_HUNG_UP = "remote_hung_up";
module.exports = WebRtcSignaling;
/** WebRTCの各処理を実装
*/
const W3CWebSocket = require('websocket').w3cwebsocket;
const WebRtcSignaling = require('./web_rtc_signaling.js');
const WebRTC = require('wrtc');
const RTCPeerConnection = WebRTC.RTCPeerConnection;
const RTCIceCandidate= WebRTC.RTCIceCandidate;
const RTCSessionDescription = WebRTC.RTCSessionDescription;
const RTCDataChannel = WebRTC.RTCDataChannel;
class WebRTCUtil{
constructor(config,firebase){
this.Config = config;
this.ws = null;
//firebaseとの接続
this.webRtcSignalingAll = null;
this.firebaseInitialize(firebase);
}
//通話 開始
call(){
console.log("call");
const _this = this;
//WebSocket接続の状態を確認
if(this.ws == null){
//WebSocket接続して接続成功したらofferを送る
const clientConfig = {tlsOptions: {rejectUnauthorized: false}};
this.ws = new W3CWebSocket(this.Config.UV4L_WEB_RTC_WEB_SOCKET, null, null, null, null, clientConfig);
this.ws.parent = this;
this.ws.onopen = this.onWsOpen;
this.ws.onmessage = this.onWsMessage;
this.ws.onclose = this.onWsClose;
this.ws.onerror = this.onWsError;
}
else{
//WSと接続済みならローカルのWSサーバーにofferを送る
this.wsOffer();
}
}
//通話 終了
hungUp(){
console.log("hungUp");
this.wsStop();
}
//firebaseとの接続 初期化
firebaseInitialize(firebase){
const _this = this;
this.webRtcSignalingAll = firebase.database().ref(this.Config.FIREBASE_DB_WEBRTC_SIGNALING+"/"+this.Config.IDENTIFIER);
this.webRtcSignalingAll.on("value", function(snapshot) {
const evt = snapshot.val();
console.log("onvalue");
console.log(evt);
//識別子をチェックして自分以外だった時だけ以下の処理を実行
if((evt == null)||(_this.Config.IDENTIFIER == evt.identifier)){
return;
}
if (evt.type === WebRtcSignaling.TYPE_ANSWER) {
//answer SDPを受信したので answer を設定
let sessionDescription = new WebRTC.RTCSessionDescription({
type : 'answer',
sdp : evt.data.sdp,
});
var command = {
command_id: "answer",
data: JSON.stringify(sessionDescription)
};
_this.wsSend(command);
}
else if (evt.type === WebRtcSignaling.TYPE_OFFER) {
//offer SDPを受信したので offerを設定
}
else if (evt.type === WebRtcSignaling.TYPE_CANDI_DATE) {
//RasPi側の wsで受け取ったicecandidateがfirebaseから届く
}
else if (evt.type === WebRtcSignaling.TYPE_REMOTE_CALL) {
//Android側から RasPi のカメラへ繋ぐ
_this.call();
}
else if (evt.type === WebRtcSignaling.TYPE_REMOTE_HUNG_UP) {
//Android側から RasPi のカメラへの接続を中断
_this.hungUp();
}
});
}
//WSでコマンドを送信する
wsSend(command){
console.log("wsSend");
if(this.ws == null){
console.log(" ws is null");
return;
}
console.log(" ws.readyState "+this.ws.readyState);
//readyState 1 OPEN
if(this.ws.readyState != 1){
return;
}
this.ws.send(JSON.stringify(command));
}
//WS接続に成功
onWsOpen(){
console.log("onWsOpen");
//WS設定が完了したら offerの処理を開始する
this.parent.wsOffer();
}
onWsClose(){
console.log("onWsClose");
}
onWsError(evt){
console.log("onWsError");
console.error(evt);
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
onWsMessage(evt){
console.log("onWsMessage");
const msg = JSON.parse(evt.data);
console.log(msg);
console.log("msg.type="+msg.type);
switch (msg.type) {
case "message":
//alert(msg.data);
console.error(msg.data);
break;
case "offer":
console.log("msg.sdp="+msg.sdp);
//RasPi側のSDPをリモートに送信
this.parent.sendSdp(WebRtcSignaling.TYPE_OFFER,msg.sdp);
break;
case "geticecandidate":
console.log("msg.data="+msg.data);
const candidates = JSON.parse(msg.data);
this.parent.sendIceCandiDates(candidates);
break;
}
}
wsStop(){
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
//ローカルのWSサーバーにofferを送る
wsOffer() {
//this.rtcUtil.createPeerConnection();
var command = {
command_id: "offer",
options: {
force_hw_vcodec: true,
vformat: "60"
}
};
this.wsSend(command);
console.log("offer(), command=" + JSON.stringify(command));
}
sendSdp(type,sdp){
console.log("sendSdp");
const webRtcSignaling = new WebRtcSignaling(this.Config.IDENTIFIER,type,{"sdp":sdp});
this.webRtcSignalingAll.set(webRtcSignaling);
}
sendIceCandiDates(iceCandiDates){
console.log("sendIceCandiDates");
const webRtcSignaling = new WebRtcSignaling(this.Config.IDENTIFIER,WebRtcSignaling.TYPE_CANDI_DATES,{"icecandidates":iceCandiDates});
this.webRtcSignalingAll.set(webRtcSignaling);
}
}
module.exports = WebRTCUtil;
const Config = require('./config.js');
const WebRTCUtil = require('./web_rtc_util.js');
const firebase = require("firebase");
class App{
constructor(){
const _this = this;
this.firebaseApp = firebase.initializeApp(Config.FIREBASE_CONFIG);
this.webRTCUtil = new WebRTCUtil(Config,firebase);
//60秒後に終了
setTimeout(function(){
this.firebaseApp.delete();
},60*1000);
}
}
new App();
パワーセーブをOFFにする
パワーセーブに入るとfirebaseやWSとの接続が切れてしまうのでOFFにする。
sudo setterm -powersave off
Raspberry Pi側の設定は終わったので次回は、PCのブラウザから接続するhtmlを作成します。
参考
uv4l-server
Firebaseリアルタイムデータベースを使ってみる
WebSocket-Node
node-webrtc
node-webrtc/issues/152
Node WebRTC build for Raspberry PI