【サーバ運用担当者必見!】WebRTCの使用帯域調査方法

こんにちは、テリーです。前記事の続きです。ビデオ会議システムを構築する場合にもっともコストに影響を与えるのが、サーバ側のインターネット回線の通信使用料です。ユーザーとしては画質はきれいでなめらかであるほどよいことが多いですが、サーバ運用担当者としては、従量制で課金されることの多い通信データの量を落としてコストをギリギリまで下げたい、しかしユーザー満足度も高止まりさせたい、と日々思い悩んでいることでしょう。プログラムで設定した値どおり、またはそれ以下のビットレートになっているか、確認したいと思ったことはありませんか? 今回はビデオ会議における通信速度の確認方法について紹介します。

対象読者

  • WebRTC SFU Soraを使用している人
  • WebRTC SFU Soraの使用を検討している人
  • WebRTC関連アプリケーションを開発している人

動作確認環境

本記事は以下の環境にて動作を確認しています。

  • Sora 2022.1.1
  • Windows 11 Home 21H2
  • Chrome 106.0.5249.119

WebRTC getStats関数の使い方

WebRTCのpeerConnectionクラスにはgetStats関数があり、WebRTCに関連するたくさんの情報が手に入ります。接続が完了した後にgetStatsを定期的に読むことで、リアルタイムの情報を取得、表示することができます。読み出しの間隔があまりに短いと読みにくいので3秒に1回あたりがオススメです。

getStatsを使ったサンプルプログラムを以下に示します。getRTCStats関数は後述する自作の関数です。第一引数にgetStatsの戻り値(Promise<RTCStatsReport>型)を渡し、第二引数に情報を表示するhtmlのselectorを指定します。

await sendrecv.connect(stream);
setInterval(() => {
    getRTCStats(sendrecv.pc.getStats(), "#info");
}, 3000);

以下は自作関数getRTCStatsの前半です。最初に第一引数statsObjectをawaitでRTCStatsReport型の値を取得します。RTCStatsReportは辞書のような型になっており、たくさんのデータが含まれています。これらをforEach関数でループして読んでいきます。typeプロパティがデータの種類を表します。状態表示でよく使うのは"inbound-rtp"、"outbound-rtp"、"stream" です。要素の順番は保証されていないので、複数の要素が絡む場合には、forEachでいったん別の変数に代入します。kindプロパティは"video"、"audio"の値が入っており、映像関連の情報か音声関連の情報かを区別できます。

