08. データの運び方: ストリームとデータグラム

🎯 この章のゴール

  • Object がどのように QUIC のストリームやデータグラムに載るかを理解する
  • SUBGROUP_HEADER・OBJECT_DATAGRAM・FETCH_HEADER の構造を読める
  • ストリームの FIN とリセットの使い分け、Object Status を知る

💡 ひとことで言うと

ストリームは書留の連続便、データグラムはハガキです。連続便は順番どおり確実に届き、途中で打ち切ることもできます。ハガキは速くて軽い代わりに、届かなくても再送されません。

3 種類のデータの入れ物

入れ物先頭用途
Subgroup ストリーム(単方向)SUBGROUP_HEADER購読の Object(Delivery Mode = Subgroup)
OBJECT_DATAGRAMType Flags購読の Object(Delivery Mode = Datagram)
Fetch ストリーム(単方向)FETCH_HEADERFETCH と fill の応答

オリジナルのパブリッシャが最初にどちらで送ったかで、その Object の Delivery Mode が決まり、以後の購読では同じモードで送らなければなりません。1 つの Group の中で両方を混ぜてもかまいません。

単方向ストリームの先頭の vi64 がストリームの種類を表します。

ID種類
0x05FETCH_HEADER
0b0XX1XXXX(ビット 4 が 1)SUBGROUP_HEADER
0x2F00SETUP(制御ストリーム)
0x132B3E28PADDING

Subgroup ストリーム

ヘッダー

SUBGROUP_HEADER {
  Type Flags (vi64),           ← 1 バイトに収まるフラグの集まり
  Track Alias (vi64),
  Group ID (vi64),
  [Subgroup ID (vi64),]        ← フラグによっては省略
  [Publisher Priority (8),]    ← フラグによっては省略
}
Type Flags のビット名前意味
0x01PROPERTIES各 Object に Properties フィールドがある
0x06(2 ビット)SUBGROUP_ID_MODE00: Subgroup ID は 0 / 01: 最初の Object ID と同じ / 10: ヘッダーに書く / 11: 予約
0x08END_OF_GROUPこの Subgroup に Group の最後の Object が含まれる
0x10(常に 1)SUBGROUP_HEADER であることを示す
0x20DEFAULT_PRIORITY優先度を省略し、購読の既定値を使う
0x40FIRST_OBJECT最初の Object が、この Subgroup で最初に公開された Object である

よくある Subgroup ID の付け方(0 固定、先頭の Object ID と同じ)なら ID を書かずに済み、優先度も既定値なら省略できます。ヘッダーを数バイトに抑える工夫です。

Object の並び

ヘッダーの後ろに Object が続きます。

{
  Object ID Delta (vi64),        ← 直前の Object ID + 1 からの差(連番なら常に 0)
  [Properties (..),]             ← PROPERTIES ビットが 1 のとき
  Object Payload Length (vi64),
  [Object Status (vi64),]        ← ペイロード長が 0 のときだけ
  [Object Payload (..),]
}

Object ID が 0, 1, 2, … と連番なら Delta はすべて 0 です。Delta が 0 でなくても、間の Object が存在しないとは推測できません(Prior Object ID Gap プロパティがあれば別、11 章)。

Object Status

ペイロード長 0 の Object は、状態を伝える特別な Object にできます。

値意味
0x0 Normal通常の Object(長さ 0 の空 Object)
0x3 End of Groupこの Object ID 以降はこの Group に存在しない
0x4 End of Trackこの Location 以降は存在しない(トラック終了)

Subgroup の終わりを示す Status はありません。Subgroup の終わりはストリームの FIN で表します。

FIN とリセットの使い分け

状況閉じ方
Subgroup の Object をすべて送ったFIN
送り切る前にやめる(遅延タイムアウト、購読キャンセル、範囲の縮小、一時停止など)リセット(RESET_STREAM / RESET_STREAM_AT)

