8
11

More than 3 years have passed since last update.

WebCodecs + WebRTC Insertable Streams で Scalable live w/ P2P を実現してみた

Posted at

これは何?

前回記事 の続き。これまでは、現実的ではなかった WebRTC P2P での live 配信。WebCodecs と WebRTC Insertable Streams 使ったら「お、これは使えるかも!?」だったので、それの紹介記事。

WebCodecs + WebRTC Insertable Streams 使った Scalable live w/ P2P のアーキテクチャ説明

まず、前回を振り返ると、WebRTC Insertable Streams を使い、以下のアーキテクチャで動作させてみました。

image.png

要は、P2Pで対抗ごとにまともにエンコーダ回すと、その処理で CPU なりが厳しくなっちゃうんで、2本目以降はダミー映像エンコードにして、 Insertable Streams で 1本目のまともにエンコードした映像データに差し替える・・・・というもの。

詳細は、前回記事を参照いただければですが、こちらの問題は、「差し替えるオリジナルのデコード済み映像データの keyframe 制御ができないため、受信側でまともに映像がみれない」というものでした。結局エンコーダは、WebRTCまかせでアンタッチャブルなんでなんともできない(涙

そこで、今回は下図に示す WebCodecs を用いたアーキテクチャに変更しました。

image.png

一番の違いは、ソースの映像を WebCodecs を使って、コントローラブルにエンコード可能としたこと。なので、WebRTCにはすべてダミー映像 (videoの track.enabled = false に設定したストリームオブジェクト) をくわせています。

そんで、 Insertable Streams で、ダミー映像をエンコードしたものが毎フレームごとに取得できるので、それを WebCodecs でエンコードしたものに差し替えるという戦略です。考え方としては、非常にシンプル。

エンコードの際、映像データの場合、 keyframe 制御が非常に重要になります。ここで、WebRTCでは、新規に視聴参加者が来たり、映像データのロスが検出されると、同図中の「エンコーダ by WebRTC」にその情報が渡され、keyframe としてエンコードがなされます(あくまで、ダミー映像ですが)。すると、 Insertable Streams ではこのとき chunk.type の値が key となるため

if( chunk.type === "key" ) {
 // WebCodecs に keyframe 要求を送る
}

と、そのプロパティをチェックし、 WebCodecs の keyframe 制御を行うことで、いい感じで映像配信ができる・・・という形です。

コードの説明

ソースコード全体は https://github.com/kokutele/kokutele-live で公開しています。
以下では、あくまで、ポイントだけ。動作原理は、先に示したとおりとなっており、詳細はスニペット中のコメントを参照ください。

送信側

まず、 WebCodecs によるエンコードの部分を抜粋したのが以下のコード

  start():void {
    // MediaStream オブジェクトから Video Track を取り出す
    const [track]:Array<MediaStreamTrack> = this._stream.getVideoTracks()

    // WebCodecs の定義。エンコードが行われる都度、 'chunk' イベントを発生する。
    const videoEncoder = new window.VideoEncoder({
      output: chunk => {
        this.emit('chunk', chunk)
      },
      error: err => {
        this.emit('error', err)
      }
    })
    videoEncoder.configure({
      codec: 'vp8',
      width: 640,
      height: 480,
      framerate: 30
    })

    // 映像トラックの読み込みを開始し、逐次 `encode()` によりエンコード処理を
    // 行う
    const videoReader = new window.VideoTrackReader(track)

    let idx = 0
    const interval = 10 * 30 // 10 sec
    videoReader.start( frame => {
      // keyframe 制御の部分。10秒に一回の periodical 制御とともに、
      // プロパティ `_reqKeyFrame` が `true` に設定されたときも、
      // keyframe 生成を行う
      const _reqKeyFrame = this._reqKeyFrame || !(idx++ % interval)
      videoEncoder.encode(frame, {keyFrame: _reqKeyFrame})
      this._reqKeyFrame = false
    })
  }

これを、 Insertable Streams でダミー映像のものと差し替えます。

      transform: (chunk, controller) => {
        const kind = chunk instanceof window.RTCEncodedVideoFrame ? 'video' : 'audio'
        // ダミー映像の chunk.type が `key` だったら、WebCodecsで、keyframe 生成
        if( kind === "video" && chunk.type === "key") {
          this._encoder.reqKeyFrame = true
        }

     // エンコードデータの差し替え処理
        if( kind === "video") {
          // replace media data
          const _chunks = chunksMap.get(this._receiver)
          const _chunk = (_chunks && _chunks.length > 0) && _chunks[idx++]

          if( _chunk ) {
            if( typeof _chunk === 'object' && _chunk.data ) {
              chunk.data = _chunk.data
            }
          } 
          controller.enqueue( chunk )
        } else if( kind==="audio" ) {
          controller.enqueue( chunk )
        }
      });

受信側

普通に、WebRTCで受信したものを処理するだけで、Insertable Streams や WebCodecs 的な特別な処理は不要です。

パフォーマンステスト

image.png

後述のサンプル Web app を使い、簡易的なパフォーマンステストを行いました。結果が上図の通り。同一端末内で複数ブラウザを立ち上げ、一つが送信側、それ以外が受信側・・・という簡易なもの。あくまで、参考として御覧ください。

同時視聴数の増加に伴い、送信側 Chrome の CPU 使用率があがっていきますが、通常のP2P配信(赤線)と、今回のスケーラブル配信(青色)とで明らかにパフォーマンスメリットが得られています。

ここで、同時接続 6 で通常の P2P 配信で CPU 使用率が下がっていますが、こちら、CPU処理的に厳しくなったためか、送信エンコードのレートを下げているためで、あくまで目視ですが 1.6Mbps -> 600kbps 程度に下げていました。なので、通常の P2P 配信だと配信限界は 5 ぐらいかな・・・ということになります(さすがに、この数では使えない)

一方、WebCodecsの場合は、通常時で 1Mbps の値となっていたのですが、こちら全く変わらず。データとしてきちんととっていないですが、20弱に試しにあげても、問題なく配信できていました。たぶん、もっと行くんじゃないかな。100のオーダーは厳しそうだけど(端末性能に依存するところですが)

サンプル Web app

で、スケーラブル P2P live 配信試せます。配信側では

にアクセスいただき、

image.png

"start sender" をクリックすると、下部のほうに視聴側のURLが払い出されるため、こちらにアクセスすることでリモート視聴できます。

予想に難くなく、 "enable scalable" のチェックを外せば、通常 P2P 配信モードになります(Chrome M87以降で、 chrome://flags/#enable-experimental-web-platform-features の enable を忘れずに)

終わりに

WebCodecs + WebRTC Insertable Streams で、「完全サーバレス」で小規模ライブ配信が可能であることを示しました。仲間内とか、社内とかだと数十ぐらいの配信で十分だったりするケース結構多いので、割と使い勝手あるのかな?と思います(かなり荒く作ってるんで、もーちっとコードの改善必要ですがww

こちらのサンプルApp、「単なる配信以上の何か」も入っているため、謎のモノクロ映像も表示されていますが、とりあえず今は気にしないでくださいw(この辺については、近日中に僕の個人ブログで記事を書く予定)

8
11
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
8
11