MOQTのCommon Catalog Formatについて
はじめに
IETFに提出されているCommon Catalog Format for moq-transportというドラフトについて紹介します。
Catalogの理解を深めることで、MoQ Transport(MOQT)上でのメディアの発見方法や色んなメディアコンテナフォーマットのMOQT上での併用方法が分かると思います。
TL;DR
- 別々に定義されていたCatalogが共通化
- 複数メディアコンテナフォーマットの同時利用が可能に
- その他の利便性もアップ
- このドラフトを参照するようにWarpやLOCのドラフトがアップデートされるはず
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には32種類ものフィールドが定義されている[1]ため、例と共に一部を説明します。
以下は各フィールドに共通したルールです。
- フィールド毎に必須または任意の項目として定義
- フィールド毎に宣言可能な位置が異なる
- root[2]、track内、catalog内、selectionParams内のうち1箇所以上で宣言可能
- rootで宣言されたフィールドは子要素に継承
- 子要素でも宣言されている場合はそちらが優先
- フィールドは順不同
- 理解できないフィールドの無視が必須
LOCの1組のaudio/video trackの例
1組の映像と音声という基本的なパターンです。
{
"version": 1,
"sequence": 0,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"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です。
sequence
はCatalog sequence number
でObjectの番号を表し0から1ずつ増加します。つまり、この例ではこのCatalogは最初のCatalog Objectということになります。
streamingFormat
はStreaming format type
を表す数値です。まだ値が定まっていないのでこの例では適当な値を入れていると思われます。[3]
streamingFormatVersion
はStreaming formatのバージョンを表す文字列で、streaming format側で定義します。こちらも同様に適当な値です。
namespace
はTrack namespace
を表しており、各trackのtrack nameがこの名前空間内で定義されます。例えばvideoの場合、Full Track Nameはconference.example.com/conference123/alice/video
となります。
packaging
はペイロードのカプセル化方法を示していて、このドラフトでは"cmaf"
/"loc"
のいずれかの値をとります。[4]
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,
"sequence": 0,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"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":"AAAAGGZ0eXBpc282AAAAAWlzbzZkYXNoAAAARWZyZWVJc29NZWRpYSBGaWxlIFByb2R1Y2VkIHdpdGggR1BBQyAxLjAuMS1yZXYwLWdkODUzOGU4YS1tYXN0ZXIAAAADLW1vb3YAAABsbXZoZAAAAADfdnly33Z5cgABX5AAAAAAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAA4bXZleAAAABBtZWhkAAAAAAPwOXgAAAAgdHJleAAAAAAAAAABAAAAAQAAA+gAAAAAAAEAAAAAAkd0cmFrAAAAXHRraGQAAAAB33Y1w992eXIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAABQAAAAIWAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAAAAAAD6AABAAAAAAG/bWRpYQAAACBtZGhkAAAAAN92NcPfdnlyAABdwAAAAAAVxwAAAAAAQGhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAAAfTWFpbmNvbmNlcHQgVmlkZW8gTWVkaWEgSGFuZGxlcgAAAVdtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADkc3RibAAAAJhzdHNkAAAAAAAAAAEAAACIYXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAUAAhYASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAADJhdmNDAU1AH//hABpnTUAfllKAoAi/NNQYGBkAAAMAAQAAAwAwhAEABWjpCTUgAAAAEHN0dHMAAAAAAAAAAAAAABBzdHNjAAAAAAAAAAAAAAAUc3RzegAAAAAAAAAAAAAAAAAAABBzdGNvAAAAAAAAAAAAAAAzaGRscgAAAAAAAAAAYWxpcwAAAAAAAAAAAAAAAEFsaWFzIERhdGEgSGFuZGxlcgAAAAA6dWR0YQAAABepVElNAAsAADAwOjAwOjAwOjAwAAAADqlUU0MAAgAAMjQAAAANqVRTWgABAAAx"
},
{
"name": "audio_aac",
"selectionParams":{"codec":"mp4a.40.5","mimeType":"audio/mp4","samplerate":48000,"channelConfig":"2","bitrate":67071},
"initData":"AAAAGGZ0eXBpc282AAAAAWlzbzZkYXNoAAAARWZyZWVJc29NZWRpYSBGaWxlIFByb2R1Y2VkIHdpdGggR1BBQyAxLjAuMS1yZXYwLWdkODUzOGU4YS1tYXN0ZXIAAAACzG1vb3YAAABsbXZoZAAAAADfdnly33Z5cgABX5AAAAAAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAA4bXZleAAAABBtZWhkAAAAAAPwSAAAAAAgdHJleAAAAAAAAAACAAAAAQAABAAAAAAAAgAAAAAAAeZ0cmFrAAAAXHRraGQAAAAB33Y1w992eXIAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAGCbWRpYQAAACBtZGhkAAAAAN92NcPfdnlyAAC7gAAAAAAVxwAAAAAARGhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAAAjTWFpbmNvbmNlcHQgTVA0IFNvdW5kIE1lZGlhIEhhbmRsZXIAAAEWbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAACnc3RibAAAAFtzdHNkAAAAAAAAAAEAAABLbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAALuAAAAAAAAnZXNkcwAAAAADGQAAAAQRQBUABgAACBXTAATXtwUCEZAGAQIAAAAQc3R0cwAAAAAAAAAAAAAAEHN0c2MAAAAAAAAAAAAAABRzdHN6AAAAAAAAAAAAAAAAAAAAEHN0Y28AAAAAAAAAAAAAADNoZGxyAAAAAAAAAABhbGlzAAAAAAAAAAAAAAAAQWxpYXMgRGF0YSBIYW5kbGVyAAAAADp1ZHRhAAAAF6lUSU0ACwAAMDA6MDA6MDA6MDAAAAAOqVRTQwACAAAyNAAAAA2pVFNaAAEAADE="
}
]
}
initData
はInitialization data
のことでtrackの初期化データをBase64で持つ文字列です。Catalogに文字列で入っているため、そのtrackを必要としない場合(例えば音声だけ必要)でも初期化データをダウンロードしてしまいます。
以下はその問題を解消したinitTrack
を用いたパターンです。(一部間違っていると思われるフィールドをドラフトから修正しています。)
{
"version": 1,
"sequence": 0,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"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
}
]
}
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,
"sequence": 0,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"namespace": "conference.example.com/conference123/alice",
"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,
"sequence": 0,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"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が表す依存関係は以下のようになります。[5]
また、この例ではrenderGroup
がrootで定義されており、すべてのtrackで同じグループであることがわかります。
+----------+
+----------->| S1T1 |
| | 1080p30 |
| +----------+
| ^
| |
+----------+ |
| S1TO | |
| 1080p15 | |
+----------+ +-----+----+
^ | SOT1 |
| | 480p30 |
| +----------+
| ^
+----------+ |
| SOTO | |
| 480p15 |---------+
+----------+
カスタムフィールドの利用の例
trackでカスタムフィールドを指定しているパターンです。
{
"version": 1,
"sequence": 0,
"streamingFormat": 1,
"streamingFormatVersion": "0.2",
"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の一部のフィールドのみを変更することを考えます。そのためにDelta Update
というCatalogの表現方式も用意されています。
Delta Update
は前回からの変更のみを記述できる方式です。一方、ここまで紹介していた単独で解釈可能なCatalogはDelta Update
に対して、Independent
と呼ばれています。Delta UpdateはIndependentのCatalogからの差分だけではなくDelta UpdateのCatalogからの差分としても表現可能です。
Delta Updateの関係性の例
Delta Updateは以下のルールに従って処理する必要があります。
-
parent sequence number field
が無い場合はIndependentとして扱う -
parent sequence number field
が有る場合はDelta Updateとして扱う-
parent sequence number field
で示されるCatalogに状態を追加したかのようにパースが必要- この場合、古い値は新しい値で上書き
-
- 以下のフィールドの変更は禁止
- Track namespace
- Track name
- Track selection properties
- これらを変えたい場合、一度trackを削除してから新しく追加が必要
trackを追加した差分の例
確立済みのビデオカンファレンスにスライドのtrackを追加する差分を表現したパターンです。
{
"sequence": 1,
"parentSequence":0,
"tracks": [
{
"name": "slides",
"selectionParams":{"codec":"av01.0.08M.10.0.110.09","width":1920,"height":1080,"framerate":15,"bitrate":750000},
"renderGroup":1
}
]
}
parentSequence
はParent sequence number
で差分の元となるCatalogを表す整数です。前述の通り、このフィールドの有無によってCatalogが独立しているかどうかを判断します。
trackを削除した差分の例
確立済みのビデオカンファレンスからスライドのtrackを削除する差分を表現したパターンです。
{
"sequence": 2,
"parentSequence":1,
"tracks": [
{
"name": "slides",
"operation": "delete"
}
]
}
operation
はTrack operations
のことです。入る値はAdd
とDelete
の2種類で、それぞれトラックの追加と削除を表しています。デフォルト値がAdd
であるため、追加差分の例ではこのフィールドは省略されています。
配信終了の差分の例
すべてのtrackの削除を表現したパターンです。
{
"sequence": 3,
"parentSequence":2,
"operation": "delete",
"tracks": [{"name": "audio"},{"name": "video"}]
}
もし全てのtrackを削除している場合(operation=delete
)は、送信自体を終了したと読み取るべきです。
他のカタログへの参照
Catalogでは自身でtrackを含むのではなく、他のCatalogへの参照という形でも記述できます。ただし、これらは両立できず、参照がある場合はCatalog内に参照しか含むことができません。
他の2つのCatalogを参照するCatalogの例
異なるnamespaceで同時に2つのstreaming formatsを参照するパターンです。
{
"version": 1,
"sequence": 0,
"catalogs": [
{
"name": "catalog-for-format-one",
"namespace": "sports.example.com/games/08-08-23/live",
"streamingFormat":1,
"streamingFormatVersion": "0.2"
},
{
"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に登録するのが必須となります。登録後にその値を使うことができます。
ただし、登録するには根拠となるRFCが必要なため、実際に値が追加されるのはかなり先になると思われます。
このドラフトでは0x0000
としてReservedな値が提案されています。
Catalogの歴史
MoQとCatalogの歴史
Catalogが初めて登場したのはdraft-lcurley-warp-03というドラフトです。[6] このドラフトではまだGroupの概念はなく、載っている情報も最低限でした。
その次の版のdraft-lcurley-warp-04ではObject/Group/Trackが登場してます。
その後Warpのドラフトでより多くの定義がされるようになります。例えば、"catalog"というtrack nameにすることが必須となりました。
その後に出たLOCのドラフトでも個別に定義をしていますが、差分更新の仕組みが無いなどの違いがあります。[7]
そしてIETF117を経てこのCommon Catalog Formatのドラフトとして定義されました。Common Catalog FormatはWarpとLOCで個別に定義されていたCatalogを統合し、改良したものになっています。
まとめ
ドラフトで説明されているCatalog自体やそれらのフィールドについて例と共に紹介しました。どのようにこれらが使われるのか想像できるようになっていると嬉しいです。
今後WarpやLOCでは自身で定義するCatalogからこのCommon Catalog Formatを参照するようになると考えられます。Common Catalog Format自身もだんだんと洗練されていくと思われるので楽しみです。
-
https://datatracker.ietf.org/doc/html/draft-wilaw-moq-catalogformat-01#section-3.2 ↩︎
-
JSONのトップレベル階層のこと ↩︎
-
実際に後ろの
cmaf
の例でもこのloc
の例と同じく1が使われています。 ↩︎ -
LOCのパッケージング手法は増えないと思いますが、WarpではCMAF以外が定義されることもあるのかもしれません ↩︎
-
依存される側から矢印が出ているのに注意が必要です ↩︎
-
このドラフトが指す"Warp"は現在のWarpとは異なるので注意が必要です。このドラフトは現在のMOQTのドラフトへと置き換えられ、Warp特有の部分が現在のWarpのドラフトとなっています。また、このドラフト内でも02と03の間でタイトルが変わるなどの変化があるので注意が必要です。同様に、現在のMOQTと別のMOQTのドラフトもあるので注意が必要です。 ↩︎
Discussion