FIN は「これで全部」という意味なので、途中で打ち切るのにも使うと、受信側やキャッシュが「Subgroup はここで終わり」と誤解してしまいます。RESET_STREAM_AT を使う場合は、reliable_size にストリームヘッダーを含めておくと、受信側がどの購読のストリームか把握でき、PUBLISH_DONE の Stream Count の勘定も合います。

リレーは、複数の Object が流れるストリームを転送するとき、Object を並べ替えたり捨てたりしてはいけません(MUST NOT)。既存の Subgroup ストリームには「次の Object」しか続けて送れません。

データグラム

OBJECT_DATAGRAM {
  Type Flags (vi64),
  Track Alias (vi64),
  Group ID (vi64),
  [Object ID (vi64),]           ← ZERO_OBJECT_ID なら省略(Object ID = 0)
  [Publisher Priority (8),]     ← DEFAULT_PRIORITY なら省略
  [Properties (..),]            ← PROPERTIES なら存在
  [Object Status (vi64),]       ← STATUS なら存在(ペイロードなし)
  [Object Payload (..),]        ← 長さフィールドなし。データグラムの残り全部
}
ビット名前意味
0x01PROPERTIESProperties あり
0x02END_OF_GROUPこの Object がこの Group の最後
0x04ZERO_OBJECT_IDObject ID を省略(= 0)
0x08DEFAULT_PRIORITY優先度を省略
0x10(予約、必ず 0)—
0x20STATUSペイロードの代わりに Object Status

データグラムは 1 Object = 1 データグラムで、再送されません。セッションの最大データグラムサイズを超える Object は通知なしに捨てられます。経路上の各セッションで最大サイズが違ううえ、リレーがプロパティを追加すると大きくなるので、余裕を持ったサイズにしましょう。

向いているのは、小さくて、失われても次のデータですぐ置き換わるもの(低ビットレート音声の 1 フレーム、センサー値、カーソル位置など)です。

Fetch ストリーム

FETCH と fill の応答は 1 本のストリームにまとめて流れます。Object ごとに Group ID や Subgroup ID が変わりうるので、各 Object の前に Serialization Flags が付き、「Subgroup ID は前と同じ / +1 / 0 / 明示」「Group ID は前と同じか」などを表して、繰り返しを省略します。

Flags の特別な値は、06 章の「隙間の印」です。

値意味
0x8CEnd of Non-Existent Range
0x10CEnd of Unknown Range
0x20CEnd of Timed-Out Range

パディング

draft-22 にはパディング用のストリーム(PADDING)とデータグラムがあります。中身に意味はなく、受信側は捨てるだけです。帯域推定のプローブ(輻輳制御が「アプリ側のデータ不足」で帯域を過小評価しないよう、ダミーデータを流す)などに使えます(09 章)。

🧪 考えてみよう

30fps の映像で、1 GOP(2 秒 = 60 フレーム)を 1 Group にするとします。すべてのフレームを 1 つの Subgroup(1 ストリーム)にするのと、フレームごとにデータグラムで送るのとでは何が違うでしょう? ストリームなら確実・順序どおりに届き、遅れたら Group ごとリセットで打ち切れます。データグラムは大きな映像フレームだとサイズ上限を超えやすく、1 フレーム欠けると以降の差分フレームも復号できません。そのため映像には Subgroup ストリームを使うのが一般的です。

📝 まとめ

  • 購読の Object は Subgroup ストリーム(1 Subgroup ≒ 1 ストリーム)かデータグラム、FETCH / fill は Fetch ストリーム
  • ヘッダーは Type Flags で ID や優先度を省略し、Object ID は差分で書いて小さく保つ
  • 全部送ったら FIN、途中でやめたらリセット。Group / Track の終わりは Object Status や END_OF_GROUP で示す

参考

✅ 理解度クイズ

Q1
購読で Object を送るとき、Subgroup と QUIC ストリームの関係として正しいものは?
Q2
SUBGROUP_HEADER の END_OF_GROUP ビットが 1 のストリームが FIN で終わった。何が分かる?
Q3
Delivery Mode が Datagram の Object の特徴は?
Q4
Subgroup ストリームの途中で、遅延タイムアウトにより残りの Object を送らないことにした。パブリッシャはどうする?