open-standards/wai/extensions/platform-encode.md

462 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WAI Extension: Platform and Hardware Encoding
> Status: Draft. Registers the record `wai.video.encode`. A sink or a packager that needs an
> AVC (ISO/IEC 14496-10) or HEVC (ISO/IEC 23008-2) rendition encodes it with an encoder the
> device already has: the operating system's media framework and its hardware media engine,
> a GPU's encode engine through the video acceleration API or through the GPU driver's own
> encode interface, or a software AVC library the user obtains separately and names at run
> time. WAI writes no AVC or HEVC encoder. This extension fixes the shape the encoder's output
> is held to (§4), what names the encoder (§5), and the **encode record**, a signed statement
> of who made an elementary stream, from what source, with which settings and at what
> measured cost (§6 to §8). A verifier holds the stream to its digest and its shape; it never
> re-encodes. An encode is never a deterministic step: no tier and no `replicate` claim covers
> the encoded pictures against the source or against a base. An encode made over a package's
> pieces is framed as one floor object per piece, each naming its encoder in `derived_from`
> as [packaging](packaging.md) §7 requires in its paragraph 7.5 (§9). No new transport. The reference
> implementation is the `platform_encode` module of `wai-rs` (feature `platform_encode`), the
> `wai encode avc|hevc`, `wai encoders` and `wai encode-record verify` commands, and
> `wai-pack encode-rung` and `wai-pack verify-rung`; the corpus is
> [`encode-conformance/`](../encode-conformance/), and its second implementation is
> `encode-conformance/encode_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
## 1. Scope
In scope: what an encode is handed and how a ladder rung's pictures are made (§4.1, §4.2);
the shape of the elementary stream (§4.3); the encoder interfaces by role and the identity
each reports (§3, §5); the encode record and its signature (§6); its verification (§7); the
energy of an encode (§8); an encode over a package's pieces (§9); conformance and
measurement (§10).
Out of scope: choosing an encoder's coding tools, which is the encoder's; rate-distortion
claims, which this extension never makes; any encoder written for WAI. The record states what
the encoder made, and a verifier checks the bytes against it.
## 2. What WAI writes, and what it does not
**2.1** WAI writes no AVC or HEVC encoder and ships none. The reference implementation links
no encoder: it loads the operating system's media framework, the video acceleration API's
library, the GPU driver's encode library, or a software library at a path the user names, each
at run time, and a build of it runs on a host without any of them and says which is missing.
**2.2** *Patent position, by mechanism.* Both standards' essential patents are licensed for
royalties (SPEC §5, *Broadcast and streaming codecs*: `patent-pool`). An encoder an operating
system or a GPU driver provides is supplied by the device's maker with the device, under that
maker's terms. A software library the user names is obtained by the user from its distributor,
separately from WAI, under the terms that distributor states, which may cover the royalties for
some uses and not for others. This extension states which encoder made a stream (§5); it states
nothing about whether a use of it is licensed. A deployer decides that from the encoder's
supplier's terms and its own use.
**2.3** *Header syntax under the video acceleration API.* That interface leaves the parameter
sets and the slice headers to the application, and the drivers reached for this revision emit
none unless the application hands them over. The reference adapter writes them (ISO/IEC
14496-10 §7.3.2.1, §7.3.2.2, §7.3.3; ISO/IEC 23008-2 §7.3.2.1 to §7.3.2.3, §7.3.6.1) from the
values it gives the driver in its parameter buffers, and chooses each picture's QP, which it
writes in both the parameter buffer and the slice header so the two agree. No picture sample
passes through it: the driver's engine codes every slice's data.
**2.4** *Not a deterministic step.* An encoder is not a pure function of its input
([packaging](packaging.md) §11): a hardware engine's output is fixed by no specification, and
the media framework's hardware encoder was measured making other bytes from the same pictures
in each round (§10.3). So no step of a pack or a verify is an encode, no verdict depends on an
encoder's output beyond the digest a record states, and an encoded rendition promises only its
capability's decoder conformance (SPEC §7: `wai.video.avc` and `wai.video.hevc` are
`StandardDefined`, so every conforming decoder outputs the same pictures from the stream).
## 3. Encoder interfaces
An encoder is reached through one of four interfaces. Each record names the one it used in
`encoder.api`.
| `api` | what it drives | where |
|---|---|---|
| `platform` | the operating system's media framework: its hardware media engine, or its software encoder where the engine is refused | the desktop, phone and TV operating systems that ship the framework |
| `va` | the video acceleration API, whose driver drives a GPU's encode engine | Linux, through a render node |
| `gpu-driver` | the encode interface a GPU's driver exports from its own library | Linux |
| `software` | a software AVC encoder library loaded at run time from a path the user names | any Unix |
**3.1** A `va` or `gpu-driver` encode is a hardware encode. A `software` encode is not, and
codes AVC only in this revision. A `platform` encode is either, and says which (§5).
**3.2** *The phone and TV operating system's codec interface (informative).* On the mobile and
TV operating system whose media codec interface exposes hardware and software encoders by
name, an adapter would: open an encoder for the codec's MIME type by name, read the name, the
canonical name and whether the interface reports it hardware-accelerated for `encoder.name` and
`encoder.hardware`; configure the codec's flexible 4:2:0 input, the target bit rate, no
B-pictures and no periodic key frame; request a key frame for exactly the pictures the plan
names; and read the device's hardware and system-on-chip identifiers and the operating system's
release for `encoder.device` and `encoder.driver`. Its output (the codec's configuration buffer,
then one buffer per access unit) is an Annex B stream held to §4.3 like any other. This revision
builds no adapter for it.
## 4. The encode
### 4.1 The source
A source is a YUV4MPEG2 stream: 8-bit 4:2:0 (`C420`, `C420jpeg`, `C420paldv`, `C420mpeg2`, or no
`C` tag), progressive (`Ip`, `I?` or no `I` tag), each side even and at most 16384, its frame rate
`F<num>:<den>` both non-zero, and at least one frame. `XCOLORRANGE=FULL` makes it full range;
anything else is the video range. Each frame is its luma plane, then its Cb and Cr planes, each
row-major with no padding. The source's **digest** is SHA-256 (FIPS 180-4) over every frame's
three planes, one frame after another, with no frame header.
### 4.2 A ladder rung's pictures
A rung asks for a size `W × H` (each even, from 16 to 8192), a frame rate (the source's), a
target average bit rate in kilobits per second (1 to 1000000) and a range (the source's). Where
the size is the source's, the encoder is handed the source's frames. Otherwise each plane is
resampled by **`wai.resample.two-tap`**, chroma at half the luma size: for an axis of `n_in`
samples resampled to `n_out`, output sample `o` reads the source at
p = ((2·o + 1) · n_in · 128) div n_out − 128
in 1/256ths of a sample, clamped to `0 ..= 256 · (n_in − 1)`; with `x0 = p >> 8`, `f = p & 255`
and `x1 = min(x0 + 1, n_in − 1)`, the sample is `(s[x0] · (256 − f) + s[x1] · f + 128) >> 8`.
Rows are resampled first, then columns, each pass rounding to 8 bits. The resampler is a pure
function in integers: every host hands its encoder the same samples.
The **IDR plan** is the frame indices at which the stream has an IDR access unit: sorted, the
first 0, each below the source's frame count. A package's plan is its pieces' starts (§9).
### 4.3 The stream
An encoder's output **MUST** be, and a producer **MUST** refuse to record one that is not:
1. an Annex B byte stream (ISO/IEC 14496-10 Annex B; ISO/IEC 23008-2 Annex B): it begins with
zero bytes and a start code prefix; a NAL unit ends where the next three bytes are
`00 00 00` or `00 00 01`; no NAL unit is empty, holds `00 00 02`, or has its forbidden bit set;
an HEVC NAL unit's `nuh_temporal_id_plus1` is not 0; every sequence parameter set reads, an
AVC one through its VUI (Annex E.1.1) where it has one and an HEVC one through its sub-layer
ordering information (`sps_max_latency_increase_plus1`); and the start of every slice header
this section reads (below) reads (`encode-stream-malformed`);
2. exactly one access unit per source frame, in presentation order (`encode-frame-count`). An
access unit begins at the first of an access unit delimiter, a parameter set, a prefix SEI or
the other non-VCL types the standard lists after the previous picture's slices (ISO/IEC
14496-10 §7.4.1.2.3, its types 6 to 9 and 14 to 18; ISO/IEC 23008-2 §7.4.2.4.4, its types 32
to 35, 39, 41 to 44 and 48 to 55), or at a slice whose `first_mb_in_slice` is 0 (AVC; the
first syntax element of its slice header) or whose `first_slice_segment_in_pic_flag` is 1
(HEVC; the first bit). NAL units after the last picture's slices that begin an access unit
holding no slice are `encode-stream-malformed`, checked with item 1;
3. an access unit's slices all IDR or none (`encode-stream-malformed`); no random access point but
an IDR (HEVC `nal_unit_type` 16 to 23 other than 19 and 20); and an IDR access unit (AVC
`nal_unit_type` 5; HEVC 19 or 20) at exactly the plan's frames (`encode-keyframe-mismatch`);
4. every IDR access unit carrying the sequence and picture parameter sets (and the video
parameter set for HEVC), the same bytes in the same order as the first IDR access unit's; no
other access unit carrying parameter set bytes the first IDR's do not; no access unit carrying
a sequence parameter set other than the stream's first; and no access unit before the first
sequence parameter set (`encode-parameter-sets`);
5. 8-bit 4:2:0 in one colour plane; progressive, which for AVC is `frame_mbs_only_flag` 1 (this
revision reads no HEVC field signalling); a single layer: no AVC NAL unit of type 14, 15, 20
or 21, and HEVC `nuh_layer_id` 0 throughout; and AVC `pic_order_cnt_type` 0 or 2, the types
this revision reads (`encode-stream-unsupported`);
6. with no reordering: HEVC `sps_max_num_reorder_pics` of the highest sub-layer 0; AVC the VUI's
`max_num_reorder_frames` 0 where the SPS has one, and, for `pic_order_cnt_type` 0, each access
unit's top field order count (§8.2.1.1: `PicOrderCntMsb + pic_order_cnt_lsb`, from the first
slice's `pic_order_cnt_lsb` and the previous reference picture's, both reset to 0 at each IDR)
above the one before it since the last IDR (`encode-stream-reordered`). The slice header is
read only as far as `pic_order_cnt_lsb`; this revision does not read `dec_ref_pic_marking`, so
a stream whose pictures carry `memory_management_control_operation` 5 may be refused here;
7. of the rung's size: the sequence parameter set's cropped picture is `W × H` (AVC frame
cropping, HEVC conformance window), and where an AVC VUI signals `video_full_range_flag` it is
the rung's range (`encode-sps-mismatch`).
Item 1 and item 2's count are checked over the whole stream first; then items 3 to 6 access
unit by access unit, in stream order, each access unit's items in that order; then item 7. The
first that fails is the reason. They hold the stream to what a piece of a package needs: each piece starts at an IDR
access unit that carries its parameter sets, so it decodes with nothing outside it, and access
unit *i* is the picture of frame *i*.
## 5. The encoder's identity
**5.1** An encode names its maker with six strings and one flag, each as the interface reports it
at run time, read as the adapter documents:
| member | meaning |
|---|---|
| `api` | the interface (§3) |
| `name` | the encoder, as the interface names it: the framework's encoder identifier and display name; the profile and entrypoint the driver offered, with the driver's vendor string; the codec and preset the GPU driver's interface names; or the library's file name with the settings it was opened with |
| `version` | the encoder's version: the framework's bundle version; the video acceleration API's version; the encode interface's version and the highest the driver supports; or the library's own version |
| `hardware` | `yes` when a fixed-function engine coded the pictures, `no` otherwise |
| `device` | the device the encoder ran on: the processor's name and model; the render node and its PCI identifiers; the GPU's name, ordinal and PCI bus |
| `driver` | the driver, or for a platform or software encoder the operating system's release and build, that ran it |
| `library_sha256` | for `software`, the SHA-256 of the file loaded; otherwise absent (`null`) |
**5.2** A producer **MUST NOT** state an identity it did not read from the interface. Where an
interface reports nothing for a member, the adapter writes what it read instead and says so in
the string; no member is empty.
**5.3** A runtime-loaded library is refused at load unless it is of the interface version the
adapter was written for: its structures may differ between versions. The record names the file
loaded by its SHA-256, so a relying party can hold it to the distributor's published digest.
## 6. The encode record
### 6.1 Members
The record is a JSON object (RFC 8259) read as [contribution](contribution.md) §2 reads a
record: strict JSON (no member named twice in one object, at most 32 levels), integers unsigned,
decimal, with no sign, fraction, exponent or leading zero and at most 2^53 − 1, no other number,
no boolean; byte strings in lowercase hexadecimal; a **term** 1 to 64 bytes of `a`–`z`, `0`–`9`,
`-`, `_` and `.`; a **date-time** RFC 3339 in UTC, `YYYY-MM-DDTHH:MM:SS`, at most nine fraction
digits, then `Z`, its day one its month has in the proleptic Gregorian calendar and its second
at most 60. It has exactly these members, at every level, none missing and none added;
a record that does not, or a member of another type or encoding, or outside the fixed words the
table gives `kind`, `hardware`, `range` and `tier`, is `record_malformed`. Every other set or
range of values is a §7 step's, and refused with that step's reason.
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-encode"` |
| `capability` | string | `wai.video.avc` or `wai.video.hevc` |
| `encoder` | object | §5.1's members: `api` a term; `name`, `version`, `device`, `driver` strings of at most 1024 bytes; `hardware` `"yes"` or `"no"`; `library_sha256` 32 bytes or `null` |
| `settings` | object | `width`, `height`, `fps_num`, `fps_den`, `kbps` integers; `range` `"video"` or `"full"`; `resample` a term, `none` or `wai.resample.two-tap` (§4.2) |
| `source` | object | `frames`, `width`, `height` integers; `sha256` 32 bytes, the source's digest (§4.1) |
| `idr` | array | the IDR plan (§4.2), integers |
| `stream` | object | `sha256` 32 bytes, the stream's SHA-256; `bytes`, `units` integers; `profile_idc`, `level_idc` integers, the SPS's (`general_profile_idc` and `general_level_idc` for HEVC); `tier` `"main"` or `"high"`, HEVC's `general_tier_flag`, and `"main"` for AVC, which has none (held to the stream at §7 step 8) |
| `energy` | object or `null` | §8 |
| `wall_us` | integer | the encode's wall time, from the first picture handed over to the last byte back |
| `package` | object or `null` | §9 |
| `issued_at` | date-time | when the signer signed it, self-asserted |
| `signer_pubkey` | 32 bytes | an Ed25519 key (RFC 8032) |
| `sig` | 64 bytes | §6.2 |
### 6.2 Signature and digest
```
sig = Ed25519(signer_key, "wai:video-encode\x01" || JCS(record without "sig"))
digest = SHA-256("wai:video-encode-record\x01" || JCS(record))
```
JCS is RFC 8785. The signature is verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key
that fails step 1 or 2 is `bad_key`, a signature that fails a later step `bad_signature`. The
record's text as the reference writes it is its JCS form. The signer is whoever ran the encode;
the record is that signer's statement of what the interface reported (§5.2).
## 7. Verification
A verifier checks, in order, and the first that fails is the reason:
1. the record reads (§6.1) (`record_malformed`);
2. its signature (§6.2) (`bad_key`, `bad_signature`);
3. `capability` is `wai.video.avc` or `wai.video.hevc` (`encode-capability`);
4. the identity: `name`, `version`, `device` and `driver` each hold a character other than
Unicode White_Space (`encode-identity-incomplete`); `api` is one of §3's; `library_sha256` is
present exactly for `software`; a `software` encode is `hardware` `"no"` and of
`wai.video.avc`; a `va` or `gpu-driver` encode is `hardware` `"yes"`
(`encode-identity-inconsistent`);
5. the settings and the source: the rung's width and height even and from 16 to 8192, the
source's even and from 2 to 16384; both rate members non-zero and at most 2^32 − 1; `kbps` from 1 to 1000000; at least one source frame; and `resample`
`none` exactly where the rung's size is the source's, `wai.resample.two-tap` otherwise
(`encode-settings-invalid`);
6. the IDR plan: its first entry 0, each above the one before, the last below `source.frames`;
and, where `package` is not `null`, its pieces' starts equal to the plan, each piece of at
least one frame and starting where the one before it ended, their frames summing to
`source.frames` (`encode-plan-invalid`);
7. the energy, where it is not `null` (§8.1) (`encode-energy-invalid`, `encode-energy-coverage`);
8. with the stream held: its SHA-256 and length are `stream.sha256` and `stream.bytes`
(`encode-stream-mismatch`); it holds to §4.3 for `source.frames` pictures and the plan, its
reasons as there; its access units number `stream.units` (`encode-frame-count`); its SPS's
cropped size is the settings' `width × height`, its profile, level and tier `stream`'s, and an
AVC VUI's signalled range the settings' (`encode-sps-mismatch`);
9. with the source held: it reads as §4.1 says, its digest is `source.sha256`, its width, height
and frame count are `source`'s, its frame rate's numerator and denominator, as its header
writes them, are the settings' `fps_num` and `fps_den`, and its range is the settings'
(`encode-source-mismatch`);
10. with the package and the floor objects held, §9.3 (`encode-package-mismatch`).
Steps 8 to 10 are made only where the verifier holds what they read; a verdict names which it
made. Nothing in them re-encodes: the source is held to its digest, and the stream to its digest
and its shape.
## 8. Energy
### 8.1 The figure
`energy` restates a measured figure with the members [jwp-receipts](jwp-receipts.md) §2.4.5
gives an energy claim, and three of this extension's own:
| member | type | meaning |
|---|---|---|
| `joules_micro` | integer | the figure, microjoules, for the whole encode the record names |
| `energy_provenance` | string | an acquisition class of [energy-measurement](energy-measurement.md) §2 |
| `energy_uncertainty` | object or `null` | `{ "relative_ppm", "window_us" }`, integers, as §2.3 there |
| `energy_counter_filtering` | string or `null` | an `OnChipCounter`'s filtering state |
| `energy_calibration_ref` | string or `null` | a `CalibratedInstrument`'s calibration reference |
| `coverage` | string | `Whole` or `Partial` ([energy-binding](energy-binding.md) §4) |
| `counter` | term | the counter read (§8.2) |
| `attribution` | string | `task`, `rail` or `bracket` (§8.2) |
| `fabrics` | array of strings | what the counter's reading covers, each of `cpu`, `gpu`, `media`, `npu`, at least one, in that order, none twice |
It is held to jwp-receipts §2.4.5's rules 1 to 3, read as written there: a non-zero figure; a
class of energy-measurement §2 other than `HwShunt`; the class's evidence whole, including the
resolution floor `joules_micro × relative_ppm ≥ 288 676`, an `OnChipCounter`'s filtering state
of `On`, `Off` or `Unknown`, and a `CalibratedInstrument`'s non-blank calibration reference; and
to a `coverage` of `Whole` or `Partial`, an `attribution` of the three, and `fabrics` as the
table says (`encode-energy-invalid`). A **hardware** encode's figure whose `fabrics` names
neither `gpu` nor `media` counts none of the engine that coded the pictures, so it **MUST** be
`Partial` (`encode-energy-coverage`). A relying party **MUST NOT** report a `Partial` figure as
the whole cost of the encode.
### 8.2 How the reference measures it
The figure is the median of an odd number of rounds (at least three) of the same encode, after
one unmetered round that loads and warms the encoder. A round's figure is a counter's advance
over the encode (`task`), or energy-measurement §3's marginal: the advance over the encode less
the mean power over an idle window just before it, times the encode's length (`bracket`); a
round whose marginal is not above zero, or whose counter fails a read, makes the whole figure
absent rather than estimated. The declared uncertainty is the rounds' spread read as rectangular,
`(max − min) / (2·√3)` relative to the median, rounded up, or energy-measurement §2.3's rounding
floor if that is larger; its window is the median round's. The class is `OnChipCounter` with
filtering `Unknown`, and the coverage `Partial`: no counter the reference reads sees all of a
host's energy.
| `counter` | what it reads | `attribution` | `fabrics` |
|---|---|---|---|
| `task` | the operating system's account of the encoding process's own energy, with the work other processes did for it billed to it | `task` | `cpu` |
| `rapl` | the processor package zones' energy counters, summed | `bracket` | `cpu` |
| `gpu-board` | a discrete GPU driver's total-energy counter for its device | `bracket` | `gpu`, `media` |
| `rapl-and-gpu-board` | both, summed | `bracket` | `cpu`, `gpu`, `media` |
The table is the reference's practice, not a rule a verifier applies: §8.1 is. No counter is
divided among its users by busy time. A `task` figure counts none of a hardware
engine's draw, since the operating system gives an unprivileged process no account of it; a
`bracket` figure on a host other work shares is machine-wide, and its rounds' spread says how
much that work moved it.
## 9. An encode over a package's pieces
### 9.1 The rendition
An **encoded rendition** of a package ([packaging](packaging.md) §11) is one encode of a video
component's source whose IDR plan is the component's pieces' starts, as its stage index lists
them (§6.2 there), and whose rung is named by a term, its **rung id**. Each piece is framed as
one floor object (packaging §5.2, §7.2): a `WAI1` envelope (SPEC §2) whose manifest is the JSON
text, with no whitespace and its members in this order,
{"wai":"1.0","media":"video","intent":"replicate","model_requirement":{"capability":C},
"conditioning":{"kind":K},"derived_from":{"motif":M,"motif_capability":MC,
"renderer":R,"settings":D},"target":{"frames":F,"h":H,"w":W}}
with `C` the capability, `K` `avc` or `hevc`, `F` the piece's frames, `H` and `W` the rung's
height and width, each string written as JCS writes it (RFC 8785 §3.2.2.2), and the
`derived_from` members:
- `motif`: the piece's base object's content hash, the stage index's entry of role base and
layer 0 for that piece, as 64 lowercase hexadecimal digits; `motif_capability`: its capability;
- `renderer`: `<encoder.name>@<encoder.version>`;
- `settings`: `BLAKE3("wai:render-settings\x01" ‖ JCS(S))`, with `S` the object
`{"source": "encoded", "api", "hardware", "device", "driver", "library_sha256",
"stream_sha256"}`, each member but `source` the record's `encoder` member of that name, and
`stream_sha256` the record's `stream.sha256`; the digest written as 64 lowercase hexadecimal
digits.
So each object names the encoder and its version in `renderer` (packaging §7.5) and binds its
device and driver through `settings`, which any verifier holding the record recomputes. Its
payload is its piece's access units as NAL units each after the four bytes `00 00 00 01`, in the
order the stream holds them: the stream's own NAL units, re-framed with one start code length,
beginning with the piece's IDR access unit and its parameter sets.
### 9.2 The record's `package`
| member | type | meaning |
|---|---|---|
| `receipt_sha256` | 32 bytes | the SHA-256 of the package receipt's file (packaging §9) |
| `rung` | term | the rung id |
| `pieces` | array | one object per piece, in order: `start` and `frames` integers, the piece's first frame and frame count; `motif` 32 bytes, the base it presents; `object` 32 bytes, its floor object's content hash ([jwp-receipts](jwp-receipts.md) §2.1) |
The objects are written `objects/<object>.wai`, the stream `<rung id>.h264` or `<rung id>.h265`,
and the record `<rung id>.encode.json`, beside the package and not inside it: the package
receipt does not list them, and a package made before them is unchanged.
### 9.3 Verification against the package
With the package receipt's file, the component's stage index and the floor objects held, a
verifier checks, in order, every failure `encode-package-mismatch`: the receipt file's SHA-256
is `receipt_sha256`; the stage index's pieces, in order, have the record's starts and frame
counts (a video component's timebase unit is one frame, packaging §2), and each piece's one
entry of role base and layer 0 has the record's `motif`; and each piece's object is present,
its content hash is `object`, it is a `WAI1` envelope with exactly §9.1's manifest for this
record and piece, and its payload is §9.1's bytes from the held stream.
A sink that decodes an encoded rendition decodes each object's payload as its capability's
stream; the record tells it who made the bytes, and nothing more.
## 10. Conformance and measurement
**10.1** *The corpus.* [`encode-conformance/`](../encode-conformance/) holds small elementary
streams made by encoders through each interface (fixtures, pinned by their SHA-256 in the
corpus's manifest and never regenerated, since no encoder is a pure function), the source they
were made from, and records generated reproducibly over them from a fixed-seed key with a fixed
`issued_at`: accepted cases, and negative controls each refused with its reason; and an encoded
rendition of a packaging golden with its objects, accepted and mutated. `wai-rs`
`tests/platform_encode.rs` requires the generator to reproduce the corpus byte for byte and every
case to get its outcome; `wai-pack` `tests/encode_rung.rs` does the same for the rendition. The
second implementation, `encode-conformance/encode_verify.py`, is written from this text: it
shares no code with `wai-rs` or `wai-pack`, reads the Annex B stream, its access units and its
parameter sets itself, verifies signatures with WAI's strict Ed25519
(`claim-conformance/_ed25519.py`) and content hashes with BLAKE3
(`claim-conformance/_blake3.py`), and mutates copies of the corpus and requires every copy to
fail.
**10.2** *Decoder conformance.* Each corpus stream, and each stream the reference encoders made
for §10.3, decodes in two independent conforming decoders, a hardware decoder and a software
one, to the same pictures byte for byte, as `StandardDefined` requires.
**10.3** *Measurement (informative).* `wai_encode_measure` encodes a source through one
interface at a rung, rounds as §8.2, and decodes the result with the platform's decoder to give
PSNR (luma, each chroma plane, and the 6:1:1 weighted mean of their squared errors) and SSIM of
luma over 8×8 windows at a step of 4. A figure is of the encoder, the host, its load and its
counter at the time it was taken; none is compared with a figure taken under another counter.
## 11. Relationship to other extensions
- [packaging](packaging.md) §7, in its paragraph 7.5, requires a floor made by an encoder that is not a pure function
of its input to name the encoder and its version; §9 here is how an AVC or HEVC rendition over
a package's pieces does it. Packaging §7.1's floor remains a capability of SPEC §5's mandatory
floor or recommended set: an encoded rendition is beside it, not in its place.
- [energy-measurement](energy-measurement.md) gives the acquisition classes, the declared
uncertainty and the bracketed marginal (§2, §3); [jwp-receipts](jwp-receipts.md) §2.4.5 gives
the members and rules §8.1 restates.
- SPEC §5, *Broadcast and streaming codecs*, registers `wai.video.avc` and `wai.video.hevc`, their
payload (the Annex B byte stream) and their licence class.
## 12. Reasons
| reason | step | meaning |
|---|---|---|
| `record_malformed` | §7 step 1 | the record does not read as §6.1 says |
| `bad_key` | step 2 | the signer's key fails SPEC §7's strict rule |
| `bad_signature` | step 2 | the signature fails it |
| `encode-capability` | step 3 | not `wai.video.avc` or `wai.video.hevc` |
| `encode-identity-incomplete` | step 4 | an identity member is blank |
| `encode-identity-inconsistent` | step 4 | the interface, `hardware`, `library_sha256` and capability disagree |
| `encode-settings-invalid` | step 5 | a size, rate, bit rate, frame count or `resample` out of range or inconsistent |
| `encode-plan-invalid` | step 6 | the IDR plan, or the package's pieces, out of order or out of range |
| `encode-energy-invalid` | step 7 | the figure's members break §8.1 |
| `encode-energy-coverage` | step 7 | a hardware encode's figure that counts no engine is stated `Whole` |
| `encode-stream-mismatch` | step 8 | the stream's SHA-256 or length is not the record's |
| `encode-stream-malformed` | §4.3 items 1 to 3 | not an Annex B stream, NAL units after the last picture that hold no slice, or an access unit mixing IDR and other slices |
| `encode-frame-count` | §4.3 item 2, step 8 | another number of access units |
| `encode-keyframe-mismatch` | §4.3 item 3 | an IDR off the plan, a planned picture not an IDR, or another random access point |
| `encode-parameter-sets` | §4.3 item 4 | parameter sets missing or changed |
| `encode-stream-unsupported` | §4.3 item 5 | not 8-bit 4:2:0 progressive single-layer, or a picture order type this revision reads not |
| `encode-stream-reordered` | §4.3 item 6 | the stream reorders pictures |
| `encode-sps-mismatch` | §4.3 item 7, step 8 | the coded size, profile, level, tier or range is not the record's |
| `encode-source-mismatch` | step 9 | the source's digest is not the record's |
| `encode-package-mismatch` | step 10 | the rendition does not match the package, the stream or the record |
| `encoder-unavailable` | producer | no encoder of the interface for the codec here (not a record reason) |
| `encoder-refused`, `encoder-failed` | producer | the interface refused the settings, or failed an encode (not record reasons) |
## 13. Security considerations
- **A record is its signer's statement.** It names what the interface reported; a signer can
name an encoder that did not run. The stream is checked, not the claim of who made it: a
relying party that must know the maker trusts the signer, or runs the encode itself.
- **A runtime-loaded library is code the user chose.** The adapter refuses a library of another
interface version and states the digest of the file it loaded; it does not check the file
against any list. A deployer that must hold the library to its distributor's published digest
compares `library_sha256` with it.
- **A stream is checked before it is recorded.** §4.3 is applied by the producer before it signs,
and again by every verifier holding the stream, so a record never names a stream whose pieces
do not each start at a self-contained IDR access unit.