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

対象読者

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

ImageFlux Live Streamingのご紹介

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

執筆
テリー (秋月 徹)

テリー (秋月 徹)

2002年よりケータイ向けライブ配信システムの開発・運用・販売を行い、プロ野球中継、音楽ライブコンサート、公営ギャンブル等の大規模ライブ配信システムの構築を担当。趣味のハッカソンでは得意の動画処理、映像処理を使ったサービスを短時間で考案・開発し、多数の優勝経験を持つ。

2024年2月公開