All checks were successful
wai-app and Create / wai-app · does this change touch it (push) Successful in 16s
wai-audit collector and player integration / does this change touch it (push) Successful in 17s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 25s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 21s
WAI operation catalogue / does this change touch it (push) Successful in 30s
wai-sink-swift native sink package / does this change touch it (push) Successful in 49s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 1m0s
wai-quantum for JavaScript / does this change touch it (push) Successful in 51s
wai-quantum studio and desktop / does this change touch it (push) Successful in 51s
wai-sink-android native sink library / does this change touch it (push) Successful in 50s
wai-ledger service / does this change touch it (push) Successful in 52s
wai-fleet service / does this change touch it (push) Successful in 53s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 1m52s
WAI Studio / does this change touch it (push) Successful in 3m22s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 4m23s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 5m35s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 6m14s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 21s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 57s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m18s
wai-fleet service / service · linux (push) Successful in 2m36s
wai-ledger service / service · linux (push) Successful in 5m38s
wai-quantum for JavaScript / javascript · linux (push) Successful in 5m38s
wai-audit collector and player integration / collector · linux (push) Successful in 6m10s
wai-quantum studio and desktop / studio · browser (push) Successful in 6m27s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 9m22s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 12m12s
WAI Studio / Studio in WebKit · linux (push) Successful in 14m9s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m54s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 6m9s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 10m30s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m12s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 53s
wai-fleet service / service · macos (push) Successful in 43s
wai-ledger service / service · macos (push) Successful in 1m0s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 7m38s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m41s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 5m46s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 12m36s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 15m57s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 10m15s
packaging §11.3: a verifier holds the first floor rung's sequence header to SPEC §5's video floor as the packager does (floor-av1-profile, floor-av1-level), before reading it further. wai-pack's verifier and pack_verify.py both do; package-mutations/ gains a planted fault for each, re-sealed with the golden's key: floor-high-profile and floor-level-above (level 5.0). Co-Authored-By: Claude <[email protected]>
2346 lines
166 KiB
Markdown
2346 lines
166 KiB
Markdown
# WAI Extension: Packaging
|
||
|
||
> Status: Draft. A packager turns sources into WAI objects under a declared policy;
|
||
> this extension fixes what a verifier recomputes. This revision defines the policy,
|
||
> its digest and what it may ask a packager to emit (§3), how a source is cut into
|
||
> pieces (§4), the objects a packager writes, refinements and stills among them, a
|
||
> piece's ladder, and the components a package carries beside its lead, audio, a score,
|
||
> vector graphics, timed text and a feed, each in the capability that suits it (§5), the
|
||
> stage index (§6), the floor, one or several, supplied as OBU streams or as an existing
|
||
> ladder's CMAF files, and a sound track's Opus floor (§7), the protected floor, those CMAF
|
||
> files under Common Encryption (`cenc` and `cbcs`) with W3C Clear Key and content protection
|
||
> systems signalled by their system IDs (§7.10), what each class is offered
|
||
> over HTTP and MoQ and how its delivery is simulated (§8), the package receipt and live
|
||
> windows (§9), the package, its verification and a re-pack from a parent (§11), and
|
||
> where each step of a pack or a verify may run (§11.6), and how a programme's audio
|
||
> renditions are written for HLS and DASH, audio description among them (§8.10), a lead's
|
||
> marks, its shots, chapters, break candidates and clips, derived from its stage index and
|
||
> written as WebVTT chapters, HLS date ranges and DASH event streams (§8.11), and a
|
||
> programme's loudness, measured in integers, with the gain to a target in a signed
|
||
> statement (§12).
|
||
> Reference impl: `wai-pack` —
|
||
> `policy`, `capcheck`, `source`, `scenecut`, `classify`, `plan`, `encode`, `png`,
|
||
> `floor`, `cmaf`, `cmaf_read`, `opus_floor`, `cenc`, `av1_tiles`, `protect`, `catalog`, `files`, `pack`, `multi`, `live`, `delivery`, `verify`,
|
||
> `fabric`, `fabric_meter`, `floor_decode`, and `wai-pack-gpu` for the steps a GPU runs;
|
||
> in `wai-rs`, the
|
||
> encoder `int_video_enc`, the refinement encoder `staged::wir1`, the stage index
|
||
> `stage_index`, the receipt `package`, the delivery simulator `stage_measure::sim` and the
|
||
> marks `pack_marks`;
|
||
> the corpus [`pack-conformance/`](../pack-conformance/), whose `multi/` holds packages of
|
||
> several components (§5.8), and beside it the composing sink's reference package
|
||
> [`wai-web/compose/`](../wai-web/compose/), five components written by `wai-pack` and held
|
||
> to the corpus check. Keywords **MUST**, **MUST NOT**,
|
||
> **SHOULD**, **MAY** are RFC 2119/8174.
|
||
|
||
## 1. Scope
|
||
|
||
A **packager** turns one or more sources into WAI objects, stage indexes, catalogs
|
||
and a package receipt, under a declared **policy**. This extension fixes what a
|
||
package contains and what a verifier recomputes. It fixes an encoder's choices only
|
||
where it names them.
|
||
|
||
## 2. Terms
|
||
|
||
- **Component**: one media stream of a package (for example video, or audio).
|
||
- **Piece**: a span of a component that decodes with no reference outside itself.
|
||
- **Stage**: one object of a piece in staged order: the base is layer 0, refinement
|
||
*k* is layer *k* ([staged-delivery](staged-delivery.md)).
|
||
- **Rung**: one entry of the policy's ladder: a capability a component's pieces are
|
||
coded in, with its role (base, refinement or floor) and parameters.
|
||
- **Class**: a delivery profile the policy names.
|
||
- **Moment**: a declared span of presentation time with tags.
|
||
- **Floor**: a rendition in a standard codec, for sinks that lack the primary.
|
||
- **Floor name**: the name a floor rung's files and catalog entries carry (§7.1): its
|
||
component's id when the component has one floor rung, the rung's id when it has
|
||
several.
|
||
- **Timebase** of a component: `num / den` units per second. For video one unit is
|
||
one frame; for audio, one sample.
|
||
- **Step**: one of the computations of a pack or a verify (§11.6), such as the base's
|
||
motion search or an object's content hash.
|
||
- **Fabric**: the kind of processor a step runs on: `cpu`, `gpu`, `media` (a hardware
|
||
video engine) or `npu` (a neural processor) (§11.6).
|
||
|
||
## 3. Policy
|
||
|
||
**3.1** A package is made under exactly one policy, a TOML 1.0 document of schema
|
||
`wai.pack.policy/1`, in UTF-8 with no byte-order mark.
|
||
|
||
**3.2** The **effective policy** is the policy with every default written in (§3.3)
|
||
and every override the packager applies (such as a command line forcing
|
||
`royalty_free_only`). An override only ever forces `royalty_free_only` on; it never
|
||
turns off what the policy asks for. Its JSON form is §3.3's members, with an optional member that
|
||
has no default omitted when absent, and arrays of tables in document order. Its
|
||
**policy digest** is
|
||
|
||
BLAKE3("wai:pack-policy\x01" ‖ JCS(effective policy as JSON))
|
||
|
||
where JCS is RFC 8785. Two spellings of one policy (member order, inline or standard
|
||
tables, dotted keys, string or integer notation, comments, an omitted default or the
|
||
default written out) therefore have one digest; the order of an array of tables is
|
||
part of the policy.
|
||
|
||
A packager **MUST** refuse with `policy-toml`, before anything else, a policy whose
|
||
bytes are not UTF-8, that begins with a byte-order mark, that is not TOML 1.0 (an
|
||
integer outside TOML's 64-bit range included), or that nests arrays and tables more
|
||
than 32 deep: the document's own table is at depth 1, and an array or a table
|
||
directly inside one at depth *k* is at depth *k* + 1. Then, before it reads any key, it
|
||
**MUST** refuse one holding anywhere a float (`policy-float`), a date or time
|
||
(`policy-datetime`), a negative integer (`policy-negative`), or an integer above
|
||
`2^53 − 1` (`policy-integer-range`), visiting members in byte order of their keys and
|
||
arrays in order.
|
||
|
||
**3.3** *Schema.* Within each table, a key this section does not define is refused
|
||
first (`policy-unknown-key`, the first in byte order), then each member in the order
|
||
listed: a required member that is absent (`policy-missing-key`), a member of another
|
||
type (`policy-type`), a value outside its range or vocabulary (`policy-value`). An
|
||
absent table reads as empty.
|
||
|
||
| Member | Type | Default | Range |
|
||
|---|---|---|---|
|
||
| `schema` | string | required | `wai.pack.policy/1` (else `policy-schema`) |
|
||
| `asset_id` | string | required | 1 to 255 bytes |
|
||
| `royalty_free_only` | boolean | `false` | |
|
||
| `lead` | string | required | 1 to 255 bytes |
|
||
| `component` | array of tables | required | |
|
||
| `component.id` | string | required | 1 to 255 bytes |
|
||
| `component.media` | string | required | `video`, `image`, `audio`, `world`, `feed`, `avatar`, `film`, `graphics`, `text` |
|
||
| `component.source` | string | absent | |
|
||
| `component.timebase` | table | required | members `num`, `den`, each 1 to `2^32 − 1`, required |
|
||
| `component.profile` | string | absent | `floor-only` (§7.8); absent, the standard profile |
|
||
| `component.lang` | string | absent | 1 to 63 ASCII letters, digits and hyphens, none first, last or doubled: a text component's language, a BCP 47 tag (§5.8) |
|
||
| `segmentation.target_ms` | integer | 2000 | |
|
||
| `segmentation.min_ms` | integer | 1000 | |
|
||
| `segmentation.max_ms` | integer | 4000 | |
|
||
| `segmentation.scene_cut` | string | `wai.pack.scenecut/1` | `wai.pack.scenecut/1` |
|
||
| `segmentation.cut_abs_permille` | integer | 300 | 1 to 1000 |
|
||
| `segmentation.cut_rel_permille` | integer | 3000 | 0 to 1000000 |
|
||
| `segmentation.cut_window` | integer | 8 | 3 to 64 (a window of fewer than 3 scores never applies `cut_rel_permille`, §4.3) |
|
||
| `segmentation.lookahead_frames` | integer | 6 | 0 to 64 |
|
||
| `segmentation.closed` | boolean | `true` | |
|
||
| `ladder` | array of tables | empty | |
|
||
| `ladder.id` | string | required | 1 to 255 bytes |
|
||
| `ladder.component` | string | required | 1 to 255 bytes |
|
||
| `ladder.capability` | string | required | |
|
||
| `ladder.role` | string | required | `base`, `refinement`, `floor` |
|
||
| `ladder.refines` | string | absent | |
|
||
| `ladder.params` | table | empty | by capability, below |
|
||
| `floor.supplied_listing` | string | absent | 64 lowercase hexadecimal digits |
|
||
| `floor.extra_keyframes` | boolean | `false` | |
|
||
| `floor.supplied_form` | string | absent | `cmaf` (§7.7); absent, the OBU form (§7.3) |
|
||
| `protection` | table | absent | absent, the package has no protected floor; present, `cpix_sha256` and `key` are required (§7.10) |
|
||
| `protection.cpix_sha256` | string | required | 64 lowercase hexadecimal digits: the SHA-256 of the CPIX document the keys come in |
|
||
| `protection.systems` | array of strings | empty | system IDs of the DASH-IF registry in canonical lowercase UUID text, none twice, none all zero, neither `1077efec-c0b2-4d02-ace3-3c1e52e2fb4b` nor `e2719d58-a985-b3c9-781a-b030af78d30e` |
|
||
| `protection.licence_uri` | string | absent | 1 to 1024 of RFC 3986's characters, no brace: the Clear Key licence URI |
|
||
| `protection.key_uri` | string | absent | as `licence_uri`, holding `{kid}` exactly once and no other brace: the HLS key URI of an `identity` key |
|
||
| `protection.key` | array of tables | required | at least one |
|
||
| `protection.key.floor` | string | required | 1 to 255 bytes: a floor name (§7.1) |
|
||
| `protection.key.scheme` | string | required | `cenc`, `cbcs` |
|
||
| `protection.key.kid` | string | required | a KID in canonical lowercase UUID text, not all zero |
|
||
| `live.layer_lag_ms` | array of integers | one 0 per layer | §3.4 |
|
||
| `class` | array of tables | empty | |
|
||
| `class.id` | string | required | 1 to 255 bytes |
|
||
| `class.max_layer` | integer | 0 | 0 to 255 |
|
||
| `class.max_kbps` | integer | absent | 1 to `2^32 − 1` |
|
||
| `class.floor_only` | boolean | `false` | |
|
||
| `moment` | array of tables | empty | |
|
||
| `moment.id` | string | required | 1 to 255 bytes |
|
||
| `moment.from_ms`, `moment.to_ms` | integer | required | `to_ms` above `from_ms` |
|
||
| `moment.tags` | array of strings | empty | |
|
||
| `rule` | array of tables | empty | |
|
||
| `rule.id` | string | required | 1 to 255 bytes |
|
||
| `rule.when.piece_kind` | array of strings | absent | `natural`, `static`, `synthetic`, `motion_high` |
|
||
| `rule.when.moment_tag` | array of strings | absent | |
|
||
| `rule.then.ladder` | array of strings | absent | |
|
||
| `rule.then.max_layer` | integer | absent | 0 to 255 |
|
||
| `rule.then.deadline_ms` | integer | absent | 1 to `2^32 − 1` |
|
||
| `defaults.deadline_ms` | integer | 1500 | 1 to `2^32 − 1` |
|
||
| `classify.static_permille` | integer | 5 | 0 to 1000 |
|
||
| `classify.static_mad_milli` | integer | 300 | |
|
||
| `classify.synthetic_bins` | integer | 12 | 0 to 64 |
|
||
| `classify.motion_high_milli` | integer | 9000 | |
|
||
|
||
A rung's `params` are those of its capability; every other capability takes none.
|
||
|
||
| Capability | Parameter | Default | Range |
|
||
|---|---|---|---|
|
||
| `wai.video.int_motion` | `block` | 16 | 4, 8, 16, 32 |
|
||
| | `search` | 7 | 0 to 32 |
|
||
| | `mv_precision` | 2 | 1, 2 |
|
||
| | `q_candidates` | `[8]` | a non-empty array, each 1 to 255 |
|
||
| | `max_sse_px_milli` | 0 | |
|
||
| `wai.video.int_refine` | `q` | required | 1 to 255 |
|
||
| `wai.video.av1`, `wai.video.av1.lossless` | `source` | `supplied` | `supplied`, `encode` |
|
||
| `wai.audio.int_refine` | `to_rate`, `q` | required | 1 to 384000; 1 to 65535 |
|
||
| `wai.audio.flac` | `source` | `encode` | `encode`, `supplied` |
|
||
| `wai.audio.opus` | `source` | `supplied` | `supplied` |
|
||
| `wai.text.zstd` | `source` | `encode` | `encode` |
|
||
|
||
`wai.audio.int_layered` is reserved for the layered integer audio base: it is not
|
||
registered yet, and its parameters are defined when it is.
|
||
|
||
**3.4** *Constraints.* A policy that passes §3.3 is checked in this order, and the
|
||
first that fails is the refusal:
|
||
|
||
1. 1 to 8 components (`policy-too-many-components`); at most 64 rungs
|
||
(`policy-too-many-rungs`), 32 classes (`policy-too-many-classes`), 32 rules
|
||
(`policy-too-many-rules`) and 32 moments (`policy-too-many-moments`).
|
||
2. Ids unique among components, rungs, classes, moments and rules taken together, so
|
||
a reference never names two things (`policy-duplicate-id`).
|
||
3. `lead` names a component (`policy-lead`).
|
||
4. `segmentation.closed` is true: every piece is closed, §4.1 (`policy-open-gop`).
|
||
5. `1 ≤ min_ms ≤ target_ms ≤ max_ms ≤ 600000`, and `max_ms` is at most `2^24` units
|
||
on every component's timebase (§4.4) (`policy-piece-bounds`).
|
||
6. Every rung names a component (`policy-ladder-component`).
|
||
7. A rung carries `refines` exactly when its role is `refinement`
|
||
(`policy-refines-role`); `refines` names a rung (`policy-dangling-refines`); no
|
||
chain of `refines` returns to a rung it passed (`policy-refines-cycle`); every
|
||
chain stays in its component and ends at a base (`policy-refines-base`); no rung is
|
||
refined twice (`policy-refines-branch`).
|
||
8. Every component has a base rung (`policy-no-base`), but a floor-only one (§7.8), which
|
||
has no base and no refinement rung, no `source`, and at least one floor rung
|
||
(`policy-floor-only`).
|
||
9. A rung's **layer** is 0 for a base or a floor, and one more than the rung it
|
||
refines for a refinement; the policy has one more layer than its highest rung
|
||
layer. No class's and no rule's `max_layer` exceeds the highest
|
||
(`policy-class-layer`).
|
||
10. A rule's `then.ladder` names known rungs, all of one component, each once:
|
||
exactly one base (none, for a floor-only component), every refinement over a rung it
|
||
names (so its layers are a chain from that base), and every floor rung of the
|
||
component (every piece has a floor, §7.1) (`policy-rule-ladder`).
|
||
11. `live.layer_lag_ms` has one entry per layer, the first 0, the entries
|
||
non-decreasing (`policy-layer-lag`).
|
||
12. The deadline values (`defaults.deadline_ms` and every rule's `then.deadline_ms`)
|
||
number at most 4 (`policy-too-many-deadlines`), and for every refinement layer
|
||
*k* and every deadline value *d*, `d − layer_lag_ms[k] ≥ 1`
|
||
(`policy-deadline-unreachable`).
|
||
13. With a `protection` table (§7.10): every key names a floor with CMAF files, the lead's AV1
|
||
floors and each sound track's Opus floor, by its floor name; and for each scheme the keys
|
||
name, every such floor has exactly one key of it, the floors of one component the same
|
||
KID (`policy-protection`).
|
||
|
||
Rules fold in document order: every matching rule applies, and a later `then` member
|
||
overrides an earlier one.
|
||
|
||
**3.5** A package receipt carries the policy digest. A verifier holding the effective
|
||
policy recomputes it; it cannot check that the packager followed the policy, except
|
||
by packing again.
|
||
|
||
**3.6** *What a packager may emit.* A packager **MUST** refuse a policy with a rung it
|
||
may not emit, reading SPEC §5's registry, never the policy. For each rung in ladder
|
||
order the first rule that holds is its refusal, and a policy's refusal is its first
|
||
rung's:
|
||
|
||
1. SPEC §5 does not register the capability: `capability-unknown:<cap>`. One capability
|
||
is read before this rule with the classes stated here, and with no licence class:
|
||
`wai.text.timed`, which SPEC §5 registers as a record, as the base of a text component,
|
||
whose payload is a WebVTT segment (§5.8): standalone media of class `StandardDefined`.
|
||
Rules 2 to 4 read those classes, and under `royalty_free_only` rule 5 refuses it
|
||
`license-unclassified:<cap>`. (`wai.graphics.vector` is registered, as standalone
|
||
media of class `StandardDefined`, royalty-free: [graphics-vector](graphics-vector.md)
|
||
§1.8.)
|
||
2. SPEC §5 registers it as a record or a derivation operation, which names no payload
|
||
format: `capability-not-payload:<cap>`.
|
||
3. It is a candidate, whose payload format no revision has pinned (SPEC §5,
|
||
*Candidate capabilities*): `capability-candidate:<cap>`.
|
||
4. A base or a floor whose capability is not standalone media, or a refinement whose
|
||
capability is not refinement-only: `capability-role:<cap>`.
|
||
5. Under `royalty_free_only`, a capability the registry gives no licence class:
|
||
`license-unclassified:<cap>`; one of any class other than royalty-free:
|
||
`license-refused:<cap>:<label>`, with the class's SPEC §5 label. The class is the
|
||
registry's, "licensed royalty-free by its standards body"; it is not an opinion on
|
||
freedom to operate, and a package states nothing about patents.
|
||
|
||
## 4. Pieces
|
||
|
||
**4.1** Every piece **MUST** be closed: its first frame, sample or tick is coded with
|
||
no reference outside the piece, and no later object of the package references into
|
||
it.
|
||
|
||
**4.2** Piece boundaries are a pure function of the source frames and the effective
|
||
policy; a floor-only component's, which has no source, of its first floor rung (§7.8).
|
||
Where the policy names `wai.pack.scenecut/1`, the boundaries of a component with a
|
||
source **MUST** be those §4.3 computes. The list of piece starts, in timebase units, is the **plan**, and its
|
||
digest is
|
||
|
||
BLAKE3("wai:pack-plan\x01" ‖ u64 LE n ‖ n × u64 LE start)
|
||
|
||
**4.3** `wai.pack.scenecut/1`. All arithmetic is integer; `>>` is a shift and every
|
||
division rounds down. For each RGB8 frame *f* of `w × h` pixels:
|
||
|
||
1. Luma `Y = (77R + 150G + 29B + 128) >> 8`, and the histogram `H_f` of 64 bins,
|
||
`Y >> 2`.
|
||
2. `D(a, b) = (Σ_i |H_a[i] − H_b[i]|) · 1000 / (2wh)`: the share of pixels, in
|
||
thousandths, whose bin differs. The score of frame *f* ≥ 1 is `s_f = D(f, f − 1)`.
|
||
3. The current piece starts at frame *p*, and `len = f − p`. Its window `W_f` is the
|
||
scores `s_g` for `p < g < f`, the last `cut_window` of them; `m_f` is their lower
|
||
median (sorted ascending, the element at index `(|W_f| − 1) / 2`).
|
||
4. **Hard cut** at *f*: `len ≥ min`, `s_f ≥ cut_abs`, and either `|W_f| < 3` or
|
||
`s_f · 1000 ≥ cut_rel · m_f`.
|
||
5. **Soft cut** at *f*: `len ≥ target` and `2 · s_f ≥ cut_abs`.
|
||
6. **Return clause.** A cut at *f* is rejected when `D(f + k, f − 1) < cut_abs` for
|
||
some *k* in `1..=min(L, last − f)`: the content came back within *L* frames, so *f*
|
||
starts a flash.
|
||
7. **Revisit clause.** A cut at *f* is rejected when `D(f, f − 1 − j) < cut_abs` for
|
||
some *j* in `1..=min(L, f − 1)`: *f* returns to content seen within *L* frames, so it
|
||
ends a flash. Frames before *p* count.
|
||
8. A hard or soft cut that clauses 6 and 7 do not reject starts a piece at *f*.
|
||
Otherwise, when `len ≥ max`, a piece starts at *f*.
|
||
|
||
Frame 0 starts the first piece. *L* is `lookahead_frames`, *last* the index of the
|
||
source's last frame, `cut_abs` and `cut_rel` the policy's permille values, and `min`,
|
||
`target` and `max` the policy's milliseconds on the component's timebase (§4.4). A
|
||
decision about frame *f* reads no frame after `f + L`, so a live segmenter decides it
|
||
once frame `f + L` has arrived, and a live and an on-demand segmenter with equal *L*
|
||
produce equal boundaries. A piece that follows a cut is flagged a scene cut; one
|
||
started at `max`, forced.
|
||
|
||
**4.4** A piece's duration is an integer number of timebase units, and lies between
|
||
`min` and `max` except the last piece of a source, which may be shorter than `min`.
|
||
A duration in milliseconds converts to timebase units as
|
||
`floor(ms · num / (1000 · den))`; a result of 0 becomes 1, and one above `2^64 − 1`
|
||
becomes `2^64 − 1`.
|
||
|
||
A piece is at most ten minutes long (`max_ms` at most 600000) and at most `2^24` units
|
||
on its component's timebase (§3.4 item 5). A verifier takes a piece whole: it decodes
|
||
every frame of a base, holds a still's one frame for every frame of its piece and hashes
|
||
each (§5.7, §6.5), and reads one floor unit per frame (§7.2). So the longest piece a
|
||
policy allows is the most work a verifier does for one piece, and a package states it.
|
||
Ten minutes is far past any piece a delivery cuts (the default `max_ms` is four
|
||
seconds); `2^24` units bounds that work on any timebase, since a timebase may have up to
|
||
`2^32 − 1` units a second.
|
||
|
||
**4.5** *Components.* Every component of a package shares one presentation timeline
|
||
that starts at 0. The policy names one **lead** component; every other component cuts
|
||
its pieces at the lead's boundary instants, each converted to its own timebase as
|
||
`floor(start_lead · num_c · den_lead / (den_c · num_lead))` (above `2^64 − 1`,
|
||
`2^64 − 1`). A sink presents piece *p* of every component from that start. A component's
|
||
piece ends where its next one starts, and its last piece at the lead's end converted the
|
||
same way, so every component spans the lead's whole timeline; a piece that would last no
|
||
unit, or more than `2^32 − 1`, is refused (`pack-piece-length`). Each piece of a component
|
||
beside the lead has flag bit 3, aligned to the lead, and no other flag (§6.2).
|
||
|
||
**4.6** *Classification.* Each video piece has a kind, which rules match
|
||
(`rule.when.piece_kind`). Over the piece's consecutive frame pairs, with `Y` the luma
|
||
of §4.3:
|
||
|
||
- `mad_milli = Σ |Y_f − Y_{f−1}| · 1000 / (wh · (n − 1))`, and 0 when the piece has
|
||
one frame (*n* = 1);
|
||
- `changed_permille` = the pixels whose luma differs, `· 1000 / (wh · (n − 1))`, and 0
|
||
when *n* = 1;
|
||
- `occupancy` = the non-empty histogram bins of the piece's first frame.
|
||
|
||
The kind is the first that holds of: **static** (`mad_milli ≤ static_mad_milli` and
|
||
`changed_permille ≤ static_permille`); **synthetic** (`occupancy ≤ synthetic_bins`);
|
||
**motion_high** (`mad_milli ≥ motion_high_milli`); **natural**.
|
||
|
||
**4.7** *Sources.*
|
||
|
||
- **Y4M**: 8-bit 4:2:0 (`C420jpeg`, `C420paldv`, `C420mpeg2`, `C420`, or no `C`
|
||
parameter) or 4:4:4 (`C444`), progressive. A header that is not `YUV4MPEG2` with a
|
||
width and a height is refused `y4m-header`; an interlaced stream or frame,
|
||
`y4m-interlaced`; another colour space, `y4m-colorspace`; a `FRAME` line that sets
|
||
the width, height or colour space, `y4m-size-change`; a frame that ends early or a
|
||
line that is not `FRAME`, `y4m-truncated`. Each pixel converts to RGB8 by BT.709
|
||
limited range, with 4:2:0 chroma taken at `(y >> 1, x >> 1)`:
|
||
- `R = clip((298(Y−16) + 459(Cr−128) + 128) >> 8)`
|
||
- `G = clip((298(Y−16) − 55(Cb−128) − 136(Cr−128) + 128) >> 8)`
|
||
- `B = clip((298(Y−16) + 541(Cb−128) + 128) >> 8)`
|
||
|
||
where `>>` is the arithmetic shift and `clip` clamps to 0..=255.
|
||
- **WAV**: RIFF/WAVE with a PCM `fmt ` chunk (format tag 1) of 16-bit samples, block
|
||
alignment `2 · channels`. Anything else is refused `wav-format`; a chunk that ends
|
||
early, `wav-truncated`.
|
||
|
||
## 5. Objects
|
||
|
||
**5.1** *One envelope.* Each object is exactly one `WAI1` or `WAI2` envelope and
|
||
nothing after it ([http-delivery](http-delivery.md) §5.1).
|
||
|
||
**5.2** *Position independence.* An object's bytes **MUST NOT** depend on its position
|
||
in a package or on the package it belongs to: no member of its manifest names a piece,
|
||
a time, an asset, a class or a component, so two pieces with identical content are one
|
||
object. The reference packager writes a base's manifest as SPEC §3 orders it, with
|
||
`media` the component's media, `intent` `replicate`, `model_requirement` the rung's
|
||
capability with no `fallback` member, `conditioning.kind` the capability's last
|
||
dot-separated segment (`int_motion`, `av1`), and `target` `{"frames": n, "h": h, "w": w}`
|
||
(the piece's frame count and dimensions, for a still too, whose one frame is held for
|
||
the piece, §5.7; for a floor, its sequence header's maximum frame height and width); a
|
||
floor's `derived_from` (§7.2) comes after `conditioning`.
|
||
|
||
**5.3** *Name.* An object is named by its content hash ([jwp-receipts](jwp-receipts.md)
|
||
§2.1), `BLAKE3("moq-jwp:object\x01" ‖ envelope)`, as `objects/<64 lowercase hex>.wai`:
|
||
the same value is its stage-index entry (§6), its receipt leaf, its MoQ content hash and
|
||
its HTTP strong `ETag` ([http-delivery](http-delivery.md) §5.2).
|
||
|
||
**5.4** *Refinements.* A refinement object carries `refines`
|
||
([staged-delivery](staged-delivery.md) §3): `base` the content hash of its piece's base,
|
||
`layer` its layer, and as producers in `prev` only objects of the layers below it, the
|
||
object its entry refines (§6.2) among them.
|
||
|
||
The reference packager writes, for each refinement rung of a piece's ladder (§5.7), one
|
||
object of `wai.video.int_refine` over the piece's `wai.video.int_motion` base, layer by
|
||
layer. Its manifest is the one `wai stage refine` writes, as SPEC §3 orders it: `media`
|
||
the component's, `intent` `replicate`, the capability with no `fallback`,
|
||
`conditioning.kind` `wir1`, `refines` with `base` the base's content hash, `prev` the
|
||
content hash of the object of the layer below (the base for layer 1) as a string, and
|
||
`layer`, and `target` null. Its payload is a `WIR1` (staged-delivery Appendix A) of unit
|
||
kind RGB8, one unit per frame of the piece, the identity predictor and one table,
|
||
written by `wai-rs`'s refinement encoder (`staged::wir1::encode`) at the rung's step
|
||
`q`:
|
||
|
||
- each sample's residual is `rdiv(source − below, q)`, the quotient rounded half away
|
||
from zero, where *below* is the stage the layer refines as a sink reconstructs it;
|
||
no frame predicts another;
|
||
- the table's `offset` is the least residual and it spans every residual (where they
|
||
span more than 4095, the 4095 around the lower median), fitted by §5.5's rule, the
|
||
escape at 1; a residual past it is coded through the escape;
|
||
- one rANS stream per frame.
|
||
|
||
The stage the layer reconstructs, `clamp(below + r·q, 0, 255)`, is what the next layer
|
||
refines. The packer applies each layer to the decoded base with the reference sink
|
||
(staged-delivery §4) before it keeps it, and the sink's output must be the encoder's.
|
||
|
||
**5.5** *Integer video.* An object of `wai.video.int_motion` carries a `WIV1` payload
|
||
with keyframe 0, as SPEC §5 pins it ([integer-payloads](integer-payloads.md) §3); this
|
||
extension defines no other layout for it, and its decode is that section's. The
|
||
reference packager writes these with one encoder (`wai-rs` `int_video_enc`) at the step
|
||
§5.6 chooses. The encoder's choices
|
||
make a payload a function of the source frames, the rung's `block`, `search` and
|
||
`mv_precision` (§3.3), and the step `q`:
|
||
|
||
- **Motion search** (frames 1 on): blocks in raster order; for each, every vector
|
||
`(dy, dx)` with both components in `−R..=R`, `R = search · mv_precision` (at most
|
||
127), `dy` outer and `dx` inner, each ascending. Its cost is the sum, over the
|
||
block's pixels inside the frame and the three channels, of `|source − prediction|`,
|
||
where the prediction is the decode's (integer-payloads §3) from the previous frame
|
||
as decoded. The least cost wins; a tie goes to the smaller `|dy| + |dx|`, then to the
|
||
earlier vector.
|
||
- **Residual**: each frame, each pixel in raster order, each of R, G, B: the
|
||
prediction (128 for frame 0) is subtracted from the source and divided by `q`,
|
||
rounding halves away from zero; the frame as decoded is `clamp(prediction + r·q, 0,
|
||
255)`, and the next frame predicts from it.
|
||
- **Table**: one residual table for the clip, with `offset` the least residual, `top`
|
||
the greatest, and `h_i` the count of residual `offset + i`, for
|
||
`n = min(top − offset + 1, 4095)` symbols. With `R = 2^16 − n − 1` and `H = Σ h_i`, symbol *i* gets frequency
|
||
`1 + ⌊R·h_i / H⌋`; the `R − Σ⌊R·h_i / H⌋` left over go one each to the symbols with
|
||
the largest `R·h_i mod H`, the lower symbol first on a tie; the escape gets 1. A
|
||
residual past the table's span is coded through the bypass escape
|
||
(integer-payloads §2).
|
||
- A clip of one frame is written with `block` 1 and `mv_precision` 1, as the format
|
||
requires.
|
||
|
||
The encoder also states the SSE of its reconstruction against the source: the sum of
|
||
squared differences over every sample. It codes in two passes (the first finds the
|
||
vectors, the reconstruction and the residuals' counts; the second derives each
|
||
residual again and codes it last first), so beyond its input it holds the
|
||
reconstruction, the vectors, the stream and the residual table: at most
|
||
`n·h·w·3 + 24n + 12·v + 3·(2·n·h·w·3 + 16) + T` bytes for `n` frames and `v` motion
|
||
vectors (`int_video_enc::working_bytes`). `T` is the widest table's, of `S = 2·255 + 1`
|
||
symbols: its fit, `16·(S + 1) + 2·8·S + 4·(S + 2)` bytes (a frequency for each symbol
|
||
and the escape, an index for each symbol and the sort's scratch, the table), and the
|
||
table's three copies and the payload's header, `3·4·(S + 2) + 64`. For a clip past the
|
||
format's caps, which the encoder refuses before it allocates, the figure is `2^64 − 1`.
|
||
|
||
**5.6** *Choosing the step.* For each piece, the base encode of a `wai.video.int_motion`
|
||
rung chooses its step from `q_candidates`, read as a set (a value listed twice is one
|
||
candidate). It encodes the piece at each candidate, from the largest down, and chooses
|
||
the first whose `floor(SSE · 1000 / (3·w·h·n)) ≤ max_sse_px_milli`, where `SSE` is the
|
||
encoder's (§5.5) and *n* the piece's frames: the largest step that meets the bound, so
|
||
candidates that meet it alike are decided by their step. Where no candidate meets it,
|
||
it chooses the smallest. The function the reference packager's base encode calls is
|
||
`wai_pack::encode::choose_q`.
|
||
|
||
**5.7** *A piece's ladder, and stills.* A piece is coded in the rungs of its **ladder**:
|
||
those the matching rules' `then.ladder` names (§3.4 folds them: the last that sets it),
|
||
where they are rungs of the piece's component; otherwise the component's default ladder,
|
||
its first base rung in ladder order, the chain of refinement rungs over it, and its floor
|
||
rungs. A rule's ladder therefore chooses a piece's representation: its base, how many
|
||
layers refine it, and nothing else (§3.4 item 10). A piece has one entry per rung of its
|
||
ladder: its base, each refinement in layer order, then each floor (§6.2), and a
|
||
verifier holds an index to that (§11.3).
|
||
|
||
A base of `wai.image.png` is a **still**: the piece's first frame, held for the piece. A
|
||
sink presents that frame for each of the piece's frames, so its prefix is the frame
|
||
repeated and its digest the clip digest of the repeats (§6.5), its `sse` is over the
|
||
repeats against the piece's frames (§6.6), and its manifest's `target` names the piece's
|
||
frames (§5.2). The reference packager writes the frame as a PNG every byte of which is a
|
||
pure function of the frame, so an independent implementation writes the same file:
|
||
|
||
- the signature, `IHDR` (the width, the height, bit depth 8, colour type 2, compression
|
||
0, filter 0, interlace 0), one `IDAT` holding the whole compressed stream, and `IEND`;
|
||
each chunk a big-endian length, its type, its data and the CRC-32 of type and data;
|
||
- every row filtered `Sub` (filter byte 1): each byte less the byte three before it in
|
||
the row, modulo 256, the first pixel's bytes less 0;
|
||
- a zlib stream (RFC 1950) of `0x78 0x01`, one final deflate block (RFC 1951) of the
|
||
fixed Huffman codes, and the Adler-32 of the filtered rows;
|
||
- the block's symbols, from the first byte of the filtered rows: where at least three
|
||
bytes remain and an earlier position within 32768 bytes starts with the same three
|
||
bytes, the latest such position is the match, as long as the two runs agree (at most
|
||
258 bytes), written as that length and the distance to it; otherwise one literal.
|
||
Every position a symbol covers that has three bytes left becomes the latest position
|
||
of its three bytes. The block ends with symbol 256, and the last byte is padded with
|
||
zero bits.
|
||
|
||
A verifier reads a still as a PNG of 8-bit RGB without interlace, by PNG's chunk rules:
|
||
every chunk's type four letters and its CRC checked; `IHDR` first; an optional `PLTE`
|
||
(a suggested palette) before the image data; the `IDAT`s consecutive; ancillary chunks
|
||
read past; any other critical chunk refused; `IEND` last, empty, and nothing after it.
|
||
It inflates the zlib stream as the zlib library's inflate does, so two verifiers agree
|
||
on every stream: the header's check bits, method 8, a window of at most 32 KiB and no
|
||
preset dictionary; a stored block's length and its complement agree; at most 286
|
||
literal/length and 30 distance codes, a complete code-length code, a repeat with a
|
||
length before it and within the code lengths, and a code for the end of the block; no
|
||
Huffman code over-subscribed, and none incomplete but a literal/length or distance code
|
||
whose longest code is one bit (a lone one-bit code, or none); a code with no symbol, or
|
||
a distance past the bytes written, refused where it is read; the Adler-32, and nothing
|
||
after it. The rows are exactly `1 + 3·width` bytes each, under the five filters. A still
|
||
a verifier does not read is `prefix-digest` (§11.3).
|
||
|
||
**5.8** *Components beside the lead.* A package carries up to 8 components on one
|
||
presentation timeline (§3.4 item 1, §4.5). Its lead is a video component, coded as §5.1–§5.7
|
||
say; every other component is cut at the lead's piece starts (§4.5) and each of its pieces
|
||
is one base object of the component's base rung, and, where its media class has one, one
|
||
floor object (§7.1). A component beside the lead has one base rung and no refinement rung.
|
||
Each base object is §5.2's manifest for its capability with `target` as below, and states
|
||
its piece's decoded-output digest (§6.5) where its tier is decode-equivalence (§6.4). Its
|
||
source is the file the packager is given for the component's id.
|
||
|
||
| media | base capability | source | the piece's object | `target` | decoded-output digest (§6.5) |
|
||
|---|---|---|---|---|---|
|
||
| `audio` | `wai.audio.flac` | a WAV (§4.7) of one or two channels, at the component's timebase `rate/1` | the piece's samples as FLAC, by the encoder below | `{"channels", "rate", "samples"}`, `samples` the piece's sample frames | SHA-256 of the piece's samples as interleaved `s16le` |
|
||
| `audio` | `wai.audio.score` | a `WSCR` ([score](score.md) §2) of one or two channels at `sample_rate/1` | a `WSCR` of the same contract with `duration` the piece's samples, holding each note that sounds in the piece, its `start` made relative to the piece, in the source's order, and in a voiced score (score §2.4) each one's voice | as `wai.audio.flac`, `channels` the contract's | SHA-256 of the piece's mixdown (score §3.4) as `s16le`, two channels interleaved |
|
||
| `graphics` | `wai.graphics.vector` | a `WVG1` ([graphics-vector](graphics-vector.md) §2) whose `t0` is 0 and whose `dur` is the component's units times `P` of its rate | the source with `t0` and `dur` set to the piece's ticks, `piece start · P` and `piece units · P`, and each `SIZE.cost` restated for that window (graphics-vector §3.4(b)); nothing else changes | `{"frames", "h", "w"}`, the first listed size | the clip digest (SPEC §7) of the piece's frames `g` as `RGBA8P` at the first listed size and the component's rate |
|
||
| `text` | `wai.text.timed` | a WebVTT document in the component's `lang` (timed-text §5.1, read by §5.3), on the timebase `1000/1` | the WebVTT segment of the piece's span (timed-text §5.2, `mpegts0` 0) | `{"lang", "ms"}`: the component's `lang`, which a segment does not state, and the piece's milliseconds | SHA-256 of the segment §5.2 writes of the cues the payload reads to (§5.3), which is the payload |
|
||
| `feed` | `wai.feed.state` | a `WWLD` of the sim `wai.world.sim.feed0` ([state-feeds](state-feeds.md)) with no assets, at `tick_rate/1` | a `WWLD` of the source's contract, its graph the state after every op of a tick before the piece, its ops those of the piece's ticks as the source signs them, sealed with the package's key at the piece's last tick | `{"ticks"}` | none: its seal states its state hash |
|
||
|
||
`wai.text.timed` is a record (SPEC §5), which a sink does not dispatch to a decoder: in a
|
||
package its payload is the WebVTT segment its extension writes, which a sink reads and draws
|
||
in its own text layer ([browser-sink](browser-sink.md) §14.3), and a packager reads it as
|
||
standalone media of class `StandardDefined`, with no licence class (§3.6 rule 1): the reader
|
||
of timed-text §5.3 reads exactly what §5.2 writes, so the cues a segment reads to are written
|
||
back byte for byte. `rate` is the component's
|
||
timebase `num`, whose `den` is 1; `P` is graphics-vector §3.1's ticks per frame. A
|
||
component's source spans the lead's whole timeline: the WAV's sample frames, the score's
|
||
`duration` and the document's `dur` are the lead's end converted (§4.5), every cue ends by
|
||
it and every op's tick is before it (`pack-source-length`). A source of another rate than
|
||
the component's timebase, or a graphics document that does not list it, is refused
|
||
(`pack-source-rate`), and one that does not read as its format, `pack-source-format` (a
|
||
WAV's and a WebVTT document's own reasons, §4.7 and timed-text §12, first).
|
||
|
||
- *Audio.* A FLAC base is of the mandatory floor (SPEC §5), so its component needs no floor
|
||
rung; it may have one, of `wai.audio.opus` supplied in the CMAF form (§7.9), which a sink
|
||
that plays Opus takes in its place. A score's has one floor rung, of `wai.audio.flac` with `source = "encode"`: each
|
||
piece's floor is the FLAC, by the encoder below, of the piece's mixdown in the score's
|
||
channels, its `derived_from` naming the piece's base, `renderer` `flac-i16/1` and the
|
||
settings `{"block": 4096, "source": "encode"}`. The encoder is a pure function, so it
|
||
names no device or version (§7.5). A note that sounds across a piece start (from its
|
||
`start` to its end, score §2.4: `start + dur + release`, and in a voiced score its
|
||
echoes' `echo_taps × echo_delay` after that) is refused (`pack-score-crossing`): a piece
|
||
is closed (§4.1), and the pieces' mixdowns then are the whole's, sample for sample.
|
||
- *Graphics.* Each piece is the document's window slice over the piece's ticks
|
||
([graphics-vector](graphics-vector.md) §6.1): a frame is keyed by its global `g`, so a
|
||
piece's frames are the whole document's (graphics-vector §3.11, P7), and every piece of
|
||
the component has one document digest (graphics-vector §6.2). The component may have one
|
||
floor rung, of `wai.text.zstd` with `source = "encode"`, and SHOULD have it when its
|
||
document holds a block that is not decorative: each piece's floor is then its transcript
|
||
in the raw Zstandard frame, in the object graphics-vector §6.3 defines, its
|
||
`derived_from` naming the piece's base and `renderer` `wvg1-transcript/1`. A document
|
||
holding a block of purpose `caption`, `subtitle` or `forced_narrative` is packed only
|
||
beside a text component in the block's language whose cues carry the same strings over
|
||
the same milliseconds (graphics-vector §6.4), and refused `caption_without_track`
|
||
otherwise. Held outline sets resolve from the sets every reference sink holds
|
||
(graphics-vector §4.4).
|
||
- *Text.* A text component names its `lang` (`pack-text-lang`); a source that is a segment
|
||
(it carries `X-TIMESTAMP-MAP`) is refused `vtt_malformed`. A cue across a piece start is in
|
||
both pieces' segments, each with its whole interval (timed-text §5.2).
|
||
- *Feed.* Each piece is closed: an op that depends on an op of another piece is refused
|
||
(`pack-feed-crossing`), and a piece must reach tick 1 (`pack-piece-length`). The pieces
|
||
chain: each piece's graph hashes to the state hash the piece before it seals.
|
||
|
||
*The FLAC encoder* (`flac-i16/1`, `wai-rs` `codecs::audio::flac_encode_i16`) writes 16-bit
|
||
samples of one or two channels as RFC 9639 FLAC, every byte a function of the samples, the
|
||
channel count and the rate: `fLaC`; one STREAMINFO block, the last metadata block, with
|
||
minimum and maximum block size 4096, minimum and maximum frame size 0, the rate, the
|
||
channels, 16 bits per sample, the inter-channel sample count and an MD5 of zeros; then per
|
||
4096 inter-channel samples (the last block fewer) one frame of fixed block size: sync
|
||
`0xFFF8`, block-size code 7, sample-rate code 0, independent channels, sample-size code 4,
|
||
the frame number (RFC 9639 §9.1.5), the block size less one in 16 bits and the header's
|
||
CRC-8; per channel, the first of the shortest of the fixed predictors of orders 0 to 4
|
||
(below the block's length; Rice coding, partition order 0, the parameter 0 to 14 whose code
|
||
is shortest, the least on a tie) and then verbatim; no wasted bits; zero bits to a byte;
|
||
the frame's CRC-16.
|
||
|
||
## 6. Stage index
|
||
|
||
**6.1** A package carries, per component, one stage index, the file `index-<id>.wsix`
|
||
(`WSIX`), per window: one window for on-demand content, a chain of windows for live
|
||
(§9.2). A window holds whole pieces. Integers are little-endian.
|
||
|
||
**6.2** *Layout.* A 160-byte header, then `n_pieces` piece records of 32 bytes,
|
||
`n_entries` entry records of 128 bytes, `n_classes` class records of 16 bytes, the class
|
||
plan, and the string table. Nothing follows the string table. Reserved and padding
|
||
bytes are zero.
|
||
|
||
| Offset | Size | Header field |
|
||
|---|---|---|
|
||
| 0 | 4 | `"WSIX"` |
|
||
| 4 | 2 | version, 1 |
|
||
| 6 | 2 | `media_class`: 0 video, 1 image, 2 audio, 3 world, 4 feed, 5 avatar, 6 film, 7 graphics, 8 text |
|
||
| 8 | 4 | `tb_num` |
|
||
| 12 | 4 | `tb_den` |
|
||
| 16 | 4 | `n_pieces` |
|
||
| 20 | 4 | `n_entries` |
|
||
| 24 | 4 | `n_strings` |
|
||
| 28 | 4 | `n_classes` |
|
||
| 32 | 32 | `policy_digest` (§3.2) |
|
||
| 64 | 32 | `prev_index_digest`: the previous window's index digest; zero for on-demand content and a first window |
|
||
| 96 | 8 | `first_piece`: the absolute number of the window's first piece |
|
||
| 104 | 8 | `first_entry`: the absolute ordinal of the window's first entry across the chain |
|
||
| 112 | 32 | `plan_digest` (§4.2) of the pieces' starts |
|
||
| 144 | 2 | `component`: the component's id, as a string index |
|
||
| 146 | 14 | reserved |
|
||
|
||
| Offset | Size | Piece record field |
|
||
|---|---|---|
|
||
| 0 | 8 | `start`, in timebase units |
|
||
| 8 | 4 | `duration`, in timebase units |
|
||
| 12 | 4 | `first_entry`, window-relative |
|
||
| 16 | 2 | `n_entries` |
|
||
| 18 | 1 | `kind`: 0 natural, 1 static, 2 synthetic, 3 motion_high (§4.6), 4 world segment, 5 audio (an audio component's piece, §5.8), 6 none (a floor-only component's piece, which no source classifies, §7.8, and a piece of a graphics, text or feed component, §5.8) |
|
||
| 19 | 1 | flags: bit 0 scene cut, bit 1 forced at `max` (§4.3), bit 2 started by a cue, bit 3 aligned to the lead (§4.5) |
|
||
| 20 | 4 | `rules`: bit *i* set when rule *i* matched the piece (§8.1) |
|
||
| 24 | 4 | `moments`: bit *i* set when the piece lies in moment *i* (§8.1) |
|
||
| 28 | 4 | reserved |
|
||
|
||
| Offset | Size | Entry record field |
|
||
|---|---|---|
|
||
| 0 | 32 | `content_hash` (§5.3) |
|
||
| 32 | 8 | `byte_len`: the whole envelope's length |
|
||
| 40 | 8 | `piece`: the absolute piece number |
|
||
| 48 | 4 | `refines`: the window-relative index of the entry this one refines; `0xFFFFFFFF` for none |
|
||
| 52 | 2 | `capability`, as a string index |
|
||
| 54 | 1 | `role`: 0 base, 1 refinement, 2 floor, 3 snapshot, 4 ops, 5 receipt, 6 asset |
|
||
| 55 | 1 | `layer` |
|
||
| 56 | 1 | `prefix_tier`: 0 none, 1 entropy-consistency, 2 decode-equivalence (§6.4) |
|
||
| 57 | 1 | flags: bit 0 deferrable, bit 1 duplicate (an earlier entry of the index has the content hash), bit 2 the manifest carries `derived_from` |
|
||
| 58 | 2 | reserved |
|
||
| 60 | 4 | `deadline_ms`: 0 for none (§8.6) |
|
||
| 64 | 32 | `prefix_digest` (§6.5) |
|
||
| 96 | 8 | `sse` (§6.6); `2^64 − 1` when not computed |
|
||
| 104 | 4 | `manifest_len`: the envelope's `man_len` |
|
||
| 108 | 4 | reserved |
|
||
| 112 | 8 | `payload_len`: the envelope's payload bytes (for a `WAI2`, every component's) |
|
||
| 120 | 8 | reserved |
|
||
|
||
`byte_len`, `manifest_len` and `payload_len` are the envelope's own lengths; for an
|
||
object whose payload format WAI checks they are its counted bytes
|
||
([staged-measurement](staged-measurement.md) §3), so a verifier also requires each such
|
||
payload to parse as its format, after the other object rules of §6.3
|
||
(`object-lengths`).
|
||
|
||
A class record is `u16` name (a string index), `u8 max_layer`, `u8` flags (bit 0
|
||
floor only), `u32 max_kbps` (0 for none) and 8 reserved bytes, in the policy's class
|
||
order. The class plan is `n_classes` rows of `n_pieces` bytes, each the highest layer
|
||
the class lists for the piece or `0xFF` for the floor only (§8.1), zero-padded to a
|
||
multiple of 8. The string table is `n_strings` strings, each a `u16` length and that
|
||
many bytes of UTF-8, at least one, zero-padded to a multiple of 8. It is the component's
|
||
id, then the class ids in policy order, then each capability at its first use, and a
|
||
package's verifier holds an index to that (§11.3).
|
||
|
||
index_digest = BLAKE3("wai:pack-stage-index\x01" ‖ file)
|
||
leaf_i = BLAKE3("wai:pack-stage\x01" ‖ entry record i)
|
||
stage_root = the jwp-receipts §2.2 Merkle root over the leaves
|
||
|
||
A reader **MUST** refuse a file that is not this layout, the first that applies, in
|
||
this order:
|
||
|
||
1. its first four bytes are not `WSIX`, a file of fewer included (`index-magic`);
|
||
2. a file of fewer than six bytes (`index-truncated`), or a version other than 1
|
||
(`index-version`);
|
||
3. a file shorter than the header, or than the tables its counts give: the piece, entry
|
||
and class records, the class plan with its padding, each string's length and bytes,
|
||
and the string table's padding, every size taken from its count before anything is
|
||
allocated (`index-truncated`);
|
||
4. a reserved or padding byte that is not zero (`index-reserved`);
|
||
5. a media class with no meaning (`index-media-class`); then, entry by entry, a role,
|
||
then a tier, with none (`index-role`, `index-tier`);
|
||
6. a string that is empty or not UTF-8, then a string index past the table
|
||
(`index-string`);
|
||
7. bytes after the string table's padding (`index-trailing`).
|
||
|
||
**6.3** *Rules.* An index lists every WAI object of its component, in piece order.
|
||
Files that are not WAI envelopes (CMAF initialization segments and fragments, FLAC
|
||
files) **MUST NOT** appear in it. A verifier **MUST** refuse an index that breaks one
|
||
of these, checked in this order:
|
||
|
||
1. A timebase of zero (`index-timebase`). An index with no previous window
|
||
(`prev_index_digest` zero: on-demand content, or a chain's first window) whose
|
||
`first_piece` or `first_entry` is not 0, or whose first piece does not start at 0, the
|
||
start of the presentation timeline (§4.5) (`window-start`). Then piece by piece: a
|
||
piece that lasts no time or does not start where the one before it ended
|
||
(`piece-order`); whose
|
||
`first_entry` is not the number of entries before it, or that has none
|
||
(`piece-entries`); of a kind or with a flag that has no meaning (`piece-kind`). Then
|
||
pieces whose entries are not the index's entries (`piece-entries`).
|
||
2. Entry by entry, in order: an entry that does not name its piece's absolute number
|
||
(`piece-entries`); a flag with no meaning (`entry-flags`); within its piece, a role
|
||
before one that comes earlier in the order base, refinement, floor, any other, or a
|
||
refinement at a lower layer than the refinement before it (`entry-order`); an entry
|
||
that is not a refinement above layer 0, a refinement at layer 0, or `refines` on an
|
||
entry that is not a refinement or missing from one (`layer-role`); a
|
||
refinement whose `refines` is outside the window (`refines-outside-window`), is not
|
||
an earlier entry of its own piece (`refines-mismatch`) or is not one layer below it
|
||
(`layer-gap`); its tier (§6.4: `tier-entropy-passthrough`, `tier-weakest`, then
|
||
`tier-mismatch`); a non-zero `prefix_digest` with a tier other than
|
||
decode-equivalence (`digest-without-tier`); a deadline on an entry that is not a
|
||
refinement (`deadline-role`); a floor whose flag bit 2 is not set exactly when its
|
||
piece has a base, so a floor presents its piece's base and a piece of floors alone
|
||
presents none (`floor-derived-from`); a
|
||
content hash an earlier entry has at another length (`dup-length`); the duplicate
|
||
flag other than set exactly when an earlier entry has the content hash (`dup-flag`).
|
||
3. Class by class: a flag with no meaning (`class-flags`); a plan value other than
|
||
`0xFF` for a floor-only class, or other than `0xFF` or at most both the class's
|
||
`max_layer` and the piece's highest layer otherwise (`class-plan`).
|
||
|
||
With the objects in hand, a verifier **MUST** also refuse, entry by entry: bytes of
|
||
another length (`object-length`) or that do not hash to the entry's content hash
|
||
(`object-hash`); bytes that are not one `WAI1` or `WAI2` envelope and nothing else
|
||
(`non-envelope-entry`); a `man_len` or a payload length other than the entry's
|
||
(`object-lengths`); a capability other than the entry's (`object-capability`; for a
|
||
`WAI2`, its first entry's); a refinement whose object is not a `WAI1` whose `refines`
|
||
names its piece's base, its layer and producers of the layers below among which the
|
||
entry it refines (§5.4, `object-refines`); and `derived_from` (SPEC §7.1) present
|
||
otherwise than bit 2 says, or not well formed (`object-derived-from`). A member whose
|
||
value is `null` is absent; for a `WAI2`, `derived_from` is present when any entry
|
||
carries it, and well formed when every entry that carries it is. A manifest that does
|
||
not parse, or names a member twice, is not an envelope (`non-envelope-entry`).
|
||
|
||
**6.4** *Tier.* The tier a capability's own decode holds is decode-equivalence for a
|
||
capability whose registry class reconstructs exactly (`StandardDefined`, and
|
||
`NeuralIntegerExact`, whose pin a sink verifies before it decodes),
|
||
entropy-consistency for `NeuralEntropyExact`, and none otherwise (SPEC §5, §7). A capability
|
||
whose registration is pending is read with the class its extension states (§3.6 rule 1):
|
||
`wai.graphics.vector` holds decode-equivalence, and so does `wai.text.timed` as a text
|
||
component's base, whose segment reads to cues written back byte for byte (§5.8).
|
||
|
||
- An entry at layer 0 states its capability's tier, except a floor, which states none
|
||
(§7.4).
|
||
- A prefix with one or more refinements holds decode-equivalence only if every object
|
||
along it does, and none otherwise ([derived-tracks](derived-tracks.md) §3, rules 1
|
||
and 2): a refinement's tier is decode-equivalence exactly when the entry it refines
|
||
states decode-equivalence and its own capability's tier is decode-equivalence.
|
||
- An entry with `layer` > 0 **MUST NOT** state entropy-consistency
|
||
(`tier-entropy-passthrough`); one with `layer` > 0 that states decode-equivalence over
|
||
a prefix that does not hold it is refused `tier-weakest`; any entry stating another
|
||
tier than the one this section gives, `tier-mismatch`.
|
||
|
||
**6.5** `prefix_digest` is the SHA-256 decoded-output digest (SPEC §7, *Digests*) of the
|
||
prefix the entry completes when `prefix_tier` is decode-equivalence, and all zero
|
||
otherwise: for video, the clip digest over RGB8 frames (for a still, of its frame held
|
||
for the piece's frames, §5.7); for an image, the RGB8 frame digest; for audio, the digest
|
||
of the s16le interleaved samples (a score's, of its mixdown); for graphics, the clip digest
|
||
over the overlay's `RGBA8P` frames at the payload's first listed size and the component's
|
||
rate; for text, the SHA-256 of the segment the payload reads to and §5.8 writes again;
|
||
for world, feed, avatar
|
||
and film entries, zero (their state hashes live in their receipts, and a feed piece's in
|
||
its seal, §5.8). A verifier decodes the prefix and compares.
|
||
|
||
**6.6** `sse` is the integer sum, over every output sample of the prefix, of the
|
||
squared difference from the source sample the packager coded (RGB8 channels, or s16
|
||
samples). It is the producer's statement: a verifier holding the source recomputes it,
|
||
and one without the source cannot. A floor's `sse` is `2^64 − 1`.
|
||
|
||
## 7. Floor and `derived_from`
|
||
|
||
**7.1** Every piece of a video, image or audio component **MUST** have a floor rendition
|
||
in a capability of SPEC §5's mandatory floor or recommended set; a packager refuses a
|
||
video component with no floor rung (`floor-missing`). A film or world package SHOULD
|
||
have one. A component may have several floor rungs: each piece then has one floor object
|
||
per floor rung, in ladder order (§5.7), so one package carries a ladder of standard-codec
|
||
renditions over one base, which is coded once. Each floor rung has a floor name (§2): the
|
||
component's id when the component has one floor rung, the rung's id when it has several.
|
||
A floor name names files (§7.3, §7.6, §8.3), so one that cannot be one path segment of a
|
||
listing, or two floor names of a package equal once `A`–`Z` are mapped to `a`–`z`, are
|
||
refused (`pack-id`, §11.1). This revision packs supplied AV1 floors (`wai.video.av1` or
|
||
`wai.video.av1.lossless`, `source = "supplied"`), one stream per floor rung, and, for a
|
||
score beside the lead, a FLAC floor it encodes from the score's mixdown (§5.8), and, for a
|
||
graphics component, a text floor it encodes from each piece's transcript (§5.8). An audio
|
||
component whose base is `wai.audio.flac` needs no floor rung, its base being of the
|
||
mandatory floor; it may have one, of `wai.audio.opus` (SPEC §5's recommended set), supplied
|
||
in the CMAF form (§7.9).
|
||
|
||
A video component's first floor rung, in ladder order, is its **floor rendition**: the one
|
||
SPEC §5's video floor has every sink that presents video decode. It MUST be `wai.video.av1`
|
||
in the video-floor profile (SPEC §5, *Video floor*): AV1's Main profile, every operating point
|
||
at the Main tier and at level 4.1 or below, and its largest frame and its frame rate (the
|
||
component's timebase) within level 4.1's limits. After that rung's checks of §7.3 or §7.7, and
|
||
before it writes anything, a packager refuses a first floor rung that is not: `floor-av1-profile`
|
||
for another capability or profile, `floor-av1-level` for the High tier, a higher level, or a
|
||
frame or a rate past the level's limits. Its other floor rungs may be any rendition this
|
||
section admits.
|
||
|
||
**7.2** *Floor objects.* Each piece's floor is one object of the floor rung's capability
|
||
(§5.2), whose manifest, except in a floor-only component (§7.8), carries `derived_from`
|
||
(SPEC §7.1, *Derived renditions*) naming the piece's base: `motif` the base's content hash, `motif_capability` its capability,
|
||
`renderer` the floor's maker, and `settings` `BLAKE3("wai:render-settings\x01" ‖
|
||
JCS(settings))` (§3.6). For a supplied floor, `renderer` is `supplied@<the supplied
|
||
listing's SHA-256>` and the settings are `{"source": "supplied", "supplied_listing":
|
||
"<that SHA-256>"}`, so neither names a position. This profile fixes these members: they
|
||
are not the producer's statement here (SPEC §7.1), and a verifier holds every floor
|
||
object to them, to its framing header and to its target (§11.3). An AV1 floor object's payload is the
|
||
length-prefixed framing the reference sink decodes (`wai-rs` `codecs::video`): `n`,
|
||
`h`, `w`, `fps_num`, `fps_den` as `u32` little-endian (the piece's temporal units, the
|
||
sequence header's maximum frame height and width, the component's timebase `num` and
|
||
`den`), then each temporal unit as a `u32` little-endian length and its bytes as the
|
||
stream holds them.
|
||
|
||
**7.3** *A supplied floor* is a directory holding, per floor rung of a video component,
|
||
`<floor name>.obu` (§7.1): an AV1 stream in the low-overhead OBU format (every OBU with
|
||
its size field), one temporal unit per source frame. Its listing (SPEC §3.1) of every
|
||
file is pinned by the policy's `floor.supplied_listing`. A packager **MUST** refuse,
|
||
before it writes anything, the directory and then each floor rung's stream in ladder
|
||
order:
|
||
|
||
- a directory whose listing does not hash to `floor.supplied_listing`
|
||
(`floor-listing-mismatch`), and a floor rung with no stream in it
|
||
(`floor-stream-missing`);
|
||
- a stream that does not split into temporal units each holding a frame, whose first
|
||
unit holds no sequence header, with a sequence header whose bytes differ from the
|
||
first's, or with a unit that starts at a shown key frame (below) without a sequence
|
||
header (`floor-av1-malformed`);
|
||
- another number of temporal units than source frames (`floor-frame-count`);
|
||
- a unit that does not start at a shown key frame at a piece's first frame, or one that
|
||
does elsewhere unless `floor.extra_keyframes` is true (`floor-keyframe-mismatch`). A
|
||
unit starts at a shown key frame when its first frame header has `frame_type`
|
||
`KEY_FRAME` and `show_frame` 1, or the sequence uses the reduced still-picture header;
|
||
a unit that shows an existing frame does not.
|
||
|
||
*Key-frame sequence headers (informative).* A floor taken from an existing ladder without
|
||
re-encoding (its CMAF samples demuxed to this form, each with its temporal delimiter put
|
||
back) needs a sequence header in every unit that starts at a shown key frame. Where a
|
||
ladder's key-frame samples lack one, a producer that puts one back must put back the
|
||
stream's own first sequence header OBU, byte for byte, after each such unit's temporal
|
||
delimiter: §7.3 refuses any other bytes (`floor-av1-malformed`). The sequence header in
|
||
the ladder's sample entry (`av1C`) is not always the stream's: an encoder may write it
|
||
before it settles its coding tools, so that it differs from the in-band one in its tool
|
||
flags, and a stream with that header put back is refused. The packager writes its own
|
||
`av1C` from the in-band header (§7.6). A ladder whose key frames carry no in-band header
|
||
cannot be taken in the CMAF form (§7.7), whose units are its samples as they are.
|
||
|
||
The plan a supplied floor is made against is the **keyframe plan**, the file
|
||
`plan-<id>.txt`: each piece's first frame in decimal, one per line, each line ended by a
|
||
line feed. Its pieces' starts are the plan of §4.2, and `wai-pack plan` writes it from the
|
||
source and the policy before any floor is encoded.
|
||
|
||
**7.4** The floor is presentation. Its output is never compared with the primary's. A
|
||
floor object's manifest writes `intent: "replicate"`, which promises only what its
|
||
capability's decoder conformance promises (SPEC §7: `wai.video.av1` is registered
|
||
`standard-tolerance`); no tier and no claim against a primary or a source covers it.
|
||
|
||
**7.5** A floor produced by an encoder that is not a pure function of its input (§11)
|
||
**MUST** name that encoder and its version in `derived_from.renderer`, and, where the
|
||
encoder is hardware, the device and the driver that ran it. An AVC or HEVC rendition encoded
|
||
over a package's pieces names its encoder this way, and binds the device and the driver that
|
||
ran it through its `settings` ([platform-encode](platform-encode.md) §9); it is beside the
|
||
floor of §7.1, not in its place, and the package does not list it.
|
||
|
||
**7.6** *CMAF.* Each floor rung's floor is also written as CMAF files, which no stage
|
||
index lists: `floor/<floor name>/init.mp4`, and one fragment per piece,
|
||
`floor/<floor name>/<SHA-256 of the fragment>.m4s`. Box layouts are ISO/IEC 14496-12's,
|
||
with the AV1 binding's sample entry; every integer is big-endian and every time 0:
|
||
|
||
- `init.mp4`: `ftyp` (major brand `cmfc`, minor version 0, compatible `cmfc`, `iso6`,
|
||
`av01`), then `moov` holding `mvhd` (version 0, timescale `tb_num`, rate 1, volume 1,
|
||
the unity matrix, next track 2), `trak` (`tkhd` version 0 with flags 3, track 1,
|
||
layer, alternate group and volume 0, the unity matrix, the width and height in 16.16; `mdia`: `mdhd` version 0, timescale
|
||
`tb_num`, language `und`; `hdlr` `vide` named `video` with its terminating zero byte;
|
||
`minf`: `vmhd` with flags 1,
|
||
`dinf` with one self-contained `url `, and `stbl`: `stsd` with one `av01` sample entry
|
||
of the width and height, data reference 1, resolution 72 dpi, frame count 1, an empty
|
||
compressor name, depth 24 and pre-defined −1, holding `av1C`, then empty `stts`,
|
||
`stsc`, `stsz` and `stco`), and `mvex` with `trex` (track 1, sample description 1).
|
||
`av1C` is marker and version 1, then the sequence header's profile, level, tier,
|
||
bit depth, monochrome and chroma fields, no initial presentation delay, then the
|
||
sequence header OBU as the stream holds it. The width and height are the sequence
|
||
header's maximum frame width and height.
|
||
- A fragment: `moof` holding `mfhd` (sequence number piece + 1) and `traf` (`tfhd` with
|
||
default-base-is-moof, track 1; `tfdt` version 1, the piece's start × `tb_den`; `trun`
|
||
version 0 with data offset, sample duration, size and flags, the offset pointing at
|
||
the `mdat` payload), then `mdat`. Each sample is a temporal unit without its temporal
|
||
delimiter, lasting `tb_den`; its flags are `0x02000000` at a shown key frame and
|
||
`0x01010000` otherwise.
|
||
|
||
The codecs string of the floor is the AV1 binding's short form,
|
||
`av01.<profile>.<level, two digits><M or H>.<bit depth, two digits>`.
|
||
|
||
**7.7** *A supplied CMAF floor.* With `floor.supplied_form = "cmaf"`, a supplied floor is
|
||
an existing ladder's CMAF files rather than OBU streams: per floor rung of floor name *f*
|
||
(§7.1), a directory `<f>/` holding `init.mp4` and the floor's media segments, which are
|
||
every other file of `<f>/`, each named `*.m4s` and in no directory below it, in byte order
|
||
of their names, at least one. The floor's temporal units are the segments' samples, in
|
||
order, each a temporal unit without its temporal delimiter: a unit is the two bytes
|
||
`0x12 0x00` (a temporal delimiter OBU with its size field and no extension) followed by the
|
||
sample. Boxes are ISO/IEC 14496-12's; a box's 32-bit size is at least 8, a size of 1 is the
|
||
64-bit size that follows its type (at least 16), and a size of 0 is refused; a box's
|
||
children tile its body:
|
||
|
||
- `init.mp4`: its top-level boxes tile the file; one `moov`, holding one `trak` and one
|
||
`mvex`; the `trak`'s `tkhd` names the track, its `mdia`'s `mdhd` gives the timescale
|
||
*T*, its `hdlr` is of handler type `vide`, and its `stsd` holds one sample entry, of type
|
||
`av01`; an `edts`, where the `trak` has one, holds an `elst` of one entry, at media
|
||
time 0 and rate 1; and the `mvex` holds one `trex`, of the track, whose defaults a
|
||
sample takes.
|
||
- a media segment: its top-level boxes tile the file: boxes of the types `styp`, `sidx`,
|
||
`prft`, `emsg`, `free` and `skip`, then one or more `moof`, each followed by an `mdat`,
|
||
and nothing else. A `moof` holds an `mfhd` and one `traf`, and nothing else; the `traf`
|
||
holds one `tfhd` of the track, one `tfdt` and one or more `trun`, and nothing else; the
|
||
`tfhd`'s flags are among `0x2`, `0x8`, `0x10`, `0x20` and `0x20000` (no base data
|
||
offset, no empty duration); each `trun`'s are among `0x1`, `0x4`, `0x100`, `0x200`,
|
||
`0x400` and `0x800`, `0x1` (its data offset) among them. A sample's duration, size and
|
||
flags are its `trun`'s (the first sample's flags its first-sample flags where the `trun`
|
||
gives them), else the `tfhd`'s defaults, else the `trex`'s; its bytes start at the
|
||
`moof`'s first byte plus its `trun`'s data offset, a run's samples one after another,
|
||
and lie in the `mdat` after the `moof`.
|
||
|
||
A packager **MUST** refuse, before it writes anything, after the directory's listing
|
||
(§7.3), floor rung by floor rung in ladder order and segment by segment in order:
|
||
|
||
1. a floor rung with no `<f>/init.mp4` (`floor-stream-missing`); another file of `<f>/`
|
||
that is not a media segment, or no media segment (`floor-cmaf-malformed`);
|
||
2. an `init.mp4` that is not the above (`floor-cmaf-malformed`), or whose timescale is 0
|
||
or whose edit list is another (`floor-cmaf-timing`);
|
||
3. a media segment that is not the above, or that holds a sample of no bytes
|
||
(`floor-cmaf-malformed`), or a sample with a composition offset other than 0 or a
|
||
`moof` whose decode time is not where the one before it ended (`floor-cmaf-timing`),
|
||
each where it is read; then one whose first sample is not a sync sample
|
||
(`sample_is_non_sync_sample` set) (`floor-cmaf-malformed`); then a sample that does
|
||
not last one frame (`duration · num = T · den`), or a segment whose first decode time
|
||
is not the frame where the segment before it ended, the first at frame 0
|
||
(`dts · num = frame · T · den`) (`floor-cmaf-timing`); then its units, as §7.3 reads a
|
||
stream's (`floor-av1-malformed`, a unit's own faults held to the end);
|
||
4. then, as §7.3: a unit count other than the source's frames (`floor-frame-count`), and
|
||
the faults held back (`floor-av1-malformed`); then an `init.mp4` whose `av01` entry has
|
||
no `av1C`, or whose `av1C` profile, level, tier, bit depth, monochrome and chroma fields,
|
||
or whose sample entry's width and height, are not the sequence header's and its
|
||
maximum frame size (`floor-cmaf-malformed`): a player configures its decoder from them,
|
||
and §8.3's playlists name the sequence header's; then a sample that is a sync sample where
|
||
its unit does not start at a shown key frame, or that is not one where its unit does
|
||
(`floor-cmaf-malformed`): the ladder's sample flags reach players unchanged, and
|
||
players seek by them;
|
||
5. other than one media segment per piece of the plan, each holding exactly its piece's
|
||
frames (`floor-cmaf-pieces`);
|
||
6. a key frame §7.3 refuses (`floor-keyframe-mismatch`).
|
||
|
||
The floor's CMAF files are then the supplied ones, byte for byte: `floor/<f>/init.mp4`
|
||
the supplied `init.mp4`, and per piece `floor/<f>/<SHA-256>.m4s` its media segment (§7.6's
|
||
writer is not used), which §8.3's playlists and MPDs list. The package also carries the
|
||
supplied floor's listing as `floor.listing`: its SHA-256 is `floor.supplied_listing`, which
|
||
the policy digest, and so the receipt, binds, and it names each of the ladder's files by
|
||
its SHA-256, so the receipt binds the ladder's bytes. A server may hold those files once,
|
||
for the ladder and the package alike (by a hard link, or by their content address): they
|
||
are the same bytes. The floor objects carry the units as §7.2 frames them, as in the OBU
|
||
form.
|
||
|
||
**7.8** *The floor-only profile.* A component of `profile = "floor-only"` is its floor
|
||
rungs alone: no base, no refinement and no source (§3.4 item 8). It is for a library that
|
||
delivers its standard-codec renditions as WAI objects, each named by its content hash,
|
||
listed in a signed stage index and bound to a signed receipt, without the integer lane.
|
||
|
||
- *Its pieces* are its first floor rung's: in the CMAF form (§7.7), one piece per media
|
||
segment; in the OBU form (§7.3), one piece from each of the stream's shown key frames
|
||
to the next, the stream's first unit one (`floor-keyframe-mismatch` otherwise), and the
|
||
first floor rung's unit count is the component's frame count. A piece of more than
|
||
`max_ms` (§4.4) is refused (`floor-piece-length`); `min_ms` and `target_ms` do not
|
||
apply, and the scene-cut segmenter does not run. Every other floor rung is held to those
|
||
pieces as §7.3 and §7.7 hold a floor to a plan. A piece's kind is `none` (§6.2), its
|
||
flags 0; a rule that states `when.piece_kind` matches none of them, and its moments are
|
||
§8.1's.
|
||
- *Its objects*: each piece has one floor object per floor rung (§7.1) and nothing else.
|
||
A floor object presents no base, so its manifest is §5.2's for its capability with no
|
||
`derived_from`, and its entry's flag bit 2 is clear (§6.3); everything else is §7.2's.
|
||
Every class lists the floor only (§8.1): no catalog lists a WAI object, the MoQ catalog
|
||
is the floors' tracks, and the package has no WAI playlist or MPD.
|
||
- *What it attests*: the receipt signs the policy, so the supplied floor's listing, and
|
||
the stage index, so every floor object's content hash, length and capability; a
|
||
verifier recomputes the dedup counts, holds every object to its hash and to §7.2's
|
||
framing, its units to the AV1 stream's structure (temporal units, one per frame,
|
||
sequence headers, shown key frames at the piece starts), and the CMAF files to the
|
||
units (§7.6) or, in the CMAF form, to the ladder's bytes through `floor.listing`
|
||
(§7.7). A floor-only package's catalogs list no WAI object: a sink receives the floor's
|
||
CMAF files through HLS or DASH, or the floor's MoQ track. In the CMAF form it can check
|
||
each segment's SHA-256 name against `floor.listing`, which the policy pins and the
|
||
receipt signs; in the OBU form a segment's name is bound only through a verifier that
|
||
rebuilds it from the objects (§7.6). So a sink's binding of a frame to a receipted
|
||
object (browser-sink §7) does not apply to a floor-only package.
|
||
- *What it does not attest*: a floor states no tier (§7.4): no decoded-output digest is
|
||
stated or checked, so nothing says what a decoder makes of the units; there is no
|
||
source, so no `sse` and no fidelity to anything; there is no primary, so no claim
|
||
against one, and the floor objects' `intent: "replicate"` promises their capability's
|
||
decoder conformance and nothing more (§7.4); and the pieces are not classified. It attests delivery, not
|
||
pictures.
|
||
|
||
**7.9** *An Opus floor.* A sound track beside the lead (§5.8) whose base is `wai.audio.flac`
|
||
may have one floor rung of `wai.audio.opus`, `source = "supplied"`, only in the CMAF form
|
||
(`floor.supplied_form = "cmaf"`) and only on a timebase of `48000/1`: anything else is
|
||
refused `pack-capability-unsupported:wai.audio.opus`. Its floor is an existing ladder's Opus
|
||
in ISOBMFF, the directory `<f>/` of the supplied floor (its floor name, §7.1) beside the
|
||
lead's floors, under the one listing the policy pins: `<f>/init.mp4` and its media
|
||
segments, the other files of `<f>/`, each `*.m4s`, in byte order of their names (§7.7).
|
||
Boxes are read as §7.7 reads them.
|
||
|
||
- `init.mp4`: its top-level boxes tile the file; one `moov`, holding one `trak` and one
|
||
`mvex`; the `trak`'s `tkhd` names the track, its `mdia`'s `mdhd` gives the timescale,
|
||
48 000 (`floor-cmaf-timing` otherwise), its `hdlr` is of handler type `soun`, and its
|
||
`stsd` holds one sample entry, of type `Opus`, an audio sample entry (ISO/IEC 14496-12
|
||
§12.2.3) whose channel count is its `dOps`'s; the `dOps` (Opus in ISOBMFF) is 11 bytes:
|
||
version 0, the output channel count, 1 or 2 and the component's source's, the pre-skip
|
||
*P* (16 bits), the input sample rate, the output gain and channel mapping family 0; an
|
||
`edts`, where the `trak` has one, holds an `elst` of one entry, at media time 0 or *P*,
|
||
at rate 1 (`floor-cmaf-timing` otherwise); and the `mvex` holds one `trex`, of the
|
||
track. Anything else is `floor-cmaf-malformed`.
|
||
- a media segment: §7.7's boxes, every sample a sync sample (`floor-cmaf-malformed`), and
|
||
each sample one Opus packet (RFC 6716 §3), held to RFC 6716 §3.4's requirements R1 to R7
|
||
(`floor-opus-malformed`). A packet lasts the samples its TOC byte gives: its frame size
|
||
at 48 kHz (`config` 0 to 11: 10, 20, 40, 60 ms by `config` mod 4; 12 to 15: 10, 20 ms by
|
||
`config` mod 2; 16 to 31: 2.5, 5, 10, 20 ms by `config` mod 4) times its frame count (1
|
||
for code 0, 2 for codes 1 and 2, the frame count byte's *M* for code 3).
|
||
|
||
A packager **MUST** refuse, before it writes anything, after the lead's floors, in this
|
||
order: `init.mp4` (`floor-stream-missing` without it, then the rules above); segment by
|
||
segment, its boxes, its samples, its first sample's decode time where the segment before it
|
||
ended, the first at 0 (`floor-cmaf-timing`), and each packet; then a sample that does not
|
||
last its packet's samples, but the floor's last, which may last from 1 to them (the
|
||
programme's end) (`floor-cmaf-timing`); then packets that last, from decode time 0, fewer
|
||
than the component's samples *N* and *P* together, or that last them without their last
|
||
(`floor-frame-count`); then media segments other than one per piece and at most one more,
|
||
the **tail**, and a packet in another segment than this: piece *k*'s segment holds the
|
||
packets whose decode time is at least the piece's start *s_k* and below the next piece's
|
||
start, the last piece's those from its start on; where there is a tail, it holds the
|
||
packets whose decode time is *N* or later (`floor-cmaf-pieces`). A muxer that cuts its
|
||
segments at the pieces' starts on the decode timeline writes such a floor: the packet that
|
||
carries a programme's last *P* samples (the encoder's delay, which the pre-skip removes)
|
||
starts at *N* when the pieces end on a whole packet, and a muxer cutting at every piece
|
||
start writes it into a segment of its own, the tail.
|
||
|
||
*The floor objects.* Each piece's floor is one object of `wai.audio.opus` (§7.2: its
|
||
manifest §5.2's, `target` the piece's `{"channels", "rate", "samples"}` as its base's,
|
||
`derived_from` naming the piece's base, `renderer` `supplied@<the supplied listing's
|
||
SHA-256>` and the settings `{"source": "supplied", "supplied_listing": "<that SHA-256>"}`).
|
||
Its payload is the Ogg Opus stream (RFC 7845) that decodes alone to exactly the piece's
|
||
samples: the packets from the one holding decode time `max(0, s_k + P − 3840)` (80 ms of
|
||
pre-roll, RFC 7845 §4.6, so a decoder that starts there has converged by the piece) to the
|
||
one holding `e_k + P − 1`, *e_k* the piece's end; its `OpusHead` (version 1, the `dOps`'s
|
||
channels, input sample rate and output gain, mapping family 0) states the pre-skip
|
||
`s_k + P` less the first packet's decode time (a piece whose pre-skip would not fit 16 bits
|
||
is refused `floor-cmaf-malformed`); its `OpusTags` holds the vendor string `wai-pack` and no
|
||
comment; and its pages, of serial number 0, are `OpusHead` (beginning of stream), `OpusTags`
|
||
and then one page per packet, sequence numbers from 0, each page's granule position the
|
||
samples of the object's packets through it, the last page's (end of stream) the pre-skip and
|
||
the piece's samples, each page's CRC RFC 3533's. Every byte is a function of the floor's
|
||
packets and the piece, so a verifier writes it again. Packets are shared between adjacent
|
||
pieces' objects (the pre-roll, and the packet a piece's last samples sit in after the
|
||
pre-skip); the CMAF files are the ladder's own, each written once.
|
||
|
||
*The files.* The package carries the floor's CMAF files as the supplied ones, byte for byte:
|
||
`floor/<f>/init.mp4`, and `floor/<f>/<SHA-256>.m4s` for every media segment, piece *k*'s and
|
||
the tail with the last piece, named in `floor.listing` (§7.7). This revision's catalogs (§8)
|
||
list the lead's floors; a player reads an Opus floor's files from `floor.listing`.
|
||
|
||
*What it attests.* The receipt signs the policy, so the listing that names the ladder's
|
||
bytes, and the index, so each floor object's content hash; a verifier holds every packet to
|
||
its segment, the segments to the pieces and each object to the stream above, so each object
|
||
carries exactly its piece's packets. A floor states no tier (§7.4): Opus is
|
||
standard-tolerance (SPEC §7), and no digest says what a decoder makes of the packets. The
|
||
FLAC base is what states each piece's samples. A piece is closed for presentation (each
|
||
object decodes alone), not in its packets: Opus carries decoder state from packet to packet,
|
||
which the pre-roll restores.
|
||
|
||
**7.10** *The protected floor.* A package whose policy has a `protection` table (§3.3) has a
|
||
**protected presentation**: its floor CMAF files (the lead's AV1 floors, §7.6 and §7.7, and each
|
||
sound track's Opus floor, §7.9) encrypted with Common Encryption (ISO/IEC 23001-7) under the
|
||
`cenc` scheme, the `cbcs` scheme or both, with the keys the table names by KID, and HLS and DASH
|
||
presentations that carry them. The package itself does not change: the table is in the policy,
|
||
so the policy digest, and the receipt that signs it (§9.1), names every KID and the SHA-256 of
|
||
the CPIX document they come in. The presentation is written beside the package by whoever holds
|
||
the keys, and is a function of the package's files and the keys: a verifier holding the CPIX
|
||
document writes it again byte for byte. The reference is wai-pack's `protect` (`wai-pack
|
||
protect`, `wai-pack protect-verify`), with `cenc` and `av1_tiles`; the corpus is
|
||
[`pack-conformance/protected/`](../pack-conformance/protected/), whose second implementation is
|
||
`protected_verify.py`.
|
||
|
||
Texts read for this section: the AV1 Codec ISO Media File Format Binding v1.3.0 (3 April 2024),
|
||
§2.4 and §4; the AV1 Bitstream and Decoding Process Specification, §5.5, §5.9, §5.11.1, §7.8,
|
||
§7.20 and §7.21; the W3C notes "ISO Common Encryption ('cenc') Protection Scheme for ISO Base
|
||
Media File Format Stream Format" and "'cenc' Initialization Data Format", §4 (the common
|
||
system ID and its `pssh`); the W3C Encrypted Media Extensions §9.1 (Clear Key); DASH-IF IOP v5.1.0
|
||
Part 6 (December 2025), §6.2 to §8; the DASH-IF registry of content protection system IDs;
|
||
`draft-pantos-hls-rfc8216bis-22` §4.4.4.4, §4.4.6.5 and §5; "Encapsulation of Opus in ISO Base
|
||
Media File Format" §4.3.5, which defines no subsample for Opus; and key-release §4.4 for the
|
||
CPIX document. ISO/IEC 23001-7 and ISO/IEC 14496-12 were not available to this revision: the
|
||
boxes and scheme rules below are written as the texts above restate them, and a browser's own
|
||
decryptor plays the corpus (`pack-conformance/protected-browser/`).
|
||
|
||
*The keys.* The CPIX document is read as key-release §4.4 steps 1 to 3 read one; a writer
|
||
refuses, before it writes anything, a package that does not verify (§11.3; `protect-package`) or
|
||
whose policy has no table (`protection-absent`), a document whose SHA-256 is not `cpix_sha256`
|
||
(`protection-cpix-mismatch`), one key-release refuses (`protection-cpix`), a key whose KID the
|
||
document does not hold (`protection-kid-unknown`), and one whose KID the document gives another
|
||
scheme than the key's (`protection-scheme-mismatch`). A floor's key under a scheme is the one key
|
||
the table names for it (§3.4 item 13). DASH-IF IOP Part 6 §6.4 asks the representations of one
|
||
content under two schemes to use two keys: a CPIX key has one scheme, so a table that uses both
|
||
schemes names two KIDs per component.
|
||
|
||
*The files*, each scheme the keys name, `cenc` first, and each floor in ladder order:
|
||
|
||
<scheme>/floor/<f>/init.mp4 the floor's init, protected
|
||
<scheme>/floor/<f>/<SHA-256 of it>.m4s each media segment of the floor, protected, in order
|
||
<scheme>/hls/master.m3u8, <scheme>/hls/floor-<f>.m3u8 (when a key line can be written, below)
|
||
<scheme>/dash/manifest.mpd
|
||
protection.json the index
|
||
LISTING the listing of every other file (SPEC §3.1)
|
||
|
||
A floor's segments are its classical playlist's, in its order (§8.3), for an AV1 floor, and the
|
||
media segments `floor.listing` names in the floor's directory, in its order, for an Opus floor
|
||
(§7.9).
|
||
|
||
*An init* is the clear one with three changes, and none other. Its one sample entry, `av01` or
|
||
`Opus`, is renamed `encv` or `enca` and gains a `sinf` as its last box: `frma` holding the clear
|
||
entry's type, `schm` (version 0, flags 0, the scheme's four characters, scheme version
|
||
`0x00010000`), and `schi` holding `tenc`. Under `cenc`, `tenc` is version 0: two zero bytes,
|
||
`default_isProtected` 1, `default_Per_Sample_IV_Size` 8 and the KID. Under `cbcs` it is version 1:
|
||
a zero byte, the pattern (`crypt_byte_block` 1 and `skip_byte_block` 9 for video, the binding's
|
||
recommended 1:9; 0 and 0 for audio, no pattern: every whole block), `default_isProtected` 1,
|
||
`default_Per_Sample_IV_Size` 0, the KID, `default_constant_IV_size` 16 and the constant IV. The
|
||
last box of `moov` is one `pssh` of the common system ID (version 1, one KID, the track's, and no
|
||
data), which every EME key system reads (the W3C note's §4); system-specific `pssh` data, whose
|
||
formats the registry does not publish, is not written. Box sizes are rewritten, each header in
|
||
the form it had.
|
||
|
||
*A media segment* is the clear one with these changes. Each `traf` gains, after its last box,
|
||
`saiz` (version 0, flags 0: a default size where every sample's auxiliary information has one
|
||
length, else 0 and each length), `saio` (version 0, flags 0, one entry: the offset of the first
|
||
sample's entry in `senc`, from the `moof`'s first byte) and `senc` (version 0, flags 2 for video,
|
||
whose samples carry subsamples, and 0 for audio; then each sample's IV at the track's IV size and,
|
||
for video, its subsample count and subsamples); for a `cbcs` audio track, whose samples have no
|
||
auxiliary information, `senc` alone (DASH-IF IOP Part 6 §6.2). Each `trun`'s data offset grows by
|
||
what its `moof` grew, a `sidx`'s first offset and referenced sizes are the protected boxes'
|
||
(a reference to another `sidx` is refused, `protected-box`), and each sample's protected bytes
|
||
are encrypted in place. Version 0 of `saiz` holds a size up to 255, so a sample with more
|
||
subsamples than fit is refused (the binding's §4.1 note).
|
||
|
||
*What a sample protects.* A video sample by the binding's §4.1, from where each tile's bytes
|
||
(its `decode_tile` structure with any trailing bits) lie: every OBU header, size field, sequence
|
||
header, frame header (in a frame OBU too), redundant frame header, metadata, padding and tile
|
||
group header stays clear; under `cbcs` each tile is one subsample whose protected bytes are the
|
||
tile's, a tile of under 16 bytes included; under `cenc` a tile of at least 16 bytes is one
|
||
subsample whose protected bytes are its whole 16-byte blocks counted back from its last byte, and
|
||
a shorter tile stays clear. Clear runs join the protected range after them, a trailing clear run is
|
||
a subsample of no protected bytes, and a clear run past 65 535 bytes is split into subsamples of
|
||
65 535 clear bytes. The tiles are found by reading each frame header (`av1_tiles`), which reads the
|
||
sequence header and what the reference frames before it left (their sizes, order hints and
|
||
segmentation features), so a track's samples are read in decode order from its `av1C`'s sequence
|
||
header on; a scalable stream (an operating point other than 0), a tile list OBU and a frame
|
||
header the AV1 text does not allow are refused (`protect-av1`). An audio sample is protected
|
||
whole: Opus in ISOBMFF defines no subsample.
|
||
|
||
*The ciphers*, AES-128 with the key. `cenc`: counter mode over the sample's protected bytes in
|
||
order, joined across its subsamples, from the counter block `IV ‖ 0` (an 8-byte IV, the low 64
|
||
bits counting blocks), the last partial block encrypted. `cbcs`: each subsample's protected bytes
|
||
(an audio sample's, whole) a chain of its own from the constant IV, over the blocks the pattern
|
||
encrypts (with 1:9, the first block of every ten; with none, every whole block), a last partial
|
||
block clear.
|
||
|
||
*The IVs.* A `cenc` sample's IV is the first 8 bytes of
|
||
`SHA-256("wai:cenc-iv\x01" ‖ key ‖ u32 len(f) ‖ f ‖ the clear segment's SHA-256 ‖ u32 i)`, *f* the
|
||
floor name and *i* the sample's place in its segment from 0; a track's `cbcs` constant IV is the
|
||
first 16 bytes of `SHA-256("wai:cbcs-iv\x01" ‖ key ‖ u32 len(f) ‖ f)`. So the IVs are unpredictable
|
||
without the key, and no counter block repeats under a key unless two segments are the same bytes,
|
||
whose ciphertexts are then the same too.
|
||
|
||
*The catalogs.* Under each scheme, the DASH presentation is one `Period` of an adaptation set of
|
||
the AV1 floors (each a `Representation` with its `codecs`, `width` and `height`) and one per Opus
|
||
floor (`codecs="opus"`, its `AudioChannelConfiguration`), each `SegmentList` as §8.3 writes one,
|
||
in the track's timescale, naming the protected files; each adaptation set carries the
|
||
`ContentProtection` descriptors of DASH-IF IOP Part 6 §7 and §8: `urn:mpeg:dash:mp4protection:2011`
|
||
with the scheme and `cenc:default_KID`; the common system ID's with its `pssh` as `cenc:pssh`; with
|
||
`licence_uri`, DASH-IF's Clear Key (`e2719d58-a985-b3c9-781a-b030af78d30e`, `value="ClearKey1.0"`,
|
||
`dashif:Laurl licenseType="EME-1.0"`); and an empty descriptor per system ID `systems` names. The
|
||
HLS presentation is written when every track has a key line: with `key_uri`, an `identity` key
|
||
(`#EXT-X-KEY:METHOD=SAMPLE-AES-CTR` under `cenc`, `METHOD=SAMPLE-AES` with the constant IV under
|
||
`cbcs`, `URI` the key URI with `{kid}` the KID's 32 lowercase hexadecimal digits,
|
||
`KEYFORMAT="identity"`, `KEYFORMATVERSIONS="1"`); and under `cbcs`, when `systems` names
|
||
`94ce86fb-07ff-4f43-adb8-93d2fa968ca2`, that system's `skd` key line (`URI="skd://<KID, hex>"` and
|
||
the `KEYFORMAT` it registers, `com.apple.streamingkeydelivery`). <!-- allow-name: an HLS KEYFORMAT string, a functional identifier -->
|
||
Each media playlist is §8.3's floor playlist with its key lines
|
||
before its `EXT-X-MAP` (an `EXT-X-KEY` covers the initialization sections declared after it), its
|
||
segment durations the samples'; the master names the key lines once each as
|
||
`EXT-X-SESSION-KEY`, the Opus floors as an `AUDIO` group, and each AV1 floor as a variant whose
|
||
`BANDWIDTH` and `AVERAGE-BANDWIDTH` add the audio group's highest to the floor's (§8.3's rates
|
||
over the protected bytes).
|
||
|
||
*The index*, `protection.json`, is RFC 8785 JSON and a line feed: `cpix_sha256`, the package's
|
||
`package_receipt` (its receipt hash) and `policy_digest`, and `tracks`, one per scheme and floor in
|
||
the order above, each `{"floor", "init", "kid", "scheme", "segments"}`, `init` and each segment the
|
||
pair `[clear SHA-256, protected SHA-256]`. It is what pairs each protected file with the clear one
|
||
the receipt binds (§7.6, §7.7).
|
||
|
||
*Checking.* A presentation is held to its package, which must verify (§11.3), and, every failure
|
||
reported with its file: `LISTING` (`protected-listing`); the index names the package, the CPIX
|
||
digest and one track per scheme and floor, each with the package's clear init and segments
|
||
(`protected-index`); each file it names is present (`protected-missing`) and each segment named by
|
||
its SHA-256 (`protected-name`); each init reads back, with the three changes removed, to the clear
|
||
init (`protected-mismatch`), its boxes as above (`protected-box`, `protected-scheme`), its IV size
|
||
the scheme's (`protected-iv-size`), its KID the table's (`protected-kid`) and its `pssh` the one
|
||
common one of that KID (`protected-pssh`); each segment's boxes (`protected-box`), its `senc` at
|
||
the init's IV size with `saiz` and `saio` agreeing (`protected-iv-size`), each sample's subsamples
|
||
those the clear sample gives (`protected-subsample`), each `cenc` IV distinct in its track
|
||
(`protected-iv`), and the segment read back to the clear one in every byte no protected range
|
||
covers (`protected-mismatch`); the catalogs and the index written again from those files
|
||
(`protected-catalog`, `protected-index`); and no other file (`protected-unexpected`). With the CPIX
|
||
document, also each IV and constant IV the derived one (`protected-iv`), each segment decrypted to
|
||
the clear one byte for byte, and every file written again byte for byte (`protected-mismatch`).
|
||
|
||
*What it attests.* With the receipt, that the clear floor's every sample is in the protected
|
||
presentation, encrypted under the keys whose KIDs the policy names, by the binding's rules; with
|
||
the CPIX document, that it decrypts to that floor byte for byte. *What it does not:* a licence.
|
||
The content protection systems are named by ID, and their licence exchanges and their own `pssh`
|
||
data are theirs (key-release §9.2): none is tested here, and none is claimed. The WAI objects of
|
||
the package, its bases and floor objects among them, are not encrypted by this section, so a
|
||
protected presentation protects the floor's delivery only where the package's other files are not
|
||
served beside it. Clear Key is for test and development (DASH-IF IOP Part 6 §8): it hands the
|
||
browser the key.
|
||
|
||
## 8. Classes, catalogs and delivery
|
||
|
||
**8.1** *What a class lists.* The stages listed for a class are a pure function of the
|
||
class, the piece and the effective policy, so every viewer of a class receives the same
|
||
catalog bytes.
|
||
|
||
- A piece that starts at unit `start` and lasts `duration` units spans the
|
||
milliseconds from `s_ms` up to `e_ms`, the floors of `start · den · 1000 / num` and of
|
||
`(start + duration) · den · 1000 / num`; it lies in moment *i* when `s_ms < to_ms` and
|
||
`e_ms > from_ms`. Rule *i* matches it when each condition the rule states holds: its
|
||
kind is in `when.piece_kind`, and a tag of a moment it lies in is in
|
||
`when.moment_tag`. Matching rules fold in document order (§3.4).
|
||
- A floor-only class lists the floor (`0xFF`), and every class lists only the floor of a
|
||
piece with no base, a floor-only component's (§7.8). Otherwise the class lists
|
||
`min(max_layer, the rules' then.max_layer, the piece's highest layer)`; under
|
||
`max_kbps`, while the listed prefix needs more than `max_kbps`, that is
|
||
`ceil(8 · bytes · num / (1000 · duration · den))` kilobits per second over the bytes of
|
||
the listed bases and refinements, the top layer is dropped, and a base that alone
|
||
needs more makes the piece floor-only for the class.
|
||
|
||
**8.2** A catalog **MUST NOT** list a refinement without the stages below it.
|
||
|
||
**8.3** *HTTP.* A package is offered in [http-delivery](http-delivery.md)'s Mode N
|
||
(§11.1 there): the classical presentation is the floor alone, and WAI objects are
|
||
offered in playlists of their own.
|
||
|
||
- A WAI object is none of the media segment formats of RFC 8216 §3. A WAI playlist is
|
||
offered only to a sink that recognizes its codecs value from its own capability list,
|
||
and a packager **MUST** write a classical master that names nothing of WAI.
|
||
- A browser's media-capabilities query answers only for the browser's own decoders,
|
||
and **MUST NOT** be used to decide whether a `wai1.*` value can be decoded
|
||
([http-delivery](http-delivery.md) §4.3).
|
||
- Each class's WAI playlist **MUST** name its class and its stage view.
|
||
- A variant lists no bases without a capability in common ([http-delivery](http-delivery.md)
|
||
§4.2, `codecs_no_common_capability`). A class's HTTP view lists a piece only where the
|
||
piece's base is of the component's default base capability (§5.7, its first base rung's),
|
||
and plays the floor for any other piece, as for a piece the class takes as floor only: a
|
||
still (§5.7) among motion bases is carried over HTTP by the floor. The class's stage view
|
||
(§8.8) and its MoQ tracks (§8.4) list it.
|
||
Durations: a piece of *d* units lasts `floor(d · den · 1000 / num)` milliseconds,
|
||
written `S.mmm` (whole seconds, a full stop, three digits). A target duration is the
|
||
largest `ceil(d · den / num)`. A rate is `ceil(8 · bytes · num / (d · den))` bits per
|
||
second; a variant's `BANDWIDTH` is the largest over the pieces it lists and
|
||
`AVERAGE-BANDWIDTH` the rate of all their bytes over all their durations. A floor
|
||
variant's bytes per piece are its fragment's; a WAI variant's are the listed bases' and
|
||
refinements', over the pieces its HTTP view lists (above). Every playlist
|
||
line ends with a line feed. For component *c*, of frame size `W×H`, and each of its
|
||
floor rungs, in ladder order, of floor name *f* (§7.1), codecs string *F* and maximum
|
||
frame size `FW×FH`:
|
||
|
||
- `hls/classic/master.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||
`#EXT-X-INDEPENDENT-SEGMENTS`, then per floor rung
|
||
`#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="F",RESOLUTION=FWxFH` and
|
||
`../classic/floor-<f>.m3u8`: a URI that names the same playlist from every class's
|
||
directory, so each class's master differs from this one only by the session-data
|
||
line, as [http-delivery](http-delivery.md) §11.1 and §11.8 require.
|
||
- `hls/classic/floor-<f>.m3u8`, per floor rung: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||
`#EXT-X-TARGETDURATION:<t>`, `#EXT-X-MEDIA-SEQUENCE:0`, `#EXT-X-PLAYLIST-TYPE:VOD`,
|
||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-MAP:URI="../../floor/<f>/init.mp4"`, then per
|
||
piece `#EXTINF:<S.mmm>,` and `../../floor/<f>/<sha256>.m4s`, then `#EXT-X-ENDLIST`.
|
||
- `hls/<class>/master.m3u8`: the classical master, byte for byte, with, when the class
|
||
lists a WAI object over HTTP, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"`
|
||
after `#EXT-X-INDEPENDENT-SEGMENTS`, and no other change.
|
||
- `hls/<class>/wai.m3u8`, when the class's HTTP view lists a WAI object: `#EXTM3U`,
|
||
`#EXT-X-VERSION:7`, `#EXT-X-INDEPENDENT-SEGMENTS`,
|
||
`#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.class",VALUE="<class>"`,
|
||
`#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.stages",URI="../../stages/<class>.json"`,
|
||
`#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="<union>",X-WAI-FLOOR="<floor>",RESOLUTION=WxH`,
|
||
`wai-<c>.m3u8`. The union and the floor are [http-delivery](http-delivery.md) §4.2's
|
||
over the objects the class lists.
|
||
- `hls/<class>/wai-<c>.m3u8`: the first floor rung's playlist's lines without
|
||
`#EXT-X-MAP`, adjacent to it ([http-delivery](http-delivery.md) §11.2) and so to every
|
||
floor rung's, which list the same pieces; per piece, `#EXT-X-GAP` when the
|
||
class's HTTP view lists nothing of it (a floor-only piece, or one whose base is not of
|
||
the default base capability), `#EXTINF:<S.mmm>,`, each refinement the class lists,
|
||
in entry order, as `#WAI-LAYER:N=<layer>,URI="../../objects/<hash>.wai#n=<byte_len>"`,
|
||
then its base as `../../objects/<hash>.wai#n=<byte_len>`: a layer line follows its
|
||
segment's `EXTINF` ([http-delivery](http-delivery.md) §10.4).
|
||
|
||
Every HLS file above and every MPD below is a rendering of
|
||
[http-delivery](http-delivery.md)'s (Appendix G for a media playlist): the reference
|
||
packager writes them with the binding's one HLS and one DASH renderer.
|
||
|
||
DASH, the same as two MPDs ([http-delivery](http-delivery.md) §11.1), each
|
||
`<?xml version="1.0" encoding="UTF-8"?>`, then
|
||
`<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:full:2011" type="static" mediaPresentationDuration="PT<S.mmm>S" minBufferTime="PT<t>S">`
|
||
over the component's whole duration and its target duration, `<Period id="p0" start="PT0S">`,
|
||
one `AdaptationSet`, `</Period>` and `</MPD>`, indented by two spaces a level:
|
||
|
||
- `dash/classic.mpd`: `<AdaptationSet id="1" contentType="video" mimeType="video/mp4" codecs="F" width="FW" height="FH" segmentAlignment="true" startWithSAP="1">`
|
||
holding, per floor rung, `<Representation id="<f>-floor" bandwidth="<b>">` with
|
||
`<SegmentList timescale="<num>">`: `<Initialization sourceURL="../floor/<f>/init.mp4"/>`,
|
||
a `SegmentTimeline` of one `<S t="<(start − first start)·den>" d="<d·den>"/>` per
|
||
piece, and one `<SegmentURL media="../floor/<f>/<sha256>.m4s"/>` per piece. With
|
||
several floor rungs, the `AdaptationSet` carries no `codecs`, `width` or `height`, and
|
||
each `Representation` carries its own after `bandwidth`:
|
||
`<Representation id="<f>-floor" bandwidth="<b>" codecs="F" width="FW" height="FH">`.
|
||
- `dash/<class>.mpd`, when the class's HTTP view lists a WAI object:
|
||
`<AdaptationSet id="1" contentType="video" mimeType="application/wai" codecs="<union>" width="W" height="H">`
|
||
holding `<EssentialProperty schemeIdUri="tag:transaction.science,2026:wai/http-delivery"/>`,
|
||
`<SupplementalProperty>`s of schemes `tag:transaction.science,2026:wai/floor` (value the
|
||
floor), `tag:transaction.science,2026:wai/class` (the class) and
|
||
`tag:transaction.science,2026:wai/stages` (`../stages/<class>.json`), then
|
||
per layer *k* up to the highest the class lists, a `Representation` with attributes
|
||
`id="<c>-l<k>"`, for *k* > 0 `dependencyId="<c>-l<k−1>"`, and `bandwidth` the peak rate of its own
|
||
layer's objects over the pieces it lists, whose
|
||
`SegmentList` has a timeline entry and a `SegmentURL media="../objects/<hash>.wai"`
|
||
for each piece the class lists to layer *k* (a gap where it does not).
|
||
|
||
The media type `application/wai` is proposed, not registered
|
||
([http-delivery](http-delivery.md) §3). The HLS, DASH and MoQ catalogs of this revision are
|
||
the lead's: each lists the lead component alone, as for a package of one component. Every
|
||
component is listed in the stage views (§8.8), which name each of its objects by content
|
||
hash, so a sink reaches a component beside the lead (§5.8) through them.
|
||
|
||
**8.4** *MoQ.* `moq/catalog.json` is an MSF catalog (MSF §5) and a line feed:
|
||
`{"tracks": [...], "version": "draft-01", "initDataList": [...]}`, its `version` the string
|
||
MSF §5.1.1 gives draft-ietf-moq-msf-01. It is RFC 8785 canonical JSON but for
|
||
`initDataList`, which MSF §5.1.7 places after `tracks`: it is written last, after
|
||
`version`, each member's value canonical. A component's bases and refinements are a stage set of
|
||
[moq-streaming-format](moq-streaming-format.md) §4.1, one track per (layer, deadline),
|
||
by layer, then by deadline ascending, each written as that section's entry: namespace
|
||
the policy's `asset_id`; name `<c>/l<k>`, or `<c>/l<k>@<deadline>` for a refinement
|
||
whose deadline is not `defaults.deadline_ms`; `packaging` `"wai"`; `isLive` false;
|
||
`role` `video`; `renderGroup` 1; `wai_staged` with `set` the component's id, `layer` *k*
|
||
and `lead_ms` null (an on-demand package has no live schedule); `wai_capabilities` its
|
||
entries' capabilities by first appearance (a still among them on the base track, §5.7);
|
||
`wai_determinism` the weakest tier of its entries, absent when that is none; and
|
||
receipts declared: `wai_receipts` true, `wai_receipt_alias` the first eight bytes of
|
||
`BLAKE3("wai:pack-receipt-alias\x01" ‖ namespace ‖ 0x00 ‖ name)` as a little-endian
|
||
integer, less than `2^53` (so two packages published under one key keep apart chains),
|
||
and `wai_receipts_track` `<name>/receipts`. A track `depends` on every track that
|
||
carries an entry below one of its own entries, down the chain to the base, in track
|
||
order: a track of each layer below it, none at or above it, and never one the catalog
|
||
does not hold. A sink receives every track a track depends on (moq-streaming-format
|
||
§4.1), so a dependency is all of them. A refinement track carries
|
||
`wai_delivery_timeout_ms` (§8.6), a catalog field this extension defines under the `wai_`
|
||
prefix of moq-streaming-format §4. The base track has `altGroup` 1 when the component has
|
||
a floor: each floor rung's floor, in ladder order, is a track `<f>/floor` for its floor
|
||
name *f* (§7.1), of `packaging` `"cmaf"` (draft-ietf-moq-cmsf-01), in the same namespace,
|
||
with `isLive` false, `altGroup` 1, `renderGroup` 1, `codec`, `width` and `height`, and
|
||
`initRef` *f*; and a sink that selects a floor subscribes to no layer track. Its
|
||
initialization segment, `floor/<f>/init.mp4` (§7.6, §7.7), is the entry of
|
||
`initDataList` that `initRef` names, as CMSF §3.1 requires: `{"data", "id": "<f>",
|
||
"type": "inline"}`, `data` the segment in base64 with padding (RFC 4648 §4); the entries
|
||
follow the floor tracks' order. The receipts tracks follow, one per media
|
||
track in its order, each `{"isLive": false, "name": "<name>/receipts", "namespace",
|
||
"packaging": "wai-receipts"}`. The catalog passes moq-streaming-format §4.1's check, and a
|
||
packager writes no other. A package holds no receipt: the publisher signs each track's
|
||
groups (a group a piece) on that track's chain as it publishes them (jwp-receipts §2.4.1;
|
||
§8.9 simulates it).
|
||
|
||
**8.5** *Priority.* The base track's publisher priority is 64 and layer *k*'s
|
||
`64 + 32·k`: a refinement track's value is strictly greater than that of every track it
|
||
depends on, so it is delivered after them (MOQT: a lower value is delivered first), and
|
||
moq-streaming-format §4.1's rule holds; a receipts track's is its media track's. The
|
||
priority is MOQT's (`DEFAULT_PUBLISHER_PRIORITY`), set by the publisher, not a catalog
|
||
field.
|
||
|
||
**8.6** *Deadline.* An entry's `deadline_ms` is the longest a refinement may arrive
|
||
after the base of its piece was published and still be presented; the rules' or
|
||
`defaults.deadline_ms`. A refinement track carries DELIVERY_TIMEOUT
|
||
`deadline_ms − live.layer_lag_ms[k]` (§3.4 has refused a policy where that is below 1).
|
||
Entries of one layer with different deadlines travel on different tracks. A relay may
|
||
drop a late refinement (MOQT §5.2), so a refinement never delays the base.
|
||
|
||
**8.7** *Presentation.* A sink presents piece *p* at the highest prefix whose objects
|
||
all arrived and verified by *p*'s presentation instant, and **MUST NOT** delay the base
|
||
to wait for a refinement. What the receipts of a dropped object say is
|
||
[jwp-receipts](jwp-receipts.md)' to define, not this extension's.
|
||
|
||
**8.8** *Stage views.* `stages/<class>.json` is RFC 8785 canonical JSON and a line feed:
|
||
`{"class": "<class>", "components": {"<c>": {"media_playlist":
|
||
"../hls/<class>/wai-<c>.m3u8", "pieces": [...]}}, "policy_digest": "<hex>"}`, with
|
||
`media_playlist` only when the class's HTTP view lists a WAI object (§8.3), and each piece
|
||
`{"dur", "p", "start", "stages": [...]}` with `"floor_only": true` when the class takes
|
||
it as floor only (and `stages` empty), and each listed stage `{"bytes", "h", "layer"}`,
|
||
a refinement's with `"refines"` (the content hash of the entry it refines) and
|
||
`"deadline_ms"`. A component beside the lead (§5.8) is a member of `components` too,
|
||
without `media_playlist`: each piece with its base as its one stage where the class lists it,
|
||
`{"floor_only": true}` and no stage where the class takes it as floor only (§8.1, from the
|
||
component's own entries and timebase).
|
||
|
||
**8.9** *Delivery, simulated.* How a package's objects reach a subscriber over a link is
|
||
simulated by staged-measurement's delivery simulator ([staged-measurement](staged-measurement.md)
|
||
§10, Appendix B); its figures are labelled simulated, with their plan, and are never a
|
||
measurement of a network. The reference packager's `delivery` states the model and the
|
||
plan, and reads the outcome:
|
||
|
||
- **Model.** Each object a class lists of each piece (§8.1; a floor-only piece gives none),
|
||
on the component's frames as units (a frame of a whole number of microseconds): base the
|
||
piece, its layer, units the piece's frames, bytes the whole envelope, and its capture
|
||
time the instant it is published. A live packer (§9.2) publishes a piece's base when the
|
||
frame that decides the piece's end has arrived (the next piece's first frame plus
|
||
`lookahead_frames`, or the source's last frame, §4.3; frame *f* arriving at
|
||
`f · frame_us`), plus the encoder's delay, and layer *k* `layer_lag_ms[k]` after its
|
||
base. Each refinement takes its track's delivery timeout, `deadline_ms −
|
||
layer_lag_ms[k]` milliseconds (§8.6), as its own (staged-measurement Appendix B).
|
||
- **Plan.** Live; the link's round trip, rate and chunk; strict priority, which is §8.5's
|
||
order; each (piece, layer) a subgroup of its own; no base timeout; the subscriber's
|
||
layer cap (moq-streaming-format §4.1, *Capping*); one decode worker, bases first, each
|
||
decode 1 µs (the simulation is of the link); playout from the start-up, and a refinement
|
||
that arrives after its piece's instant discarded (§8.7).
|
||
- **Outcome.** Per piece, at its first frame's presentation instant (the start-up plus its
|
||
start): the highest layer presented by then, or none where its base arrived later; its
|
||
base's arrival; the layers dropped or abandoned. A class that lists nothing of any piece
|
||
sends nothing.
|
||
|
||
Under §8.5's priorities a refinement delays a base by at most one chunk and one decode
|
||
(staged-measurement §10.4). The publisher seals each track's group with a group receipt on
|
||
the track's chain (`wai_receipt_alias`, §8.4); a subscriber accepts a delivered object by
|
||
its Merkle proof, and holds a dropped one by inclusion alone, never as delivered
|
||
([jwp-receipts](jwp-receipts.md) §4.4).
|
||
|
||
**8.10** *Audio renditions.* A programme's audio is offered as **renditions**: its main audio in
|
||
each language it has, dubs, alternates, commentaries and audio description. Each is described by
|
||
an **entry**, and a set of entries is written as HLS `EXT-X-MEDIA` tags of type `AUDIO`
|
||
(`draft-pantos-hls-rfc8216bis-22` §4.4.6.1) and as DASH audio adaptation sets, with the roles and
|
||
accessibility descriptors of ETSI TS 103 285 V1.4.1 §6.1.2. An **audio description** rendition is
|
||
a broadcast mix: the programme audio with an [access-services](access-services.md) §5 description
|
||
mixed in by its mixing instructions. Its entry names the description record by its record id
|
||
(access-services §2), and the writer holds the entry to that record. The reference is the
|
||
`audio_renditions` module of `wai-rs` (feature `audio_renditions`); its corpus is
|
||
[`pack-conformance/renditions/`](../pack-conformance/renditions/), and its second implementation
|
||
`pack-conformance/renditions_verify.py`.
|
||
|
||
**8.10.1** *An entry* is a JSON object with exactly these members, each required:
|
||
|
||
| member | value |
|
||
|---|---|
|
||
| `id` | 1 to 64 of the ASCII letters, digits, `.`, `_` and `-`: the DASH representation's id |
|
||
| `group` | as `id`: the HLS group, `GROUP-ID` |
|
||
| `name` | 1 to 64 of the ASCII letters, digits, space, `.`, `_`, `-`, `(` and `)`, neither first nor last a space: what a player shows, `NAME` |
|
||
| `language` | a language tag read as [live-events](live-events.md) §4.10 reads one |
|
||
| `role` | `main`, `alternate`, `dub`, `commentary` or `description` |
|
||
| `capability` | the capability of its samples, from the table below |
|
||
| `codecs` | its RFC 6381 codecs value, from that capability's row |
|
||
| `channels` | an integer, 1 to 32: the most channels a segment of it holds |
|
||
| `bandwidth` | an integer, 1 to 2^32 − 1: its peak rate in bits per second |
|
||
| `uri` | 1 to 1024 of the ASCII letters, digits, `.`, `_`, `-`, `/`, `:`, `~` and `%`: its media playlist |
|
||
| `default` | `yes` or `no` |
|
||
| `description` | for the role `description`, the description record's id in 64 lowercase hexadecimal digits; `null` otherwise |
|
||
|
||
| capability | codecs |
|
||
|---|---|
|
||
| `wai.audio.aac` | `mp4a.40.2`, `mp4a.40.5`, `mp4a.40.29` |
|
||
| `wai.audio.opus.packet` | `opus` |
|
||
| `wai.audio.flac` | `fLaC` |
|
||
| `wai.audio.ac3` | `ac-3` |
|
||
| `wai.audio.eac3` | `ec-3` |
|
||
|
||
A rendition's media segments hold its samples as the ISO base media file format carries its
|
||
codec (for AC-3 and E-AC-3, ETSI TS 102 366 Annex F, which ATSC A/52 Annex F names); WAI decodes
|
||
none of them.
|
||
|
||
**8.10.2** *Rules.* The entries are held to these rules in order, the first that fails naming the
|
||
refusal:
|
||
|
||
1. every entry has §8.10.1's shape, and there is at least one (`rendition-malformed`);
|
||
2. each entry's capability is in the table (`rendition-capability`) and its codecs value in its
|
||
row (`rendition-codecs`);
|
||
3. no two entries share an `id`, a `uri`, or a `group` and a `name`: the HLS text requires the
|
||
names of a group to differ (`rendition-duplicate`);
|
||
4. a `default` of `yes` is a main's, and a group has at most one (`rendition-default`);
|
||
5. an entry names a description exactly when its role is `description`, and the record it names
|
||
is among those given (`rendition-description-unbound`); that record reads and verifies as
|
||
access-services §2 reads a record, is of kind `wai-description`, and meets access-services
|
||
§5.2's rules (`rendition-description-invalid`); and its `lang` is the entry's `language`,
|
||
byte for byte (`rendition-description-language`);
|
||
6. no two entries of a group that a player chooses unasked (§8.10.3) have one language and one
|
||
set of characteristics (`rendition-ambiguous`): the HLS text asks such members to differ in
|
||
them;
|
||
7. every group has the same members, by name, each with the same language, role, default and
|
||
description in every group (`rendition-groups-differ`): the HLS text requires the groups of
|
||
one type to hold the same members with identical attributes but for their URI and their
|
||
channels, so a group of stereo AAC and a group of 5.1 E-AC-3 offer the same choices.
|
||
|
||
**8.10.3** *HLS.* Each entry, in order, is one tag:
|
||
|
||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="G",NAME="N",LANGUAGE="L",DEFAULT=D,AUTOSELECT=A,CHANNELS="C",CHARACTERISTICS="X",X-WAI-DESCRIPTION="R",URI="U"
|
||
|
||
`G`, `N`, `L`, `U` and `C` are its group, name, language, URI and channels; `D` is `YES` for a
|
||
default and `NO` otherwise; `A` is `YES` for the roles `main`, `dub` and `description`, the
|
||
renditions a player may choose because they match its language or its listener's accessibility
|
||
setting, and `NO` for `alternate` and `commentary`, which a listener asks for. `X` is, for an
|
||
audio description, `public.accessibility.describes-video`, then `public.machine-generated` when
|
||
its record's voicing is `machine` or `machine-reviewed`, as access-services §5.6 item 5 requires
|
||
the synthesized voice to be labelled; `CHARACTERISTICS` is left out when `X` is empty. `R` is an
|
||
audio description's record id, and `X-WAI-DESCRIPTION` is left out for any other role: a client
|
||
ignores an attribute it does not recognise (the HLS text, §6.3.1), and one that reads it fetches
|
||
the record and its DAPT document (access-services §5.5).
|
||
|
||
**8.10.4** *DASH.* Each entry, in order, is one adaptation set, its `id` 2 and up (1 is the
|
||
video's, §8.3), indented as §8.3's:
|
||
|
||
<AdaptationSet id="K" contentType="audio" mimeType="audio/mp4" lang="L" codecs="C" segmentAlignment="true">
|
||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="O"/>
|
||
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"/>
|
||
<SupplementalProperty schemeIdUri="tag:transaction.science,2026:wai/description" value="R"/>
|
||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="H"/>
|
||
<Representation id="I" bandwidth="B"/>
|
||
</AdaptationSet>
|
||
|
||
`O` is the role, but `alternate` for an audio description, with the `Accessibility` descriptor
|
||
of value 1 (for the visually impaired): ETSI TS 103 285 §6.1.2 Table 5's broadcast-mix audio
|
||
description. The `Accessibility` and `SupplementalProperty` lines are written for an audio
|
||
description only. `H` is the channels, `I` the `id`, `B` the bandwidth and `C` the codecs. The
|
||
segment addressing of each representation is §8.3's.
|
||
|
||
**8.10.5** *What it does not do.* The writer states renditions; it does not mix. The mix of an
|
||
audio description is its producer's, by the record's mixing instructions, and a receiver-mixed
|
||
description (a description stem a player mixes) is not a rendition here.
|
||
|
||
**8.10.6** *Reasons.*
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `rendition-malformed` | §8.10.2 rule 1 |
|
||
| `rendition-capability` | §8.10.2 rule 2 |
|
||
| `rendition-codecs` | §8.10.2 rule 2 |
|
||
| `rendition-duplicate` | §8.10.2 rule 3 |
|
||
| `rendition-default` | §8.10.2 rule 4 |
|
||
| `rendition-description-unbound` | §8.10.2 rule 5 |
|
||
| `rendition-description-invalid` | §8.10.2 rule 5 |
|
||
| `rendition-description-language` | §8.10.2 rule 5 |
|
||
| `rendition-ambiguous` | §8.10.2 rule 6 |
|
||
| `rendition-groups-differ` | §8.10.2 rule 7 |
|
||
|
||
**8.11** *Marks.* A lead offers **marks** a sink navigates by: its **shots**, the pieces that
|
||
follow a cut of §4.3 as its stage index flags them; its **chapters**, titled spans that each begin
|
||
at a piece's start; its **break candidates**, the piece starts at which a break may be inserted
|
||
without cutting into a shot or a clip; and its **clips**, the run of whole pieces each moment of
|
||
the policy covers (§8.1). The marks are a pure function of the lead's stage index, the effective
|
||
policy and the chapters a packager is given, so a verifier holding the index and the policy
|
||
derives them again and compares. They are written as one JSON document, the record
|
||
`wai.delivery.marks` (SPEC §5), and in three forms a player of each kind reads: a WebVTT chapters
|
||
document, the date ranges of an HLS media playlist and the event streams of a DASH Period. Each
|
||
form reads back to what it was written from. A sink that assembles a highlight reel of its own
|
||
takes its clips from the document ([sink-predicate](sink-predicate.md) §9). The reference is the
|
||
`pack_marks` module of `wai-rs` (feature `pack_marks`); its corpus is
|
||
[`pack-conformance/marks/`](../pack-conformance/marks/), and its second implementation
|
||
`pack-conformance/marks_verify.py`. The module writes the marks from a package's index and policy;
|
||
the reference packager does not yet write them into a package or its playlists.
|
||
|
||
Texts read for this section, each at its source: WebVTT, W3C Candidate Recommendation Draft of
|
||
2026-05-20, §1.6, §3.5, §4.1, §4.2.3, §4.5.1, §4.6.2 and §6.6; `draft-pantos-hls-rfc8216bis-22`
|
||
§4.2 and §4.4.5.1; and the MPD schema of ISO/IEC 23009-1:2026 at its revision of 2026-06-13 (its
|
||
types `PeriodType`, `EventStreamType` and `EventType`, and its example MPD G9). The clause text of
|
||
ISO/IEC 23009-1 was not read.
|
||
|
||
What the shots rest on. `wai.pack.scenecut/1` cuts a source in integers, over its frames' luma
|
||
histograms, under thresholds the policy pins (`cut_abs_permille`, `cut_rel_permille`,
|
||
`cut_window`, `lookahead_frames`, §4.3), and its cuts are the piece boundaries. The index flags each
|
||
piece that follows a cut (§6.2, flag bit 0) and binds the policy by its digest. So a shot of the
|
||
marks is a cut that is also a closed piece's start, where a sink can seek and a break can be
|
||
spliced; a cut within `min_ms` of a piece's start starts no piece (§4.3 clause 4) and is no shot
|
||
here. A verifier holding the source recomputes the cuts (§11.3); one holding the index and the
|
||
policy holds the marks to them.
|
||
|
||
**8.11.1** *Inputs.* The lead's stage index (§6.2); the effective policy in its JSON form (§3.2);
|
||
and the **chapters** a packager is given: a language, and 0 to 1,024 chapters, each a title and
|
||
the instant it begins at, `from_ms`, in milliseconds on the presentation timeline.
|
||
|
||
- A **title** is 1 to 1,024 bytes of UTF-8. Each of its code points is in U+0020–U+007E but
|
||
U+0022, or in U+00A0–U+D7FF, U+E000–U+FFFD or U+10000–U+10FFFF and is not U+2028, U+2029 or
|
||
U+FEFF; and it neither begins nor ends with U+0020 nor holds two of them in a row. It is one
|
||
line, which a WebVTT cue holds; it has no quotation mark, which an HLS quoted string cannot hold
|
||
(`draft-pantos-hls-rfc8216bis-22` §4.2); and it has no space that a reader's handling of white
|
||
space would change.
|
||
- A **language** is a tag as [live-events](live-events.md) §4.10 reads one. A **token** is
|
||
route-record §2's.
|
||
- A piece *p* spans `s(p)` to `e(p)` milliseconds, the floors of `start · den · 1000 / num` and of
|
||
`(start + duration) · den · 1000 / num` on the index's timebase (§8.1). The lead's **end** is
|
||
`e` of its last piece.
|
||
|
||
**8.11.2** *Derivation.* The steps run in order, and the first that fails names the refusal.
|
||
|
||
1. *The index.* It reads (§6.2) and holds §6.3's rules 1 to 3; it is on-demand content
|
||
(`prev_index_digest` zero) of at least one piece; its `plan_digest` is §4.2's over its pieces'
|
||
starts; every `s(p)` and `e(p)` is at most 2^53 − 1; the `s(p)` strictly ascend, so no two
|
||
pieces begin in one millisecond; and the last piece's `e` is above its `s`. Otherwise
|
||
`marks-index`.
|
||
2. *The policy.* It is a document (route-record §2) whose digest (§3.2) is the index's
|
||
`policy_digest` and whose `lead` is the index's component. Its `moment` is an array of objects,
|
||
each of exactly the members `id` (a string), `from_ms` and `to_ms` (integers, `to_ms` above
|
||
`from_ms`) and `tags` (an array of strings). Otherwise `marks-policy`. Then there are at most 32
|
||
moments, their ids are distinct tokens, and each moment's tags are distinct tokens; otherwise
|
||
`marks-moment`.
|
||
3. *The moments and the clips.* For every piece *p* and every bit *k* from 0 to 31 of its
|
||
`moments`, the bit is set exactly when there is a moment *k* and *p* lies in it, `s(p)` below its
|
||
`to_ms` and `e(p)` above its `from_ms` (§8.1); otherwise `marks-moment`. Each moment some piece
|
||
lies in is a **clip**: the moment's id and tags; its first piece, the first that lies in it; its
|
||
number of pieces, to the last that lies in it; and its `start_ms` and `end_ms`, `s` of the first
|
||
and `e` of the last. A moment no piece lies in has no clip.
|
||
4. *The chapters.* There are at most 1,024; a language is given exactly when there is a chapter,
|
||
and it reads (`marks-chapter`). Every title is one (`marks-title`). The instants strictly
|
||
ascend, each below the lead's end (`marks-chapter`). A chapter **begins at** the last piece
|
||
whose `s` is at or before its instant, and no two chapters begin at one piece
|
||
(`marks-chapter`). Its `start_ms` is `s` of that piece, and its `end_ms` the next chapter's
|
||
`start_ms`, or the lead's end for the last.
|
||
5. *The shots.* Every piece after the first whose flag bit 0 is set, with its `s`.
|
||
6. *The break candidates.* Every piece after the first, with its `s` and the first of these that
|
||
holds: its flag bit 2 is set, `cue` (a cue started it); it lies inside a clip, one of a clip's
|
||
pieces other than its first, and it is no candidate; a chapter begins at it, `chapter`; its flag
|
||
bit 0 is set, `cut`. A piece of which none holds is no candidate.
|
||
|
||
A break candidate is an offer: a decider or a sink places a break there or not
|
||
([live-ads](live-ads.md), [sink-predicate](sink-predicate.md)), and the marks place none. Piece 0
|
||
is no candidate: a break before the programme is a pre-roll, which a schedule places.
|
||
|
||
**8.11.3** *The document.* The marks are a JSON object of exactly these members, written as its
|
||
RFC 8785 canonical form and a line feed:
|
||
|
||
| member | value |
|
||
|---|---|
|
||
| `kind` | `"wai-pack-marks"` |
|
||
| `version` | 1 |
|
||
| `component` | the lead's id, 1 to 255 bytes |
|
||
| `index` | the lead's index digest (§6.2): 64 lowercase hexadecimal digits |
|
||
| `duration_ms` | the lead's end, at least 1 |
|
||
| `lang` | the chapters' language, or `null` when there is no chapter |
|
||
| `shots` | each shot, `{"piece", "at_ms"}`, in piece order |
|
||
| `chapters` | each chapter, `{"title", "from_ms", "piece", "start_ms", "end_ms"}`, in order |
|
||
| `breaks` | each break candidate, `{"piece", "at_ms", "why"}`, in piece order; `why` is `"cue"`, `"chapter"` or `"cut"` |
|
||
| `clips` | each clip, `{"id", "tags", "first_piece", "pieces", "start_ms", "end_ms"}`, in the order of its moment in the policy |
|
||
|
||
Its **digest** is `BLAKE3("wai:pack-marks\x01" ‖ document)`, over the document's bytes with its
|
||
line feed.
|
||
|
||
A **reader** is given a text and refuses it `marks-malformed` unless it holds at most 16,777,216
|
||
bytes and ends with one line feed, before which is a document (route-record §2) that is its own
|
||
canonical form, of exactly the members above, each of its form, `kind` and `version` as stated, and:
|
||
|
||
- the shots and the break candidates strictly ascend in `piece` and in `at_ms`, each with `piece`
|
||
at least 1 and `at_ms` below `duration_ms`;
|
||
- there are at most 1,024 chapters; `lang` reads exactly when there is one, and is `null`
|
||
otherwise; every title is one; each chapter has `start_ms` ≤ `from_ms` < `end_ms` ≤
|
||
`duration_ms`; each chapter's `piece` is above the one before it, and each ends where the next
|
||
starts; and the last ends at `duration_ms`;
|
||
- there are at most 32 clips, their ids distinct tokens, each clip's tags distinct tokens, its
|
||
`pieces` at least 1, and `start_ms` < `end_ms` ≤ `duration_ms`.
|
||
|
||
A **verifier** is given the lead's index, the effective policy and a text. It reads the text,
|
||
derives the marks (§8.11.2) from the index, the policy and the text's own language and chapters
|
||
(each a title and its `from_ms`), and refuses with the derivation's reason, or `marks-mismatch`
|
||
unless the document of what it derived is the text, byte for byte. So a document that leaves out a
|
||
shot, offers a candidate inside a clip, widens a clip or begins a chapter anywhere but at its
|
||
piece's start is refused, and one whose titles another packager chose is not.
|
||
|
||
**8.11.4** *WebVTT.* The chapters as a WebVTT file using chapter title text (WebVTT §4.6.2): the
|
||
bytes `WEBVTT` and a line feed; then for each chapter *k*, from 1, a line feed; *k* in decimal
|
||
and a line feed, the cue's identifier; its `start_ms` and `end_ms` each as `HH:MM:SS.mmm` (hours of
|
||
two digits or more, minutes and seconds of two, milliseconds of three), joined by ` --> `, and a
|
||
line feed; and its title, with `&`, `<` and `>` written `&`, `<` and `>`, and a line feed.
|
||
The cues are consecutive, so the file uses only nested cues (WebVTT §4.5.1), and each cue's
|
||
payload is chapter title text (§4.2.3 there) with no tag, which WebVTT §6.6 reads back to the
|
||
title. The file states no language: a player takes the track's, and a reader is given it. A reader
|
||
of the form reads each chapter's title and span, and refuses as `marks-vtt` a text that is not what
|
||
this section writes of them: another header, a byte-order mark, a line end other than a line feed, a
|
||
character reference other than the three, a `<` or a `>`, a time not as written, an identifier out
|
||
of sequence, a title that is not one (§8.11.1), or chapters that last no time or do not each end
|
||
where the next begins.
|
||
|
||
**8.11.5** *HLS.* The lines a media playlist carries, given `pdt0`, the program date-time of
|
||
media time 0 in milliseconds since 1970-01-01T00:00:00Z, each ended by a line feed:
|
||
|
||
- `#EXT-X-PROGRAM-DATE-TIME:` and `pdt0` as `YYYY-MM-DDTHH:MM:SS.mmmZ`, in UTC with three digits
|
||
after the point: a playlist that holds a date range holds a program date-time
|
||
(`draft-pantos-hls-rfc8216bis-22` §4.4.5.1);
|
||
- for each chapter *k*, from 1:
|
||
`#EXT-X-DATERANGE:ID="chapter-k",CLASS="science.transaction.wai.chapter",START-DATE="D",DURATION=S,X-WAI-TITLE="T"`;
|
||
- for each break candidate *k*, from 1:
|
||
`#EXT-X-DATERANGE:ID="break-k",CLASS="science.transaction.wai.break",START-DATE="D",DURATION=0.000,X-WAI-WHY="W"`;
|
||
- for each clip:
|
||
`#EXT-X-DATERANGE:ID="clip-I",CLASS="science.transaction.wai.clip",START-DATE="D",DURATION=S`,
|
||
then `,X-WAI-TAGS="G"` when it has tags, `G` its tags joined by `,`.
|
||
|
||
`D` is `pdt0` plus the mark's start, written as `pdt0` is; `S` is its `end_ms` less its `start_ms`
|
||
as seconds with three digits after the point (`5.600`); `T` its title, `W` its `why` and `I` its id.
|
||
A candidate is an instant, of duration 0 (§4.4.5.1 there). A packager writes the lines after a
|
||
media playlist's header tags and before its first segment, whose media time is 0, so that the
|
||
program date-time applies to it. A mark whose date would fall after 9999-12-31T23:59:59.999Z is
|
||
refused `marks-hls`. The three classes are WAI's, and `X-WAI-TITLE`, `X-WAI-WHY` and `X-WAI-TAGS`
|
||
are attributes of those classes; no `CUE` is written, since a candidate triggers nothing, and a
|
||
player that knows none of the classes passes the tags by. A reader, given `pdt0`, reads each line's
|
||
attribute list (§4.2 there) and refuses as `marks-hls` lines that are not what this section writes
|
||
of the marks they carry: a first line other than the program date-time, a date before `pdt0` or not
|
||
as written, a class this section does not write, attributes in another order, a value unquoted that
|
||
is written quoted, a title, tag or reason that is not one, or chapters that do not meet.
|
||
|
||
**8.11.6** *DASH.* The event streams of the Period that holds the lead, written before its first
|
||
`AdaptationSet` (an `EventStream` precedes it in the schema's `PeriodType`) and indented as §8.3's
|
||
MPD is, each line ended by a line feed. For each of the chapters, the break candidates and the clips,
|
||
in that order, when there is one: the line
|
||
` <EventStream schemeIdUri="tag:transaction.science,2026:wai/marks" value="V" timescale="1000">`,
|
||
`V` being `chapter`, `break` or `clip`; for each mark *k*, from 1, the line
|
||
` <Event presentationTime="P" duration="L" id="k">C</Event>`; and the line
|
||
` </EventStream>`. `P` is the mark's start in milliseconds (the Period starts at 0, §8.3), `L` its
|
||
duration (0 for a candidate) and `C` its content, with `&`, `<` and `>` written as in §8.11.4: a
|
||
chapter's title, a candidate's `why`, or a clip's id and then its tags, each after one space. The
|
||
content is the event's own text, which the schema's `EventType` holds (it is mixed, and
|
||
`messageData` is prohibited there), as its example G9 carries a phone number. A reader refuses as
|
||
`marks-dash` text that is not what this section writes.
|
||
|
||
**8.11.7** *Reasons.*
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `marks-index` | §8.11.2 step 1 |
|
||
| `marks-policy` | §8.11.2 step 2 |
|
||
| `marks-moment` | §8.11.2 steps 2 and 3 |
|
||
| `marks-chapter` | §8.11.2 step 4 |
|
||
| `marks-title` | §8.11.2 step 4 |
|
||
| `marks-malformed` | §8.11.3 |
|
||
| `marks-mismatch` | §8.11.3 |
|
||
| `marks-vtt` | §8.11.4 |
|
||
| `marks-hls` | §8.11.5 |
|
||
| `marks-dash` | §8.11.6 |
|
||
|
||
## 9. Package receipt
|
||
|
||
**9.1** A version-2 package receipt binds the asset id; the policy digest; its parent
|
||
kind and parent receipt hash; per component its id, media class, index digest and stage
|
||
root; the entry, distinct-object, referenced-byte and stored-byte counts, which a
|
||
verifier recomputes from the indexes; the packager `builtin:<crate>@<version>`; the
|
||
energy figure with its class, or the unmetered form; the grant; and the signer. Its
|
||
signing payload, big-endian:
|
||
|
||
labelled_domain("wai:pack-receipt\x01", label)
|
||
u32 len ‖ asset_id ‖ policy_digest[32]
|
||
u8 parent_kind (0 none, 1 live-window, 2 re-pack) ‖ [parent_receipt_hash[32] if not 0]
|
||
u32 n ‖ n × ( u32 len ‖ id ‖ u8 media_class ‖ index_digest[32] ‖ stage_root[32] )
|
||
u64 total_entries ‖ u64 unique_objects ‖ u64 total_bytes ‖ u64 stored_bytes ‖ u64 work_units
|
||
u32 len ‖ packager
|
||
u64 joules_micro ‖ write_label(label)
|
||
grant_hash[32] ‖ u32 len ‖ capability ‖ u64 joule_ceiling_micro ‖ u8 0 | u8 1 ‖ u64 funds_ceiling
|
||
signer_pubkey[32] ‖ u32 len ‖ signer_id
|
||
|
||
receipt_hash = BLAKE3("wai:video-package-receipt-id\x01" ‖ payload ‖ sig)
|
||
|
||
`labelled_domain` and `write_label` are [energy-measurement](energy-measurement.md)
|
||
§2.8's: unmetered, the domain as written and no label; metered, its final byte `\x02`
|
||
and the class's signed encoding. `total_entries` counts every entry; `unique_objects`
|
||
the distinct content hashes over every component; `total_bytes` the entries'
|
||
`byte_len`; `stored_bytes` the `byte_len` of each content hash's first entry;
|
||
`work_units` equals `total_entries`. A receipt whose content hash comes at two lengths
|
||
has no counts. The signature is Ed25519 by `signer_pubkey`, verified as SPEC §7
|
||
*Signatures* requires; a signature or a key that rule refuses is `receipt-signature`.
|
||
|
||
Its JSON form, `package.receipt.json`, is RFC 8785 canonical, hexadecimal lowercase,
|
||
integers as numbers no greater than `2^53 − 1` (a packager refuses to seal anything
|
||
else, `receipt-json`): `asset_id`; `components`, each `{"id", "index":
|
||
"index-<id>.wsix", "index_digest", "media_class", "stage_root"}`; `grant`
|
||
`{"capability", "funds_ceiling", "grant_hash", "joule_ceiling_micro"}` with `null` for
|
||
no ceiling (a joule ceiling of `2^64 − 1`); `joules_micro`; `kind` `"package"`;
|
||
`packager`; `parent_kind` (`none`, `live-window` or `re-pack`); `parent_receipt_hash`
|
||
(`null` for none); `policy_digest`; `receipt_hash`; `sig`; `signer_id`;
|
||
`signer_pubkey`; `stored_bytes`; `total_bytes`; `total_entries`; `unique_objects`;
|
||
`version` 2; `work_units`; `energy_provenance`, the class's label (energy-measurement
|
||
§2.7) or `null` when unmetered; and the class's evidence members exactly as its class
|
||
carries them. Every member is present, with `null` where it has no value
|
||
(`parent_receipt_hash`, `energy_provenance`, a grant's ceilings) and nowhere else.
|
||
|
||
A reader refuses, before anything else, JSON that is not RFC 8259 or that names a member
|
||
twice in one object, at any depth (`receipt-json`). It then reads `"version"`: absent or
|
||
1 is a version-1 receipt (§9.3), 2 a version-2 one, anything else `receipt-version`. It
|
||
then refuses (`receipt-json`) a member it does not define or one missing, `null` where
|
||
the member has a value, a value of another form (a number that is not an integer of its
|
||
range, `-0` among them; a string that is not Unicode scalar values), evidence other than
|
||
exactly what the class carries, a class label energy-measurement §2.7 refuses, a parent
|
||
kind and hash that disagree, an `index` other than `index-<id>.wsix`, and a
|
||
`receipt_hash` that is not the receipt's. A reader does not require the canonical form;
|
||
a writer writes it, and a package's `package.receipt.json` must be it (§11.3).
|
||
|
||
A verifier of a receipt against its indexes, in its components' order, refuses, the
|
||
first that applies: no component, or another number of indexes than components, a live
|
||
window's receipt included (`receipt-index-count`); an index that breaks §6 (its
|
||
reason); one that does not hash to its `index_digest` (`receipt-index-digest`), has
|
||
another stage root (`receipt-stage-root`), names another component or media class
|
||
(`receipt-component`) or another policy digest (`receipt-policy-digest`); two components
|
||
of one id (`receipt-component`); other counts, or a content hash at two lengths in any of the
|
||
indexes (`receipt-counts`); a parent kind that does not
|
||
match the indexes' `prev_index_digest`, which is zero in every index for none and a
|
||
re-pack and non-zero in every one for a live window (`receipt-parent-kind`); an energy
|
||
figure and class that do not cohere, or a legacy label (`receipt-energy`); a signature
|
||
that does not verify (`receipt-signature`).
|
||
|
||
**9.2** Live windows chain by the parent link with parent kind `live-window`. A re-pack
|
||
of an earlier package **MAY** name it with parent kind `re-pack`, and one that takes its
|
||
bases and refinements from it **MUST** (§11.5).
|
||
|
||
The reference live packer (`wai-pack live`, `wai_pack::live`) takes a source a frame at a
|
||
time, cuts it with the segmenter of §4.3, which decides a frame once `lookahead_frames`
|
||
more have arrived, and codes each piece as it closes with the coder the on-demand packer
|
||
uses (§5.4–§5.7). So a live and an on-demand pack of one source under one policy write
|
||
the same plan and the same bases and refinements, byte for byte. It writes no floor: a
|
||
supplied floor is made from the whole source (§7.3), so it is the on-demand package's.
|
||
Its pieces are gathered into windows: each a stage index of its pieces' bases and
|
||
refinements (`first_piece` and `first_entry` continuing the chain, `prev_index_digest`
|
||
the previous window's index digest, zero for the first) sealed by a version-2 receipt
|
||
whose parent is the previous window's (`live-window`; none for the first). A piece's
|
||
objects are published when the frame that decides its end has arrived: its base then,
|
||
and layer *k* `layer_lag_ms[k]` after it (§8.6, §8.9).
|
||
|
||
It writes as it goes: each object when it is published, and each window's index and
|
||
then its receipt when the window is sealed, every file whole (written beside its place
|
||
and renamed into it); the plan file and `LISTING` at the end of the source. It keeps no
|
||
record per file of the session: each file's listing line goes to a hidden file beside
|
||
the package as the file is written, and is sorted into `LISTING` at the end (the one
|
||
moment it holds a line per file); an object already written is known by its file; an
|
||
object's bytes are not held once written. What it holds is what no window has sealed
|
||
yet: those pieces' entry fields and the content hash of each object they published
|
||
first. So with windows of `n` pieces that is at most `n` pieces', and with one window
|
||
over the whole source (the default) every piece's until the end. Besides those it holds
|
||
the frames of the piece being cut and each piece's first frame, eight bytes a piece, for
|
||
the plan file. A source that fails before its end (a short frame, a frame of another
|
||
size) leaves every sealed window and the objects it lists, removes the objects only
|
||
unsealed pieces published, writes no plan file or `LISTING`, and is an error that names
|
||
what was kept.
|
||
|
||
**9.3** A version-1 receipt (`wai.video.package`'s segment map) keeps its bytes and
|
||
still verifies. Its JSON form is RFC 8785 canonical with `version` 1: `asset_id`,
|
||
`energy_provenance` (`null` when unmetered) with its evidence, `grant`, `joules_micro`,
|
||
`kind` `"package"`, `parent_receipt_hash`, `receipt_hash`, `segment_map_root`,
|
||
`segments` (each `{"byte_len", "content_hash", "duration_ms", "index"}`), `sig`,
|
||
`signer_id`, `signer_pubkey`, `stored_bytes`, `total_bytes`, `total_segments`,
|
||
`unique_segments`, `work_segment_ops`. A reader reads one without `version` as version 1,
|
||
and, because version-1 receipts were issued before this form, keeps them verifying:
|
||
`energy_provenance` may be absent (read as unmetered), an evidence member written `null`
|
||
is absent, and an on-chip counter's filtering, absent, reads as unknown. It refuses a
|
||
member named twice as version 2's reader does.
|
||
Its signing payload, big-endian, is
|
||
|
||
"wai:video-package-receipt\x01"
|
||
u32 len ‖ asset_id ‖ segment_map_root[32]
|
||
u64 total_segments ‖ u64 unique_segments ‖ u64 total_bytes ‖ u64 stored_bytes ‖ u64 work_segment_ops
|
||
u64 joules_micro ‖ class (u8 0 when unmetered, else the class's signed encoding)
|
||
the grant as §9.1 writes it
|
||
u8 0 | u8 1 ‖ parent_receipt_hash[32]
|
||
u64 n ‖ n × BLAKE3("wai:video-package-seg\x01" ‖ u64 index ‖ content_hash[32] ‖ u64 duration_ms ‖ u64 byte_len)
|
||
signer_pubkey[32] ‖ u32 len ‖ signer_id
|
||
|
||
`segment_map_root` is the jwp-receipts §2.2 Merkle root over those segment leaves, the
|
||
counts are the segments' (the bytes of one copy per content hash for `stored_bytes`), and
|
||
`receipt_hash` is version 2's.
|
||
Both versions share the identity domain, so the figure profile
|
||
`wai:video-package-receipt-id` ([energy-measurement](energy-measurement.md) §6.1)
|
||
covers both; a version-2 receipt's figure is read only with the indexes it verifies
|
||
against.
|
||
|
||
**9.4** Where the energy figure is not the unmetered form, the packager **MUST** publish
|
||
a `measurement` claim about it ([jwp-receipts](jwp-receipts.md) §2.4.8,
|
||
[energy-measurement](energy-measurement.md) §6) as
|
||
`claims/<receipt_hash>.measurement.json`: subject the receipt, figure profile
|
||
`wai:video-package-receipt-id`, signed with the package key and checked against the
|
||
receipt before it is written. A verifier refuses a metered package without it
|
||
(`measurement-claim-missing`) or with one that does not verify and check against the
|
||
receipt (`measurement-claim-invalid`). The figure brackets the whole pack: it starts once
|
||
the policy's own refusals (§3.4, §11.1) have passed, before the source's first read, and
|
||
covers both reads of the source, the supplied floor's checks (§7.3, §7.7), a parent's
|
||
verification (§11.5), every piece's coding and every file written, and stops before the
|
||
receipt is sealed. A packager **MUST NOT** leave any of that work outside it.
|
||
|
||
## 10. Royalty-free packaging
|
||
|
||
A packager run with `royalty_free_only = true` **MUST** refuse to emit any object whose
|
||
capability the registry does not classify as royalty-free, including one it does not
|
||
classify at all (§3.6, rule 5). A package receipt says nothing about patents.
|
||
`royalty_free_only` records the registry class the packager read, "licensed
|
||
royalty-free by its standards body"; it is not a freedom-to-operate opinion.
|
||
|
||
## 11. Package, re-pack equivalence and verification
|
||
|
||
**11.1** *The package* is a directory of these files and no other file or directory
|
||
(every directory below it holds one of them):
|
||
|
||
policy.toml policy.json plan-<c>.txt index-<c>.wsix package.receipt.json
|
||
objects/<content hash>.wai …
|
||
floor/<f>/init.mp4 floor/<f>/<sha256>.m4s … (per floor rung, of floor name f)
|
||
floor.listing (a supplied CMAF floor's listing, §7.7)
|
||
hls/classic/… hls/<class>/… dash/… moq/catalog.json stages/<class>.json
|
||
claims/<receipt_hash>.measurement.json (a metered package)
|
||
LISTING
|
||
|
||
`policy.toml` is the policy as given; `policy.json` its effective canonical JSON
|
||
(§3.2). `LISTING` is the listing (SPEC §3.1) of every other file, and its SHA-256 is the
|
||
**pack digest**. A component or class id or a floor name (§7.1) that cannot be one path
|
||
segment of a listing, two class ids or two floor names equal once `A`–`Z` are mapped to
|
||
`a`–`z`, and a class named `classic` in any case (ids name paths, and paths fold case)
|
||
are refused (`pack-id`).
|
||
|
||
**11.2** *Re-pack equivalence.* Given the same source bytes, effective policy, supplied
|
||
floor, signing key and packager identity, a conforming reference packager **MUST**
|
||
produce byte-identical package files on every machine and whichever fabric runs each
|
||
step (§11.6), except the energy figure and measurement claim of a metered package and
|
||
floor objects §7.5 covers. A verifier
|
||
**MAY** pack again and compare listings. A packager whose identity differs is not held
|
||
to this. A re-pack from a parent (§11.5) is held to it with the parent among its inputs;
|
||
and where the parent was packed by a conforming packager of the same identity, from the
|
||
same source, under base
|
||
and refinement rungs of the same capabilities and parameters, its files are those of a
|
||
pack that codes every piece, but for its receipt's parent and so `LISTING`.
|
||
|
||
**11.3** *Verification.* A verifier of a package, without its sources, applies these
|
||
checks in this order and refuses at the first that fails; a check that reads files a
|
||
failed check names is not applied:
|
||
|
||
1. Every file below the package's directory, read strictly — a name beginning with `.`
|
||
and a `*.sha256` file included, as a listing (SPEC §3.1) would not skip them — is a
|
||
regular file whose path a listing can carry; a symbolic link, a directory that
|
||
cannot be read or holds nothing, or anything else is refused (`unexpected-file`).
|
||
2. `LISTING` is present (`listing-missing`) and is the listing of every other file
|
||
(`listing-mismatch`).
|
||
3. `package.receipt.json` reads as a version-2 receipt (§9.1: `receipt-json`,
|
||
`receipt-version`) and is that receipt's canonical form, byte for byte
|
||
(`receipt-json`).
|
||
4. `policy.json` hashes to the receipt's policy digest (`policy-digest-mismatch`);
|
||
`policy.toml` is a policy (its own reason, §3.2–§3.4); `policy.json` is its
|
||
effective canonical JSON, `royalty_free_only` forced on where `policy.json` says
|
||
`true` (`policy-json-mismatch`; an override never turns it off, §3.2, so a
|
||
`policy.json` that says `false` over a `policy.toml` that says `true` is refused
|
||
here); its ids and floor names can name files (`pack-id`, §11.1); and under that effective policy it
|
||
asks for nothing a packager may not emit (§3.6's reason; under `royalty_free_only`, a
|
||
patent-pool floor is `license-refused`).
|
||
5. The receipt's components are the policy's, by id, in order, each of the policy's
|
||
media (`policy-component-mismatch`), so a package is never a receipt over fewer
|
||
components than its policy has; each index is present (`index-missing`); the receipt
|
||
verifies against them (§9.1, the index rules of §6 among its reasons).
|
||
6. Per component, in order (a component beside the lead as the item after this list
|
||
says):
|
||
- the index against the policy (`index-policy-mismatch`): its timebase and media
|
||
class are the component's; its string table is the component's id, the class ids
|
||
in policy order, then each capability at its first use; its class records are the
|
||
policy's classes, in order, with their `max_layer`, floor-only flag and `max_kbps`;
|
||
every piece's kind is one a video piece has, `none` exactly in a floor-only
|
||
component (§7.8); its rules and moments are §8.1's from
|
||
its kind and span; its entries are the rungs of its ladder (§5.7), by role,
|
||
capability and layer: its base, each refinement in layer order, each floor (a
|
||
floor-only component's, its floors alone); each
|
||
refinement's deadline is its plan's (§8.6); and its class plan is §8.1's from its
|
||
entries;
|
||
- the plan file holds the index's starts and its digest is the header's
|
||
(`plan-mismatch`);
|
||
- every entry's object is present (`object-missing`) and keeps the object rules of
|
||
§6.3, its counted bytes included;
|
||
- before any frame is decoded, each piece's floor objects, where the policy has floor
|
||
rungs and an object's payload reads as §7.2's framing, hold one unit per frame of
|
||
their piece (`floor-object-mismatch`);
|
||
- every base and refinement, in entry order: a base decodes by its capability (a
|
||
still is a PNG §5.7 reads, its one frame held for the piece), or it is
|
||
`prefix-digest`; its manifest is the one §5.2 states for its piece's frames and its
|
||
frame size (`object-manifest`); a refinement's manifest is §5.4's
|
||
(`object-manifest`), and it applies in layer order, to every frame of the stage
|
||
below it, by a staged-delivery §4 sink under the reference tools' cost limit and
|
||
chain budget, or it is `prefix-digest`; a base that does not decode, or a layer that
|
||
does not apply, ends the check;
|
||
- then every decode-equivalent prefix decodes to its `prefix_digest`
|
||
(`prefix-digest`);
|
||
- when all of that holds, the floors, floor rung by floor rung in ladder order: per
|
||
piece, one floor object per floor rung, the rung's the *k*-th floor entry for the
|
||
*k*-th floor rung, of the rung's capability, whose renderer, settings, framing
|
||
header, unit count and target are §7.2's (`floor-object-mismatch`) and which
|
||
presents the piece's base (`floor-motif-mismatch`), or, in a floor-only component,
|
||
which carries no `derived_from` (`floor-object-mismatch`); its units each one temporal unit
|
||
holding a frame, the first of the rung's first piece carrying a sequence header,
|
||
which, for the component's first floor rung, is within the video floor as §7.1 holds
|
||
the floor rendition (`floor-av1-profile`, `floor-av1-level`), checked before the
|
||
header is read further;
|
||
every sequence header the first's bytes, every shown key frame carrying one
|
||
(`floor-av1-malformed`); each piece opening on a shown key frame, and no other unit
|
||
being one unless the policy allows extra key frames (`floor-keyframe-mismatch`); and
|
||
the rung's CMAF files are the ones its temporal units give (§7.6,
|
||
`floor-cmaf-mismatch`); or, for a supplied floor in the CMAF form (§7.7), `floor.listing`
|
||
is a listing that hashes to `floor.supplied_listing`, `floor/<f>/init.mp4` is the file
|
||
of its line `<f>/init.mp4` and reads as §7.7's initialization segment, its `av1C` and
|
||
sample entry size the floor's sequence header's, and the
|
||
listing's media segments of `<f>/`, in its order, are one per piece, piece *k*'s
|
||
`floor/<f>/<its SHA-256>.m4s`, reading as §7.7's media segment, opening on a sync
|
||
sample, each sample one frame and the first at the piece's start, its samples the
|
||
piece's floor units without their temporal delimiters, each a sync sample exactly
|
||
when its unit starts at a shown key frame (`floor-cmaf-mismatch`);
|
||
- then the catalogs are the ones the index, the objects and the floor give, and an
|
||
index no catalog can be written for (a start past `2^53 − 1`) is refused
|
||
(`catalog-mismatch`).
|
||
A component beside the lead (§5.8) is held, in this order: to the index against the
|
||
policy as above, its pieces of kind `audio` for an audio component and `none` otherwise,
|
||
with no length bound of its own, and to the lead's pieces converted (§4.5), each flagged
|
||
aligned to the lead and nothing else (`index-policy-mismatch`); to its plan file
|
||
(`plan-mismatch`); to every entry's object and the object rules of §6.3 (`object-missing`
|
||
and §6.3's reasons); then piece by piece, to its base, which decodes by its capability
|
||
or is `prefix-digest`, is the envelope §5.8 writes of its own payload with its piece's
|
||
`target` (`object-manifest`), holds its piece's span (a FLAC's samples, a score's
|
||
`duration`, a document's window as `object-manifest`, a segment's `X-TIMESTAMP-MAP` time,
|
||
a feed's ticks and last tick), and decodes to its entry's `prefix_digest`
|
||
(`prefix-digest`): a segment's, to the cues it reads to written again as the segment of
|
||
its piece's span, which is its payload byte for byte; a feed's, to a replay of its sealed ops whose seal verifies under the
|
||
receipt's key and whose graph hashes to the state the piece before it seals; and a
|
||
score's floor, which is §5.8's envelope for its piece's base and decodes to that base's
|
||
mixdown, sample for sample, in its channels and at its rate (`floor-object-mismatch`); a
|
||
graphics piece's document digest, which is the pieces' before it
|
||
(`graphics-document-mismatch`, [graphics-vector](graphics-vector.md) §6.2), and its text
|
||
floor, which is the object graphics-vector §6.3 writes of its base's transcript, byte for
|
||
byte (`floor-object-mismatch`); and
|
||
a sound track's Opus floor (§7.9): once its first base holds, `floor.listing` hashes to
|
||
`floor.supplied_listing`, the files of its `<f>/` are the package's `floor/<f>/init.mp4`
|
||
and `floor/<f>/<SHA-256>.m4s`, and they hold §7.9's checks against the pieces
|
||
(`floor-cmaf-mismatch`, naming §7.9's reason); then piece by piece, each base's samples
|
||
are its entry's digest, and its floor object names that base (`floor-motif-mismatch`), is
|
||
§7.9's envelope for it, and carries the Ogg Opus stream §7.9 writes of the floor's packets
|
||
for the piece (`floor-object-mismatch`). Its stage view members are then §8.8's. Once
|
||
every component holds, each graphics component's caption intervals, from its pieces,
|
||
are held to the cues of a text component of their language, from its segments
|
||
(`caption_without_track`, graphics-vector §6.4).
|
||
7. A metered package's measurement claim (§9.4: `measurement-claim-missing`,
|
||
`measurement-claim-invalid`, a fabric record naming a step this extension does not
|
||
define, §11.6, among the latter); an object file no entry names (`object-unlisted`); a
|
||
catalog file past the ones given (`catalog-unexpected`); any other file
|
||
(`unexpected-file`).
|
||
|
||
**11.4** *What this revision packs.* The reference packager packs a video component as its
|
||
lead, and beside it up to seven components of the media and capabilities of §5.8, each from
|
||
its source, one object a piece, a score's encoded FLAC floor, and a sound track's Opus
|
||
floor supplied in the CMAF form (§7.9). The lead is packed as one
|
||
video component:
|
||
bases of `wai.video.int_motion` (§5.5, §5.6) and stills of `wai.image.png` (§5.7);
|
||
refinements of `wai.video.int_refine` over an `int_motion` base (§5.4); each piece by its
|
||
ladder, a rule's ladder subset included (§5.7); and a supplied AV1 floor per floor rung,
|
||
one or several (§7.1), in the OBU form (§7.3) or the CMAF form (§7.7); and floor-only
|
||
components (§7.8). Its live
|
||
packer packs the same pieces in windows, without a floor (§9.2). It runs `base-search`,
|
||
`sse` and `object-hash` on a GPU that qualifies, where it is built to and asked to, and
|
||
every other step on the CPU (§11.6). It refuses by name,
|
||
before writing anything, what it does not pack yet: another medium
|
||
(`pack-media-unsupported`), a floor to encode (`pack-floor-encode-unsupported`), and a rung
|
||
of a capability it does not encode, or a refinement over a base it does not refine
|
||
(`pack-capability-unsupported:<cap>`, every one naming the capability of the rung it does
|
||
not pack: beside the lead, a refinement, a second base, or a floor its base does not take);
|
||
a lead other than video, a medium beside the lead that §5.8 does not name, and a re-pack
|
||
of several components (`pack-media-unsupported`); a source that does not span the lead's
|
||
timeline, is not of the component's rate or does not read (`pack-source-length`,
|
||
`pack-source-rate`, `pack-source-format`), a note across a piece start
|
||
(`pack-score-crossing`), caption blocks in a graphics document that no text component
|
||
carries (`caption_without_track`, graphics-vector §6.4), a
|
||
text component with no `lang` (`pack-text-lang`), and a feed op that depends on another
|
||
piece's (`pack-feed-crossing`), §5.8; and a component of the standard profile with no source (`pack-source-missing`;
|
||
a floor-only component has none, §7.8) or whose source's frame rate is not its timebase
|
||
(`pack-source-rate`). Where its own work fails it
|
||
refuses by name too, and leaves nothing written (a file it wrote before the refusal is
|
||
removed): an encode the encoder refuses, such as a piece
|
||
past the samples it holds, or an object it wrote that does not count (`pack-encode`); a
|
||
piece past `2^32 − 1` units (`pack-piece-length`); catalogs it cannot write, the case
|
||
§11.3 refuses as `catalog-mismatch` (`pack-catalog`); a meter that was asked for and
|
||
gives no figure, a span too short to reach a microjoule or a figure past `2^64 − 1`
|
||
(`pack-meter`, never an unmetered package in its place); a measurement claim it cannot
|
||
write (`pack-measurement-claim`); a listing it cannot write (`pack-listing`); and a source
|
||
that does not read the same twice (`pack-source-changed`) or a supplied floor stream that
|
||
does not (`floor-listing-mismatch`): it reads each a frame or a temporal unit at a time,
|
||
once to cut and check it and once to code it, and holds neither whole. What it holds is
|
||
not a function of the source's length but for a record per piece and per entry, a byte
|
||
per frame of each floor (whether it starts at a key frame) and a record per packet of an
|
||
Opus floor (its segment, place, decode time and samples): the frames of the pieces it
|
||
codes at once, which it chooses, each piece coded alone (§4.1), so the package is the same
|
||
for any number; one piece of each floor; and, for the catalogs, one object at a time,
|
||
read back from where it wrote it; except a re-pack, which holds its parent whole while it
|
||
verifies it (§11.5), so its memory grows with the parent. Each file is written whole as it
|
||
is made. It
|
||
writes a package only into a directory that is new or holds nothing, and refuses any
|
||
other before it packs (`pack-out-not-empty`): written among another package's files, it
|
||
would leave behind the ones it does not write.
|
||
|
||
**11.5** *Re-pack from a parent.* A packager **MAY** take every piece's base and
|
||
refinements from an earlier package, its **parent**, instead of coding them, so a
|
||
component's integer base is coded once and taken by every package made from the same
|
||
source after it. It reads the source as for a pack that codes (§4: its pieces and their
|
||
kinds, §4.3, §4.6, are this source's), and **MUST** refuse, checks 1 and 2 before it
|
||
writes anything and check 3 as it reads each piece, leaving nothing written (§11.4):
|
||
|
||
1. a parent that does not verify as §11.3 verifies a package (`repack-parent-invalid`,
|
||
with the reason the parent fails for);
|
||
2. a parent with no component of the component's id, media and timebase, or whose pieces
|
||
are not this source's, piece by piece: another number of pieces, or a piece of
|
||
another start, duration, flags or kind, or whose ladder (§5.7, under the parent's
|
||
policy) codes its base and refinements in rungs of other capabilities or other
|
||
parameters (§3.3) than this policy's ladder for the piece, in layer order, or with
|
||
another deadline (§8.6) (`repack-parent-mismatch`);
|
||
3. a piece whose prefixes, each the parent's base decoded and its refinements applied in
|
||
layer order by a staged-delivery §4 sink, do not reconstruct this source with the
|
||
`sse` the parent's entry states (§6.6) (`repack-source-mismatch`): a parent packed
|
||
from another source.
|
||
|
||
It then writes, for each piece, the parent's base and refinement objects and their
|
||
entries' fields (§6.2: content hash, lengths, capability, role, layer, tier, prefix
|
||
digest, `sse`, deadline) in place of coding them, and the floors as it would; its
|
||
receipt names the parent's receipt with parent kind `re-pack` (§9.1). A verifier holding
|
||
the parent **MAY** check a re-pack against it: the parent verifies
|
||
(`repack-parent-invalid`), the re-pack's receipt names the parent's receipt with parent
|
||
kind `re-pack`, and each component's pieces are the parent's, piece by piece, of the same
|
||
start, duration, kind and flags, whose base and refinement entries are the parent's
|
||
entries, every field the same but the entry's place in its index: the entry it refines
|
||
counted from its piece's first entry, and the duplicate flag, which an index's own
|
||
entries decide (`repack-parent-mismatch`). A re-pack holds no source of its parent's
|
||
bases; what binds a reused base to this source is the third check, and what binds it to
|
||
the parent is the receipt.
|
||
|
||
**11.6** *Steps and fabrics.* The steps of a pack and a verify are named by these terms:
|
||
|
||
| Step | What it computes |
|
||
|---|---|
|
||
| `source-decode` | a source's frames from a coded rendition, before a pack, by a producer's tool (§4.7 reads the result) |
|
||
| `source-read` | a source's frames or samples (§4.7) |
|
||
| `segment` | the pieces (§4.3) and their kinds (§4.6) |
|
||
| `base-search` | the integer base's motion search (§5.5) |
|
||
| `base-code` | the rest of the integer base (§5.5), its step (§5.6) and a still (§5.7) |
|
||
| `refine-code` | refinement layers, each applied by the reference sink (§5.4) |
|
||
| `sse` | an entry's `sse` (§6.6) |
|
||
| `object-hash` | an object's content hash (§5.3) |
|
||
| `digest` | the SHA-256 digests: decoded-output digests (§6.5), listings, file names |
|
||
| `floor-read` | a supplied floor's units, CMAF boxes and listing (§7.3, §7.7) |
|
||
| `floor-decode` | a floor's units decoded to pictures (item 4 below) |
|
||
| `index` | the stage indexes, catalogs, receipt and claim (§6, §8, §9) |
|
||
| `io` | reading and writing files |
|
||
|
||
Every step is a function of its inputs, defined in integers (and `floor-decode` by the
|
||
AV1 decoding process), and none names a fabric:
|
||
|
||
1. *Packages.* A packager **MAY** run a step on any fabric whose output is the step's
|
||
for every input. It **MUST NOT** run a step on a backend that has not, on each device
|
||
it opens and before it is used, given the step's output for every input of a
|
||
qualifying set, nor at all on one that computes otherwise than the step's
|
||
definition, such as in floating
|
||
point where the step is defined in integers, or with a learned approximation. A
|
||
backend that cannot take a call (a size, a lost device) leaves it to the CPU, with the
|
||
same output. So §11.2 holds across fabrics: the package files are the same bytes
|
||
whichever fabric ran each step, and only a metered package's measurement claim says
|
||
where they ran. A qualifying set holds, at the least:
|
||
- for the motion search (§5.5), frame pairs of noise, of a smooth field, of a flat
|
||
field (every candidate ties in cost) and of alternating columns (two vectors tie
|
||
in cost and in length), at sizes that are not a whole number of blocks, at three
|
||
block sizes or more, at full-pel and half-pel precision, and with search ranges
|
||
that reach the frame's edge;
|
||
- for SSE, inputs of at least three lengths, among them one byte and one past
|
||
4096 bytes;
|
||
- for BLAKE3, messages of 0 and 1 bytes, of 1023, 1024 and 1025 bytes around a chunk,
|
||
and longer than four chunks.
|
||
(Informative: the reference packager's set is `wai_pack::fabric::qualifying_frames`
|
||
and the inputs of `wai_pack::fabric::qualify`. Its GPU kernels are held to integer
|
||
types, with no subgroup operation and no extension, on the IR of the translator that
|
||
compiles them, `wai-pack-gpu/tests/integer_only.rs`.)
|
||
2. *Verdicts.* A verifier's verdict and its reason are functions of the package
|
||
(§11.3). A verifier **MAY** compute a check on another fabric where the package states
|
||
the value the check compares with, such as an object's content hash: a match shows
|
||
that the fabric computed the check, and a mismatch **MUST** be computed again on the
|
||
reference implementation before it is reported. Every other check runs on a backend
|
||
held to the reference's output as item 1 holds a packager's. A verdict that changes
|
||
with the fabric is the verifier's fault, never the package's.
|
||
3. *Hardware encoders.* A hardware encoder's output depends on the chip, its firmware
|
||
and driver, and its settings, and no rule fixes it, so no step of this extension is
|
||
computed by one. A floor a packager is given is an input, whatever made it (§7.3,
|
||
§7.7); a floor a packager encodes on such hardware is one §7.5 covers, whose
|
||
`derived_from.renderer` names what §7.5 requires, and §11.2 does not hold its bytes.
|
||
4. *Decoding a floor.* The AV1 decoding process fixes every output sample of a stream
|
||
whose sequence header has `film_grain_params_present` 0, and of every stream before
|
||
film grain is applied (AV1 §7.2), so conformant decoders on any fabric give the same
|
||
pictures of a floor's units. A floor states no decoded-output digest (§7.4), so a
|
||
decode of it has no stated value to compare with: a verifier that decodes floors
|
||
**MUST NOT** refuse a package for what a decoder other than the reference gives, nor
|
||
read a hardware decoder's error or concealment as a verdict. A hardware decoder's
|
||
frames may be held in a wider format than the stream's bit depth (a 10-bit sample
|
||
shifted into the high bits of a 16-bit word); a reader of them recovers each sample
|
||
exactly or refuses the frame, and never rounds. (Informative: `wai-pack floor-decode`
|
||
reports each piece's digest from either decoder, for comparing them.)
|
||
5. *The fabric record.* A metered package's measurement claim **MAY** carry a fabric
|
||
record (jwp-receipts §2.4.8, energy-measurement §6.3): each step the metered span ran,
|
||
with every fabric that ran it and its backend, and the figure's share on each. Its
|
||
steps are this section's terms (§11.3 step 7). An unmetered package carries no claim,
|
||
so no record: which fabric ran a step is not part of the package, and two packages
|
||
that differ only in where their steps ran are the same files.
|
||
|
||
## 12. Loudness
|
||
|
||
**12.1** A packager states the loudness of each audio component's programme and the gain that
|
||
brings it to a target, in a signed **loudness statement**. The measurement is ITU-R BS.1770's
|
||
integrated loudness (K-weighting, 400 ms blocks every 100 ms, an absolute gate at −70 LKFS and a
|
||
relative gate 10 LU below), computed in integers by the procedure below, so that every
|
||
implementation states the same figure to the thousandth of an LU and a verifier holding the
|
||
programme recomputes it exactly. The figures are thousandths: an LU, a dB, LKFS. The statement
|
||
says how far a sink scales its output; the packaged audio is the source's, unchanged. The
|
||
reference is the `loudness` module of `wai-rs` (feature `loudness`) and `wai-pack loudness`; the
|
||
corpus is [`pack-conformance/loudness/`](../pack-conformance/loudness/), and its second
|
||
implementation `pack-conformance/loudness_verify.py`.
|
||
|
||
**12.2** *Measurement.* The programme is 16-bit samples, interleaved, at 48 000 Hz; another rate,
|
||
or a layout other than one channel, two (L, R) or six (L, R, C, LFE, Ls, Rs, WAV's order), is not
|
||
measured in this revision (`loudness-unsupported`). The channels' weights, in hundredths, are 100
|
||
for L, R, C and a single channel, 141 for Ls and Rs (BS.1770's 1.41), and 0 for the LFE, which is
|
||
excluded.
|
||
|
||
1. *Filter.* Each channel's samples, each multiplied by 2^8, pass through two second-order
|
||
sections in turn, each computing, from its inputs *x* and its outputs *y*,
|
||
*y*ₙ = ⌊(b0·*x*ₙ + b1·*x*ₙ₋₁ + b2·*x*ₙ₋₂ − a1·*y*ₙ₋₁ − a2·*y*ₙ₋₂ + 2^29) / 2^30⌋, from a
|
||
state of zeros, with BS.1770's coefficients at 48 kHz in units of 2^−30, each rounded to the
|
||
nearest integer: the first section (b0, b1, b2, a1, a2) = (1 648 327 767, −2 890 186 776,
|
||
1 286 764 483, −1 815 331 593, 786 495 243), the second (1 073 741 824, −2 147 483 648,
|
||
1 073 741 824, −2 136 797 184, 1 063 081 984). Every product and sum is exact (they fit in 128
|
||
bits).
|
||
2. *Blocks.* A programme of *F* frames has ⌊(*F* − 19 200) / 4 800⌋ + 1 blocks when *F* ≥ 19 200
|
||
and none otherwise; block *j* is frames 4 800·*j* to 4 800·*j* + 19 199. Its **power** *P*ⱼ is
|
||
the sum over the channels of the weight times the sum of the squares of the filtered samples
|
||
in it, an exact integer.
|
||
3. *Loudness.* The loudness of a power *P* summed over *n* blocks is
|
||
−691 + *M*(*P*, 100 × 19 200 × 2^46 × *n*), *M* of §12.3: BS.1770's −0.691 + 10 log10 of the
|
||
mean weighted square, with 2^46 undoing the 2^8 and the 16-bit full scale (2^15 squared). A
|
||
block's loudness is that of *P*ⱼ over one block; a block of power 0 has none.
|
||
4. *Gates.* The blocks whose loudness is above −70 000 pass the absolute gate. With none, the
|
||
programme has no integrated loudness (`null`). Otherwise the relative gate is the loudness of
|
||
their powers' sum over their count, less 10 000; the blocks of those whose loudness is above it
|
||
are the **gated** blocks, and the **integrated loudness** is the loudness of their powers' sum
|
||
over their count.
|
||
5. *Peak.* The **peak** is the greatest absolute sample, 0 to 32 768, over every channel; its
|
||
figure is 2 × *M*(peak, 32 768), in thousandths of a dB below full scale, `null` for a peak of 0.
|
||
It is the sample peak: BS.1770's true peak, from oversampling, is not measured here.
|
||
|
||
**12.3** *The logarithm.* For integers *p*, *d* ≥ 1, *M*(*p*, *d*) =
|
||
⌊10 000 × (*L*(*p*) − *L*(*d*)) × 1 292 913 986 / 2^64⌋, where 1 292 913 986 is log10(2) in units
|
||
of 2^−32, rounded, and *L*(*x*) is log2(*x*) in units of 2^−32 by this procedure: *n* is the index
|
||
of *x*'s highest set bit; *m* is *x* shifted to put that bit at bit 62 (left by 62 − *n*, or right
|
||
by *n* − 62, dropping the bits shifted out); *r* = *n* × 2^32; then, for *k* from 31 down to 0,
|
||
*m* becomes ⌊*m*² / 2^62⌋, and where *m* ≥ 2^63, *m* is halved (dropping its low bit) and 2^*k*
|
||
added to *r*; *L*(*x*) is *r*. Every step is an exact integer operation (*m*² fits in 128 bits),
|
||
and ⌊·⌋ rounds toward negative infinity. A 1 kHz full-scale tone on one channel measures −3 004,
|
||
BS.1770's calibration of −3.01 LKFS.
|
||
|
||
**12.4** *The gain.* Given a target *T* (thousandths of an LKFS) and a peak ceiling *C*
|
||
(thousandths of a dB of full scale), the gain is *T* minus the integrated loudness, unless that
|
||
would put the peak above *C*: where the peak's figure is not `null` and the gain exceeds *C* minus
|
||
it, the gain is *C* minus the peak's figure and the statement says the ceiling **limited** it. A
|
||
programme with no integrated loudness has no gain. A sink that normalises scales its output by
|
||
10^(gain / 20 000).
|
||
|
||
**12.5** *The statement* is RFC 8785 canonical JSON and a line feed, with exactly these members:
|
||
`kind` `"wai.pack.loudness"`; `version` 1; `method` `"bs1770-int/1"`; `component`, the
|
||
component's id (1 to 255 of the ASCII letters, digits, `.`, `_` and `-`); `source`
|
||
`{"channels", "frames", "rate", "sha256"}`, the programme's layout, frame count, rate and the
|
||
SHA-256 of its samples as 16-bit little-endian, interleaved; `blocks`, `gated` and `peak`
|
||
(integers); `integrated_milli`, `peak_milli` and `gain_milli` (signed integers, or `null` as
|
||
§12.2 and §12.4 say); `target_milli` and `ceiling_milli` (signed integers); `limited` (`"yes"` or
|
||
`"no"`); `receipt_hash`, the package receipt it belongs to (§9.1, lowercase hexadecimal), or
|
||
`null`; `signer_pubkey`; and `sig`. A signed integer is a JSON string: `0`, or an optional `-`, a
|
||
digit 1 to 9 and any digits, less than 2^53 in magnitude; every other integer is a JSON number
|
||
below 2^53. The signature is Ed25519 by `signer_pubkey`, the package's key, over
|
||
`wai:pack-loudness\x01` followed by the statement's canonical form without `sig`. A package with
|
||
an audio component carries its statement as `loudness/<component>.json`; this revision's packer
|
||
packs audio components (§5.8) without one, and `wai-pack loudness` writes and verifies a
|
||
component's statement from its WAV source (§4.7).
|
||
|
||
**12.6** *Verification.* A verifier refuses, in order, the first that applies:
|
||
|
||
1. text that is not JSON, names a member twice, or has another member, a member missing or of
|
||
another form (`loudness-malformed`);
|
||
2. a signature that does not verify as SPEC §7 *Signatures* requires (`loudness-signature`);
|
||
3. a method, rate or layout this revision does not measure (`loudness-unsupported`);
|
||
4. figures that do not follow from each other: `blocks` other than §12.2's count for `frames`;
|
||
`gated` above `blocks`; `gated` 0 and an integrated loudness, or the reverse; a peak figure
|
||
that is not `peak`'s; a gain or `limited` that is not §12.4's from the integrated loudness,
|
||
the peak figure, the target and the ceiling (`loudness-gain`). This needs no samples;
|
||
5. with the receipt hash it expects, another (`loudness-receipt-mismatch`);
|
||
6. with the programme's samples: another SHA-256, rate or layout (`loudness-source-mismatch`),
|
||
then any figure of §12.2 that its recomputation does not give (`loudness-not-recomputed`).
|
||
|
||
**12.7** *Reasons.*
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `loudness-malformed` | §12.6 step 1 |
|
||
| `loudness-signature` | §12.6 step 2 |
|
||
| `loudness-unsupported` | §12.2, §12.6 step 3 |
|
||
| `loudness-gain` | §12.6 step 4 |
|
||
| `loudness-receipt-mismatch` | §12.6 step 5 |
|
||
| `loudness-source-mismatch` | §12.6 step 6 |
|
||
| `loudness-not-recomputed` | §12.6 step 6 |
|
||
|
||
**12.8** *Conformance.* [`pack-conformance/loudness/`](../pack-conformance/loudness/) holds
|
||
`vectors.json`: programmes as formulas of integers (`sine_1k`, one period of a 1 kHz tone at
|
||
48 kHz, 32 767 × sin(2π*k*/48) rounded to an integer as the table below fixes it, scaled and
|
||
floored; a linear congruential generator's noise; silence) with their measurements, among them
|
||
BS.1770's calibration tone, a programme the relative gate cuts in half, one under the absolute
|
||
gate, 5.1 with the surround weight and with the LFE alone, and one shorter than a block; §12.3's
|
||
*L* of chosen integers, from 1 to 2^128 − 1; and signed statements, each verified or refused
|
||
with a reason of §12.7, every reason given. `wai_loudness_vectors verify` and
|
||
`wai-rs/tests/loudness.rs` require every outcome and the file byte for byte, and §12.2's
|
||
coefficients and the table below as this text states them; the generator holds each programme to
|
||
the loudness BS.1770's calibration or its construction declares. `loudness_verify.py`, written
|
||
from this text and sharing no code with wai-rs, does the same, mutates copies of the corpus and
|
||
requires every copy to fail, and drops each of its own checks in turn and requires the corpus to
|
||
catch the weakened verifier.
|
||
|
||
The tone's period, which the corpus holds as `sine_1k`:
|
||
|
||
sine_1k = [0, 4277, 8481, 12539, 16383, 19947, 23170, 25996, 28377, 30273, 31650, 32487,
|
||
32767, 32487, 31650, 30273, 28377, 25996, 23170, 19947, 16383, 12539, 8481, 4277, 0,
|
||
-4277, -8481, -12539, -16383, -19947, -23170, -25996, -28377, -30273, -31650, -32487,
|
||
-32767, -32487, -31650, -30273, -28377, -25996, -23170, -19947, -16384, -12539, -8481,
|
||
-4277]
|
||
|
||
## 13. Reasons
|
||
|
||
| Reason | Where |
|
||
|---|---|
|
||
| `policy-toml` (not UTF-8, a byte-order mark, not TOML 1.0, nesting past 32), `policy-float`, `policy-datetime`, `policy-negative`, `policy-integer-range` | §3.2 |
|
||
| `policy-unknown-key`, `policy-missing-key`, `policy-type`, `policy-value`, `policy-schema` | §3.3 |
|
||
| `policy-too-many-components`, `policy-too-many-rungs`, `policy-too-many-classes`, `policy-too-many-rules`, `policy-too-many-moments`, `policy-duplicate-id`, `policy-lead`, `policy-open-gop`, `policy-piece-bounds`, `policy-ladder-component`, `policy-refines-role`, `policy-dangling-refines`, `policy-refines-cycle`, `policy-refines-base`, `policy-refines-branch`, `policy-no-base`, `policy-floor-only`, `policy-class-layer`, `policy-rule-ladder`, `policy-layer-lag`, `policy-too-many-deadlines`, `policy-deadline-unreachable`, `policy-protection` | §3.4 |
|
||
| `capability-unknown`, `capability-not-payload`, `capability-candidate`, `capability-role`, `license-unclassified`, `license-refused`, each naming the capability (and the licence label) | §3.6 |
|
||
| `y4m-header`, `y4m-interlaced`, `y4m-colorspace`, `y4m-size-change`, `y4m-truncated`, `wav-format`, `wav-truncated` | §4.7 |
|
||
| `index-magic`, `index-version`, `index-truncated`, `index-reserved`, `index-media-class`, `index-role`, `index-tier`, `index-string`, `index-trailing` | §6.2 |
|
||
| `index-timebase`, `window-start`, `piece-order`, `piece-entries`, `piece-kind`, `entry-flags`, `entry-order`, `layer-role`, `refines-outside-window`, `refines-mismatch`, `layer-gap`, `tier-entropy-passthrough`, `tier-weakest`, `tier-mismatch`, `digest-without-tier`, `deadline-role`, `floor-derived-from`, `dup-length`, `dup-flag`, `class-plan`, `class-flags` | §6.3, §6.4 |
|
||
| `object-length`, `object-hash`, `non-envelope-entry`, `object-lengths`, `object-capability`, `object-refines`, `object-derived-from` | §6.3 |
|
||
| `floor-missing`, `floor-listing-mismatch`, `floor-stream-missing`, `floor-av1-malformed`, `floor-frame-count`, `floor-keyframe-mismatch`, `floor-av1-profile`, `floor-av1-level` | §7.1, §7.3 |
|
||
| `floor-stream-missing`, `floor-cmaf-malformed`, `floor-cmaf-timing`, `floor-cmaf-pieces` | §7.7 |
|
||
| `floor-stream-missing`, `floor-cmaf-malformed`, `floor-cmaf-timing`, `floor-opus-malformed`, `floor-frame-count`, `floor-cmaf-pieces`, `pack-capability-unsupported:wai.audio.opus` | §7.9 |
|
||
| `floor-keyframe-mismatch`, `floor-piece-length` | §7.8 |
|
||
| `protect-package`, `protection-absent`, `protection-cpix-mismatch`, `protection-cpix`, `protection-kid-unknown`, `protection-scheme-mismatch`, `protect-av1`, `protect-listing` | §7.10, writing |
|
||
| `protected-listing`, `protected-index`, `protected-missing`, `protected-unexpected`, `protected-box`, `protected-scheme`, `protected-iv-size`, `protected-kid`, `protected-pssh`, `protected-subsample`, `protected-iv`, `protected-name`, `protected-mismatch`, `protected-catalog` | §7.10, checking |
|
||
| `floor-object-mismatch`, `floor-motif-mismatch`, `floor-av1-profile`, `floor-av1-level`, `floor-av1-malformed`, `floor-keyframe-mismatch`, `floor-cmaf-mismatch`, against a package | §11.3 |
|
||
| `receipt-json`, `receipt-version`, `receipt-index-count`, `receipt-index-digest`, `receipt-stage-root`, `receipt-component`, `receipt-policy-digest`, `receipt-counts`, `receipt-parent-kind`, `receipt-energy`, `receipt-signature` | §9.1 |
|
||
| `measurement-claim-missing`, `measurement-claim-invalid` | §9.4 |
|
||
| `unexpected-file`, `listing-missing`, `listing-mismatch`, `policy-digest-mismatch`, `policy-json-mismatch`, `policy-component-mismatch`, `index-missing`, `index-policy-mismatch`, `plan-mismatch`, `object-missing`, `object-manifest`, `prefix-digest`, `catalog-mismatch`, `catalog-unexpected`, `object-unlisted` | §11.3 |
|
||
| `pack-id`, `pack-source-missing`, `pack-source-rate`, `pack-media-unsupported`, `pack-floor-encode-unsupported`, `pack-capability-unsupported` (naming the capability) | §11.1, §11.4 |
|
||
| `pack-encode`, `pack-piece-length`, `pack-catalog`, `pack-meter`, `pack-measurement-claim`, `pack-listing`, `pack-source-changed`, `pack-out-not-empty` | §11.4 |
|
||
| `pack-source-length`, `pack-source-format`, `pack-score-crossing`, `pack-text-lang`, `pack-feed-crossing` | §5.8, §11.4 |
|
||
| `caption_without_track`, writing and against a package; `graphics-document-mismatch`, against a package | §5.8, §11.3, [graphics-vector](graphics-vector.md) §6 |
|
||
| `repack-parent-invalid`, `repack-parent-mismatch`, `repack-source-mismatch` | §11.5 |
|
||
| `loudness-malformed`, `loudness-signature`, `loudness-unsupported`, `loudness-gain`, `loudness-receipt-mismatch`, `loudness-source-mismatch`, `loudness-not-recomputed` | §12 |
|
||
| `rendition-malformed`, `rendition-capability`, `rendition-codecs`, `rendition-duplicate`, `rendition-default`, `rendition-description-unbound`, `rendition-description-invalid`, `rendition-description-language`, `rendition-ambiguous`, `rendition-groups-differ` | §8.10 |
|
||
| `marks-index`, `marks-policy`, `marks-moment`, `marks-chapter`, `marks-title`, `marks-malformed`, `marks-mismatch`, `marks-vtt`, `marks-hls`, `marks-dash` | §8.11 |
|