async function getRTCStats(statsObject, selector) {
  let inboundRTPVideoStats = [];
  let streamStats = [];
  let outboundRTPVideoStat = undefined;
  (await statsObject).forEach((stat) => {
    if (stat.type == "inbound-rtp" && stat.kind == "video") {
      inboundRTPVideoStats.push(stat);
    }
    else if (stat.type == "stream") {
      streamStats.push(stat);
    }
    else if (stat.type == "outbound-rtp" && stat.kind == "video") {
      outboundRTPVideoStat = stat;
    }
  });
  ...(後述)

アップロード情報の取得

自分のパソコンからサーバにデータを送ることをアップロードと言いますが、WebRTCではOutboundという表現をします。Soraの場合、1接続で1ビデオ、1音声までと決まっています。上述のoutboundRTPVideoStat変数から値を取得し、画面に表示するテキストを求めます。outboundRTPVideoStatのプロパティでよく使うのは frameWidth、frameHeight、framesPerSecond、bytesSent、bytesReceivedです。bitrateを算出する関数getBitrateについては後述します。

  if(outboundRTPVideoStat) {
    const s = outboundRTPVideoStat;
    let t = "framesize: " + s.frameWidth + "x" + s.frameHeight;
    t += "\n" + "fps: " + s.framesPerSecond;
    t += "\n" + "bitrate: " + getBitrate(s)[0];
    t += "\n";
    document.querySelector(selector).innerText = t;
  }

ダウンロード情報の取得

サーバから自分のパソコンにデータを受け取ることをダウンロードと言いますが、WebRTCではInboundという表現をします。ビデオ会議では複数の映像や音声が届きますので、配列変数inboundRTPVideoStatsに格納し、その後整理して表示します。inboundRTPVideoStatsのプロパティでよく使うのは、outboundRTPVideoStatと同様に、 frameWidth、frameHeight、framesPerSecond、bytesSent、bytesReceivedです。bitrateを算出する関数getBitrateについては後述します。

Inbound情報の画面表示では、配列のデータを区別して、画面のどのDOMエレメントに表示するかを算出する処理が必要です。接続の受信時にあらかじめDOMエレメントのIDを割り当てておくと簡単です。下記の例では、ビデオトラックを含むメディアストリームを求め、そのストリームIDを接続IDとしています(プログラムの6行目)。DOMエレメントのIDの命名規則は読者様のWebアプリケーションによって読み替えてください。

  inboundRTPVideoStats.forEach(s => {
    let t = "framesize: " + s.frameWidth + "x" + s.frameHeight;
    t += "\n" + "fps: " + s.framesPerSecond;
    t += "\n" + "bitrate: " + getBitrate(s)[1];
    t += "\n";
    const connection_id = streamStats.find(st=>st.trackIds.some(tid=>tid==s.trackId)).streamIdentifier;
    const e = document.querySelector("#" + 'remotevideo-info-' + connection_id);
    if(e) e.innerText = t;
  });

ビットレートの算出

上述のコードから呼ばれる、ビットレートを算出する自作関数getBitrateです。直近のビットレートを算出する関数はWebRTC標準関数の中には存在しません。getStats関数で取得できる値は累計のデータバイト数です。Inboundの場合はbytesReceivedプロパティ、Outboundの場合はbytesSentプロパティに、累計のデータバイト数が入っています。そこで、本関数を呼び出しごと(3秒ごと)に前回(3秒前)のデータとの差を求め、インターバル間隔の3(秒)で割り、さらに8をかけることで、直近の平均ビットレートが求まります。送信、受信ともに計算工程が同じなため、同時に計算しています。戻り値は要素2つの配列で、1つ目が送信ビットレート、2つ目が受信ビットレートです。

function getBitrate(stat) {
  const bytesSent = stat.bytesSent ?? 0;
  const bytesReceived = stat.bytesReceived ?? 0;
  window._bytesSents ??= {};
  window._bytesReceiveds ??= {};
  const bitrateSent = parseInt(((bytesSent - (window._bytesSents[stat.id] ?? 0)) * 8) / 3);
  const bitrateReceived = parseInt(((bytesReceived - (window._bytesReceiveds[stat.id] ?? 0)) * 8) / 3);
  window._bytesSents[stat.id] = bytesSent;
  window._bytesReceiveds[stat.id] = bytesReceived;
  return [bitrateSent, bitrateReceived];
}

サンプル

期待通り動作していることを確認するために、解像度、フレームレート、ビットレートをリアルタイムに表示するサンプルを作成しました。顔映像を100kbps、画面共有映像を3Mbpsとして接続しました。

画面上部の手が映っているのが顔映像。bitrateが100kbps程度になっている。画面下部の画面共有映像はbitrateが2.6Mbps程度出ている

まとめ

WebRTCのgetStats関数を使って、リアルタイムのビデオビットレートを画面に表示する方法を紹介しました。リアルタイムビットレートを表示しながら検証すると、画質はもう少し下げられるかも? といった主観的な判断が可能になります。プログラムミス、設定ミス等で、開発者の意図と違うビットレートが出ていることを発見することもできます。ぜひ挑戦してみてください。

有料サービスのご紹介

WebRTCを利用した会員制ライブ配信サービスを短期間で導入したい方、大規模配信のためのサーバ構築・インフラ運用を専門家に任せたい方は「ImageFlux Live Streaming」をご検討下さい。ポスプロ会社様向けに動画編集のコラボレーションシステムとして利用された実績があります。WebRTC SFUという技術を用いて、配信映像をサーバに中継させる仕組みです。1対多の会員制映像配信に特に強みがあり、初期の利用コストが低価格に抑えられます。