WebCodecs と Insertable Streams for MediaStreamTrack API で超低遅延プレイヤーを作った話

## はじめに

Chrome M94 から WebCodecs が登場しました。
これにより、ブラウザで h264, aac などのデコードが出来るようになりました。
また、Insertable Streams for MediaStreamTrack API も同時に登場しました。

なら、これを組み合わせれば、WebRTC より融通の利く超低遅延プレイヤーが出来るのでは???

というわけで、WebCodecs と  Insertable Streams for MediaStreamTrack API を使って
mpegts をそのまま再生する超低遅延プレイヤーを作ってみました。

## 前提: 既存の低遅延配信

まず、既存のブラウザ上での低遅延配信について説明します。
既存のブラウザ上でのデファクトの低遅延配信は以下の4つが挙げられるかと思います。

* CMAF-ULL
* Apple LL-HLS
* HTTP Streaming (or WebSocket)
* WebRTC

それぞれの特徴については以下の通りになります。

### CMAF-ULL (LHLS/DASH-ULL)

遅延 1 秒程度と遅いが、いわゆる低遅延配信のデファクトみたいな存在です。
ただ、Apple LL-HLS が出来てから LHLS は少し陰りがあること [^lhls]、MPEG-DASH が苦手なので今回はパスします。[^mpeg-dash]

[^lhls]: LL-HLS はオリジンサーバを立てる必要があるのが難点ですが、 LL-HLS をサポートしだしているような...
[^mpeg-dash]: マルチピリオドを使いたい以外の場合に MPEG-DASH を利用する事ってあるんでしょうか...?

### Apple LL-HLS

LL-HLS は遅延 1 秒程度で遅延がけっこうありますが、Apple標準の低遅延配信の方式です。

これは実際に実験してみた結果なのですが、
1フレーム単位でパーシャルセグメントを切っても 1 秒バッファリングしてしまいました。
なので、1秒くらいの低遅延を目指した方式であると考えられます。

仕様が載っているので、もう既に試してらっしゃる方が殆どだとは思いますが、
一応ながら自作して挙動を検証した際のソースコードを貼っておきます。

https://github.com/monyone/node-llhls-origin-example

### HTTP-Streaming

#### MSE を利用するタイプ

MSE を利用する低遅延プレイヤーとしては、flv.js や mpegts.js [^mpegts.js] などが有名かと思われます。
ただ、MSE 自体で 0.5 秒ほどバッファするため超低遅延にはならないようです。

https://github.com/xqq/mpegts.js

[^mpegts.js]: 一応ですが、mpegts.js の ts demuxer のコントリビュータなので紹介しています。

#### Canvas と WebAudio を利用するタイプ

Canvas を利用するタイプでは、バッファリングを JS 上で管理するため超低遅延になります。
ただ、デコードも JS 上で行う事が多く、超低遅延という要件は満たします。
ただし、CPU使用率が高いなどの欠点があります。[^canvas]
また、WebAudio はユーザ操作が必要なため、自動再生とは相性が悪いという問題もあります。

様々なプラットフォームでWebRTC以外で超低遅延をする場合にはこの選択かと思われます。

[^canvas]: WebCodecs の登場でデコードの負荷は問題にならなくなったので、復権もあり得るとは思います。

### WebRTC

WebRTC は遅延 0.1 ~ 0.2 程度と納得の超低遅延で P2P での送信ができます。
ただ、個人的には P2P に寄っているが目立つため、簡素化できないかなと思いました。

個人的に課題だなと思う点は以下になります。

* そもそも固定の動画を送るための規格ではなく、ベストエフォートの通信をすること
* コンテナを含めて動画を送れないため、メタデータを送る際に工夫が必要なこと
* 映像/音声などを再エンコードしてしまう事

こちらも、遅延などの課題を実際に試した際のソースコードを貼っておきます。

https://github.com/monyone/webrtc-node-sender-experimental

## 本題: WebCodecs と Insertable Streams for MediaStreamTrack API

WebRTC の超低遅延と、HTTP-Streaming のような自由さがあれば、
自由な映像ソースを使って、RTPの縛りなしで超低遅延プレイヤーが実現できます。

このミッシングピースが WebCodecs と MediaStreamTrackGenerator により解消しました。

つまるところ、以下が可能になったという事です。

* WebCodecs よりブラウザ内蔵のデコーダで VideoFrame, AudioData へデコードが可能になった
* MediaStreamTrackGenerator により、 VideoFrame, AudioData を直接 video 要素へ出力できるようになった

WebRTC と HTTP-Streaming のいい所取りをして、プレイヤーを作れるようになりました。

## 実装: zlplayer

実際に作ってみたのが、以下のリポジトリになります。[^zlplayer]

https://github.com/monyone/zlplayer

LAN内で遅延 0.1 秒未満で video 要素にリップシンクを合わせた動画をコンテナごと再生できます。
まだまだ開発中なので、フィードバックなどお待ちしております。

[^zlplayer]: zero latency の略にしたつもりですが、結構被っていてショックでした...

## 余談: 苦労した点

### リップシンクとスムージング

リップシンクを合わせるためには、映像と音声を合わせるためのバッファリングが必要です。
また、受信毎に表示するとジッタ揺れで映像がなめらかに映らないという事が起こります。
このため、動画の表示タイミングをタイミングに合わせて調整する必要があります。

今回は、音声をベースに、音声の再生位置に合わせて動画を表示するようにしました。

ただし、MediaStreamTrack を使った video 要素では音声のバッファ量、再生位置は取れないため
バッファ量を都度計算し、経過時刻からストールした時間を引くことで、音声の再生位置を割り出しています。

## まとめ

WebCodecs と Breakout Box (長いので略しました) で超低遅延プレイヤーを実装してみました。
今まで、さまざまな苦労の元実現していた超低遅延がようやっと身近になった気がします。

皆様もぜひ自作のプレイヤーを作って、超低遅延の再生をお楽しみいただければと思います。

## あとがき

Chrome M97 から WebTransport が GA されました。今回は HTTP-Streaming を使っていますが、
WebTransport を使ってよりロバストな環境で使えるプレイヤーを組んでみたいものです。

すでに、SRT over QUIC を chrome で受けられる環境が整ったというわけですので
超低遅延プレビューなどの用途に活用があるのではないかと思います。