WebRTCを使ってみた

http://ando-yasushi.appspot.com/wavycamera/index.html

WebRTCを使うとJavaScriptでカメラやマイクを扱えるということで、ちょっと試してみてることがあったんだけど、いくらやってもなかなかうまい感じに動かないので気分転換に他のもの作ってみた。わりとさくっと。

どこかで見た動画が元ネタなんだけど見つからず・・・。

WebRTCはまだChrome開発版くらいでしか使えず、しかも chrome://flags でMediaStreamを有効にする必要があるから、いま作っても見られる人は少ないんだろうけど、JavaScriptでリアルタイムに映像を処理できるってやっぱり夢が広がるねぇ。

・・・

使い方

Chrome Canaryをダウンロード

ウェブ系の開発者ならChrome Canaryくらいインストール済みのはずなんだけど、まぁ一応。

http://tools.google.com/dlpage/chromesxs

で、インストール

MediaStreamをenable

WebRTCを使うために chrome://flags を開いてMediaStreamを有効に。

で、Chrome Canaryを再起動。

試す

http://ando-yasushi.appspot.com/wavycamera/index.html

マシンパワーに結構依存するかも。WebWorkerとか使って重そうな処理は裏に回すべきなのかな・・・。

なにはともあれEnjoy!

・・・

作り方

リファクタリング前で恥ずかしいけども。

カメラ入力を読み取り

navigator.webkitGetUserMedia関数の第一引数に 'video'*1 を指定して、第二・第三引数に成功時・失敗時のハンドラをそれぞれ与える。

navigator.webkitGetUserMedia(
  'video',
  function(s) {self.successHandler(s)},
  function(e) {self.errorHandler(e)}
);
カメラからの入力を表示

webkitGetUserMedia成功時のハンドラ内でカメラからの入力をvideoタグのsrcに設定。

successHandler:function(stream) {
  this.videoElm.src = window.webkitURL.createObjectURL(stream);
  this.step();
},
ぐにゃぐにゃにする

video要素の表示内容を一時的なcanvasに書きだして表示内容を分割、ImageDataの配列として(デフォルトで)150フレーム分を保持

step:function() {
  var self = this;

  var context = document.getElementById('temporaryCanvas').getContext('2d');
  context.drawImage(this.videoElm, 0, 0);
  var stripes = [];
  for (var i = 0; i < this.frameCount; i++) {
    var dy = this.height / this.frameCount;
    stripes.push(context.getImageData(0, dy * i, this.width, dy));
  }
  this.frames[this.initialFrame] = stripes;
  this.initialFrame = (this.initialFrame + 1) % this.frameCount;

場所ごとに違うフレームのImageData取り出して、表示用のCanvasに貼りつけ。

  for (var i = 0; i < this.frameCount; i++) {
    var frame = this.frames[(this.initialFrame + i) % this.frameCount];
    if (frame && frame[i]) this.targetContext.putImageData(frame[i], 0, this.height / this.frameCount * i);
  }
  setTimeout(function() {self.step()}, this.interval);
}
完成


お察しのとおりこの画像けっこう気に入ってる。

全部
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Wavy Camera</title>
  <style>
  body {
    background-color:black;
  }
  video {
    display:none;
  }
  #canvas {
    position:absolute;
    top:50%;
    left:50%;
    margin-top:-150px;
    margin-left:-200px;
  }
  .temporary {
    display:none;
  }
  </style>
  <script>
  var DelayedFrame = function(opt) {
    if (!opt) opt = {};
    this.initialFrame = 0;
    this.frameCount = opt.frameCount || 150;
    this.width = opt.width || 400;
    this.height = opt.height || 300;
    this.frames = [];
    this.videoId = opt.videoId;
    this.interval = opt.interval || 1;
  };
  DelayedFrame.start = function(opt) {
    if (navigator.webkitGetUserMedia) {
      window.addEventListener('load', function() {
        new DelayedFrame(opt).start();
      });
    }
    else {
      alert('This site rquires WebRTC. Please get Chrome Canary and set MediaStream available.');
    }
  };
  DelayedFrame.prototype = {
    start: function() {
      if (this.videoId) {
        this.videoElm = document.getElementById(this.videoId);
      }
      else {
        this.videoElm = document.getElementsByTagName('video')[0];
      }
      this.targetContext = document.getElementById('canvas').getContext('2d');

      var self = this;
      navigator.webkitGetUserMedia(
        'video',
        function(s) {self.successHandler(s)},
        function(e) {self.errorHandler(e)}
      );
    },
    successHandler:function(stream) {
      //console.log(stream);
      this.videoElm.src = window.webkitURL.createObjectURL(stream);
      this.step();
    },
    errorHandler:function(error) {
      console.log(error);
    },
    step:function() {
      var self = this;

      var context = document.getElementById('temporaryCanvas').getContext('2d');
      context.drawImage(this.videoElm, 0, 0);
      var stripes = [];
      for (var i = 0; i < this.frameCount; i++) {
        var dy = this.height / this.frameCount;
        stripes.push(context.getImageData(0, dy * i, this.width, dy));
      }

      this.frames[this.initialFrame] = stripes;
      this.initialFrame = (this.initialFrame + 1) % this.frameCount;
      for (var i = 0; i < this.frameCount; i++) {
        var frame = this.frames[(this.initialFrame + i) % this.frameCount];
        if (frame && frame[i]) this.targetContext.putImageData(frame[i], 0, this.height / this.frameCount * i);
      }
      //window.webkitRequestAnimationFrame(function() {self.step()});
      setTimeout(function() {self.step()}, this.interval);
    }
  };

  DelayedFrame.start();
  </script>
</head>
<body>
<video id="video" width="400" height="300" autoplay></video>
<canvas id="canvas" width="400" height="300"></canvas>
<canvas id="temporaryCanvas" width="400" height="300" style="display:none;"></canvas>
</body>
</html>

*1:文字列で指定するのはイケテないと思う・・・