23
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

webRTCを導入して360度映像を低遅延で生配信する

Last updated at Posted at 2018-08-17

昨日はRTMPを使用するところまで行ったが、
今日はより少ない遅延を求めてwebRTCを導入してみる。(macユーザー)

最終的な目標は360度カメラで撮った映像を遅延をなるべく無くして、
スマホ上に配信すること。

#デモを試してみる
以下のサイトが大変役に立った。
感謝感謝。
WebRTCでキャスしよう!片方向リアルタイム映像配信を作ろう
とりあえずこれをコピペして試せた。
確かに遅延は1秒以下だ。
素晴らしい。。。

ここからカスタマイズしていく。

#ソースの検討
どうやって、配信元のウェブブラウザにライブ映像を届けるか考えられるのは、

###CamTwist
CamTwistはwebカメラから入力映像を受け取り、加工を加えて、仮想webカメラとして出力する
したがって、デモのコードをそのまま使える。
###OBS
obsでもプラグインを使うことで同様の機能を使えるようになる。
下記サイトに導入方法や使い方が書かれている。(Windows用だった。。。)

#####Windows
OBSを使ってPixivSketchLIVEで配信する動機
#####Mac
iVirtualCamera
があるのだが、
This project is incompatible with the latest OS, it may causes the system crash at boot.
ということで、大変そうなのでCamTwistを使うことにする。

#カスタマイズ
昨日動いてたコードが動かない・・・・
websocketのconnectとdisconnentのタイミングの問題だったみたい。

##nodejsをhtmlサーバーとして外部から接続できるようにする
シグナリングサーバをhtmlサーバーとしても使用する。
以前つくっったnode.jsのサーバー用コードをシグナリングサーバーファイルと統合する。

app.js
var BROADCAST_ID = '_broadcast_';

// -- create the socket server on the port ---
var app = require('http').createServer(handler);
var socketio = require('socket.io').listen(app);
var port = 9001;
app.listen(port);
console.log('signaling server started on port:' + port);
 
// ---------- read html part ----------
var fs = require('fs');
var path = require('path');
var mime = {
// 読み取りたいMIMEタイプはここに追記
    ".html": "text/html",
    ".css":  "text/css",
    ".js":  "text/javascript",
};

//ファイルの読み込み
function handler(req, res) {
    console.log(req.url)
    if (req.url == '/') {
        filePath = '/index.html';
  } else {
    filePath = req.url;
  }
  var fullPath = __dirname + filePath;

  res.writeHead(200, {"Content-Type": mime[path.extname(fullPath)] || "text/plain"});
  fs.readFile(fullPath, function(err, data) {
    if (err) {
      // エラー時の応答
    } else {
      res.end(data, 'UTF-8');
    }
  });
}

// ---------- signaling part ----------
// This callback function is called every time a socket
// tries to connect to the server
socketio.on('connection', function(socket) {
 
    // ---- multi room ----
    socket.on('enter', function(roomname) {
      socket.join(roomname);
      console.log('id=' + socket.id + ' enter room=' + roomname);
      setRoomname(roomname);
    });
 
    function setRoomname(room) {
      //// for v0.9
      //socket.set('roomname', room);
 
      // for v1.0
      socket.roomname = room;
    }
 
    function getRoomname() {
      var room = null;
 
      //// for v0.9
      //socket.get('roomname', function(err, _room) {
      //  room = _room;
      //});
 
      // for v1.0
      room = socket.roomname;
 
      return room;
    }
 
 
    function emitMessage(type, message) {
      // ----- multi room ----
      var roomname = getRoomname();
 
      if (roomname) {
        console.log('===== message broadcast to room -->' + roomname);
        socket.broadcast.to(roomname).emit(type, message);
      }
      else {
        console.log('===== message broadcast all');
        socket.broadcast.emit(type, message);
      }
    }
 
 
    // When a user send a SDP message
    // broadcast to all users in the room
    socket.on('message', function(message) {
        message.from = socket.id;
 
        // get send target
        var target = message.sendto;
        if ( (target) && (target != BROADCAST_ID) ) {
          console.log('===== message emit to -->' + target);
          socket.to(target).emit('message', message);
          return;
        }
 
        // broadcast in room
        emitMessage('message', message);
    });
 
    // When the user hangs up
    // broadcast bye signal to all users in the room
    socket.on('disconnect', function() {
        console.log('-- user disconnect: ' + socket.id);
        // --- emit ----
        emitMessage('user disconnected', {id: socket.id});
 
        // --- leave room --
        var roomname = getRoomname();
        if (roomname) {
          socket.leave(roomname);
        }
 
    });
 
});

また、ページが読み込まれたときに自動で映像を受信するように変更した。
listen.jsは上記のサイトと全く同じ。

index.html
<!DOCTYPE html>
<html>

<head>
    <title>broadcast watch</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <script>
        window.onload = function(){
            sendRequest();
            console.log("req");
        }
    </script>
</head>

<body>
    <video id="remote-video" autoplay controls muted></video>
    <!---- socket ※自分のシグナリングサーバーに合わせて変更してください ------>
    <script src="socket.io/socket.io.js"></script>
    <script src="./js/listen.js" type="text/javascript"></script>
