Media over QUIC Transport (MOQT)のCommon Catalog Formatについて (WG版)
はじめに
IETFに提出されているCommon Catalog Format for moq-transportというドラフトについて紹介します。
Catalogの理解を深めることで、MoQ Transport(MOQT)上でのメディアの発見方法や色々なメディアコンテナフォーマットのMOQT上での併用方法が分かると思います。
この記事は以前のCatalogの記事の更新版になっています。
TL;DR
- 別々に定義されていたCatalogが共通化
- 複数メディアコンテナフォーマットの同時利用が可能に
- Catalogの差分表現はJSON PATCH[1]
- 差分表現の利用は明示的な宣言が必要
- その他の利便性もアップ
- Warpはこのドラフトを参照
draft-wilaw-moq-catalogformat-01版との主な差分
- Catalog Fieldsから以下が削除
- Sequence
- Parent sequence number
- Track Operations
- Catalog Fieldsに以下が追加
- SupportsDeltaUpdates
- Common track fields
- ここに共通で使われるフィールドが入る
- namespaceが無い場合はcatalogのものを継承するように
- Catalog Delta UpdatesがCatalog Patchになり、形式もJSON PATCH[1:1]に変更
- 差分表現の利用は明示的な宣言が必要
Catalog
Catalog
は名前の通りカタログであり、MOQT
上で配信されているコンテンツ(Track)を購読するために必要な情報が載ったJSONです。
MOQTについて簡単に説明すると、QUIC
を用いてメディアを送受信するためのプロトコルで、それを可能にするためにクライアント・サーバ間で送り合うメッセージについて定めています。
メディアはTrack Namespace
とTrack Name
の組からなるFull Track Name
によって識別されます。
MOQTやそのモデルについては@flano_yukiさんのMOQTについてのブログ記事が詳しいです。
Catalogは特別なMOQTのTrackであり"Catalog"
というTrack Nameを持ちます。
配信側は配信しているTrackの宣伝のためにCatalogを使用し、購読側はTrackの購読のために使用します。そのため、メディアを受信するにはまずCatalogを取得する必要があります。
下図のようにCatalogの中身はRelayサーバでは不透明として扱われるため、中身をE2E暗号化(E2EE)可能です。
MoQ Objectが受け取られるまでの流れ
このドラフトではversion 1のCatalogを定義しています。
将来のバージョンでの後方互換性は保証されないため、より新しいバージョンが定義された場合は注意が必要です。特に購読側では理解できないバージョンのパースが禁止されているため、使っている仕様の部分に変更があるかどうかに関わらず、バージョンを上げると互換性が失われます。
Catalogの例
Catalogには31種類ものフィールドが定義されている[2]ため、例と共に一部を説明します。
以下は各フィールドに共通したルールです。
- フィールド毎に必須または任意の項目として定義
- フィールド毎に宣言可能な位置が異なる
- root[3]、track内、Common Track Fields内、catalog内、selectionParams内のうち1箇所以上で宣言可能
- Common Track Fields内で宣言されたフィールドは子要素に継承
- 子要素でも宣言されている場合はそちらが優先
- フィールドは順不同
- 理解できないフィールドの無視が必須
LOCの1組のaudio/video trackの例
1組の映像と音声という基本的なパターンです。
{
"version": 1,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"commonTrackFields": {
"namespace": "conference.example.com/conference123/alice",
"packaging": "loc",
"renderGroup": 1
},
"tracks": [
{
"name": "video",
"selectionParams":{"codec":"av01.0.08M.10.0.110.09","width":1920,
"height":1080,"framerate":30,"bitrate":1500000}
},
{
"name": "audio",
"selectionParams":{"codec":"opus","samplerate":48000,"channelConfig":"2",
"bitrate":32000}
}
]
}
version
は前述のCatalog versionを示していて、このドラフトでは常に1です。
streamingFormat
はStreaming format type
を表す数値です。まだ値が定まっていないのでこの例では適当な値を入れていると思われます。[4]
streamingFormatVersion
はStreaming formatのバージョンを表す文字列で、streaming format側で定義します。こちらも同様に適当な値です。
commonTrackFields
はTrack Fieldsの集合を持つオブジェクトで、それらは全てのtrackに継承されます。ただし、Trackで値が定義されている場合はそちらが優先されます。
namespace
はTrack namespace
を表しており、各trackのtrack nameがこの名前空間内で定義されます。例えばvideoの場合、Full Track Nameはconference.example.com/conference123/alice/video
となります。
このフィールドは任意であり、その場合はcatalogのnamespaceを継承します。
packaging
はペイロードのカプセル化方法を示していて、このドラフトでは"cmaf"
/"loc"
のいずれかの値をとります。[5]
renderGroup
は同時に描写されるべきtrackのグループを示しています。この例のようにvideoとaudioを同時に描写するよう指示するのが一般的です。
tracks
はTrack objectの配列です。後述のcatalogs
とは排他的な関係でどちらか片方しか1つのCatalog内には存在できません。
name
はTrack name
を定義しています。track nameはtrackの識別に使われ、名前空間内でユニークであることが必須です。
selectionParams
は購読時にtrackを選択するのに利用可能なkey-valueを保持するオブジェクトです。このフィールド自体は任意ですが、存在する場合は空オブジェクト(selectionParams: {}
)にするのは禁止です。
CMAFの1組のaudio/video trackの例
CMAFの初期化セグメントをBase64で自身に含んでいるパターンです。
{
"version": 1,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"commonTrackFields": {
"namespace": "sports.example.com/games/08-08-23/12345",
"packaging": "cmaf",
"renderGroup":1
},
"tracks": [
{
"name": "video_1080",
"selectionParams":{"codec":"avc1.640028","mimeType":"video/mp4",
"width":1920,"height":1080,"framerate":30,"bitrate":9914554},
"initData":"AAAAGG...BAAAx"
},
{
"name": "audio_aac",
"selectionParams":{"codec":"mp4a.40.5","mimeType":"audio/mp4",
"samplerate":48000,"channelConfig":"2","bitrate":67071},
"initData":"AAAAGG...EAADE="
}
]
}
initData
はInitialization data
のことでtrackの初期化データをBase64で持つ文字列です。Catalogに文字列で入っているため、そのtrackを必要としない場合(例えば音声だけ必要)でも初期化データをダウンロードしてしまいます。
以下はその問題を解消したinitTrack
を用いたパターンです。
{
"version": 1,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"supportsDeltaUpdates": true,
"commonTrackFields": {
"namespace": "sports.example.com/games/08-08-23/12345",
"packaging": "cmaf",
"renderGroup":1
},
"tracks": [
{
"name": "video_4k",
"selectionParams":{"codec":"avc1.640033","mimeType":"video/mp4",
"width":3840,"height":2160,"framerate":30,"bitrate":14931538},
"initTrack":"init_video_4k",
"altGroup": 1
},
{
"name": "video_1080",
"selectionParams":{"codec":"avc1.640028","mimeType":"video/mp4",
"width":1920,"height":1080,"framerate":30,"bitrate":9914554},
"initTrack":"init_video_1080",
"altGroup": 1
},
{
"name": "video_720",
"selectionParams":{"codec":"avc1.64001f","mimeType":"video/mp4",
"width":1280,"height":720,"framerate":30,"bitrate":4952892},
"initTrack":"init_video_720",
"altGroup": 1
},
{
"name": "audio_aac",
"selectionParams":{"codec":"mp4a.40.5","mimeType":"audio/mp4",
"samplerate":48000,"channelConfig":"2","bitrate":67071},
"initTrack":"init_audio_aac",
"altGroup": 2
},
{
"name": "audio_ec3",
"selectionParms":{"codec":"ec-3","mimeType":"audio/mp4",
"samplerate":48000,"channelConfig":"F801","bitrate":256000},
"initTrack":"init_audio_ec3",
"altGroup": 2
}
]
}
supportsDeltaUpdates
については後述。
initTrack
はInitialization track
のことで初期化データを保持する別のtrackの名前の文字列です。初期化データの実体はそのtrackから取得するため、Catalogを取得するだけでは不要なデータの取得が発生しません。initTrack
で指定されているtrackはtracks直下に追加することは禁止されており、initTrackからのみ参照可能です。
CMAFとLOCの併用例
映像はCMAFで、音声はLOCのパターンです。
{
"version": 1,
"sequence": 0,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"namespace": "output.example.com/event/12345",
"renderGroup":1,
"tracks": [
{
"name": "video0",
"selectionParams":{"codec":"avc1.64001f","mimeType":"video/mp4","width":1280,"height":720,"framerate":30,"bitrate":4952892},
"initTrack":"init_video_720",
"packaging":"cmaf",
},
{
"name": "audio",
"selectionParams":{"codec":"opus","samplerate":48000,"channelConfig":"2","bitrate":32000},
"packaging": "loc",
}
]
}
packaging
はtrackレベルでも指定できるため、この例ではtrack毎にcmafとlocに分かれています。
個別のフォーマット毎のCatalogの定義ではこのような用法は不可能でしたが、Common Catalog Formatとして両方を扱うことで可能になりました。
その他
3つのvideo trackと1つのaudio trackの例
映像がサイマルキャストのパターンです。
{
"version": 1,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"supportsDeltaUpdates": true,
"commonTrackFields": {
"renderGroup": 1,
"packaging": "loc"
},
"tracks":[
{
"name": "hd",
"selectionParams": {"codec":"av01","width":1920,"height":1080,
"bitrate":5000000,"framerate":30},
"altGroup":1
},
{
"name": "md",
"selectionParams": {"codec":"av01","width":720,"height":640,
"bitrate":3000000,"framerate":30},
"altGroup":1
},
{
"name": "sd",
"selectionParams": {"codec":"av01","width":192,"height":144,
"bitrate":500000,"framerate":30},
"altGroup":1
},
{
"name": "audio",
"selectionParams":{"codec":"opus","samplerate":48000,"channelConfig":"2",
"bitrate":32000}
}
]
}
altGroup
はAlternate groupでお互いに代替のバージョンであるtrackのグループを示します。renderGroup
とは異なり、購読側では典型的にグループ内から購読するtrackを1つ選びます。一般的にはこの例のように同じコンテンツを異なるビットレートで提示するような使い方をします。
サイマルキャストの例
Scalable Video Codec(SVC)の例
音声と時間的に2層、空間的にも2層になったSVCの組み合わせのパターンです。
{
"version": 1,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"supportsDeltaUpdates": true,
"commonTrackFields": {
"namespace": "conference.example.com/conference123/alice",
"renderGroup": 1,
"packaging": "loc"
},
"tracks":[
{
"name": "480p15",
"selectionParams": {"codec":"av01.0.01M.10.0.110.09","width":640,
"height":480,"bitrate":3000000,"framerate":15}
},
{
"name": "480p30",
"selectionParams": {"codec":"av01.0.04M.10.0.110.09","width":640,
"height":480,"bitrate":3000000,"framerate":30},
"depends": ["480p15"]
},
{
"name": "1080p15",
"selectionParams": {"codec":"av01.0.05M.10.0.110.09","width":1920,
"height":1080,"bitrate":3000000,"framerate":15},
"depends":["480p15"]
},
{
"name": "1080p30",
"selectionParams": {"codec":"av01.0.08M.10.0.110.09","width":1920,
"height":1080,"bitrate":5000000,"framerate":30},
"depends": ["480p30", "1080p15"]
},
{
"name": "audio",
"selectionParams":{"codec":"opus","samplerate":48000,"channelConfig":"2",
"bitrate":32000}
}
]
}
depends
はDependenciesを表す配列で、そのtrackの依存先のtrack nameを保持しています。track nameしか指定できないため、依存先も同じ名前空間にあると仮定されます。
この例のdependsが表す依存関係は以下のようになります。[6]
また、この例ではrenderGroup
がrootで定義されており、全てのtrackで同じグループであることがわかります。
+----------+
+----------->| S1T1 |
| | 1080p30 |
| +----------+
| ^
| |
+----------+ |
| S1TO | |
| 1080p15 | |
+----------+ +-----+----+
^ | SOT1 |
| | 480p30 |
| +----------+
| ^
+----------+ |
| SOTO | |
| 480p15 |---------+
+----------+
カスタムフィールドの利用の例
trackでカスタムフィールドを指定しているパターンです。
{
"version": 1,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"commonTrackFields": {
"namespace": "conference.example.com/conference123/alice",
"packaging": "loc",
"renderGroup": 1
},
"tracks": [
{
"name": "video",
"selectionParams":{"codec":"av01.0.08M.10.0.110.09","width":1920,
"height":1080,"framerate":30,"bitrate":1500000},
// 以下がカスタムフィールド
"com.example-billing-code": 3201,
"com.example-tier": "premium",
"com.example-debug": "h349835bfkjfg82394d945034jsdfn349fns"
},
{
"name": "audio",
"selectionParams":{"codec":"opus","samplerate":48000,"channelConfig":"2",
"bitrate":32000}
}
]
}
com.example-
で始まっているフィールドがCustom field
です。カスタムフィールドは将来のバージョンも含めてこのドラフトで定義されるフィールドとの名前衝突を防ぐため、reverse domain name notation
を先頭につけるべきとしています。これはJavaのパッケージ名などで使われていて、上の例ではexample.com
からcom.example-
が使われています。
Catalogの差分表現
ここまでいくつかのCatalogの例を挙げてきました。映像や音声を後から追加・削除するユースケースを考えると、一部の更新のために同じサイズのCatalogをもう一度読み込むのは無駄が大きいです。そこでCatalogの一部のフィールドのみを変更することを考えます。そのためにPatch
というCatalogの表現方式も用意されています。
Patch
は前回からの変更のみを記述できる方式です。Patch
はPatch
のCatalogからの差分としても表現可能です。差分はJSON PATCH[1:2]の形式で表現されます。
差分表現を利用するには元のCatalogでSupports delta updates
をtrue
で含めておく必要があります。このフィールドは省略可能で、その場合はfalse
扱いになります。
つまり、明示的に差分表現の利用を宣言しないと利用することが出来ません。
これは、差分情報を扱うには以前の状態を持っておき、それに対して差分表現を作る必要があるので処理が複雑になるからではないかなと思います。ただ、差分表現の有無をネゴシエーション出来るようには見えないので、購読側は対応必須になるような気もします。
Patchの関係性の例
Patchは以下のルールに従って処理する必要があります。
- 1つの配列である
- 配列の各オブジェクトが1つの操作を表現
- 操作は表れた順に適用
- 以下のフィールドの変更は禁止
- Track namespace
- Track name
- Track selection properties
- これらを変えたい場合、一度trackを削除してから新しく追加が必要
trackを追加した差分の例
確立済みのビデオカンファレンスにスライドのtrackを追加する差分を表現したパターンです。
[
{
"op": "add",
"path": "/tracks/-",
"value": {
"name": "slides",
"selectionParams": {
"codec": "av01.0.08M.10.0.110.09",
"width": 1920,
"height": 1080,
"framerate": 15,
"Bitrate": 750000
},
"renderGroup": 1
}
}
]
この例ではtracks
配列にvalue
のオブジェクトが追加されます。
trackを削除した差分の例
確立済みのビデオカンファレンスからスライドのtrackを削除する差分を表現したパターンです。
[
{ "op": "remove", "path": "/tracks/2"}
]
配信終了の差分の例
全てのtrackの削除と送信終了を表現したパターンです。
[
{ "op": "remove", "path": "/tracks/2"},
{ "op": "remove", "path": "/tracks/1"},
{ "op": "remove", "path": "/tracks/0"},
]
もし全てのtrackを削除している場合(operation=delete
)は、送信自体を終了したと読み取るべきです。
他のカタログへの参照
Catalogでは自身でtrackを含むのではなく、他のCatalogへの参照という形でも記述できます。ただし、これらは両立できず、参照がある場合はCatalog内に参照しか含むことができません。
他の2つのCatalogを参照するCatalogの例
異なるnamespaceで同時に2つのstreaming formatsを参照するパターンです。
{
"version": 1,
"catalogs": [
{
"name": "catalog-for-format-one",
"namespace": "sports.example.com/games/08-08-23/live",
"streamingFormat":1,
"streamingFormatVersion": "0.2",
"supportsDeltaUpdates": true,
},
{
"name": "catalog-for-format-five",
"namespace": "chat.example.com/games/08-08-23/chat",
"streamingFormat":5,
"streamingFormatVersion": "1.6.2"
}
]
}
この例では最初のカタログのみ差分表現をサポートしています。
参照を持つカタログ
補足
Catalogの暗号化
Catalogのコンテンツは暗号化できます。実際の暗号化方式や鍵のシグナリング方法についてはCatalogを用いるstreaming format側に委ねられます。
MoQ Streaming Format Typeの登録
例ではStreaming Format Typeに適当な値が入っていましたが、実際には登録された値を使う必要があります。
各streaming formatが値を"MoQ Streaming Format Type"
という新しいregistryに登録するのが必須となります。登録後にその値を使うことができます。値の範囲は0x0000-0xFFFF
です。
このドラフトでは0x0000
としてReservedな値が提案されています。
MoQ Common Catalog Fieldsの登録
カタログのフィールドについても同様に行っています。
ここでは各フィールド名やその属性を定義しています。
Catalogの歴史
MoQとCatalogの歴史
Catalogが初めて登場したのはdraft-lcurley-warp-03というドラフトです。[7] このドラフトではまだGroupの概念はなく、載っている情報も最低限でした。
その次の版のdraft-lcurley-warp-04ではObject/Group/Trackが登場してます。
その後Warpのドラフトでより多くの定義がされるようになります。例えば、"catalog"というtrack nameにすることが必須となりました。
その後に出たLOCのドラフトでも個別に定義をしていますが、差分更新の仕組みが無いなどの違いがあります。[8]
そしてIETF117を経てこのCommon Catalog Formatのドラフトとして定義されました。Common Catalog FormatはWarpとLOCで個別に定義されていたCatalogを統合し、改良したものになっています。
2024/07に出た現在のWarpのドラフトではCatalogにはCommon Catalog Formatを使うと書かれています。
まとめ
ドラフトで説明されているCatalog自体やそれらのフィールドについて例と共に紹介しました。どのようにこれらが使われるのか想像できるようになっていると嬉しいです。
現在、LOCのドラフトはExpiredになってしまっていますが、後に同じようなドラフトが出てきた際にこのドラフトを参照する形になるのか気になります。
-
https://datatracker.ietf.org/doc/html/draft-ietf-moq-catalogformat-01#section-3.2 ↩︎
-
JSONのトップレベル階層のこと ↩︎
-
実際に後ろの
cmaf
の例でもこのloc
の例と同じく1が使われています。 ↩︎ -
LOCのパッケージング手法は増えないと思いますが、WarpではCMAF以外が定義されることもあるのかもしれません ↩︎
-
依存される側から矢印が出ているのに注意が必要です ↩︎
-
このドラフトが指す"Warp"は現在のWarpとは異なるので注意が必要です。このドラフトは現在のMOQTのドラフトへと置き換えられ、Warp特有の部分が現在のWarpのドラフトとなっています。また、このドラフト内でも02と03の間でタイトルが変わるなどの変化があるので注意が必要です。同様に、現在のMOQTと別のMOQTのドラフトもあるので注意が必要です。 ↩︎
Discussion