LoginSignup
6
11

More than 5 years have passed since last update.

Raspberry Pi3にuv4l-webrtcをインストールしてNAT外から接続してみる 1

Last updated at Posted at 2017-04-17

前回で uv4l-webrtcの環境ができたのでfirebase等を使ってルーターを超えて接続できるようにしていきます。

uv4l-server (uv4l-webrct) 設定を調整する。

ここで uv4l-uvc.conf の webtct に関する設定を変更します。

  1. 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 にする 
    
    
  2. リブートして設定を有効にする。

    sudo reboot
    

    webrtc-receive-video を no にしておかないとxwindowを立ち上げている場合、受信した映像が前面に表示されてしまいます。

uv4l-webrtc の WebSocket コマンドについて

uv4l-webrtcとシグナリングする場合に WebSocketでローカルのサーバーへコマンドを送る必要があります。

接続先

WebSocketで接続する場合の接続先です。

wss://localhost:8090/stream/webrtc

コマンド

WebSocketローカルサーバーへ送信する必要のあるコマンド

  • offer

    answer
    var command = {
        command_id: "offer",
        options: {
            force_hw_vcodec: true,
            vformat: "60" /* 1280x720 30 fps */
        }
    };
    ws.send(JSON.stringify(command));
    
  • answer

    answer
    var command = {
        command_id: "answer",
        data: JSON.stringify(sessionDescription)
    };
    ws.send(JSON.stringify(command));
    
  • addicecandidate

    addicecandidate
    const 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アプリのコード

config.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();
web_rtc_signaling.js
/** 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;
web_rtc_util.js
/** 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;
app.js
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

6
11
0

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
6
11