</body>

</html>

シグナリングサーバーの書き換えがうまくできていないと、ローカルでは動いても、外部からアクセスした場合、シグナリングができないことがある。
スマホだとうまくいかないことが多い。やはりどこかにボタンを仕込むのが確実かも。

##360度映像に対応する
###映像のアスペクト比を2:1にする
360度映像形式は
CamTwist
Preference/General/Video_Sizecustomを選択し、サイズを変更
今回は920*1960に設定した。
設定したのちCamTwistをリスタート

webRTC

  • talk側

    videoタグの設定を変更する

talk.html
<!DOCTYPE html>
<html>

<head>
    <title>Broadcast Talk</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>

<body>
    <button type="button" onclick="startVideo();">Start video</button>
    <button type="button" onclick="stopVideo();">Stop video</button> &nbsp;&nbsp;&nbsp;&nbsp;
    <button type="button" onclick="tellReady();">On Air</button>
    <br />
    <div style="position: relative;">
        <video id="local-video" autoplay controls style="width: 100%;"></video>
    </div>

    <!---- socket ※自分のシグナリングサーバーに合わせて変更してください------>
    <script src="http://localhost:9001/socket.io/socket.io.js"></script>
    <script src="./js/talk.js" type="text/javascript"></script>
</body>

</html>
  • listen側

videoタグの設定を変更
resizeRemoteVideo()のファンクションを削除

index.html
<!DOCTYPE html>
<html>

<head>
    <title>broadcast watch</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <script>
        window.onload = function(){
            sendRequest();
        }
    </script>
</head>

<body>
    <video id="remote-video" autoplay controls muted style="width: 100%;"></video>

    <!---- socket ※自分のシグナリングサーバーに合わせて変更してください ------>
    <script src="socket.io/socket.io.js"></script>

    <script src="./js/listen.js" type="text/javascript"></script>
</body>

</html>

これで、2:1の動画が送れるようになった。

###VR化する
A-frameを使ってこれをVRにする。

A-frameを使うのは2回目なのだがなぜか手間取った。
####通常の360度映像の場合
まずはwebRTCではなく普通の映像ファイルをソースとして使う方法。
A-frameの最新版は0.8.0なのだが、それを使うとなぜか映像が真っ白になって表示されなくなるので0.7.0を使った。
スマホ再生の0.7.0の問題は補足1。
(追記2018.08.19)
A-frameの最新バージョンは0.8.2でした。
これを使うと、映像が白くなりません。
また、スマホでもうまく再生できます。

ポイントはCamera位置を親entityで1.6m下げているところ。
A-frameのデフォルトカメラは人の視点にあわせるため高さが1.6mに設定されている。
今回は360度映像を使うので0mに視点をあわせる。

index.html
<!DOCTYPE html>
<html>

<head>
    <title>broadcast watch</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script>
</head>

<body>
    <a-scene>
        <a-assets>
            <video id="video" autoplay loop="true" src="video/test.mp4"></video>
        </a-assets>
        <!-- camera -->
        <a-entity id="camera" position="0 -1.6 0" rotate="0 0 0">
            <a-camera id="acamera"></a-camera>
        </a-entity>
        <!-- video sphere -->
        <a-videosphere src="#video"></a-videosphere>
    </a-scene>
</body>

</html>

####webRTC
これをベースとして、webRTCの映像を利用できるように書き換える。
書き換えはとても簡単

index.html
<!DOCTYPE html>
<html>

<head>
    <title>broadcast watch</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script>
    <script>
        window.onload = function(){
            sendRequest();
        }
    </script>
</head>

<body>
    <a-scene>
        <a-assets>
            <video id="remote-video" autoplay loop="true"></video>
        </a-assets>
        <!-- camera -->
        <a-entity id="camera" position="0 -1.6 0" rotate="0 0 0">
            <a-camera id="acamera"></a-camera>
        </a-entity>
        <a-videosphere src="#remote-video"></a-videosphere>
    </a-scene>

    <!---- socket ※自分のシグナリングサーバーに合わせて変更してください ------>
    <script src="socket.io/socket.io.js"></script>

    <script src="./js/listen.js" type="text/javascript"></script>
</body>

</html>

これでめでたく360度映像をリアルタイム中継できるようになった!

####比較
webRTCとvideoファイルを読み込む方法の比較
いかに出力される映像のキャプチャを比較した。
伝わるかわからないが明らかにwebRTCの方の画質が悪かった。
また再生もカクカクなりやすい。

Presentation1.png Screen Shot 2018-08-17 at 15.38.48.png

#補足
##補足1
A-frame0.7.0で実現した360度映像をスマホでみると、すごいグッラグラする。
理由はわからないが解決方法。
A-frame0.8.0をローカルにダウンロードする(追記: 0.8.2でした)
a-frameのソースをローカルのaframe-master.min.jsにすると解決する。
(追記 2018.08.19)
原因わかりました。
Chrome66以降の仕様変更によるものらしいです。
A-frame0.8.2で対応してます。
Camera is super sensitive to motion on Chrome Beta 66 on Android
#まとめ
とりあえずシステムはできた。
あとはどうすればクオリティが上がるか。

23
23
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
23
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?