LoginSignup
1
0

More than 3 years have passed since last update.

SkyWay 配信者側で加工した映像を配信する

Last updated at Posted at 2020-08-25

概要

弊社のインターンのお手伝いで、たまたまWebRTCを触る機会があったので、

気軽に加工した映像を配信したりできるのかなと思って試したので記します :pencil:

※ 本来なら、MCU ( Multipoint Control Unit )とかで映像加工するのかなと思うんですが、

今回は、インターン期間中に気軽に実装できそうなものくらいの感覚なので

配信者側(クライアント側)で映像を加工して配信する想定です。

本題

今回は、SkyWay さんのサービスを利用します。(簡単にビデオ通話が実装できます)

結論から言うと、 カメラから取得した映像ストリームをCanvasに描画し、

Canvasで加工したものを再び映像ストリームとして取得したものを配信すれば可能 です。

なので、👇のように、白黒モザイク加工を施した上で、Websocketで受け取ったコメントを映像に載せて配信なんかもできます

簡易版として、チュートリアル 1:1ビデオ通話のサンプルコードを元に、

カメラから取得した映像を 白黒加工して配信 していきます

カメラから映像を取得したら発火する処理に追記してく感じです :pencil:

<script>
let localStream;

navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then( stream => {
  /**
   * ここに映像を加工する処理を書く
   */

  // 着信時に相手にカメラ映像を返せるように、グローバル変数に保存しておく
  localStream = stream;

最初にカメラから取得した映像を流すためのVideoタグに加えて、加工用のCanvasタグを追加しておきます

<canvas id="my-canvas" width="640" height="360"></canvas>
<video id="my-video" width="640" height="360" autoplay></video>

追加したCanvasに Videoに流しているカメラから取得した映像を描画しながら、ピクセル単位で白黒にします

navigator.mediaDevices.getUserMedia(
..
)
.then( stream => {
    const videoElm = document.getElementById('my-video')
    const canvasElm = document.getElementById('my-canvas')
    const ctx = canvasElm.getContext('2d'); // CanvasRenderingContext2D インスタンス

    setInterval(() => {
        ctx.drawImage(videoElm, 0, 0, canvasElm.width, canvasElm.height); // Videoに描画されている内容をCanvasにも描画する
        var frame = ctx.getImageData(0, 0, canvasElm.width, canvasElm.height); // ImageData インスタンス
        var pixels = frame.data.length / 4; // ピクセルサイズ長 (今回の場合 640 x 360)

        for (var i = 0; i < pixels; i++) {
            const grey = (frame.data[i * 4 + 0] + frame.data[i * 4 + 1] + frame.data[i * 4 + 2]) / 3;

            frame.data[i * 4 + 0] = grey // Red
            frame.data[i * 4 + 1] = grey // Green
            frame.data[i * 4 + 2] = grey // Blue
        }
        ctx.putImageData(frame, 0, 0);
    }, 10000/120);

    videoElm.srcObject = stream;
    videoElm.play();

    // Canvasから映像ストリームを取得する
    localStream = canvasElm.captureStream(120); // 120fps

順に見ていきます

カメラから映像を取得できたら、Canvasから CanvasRenderingContext2D という2次元のレンダリングコンテキストを取得してきます。

これに手を加えていくことでCanvasを加工していくことが可能です。

    const ctx = canvasElm.getContext('2d');

次に、 setInterval で定期的に、さきほどの ctx に対して加工を加えていきます

(今回の場合、120fps で加工を加えていきます)

    setInterval(() => {
        /**
         * ここに ctx に対して加工を加えていく処理を書く
         */
    }, 10000/120);

setInterval 内での処理に入り、 最初に Videoに描画されている内容をCanvasにも描画します

    ctx.drawImage(videoElm, 0, 0, canvasElm.width, canvasElm.height);

Videoの映像が描画された ctx を加工していきたいので、 getImageData を実行し ImageData を取得してきます

ImageDatadata を実行すると、 1ピクセルごとのRGBAの値が格納された一次元配列(正確には Uint8ClampedArray)を取得できます。
RGBA: 赤・緑・青・アルファの値が 0 ~ 255 で格納されてます

なので配列の中身としては以下のようなものになります

[
  1ピクセル目の赤, 1ピクセル目の緑, 1ピクセル目の青, 1ピクセル目のアルファ,
  2ピクセル目の赤, 2ピクセル目の緑, 2ピクセル目の青, 2ピクセル目のアルファ,
  ..
]

4 で割ってCanvasのピクセルサイズ長を算出しているのはそのためです

        var frame = ctx.getImageData(0, 0, canvasElm.width, canvasElm.height);
        var pixels = frame.data.length / 4;

後は、ピクセルサイズ長だけ繰り返しで、RGBの平均値を算出し、値を代入していきます

        for (var i = 0; i < pixels; i++) {
            const grey = (frame.data[i * 4 + 0] + frame.data[i * 4 + 1] + frame.data[i * 4 + 2]) / 3;

            frame.data[i * 4 + 0] = grey // Red
            frame.data[i * 4 + 1] = grey // Green
            frame.data[i * 4 + 2] = grey // Blue
        }

setInterval の最後に ctxputImageData を実行すれば、Canvasに白黒が反映されます

        ctx.putImageData(frame, 0, 0);

加工したCanvasから映像ストリームを取得するには captureStream を利用します

(setInterval に合わせて 120fps にしてあります)

    localStream = canvasElm.captureStream(120);

これで、加工した映像ストリームを取得できたので、カメラから取得した映像ストリームをP2P通信に流すのと同じようにすればできます。

    const mediaConnection = peer.call(theirID, localStream);

  

最後に、今回はSkyWayを利用して、さくっとクライアント側で映像を加工して配信する方法を試してみました。

おそらく本来はこういう感じにはしないのでは(?)と思ったりしているので、詳しい方がいらっしゃれば教えていただきたいところです :bow_tone1:

  

あと、 setInterval を利用しているので、ChromeやFirefoxなどのブラウザを利用していて、別タブに移動したりすると、非アクティブと認識され、映像がカクカクするようになるので注意です :rotating_light:

MDNの setInterval にも以下のような記載があります

Starting in Gecko 5.0 (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2), intervals are clamped to fire no more often than once per second in inactive tabs.

1
0
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
1
0