ライブストリーミング市場を含めて映像配信の分野で近年盛り上がってきているMedia over QUIC Transportプロトコル(以下MoQT)について,MoQTクライアントの実装とライブ配信を試せるWebアプリを作ってみた.
Media over QUIC Transportとは
MoQTは,主にライブストリーミングを対象とした,低遅延ストリーミングプロトコルである.MoQTは単一もしくは複数の配信者(Publisher),リレーサーバー,視聴者(Subscriber)からなるPub/Sub型のアプリケーションを想定している.
MoQTの特長はQUICを利用することによる低遅延の実現やプロトコル自体の柔軟性の高さが挙げられる.それぞれの特長についてはこの後の章で解説する.
データ構造
MoQTにおいてデータはオブジェクトという最小単位でパッケージングされる.1つ以上のオブジェクトの集合がグループであり,グループの集合がトラックと定義される.この分類はMoQTの多様なユースケースに対応するよう設計されている.例えば映像配信において1フレームを1オブジェクトとしてパッケージングした場合(こういうエクストリームなこともMoQTならできる!),1つのIフレームに依存するP/BフレームをグループにまとめることでSubscriberが最新の映像をリクエストした場合は最新のグループを配信する,というような処理が可能になる.言い換えれば,fMP4などでいうGOPの単位をこのGroupという単位に置き換えることでその依存関係を保ちつつ,伝送はオブジェクト単位で行うことができるのである.
MoQTの柔軟性
MoQTの特長として,プロトコルの柔軟性が高いことが挙げられる.MoQTはライブストリーミングをはじめとして,低遅延かつ多様な品質,多様な規模の配信に対応するように設計されている.オブジェクトのペイロードフォーマットは指定されておらず,映像,音声に限らずただのテキストデータやバイナリーデータを入れることもできる.映像配信の場合,当然コーデックの設定,エンコーディングやデコーディングの処理はアプリケーション開発者に委ねられる.
これにより,例えば低帯域での配信が求められる環境ではビットレートを下げ,高品質な配信が求められる環境では高いビットレート,高いフレームレートで配信するといった柔軟な処理が実装できる.また複数の映像品質を複数トラックに分けて配信することで,HLSやDASHでサポートされているアダプティブビットレートストリーミング(ABR)の実装も可能になる.
MoQTで低遅延🏎️
MoQTが低遅延配信を実現できる理由はQUICの多重化ストリームとこの柔軟性の高さにある.RTMPやHLSをはじめとしたストリーミングプロトコルの多くはTCPをベースにしているが,TCPにはHead-of-Line Blocking(HoL Blocking)と呼ばれる問題が存在する.HoL Blockingとは,順序制御がなされた一つのストリームで先頭のパケットが遅延,紛失した場合,受信側が後続のパケットを処理できない問題を指す.TCPはパケットを送信順序で受信し,また信頼性のために欠けているパケットの再送要求を行うが,これによりHoL Blockingが発生する.映像配信サービスにおいてこれはリバッファリングに直結し,ユーザー体験を低下させる原因となっている.
QUICではこれを多重化ストリームにより解決する.多重化ストリームとは,QUICの1つのコネクションの中で複数のストリームを確立できる機能のことである.QUICのストリームはTCPのストリーム同様送信順序通りに受信されるが,ストリーム同士に依存関係はない.つまりあるストリームでパケットの遅延や紛失が発生しても,他のストリームでは通常通りパケットの送受信が行われるのである.これは映像配信においては非常に有益であり,MoQTオブジェクトごとに別のストリームで送信すれば(stream-per-object),ネットワーク混雑時にもより少ない遅延で配信することが可能になる.
多重化ストリームの図 from QUIC-EST: A QUIC-Enabled Scheduling and Transmission Scheme to Maximize VoI with Correlated Data Flows
stream-per-objectを用いることはネットワーク混雑時以外にも効果をもたらす.VODやライブストリーミングにおいて用いられるプロトコルのほとんどは,映像を数秒単位のセグメントに区切って伝送する.この方法では,どんなにセグメント分割を効率的に行おうともその数秒間データを待つ必要が生まれ,その分遅延が発生する.LL-HLSやLL-DASHなど"Low-Latency"を謳うプロトコルが1秒以下の遅延を実現できないのはこれが主な理由である.一方,MoQTでstream-per-objectを用いれば,フレームをいくつも待つ必要はなくなり,バッファリングやセグメント分割の遅延が消滅する.こうなれば遅延の主要因は伝送遅延とエンコード/デコード処理になってくるわけだが,IETFのmoq WGではLow-overhead ContainerなるMoQTでの映像配信に向けたコンテナフォーマットが議論されており,これを用いれば名前の通り少ないオーバーヘッドでエンコード/デコード処理を行うことができる.伝送遅延についても上述のHoL Blockingの解決により他プロトコルに対してかなり優位に立ち,結果的に1秒を大きく下回る遅延での配信が可能になるのである.
作ったもの
ここまでMoQTの良さを延々と述べてきたわけだが,実際に想像通りの動作をするかどうかは作ってみないとわからないものである.
今回はMoQT Publisher/Subscriberを自前実装し,ライブ配信のテストを行った.コンテナフォーマットには先に少し紹介したLow-overhead Containerを使用し,リレーサーバーはMeta社が提供するMoQTサーバーMoxygen(名前のセンスが🙆)を利用した.リレーサーバーをus-west-2リージョンのAmazon EC2に配置し,クライアントについては下の画像のような,Publisher,Subscriberを同時に見れるダッシュボード的なWebアプリを作った.
ソースコードはkota-yata/media-over-quic-experimentにある.
遅延を測ってみる
せっかくプロトコルの実装をしたので,遅延についても計測してみた.今回は遅延を以下の3種類に分類して計測を行った.
- 遅延1: エンコード/パッケージによる遅延
- 遅延2: インターネットを通じた伝送遅延
- 遅延3: デコード/デパッケージによる遅延
計測については10秒ほどライブ配信を回し,その間の全てのフレームについて,WebCodecsがエンコードを開始する瞬間~MoQTのパッケージが終わってWebTransportで送り出す瞬間(遅延1),Publisherでパケットを送り出した瞬間~Subscirberがパケットを受け取った瞬間(遅延2),Subscriberがパケットを受け取った瞬間~WebCodecsでのデコードが終わって描画する瞬間(遅延3)にperformance.now()
関数を仕込んで計測を行った.
また計測時の映像品質はFull HD(1920px*1080px)の60FPS,映像コーデックはH.264でキーフレームは60フレームごと,音声コーデックはOpusである.これらのパラメータを全て自分でいじれるのもMoQTの嬉しいポイントである(例えばWebRTCでこういうことはできない).
計測結果
遅延1については平均12.2ms,遅延2は117.3ms,遅延3は10.3msであった.今回は純粋なエンコード/デコード,パッケージ/デパッケージ処理しか行っておらず,例えばジッター調整のためのバッファーなどは設けていないので遅延1,3はかなり高速になっている.遅延要素を合計しても150msを切るという結果はかなり革命的であり,QUICが普及すれば低遅延の夢がかなり広がるんじゃないかというのがこの結果を受けた感想である.
おわりに
IETF120に参加したときにmoq WGの人たちとInteropをやり,この実装のDraft-04でのテストが成功した.現状プロトコルの実装とアプリの実装が混在してるので,正式な実装として認知されるためにもなるはやで分けたいところである.名前はなんかMoxygenみたいなセンス良いやつにしたいな.