open-standards/wai/wai-rs/tests/av1_refine.rs
Transaction Science bf1de9b1ad
Some checks failed
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 17s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 18s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 1m6s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Failing after 47s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 2m44s
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has been cancelled
wai-app and Create / wai-app · does this change touch it (push) Has been cancelled
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
wai-fleet service / does this change touch it (push) Has been cancelled
wai-fleet service / service · linux (push) Has been cancelled
wai-fleet service / service · macos (push) Has been cancelled
WAI Studio / does this change touch it (push) Has been cancelled
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (push) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 3m33s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 4m41s
wai-audit collector and player integration / does this change touch it (push) Has been cancelled
wai-audit collector and player integration / collector · linux (push) Has been cancelled
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Has been cancelled
WAI operation catalogue / does this change touch it (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
wai-key-proxy / does this change touch it (push) Has been cancelled
wai-key-proxy / proxy · linux (push) Has been cancelled
wai-ledger service / does this change touch it (push) Has been cancelled
wai-ledger service / service · linux (push) Has been cancelled
wai-ledger service / service · macos (push) Has been cancelled
wai-quantum for JavaScript / does this change touch it (push) Has been cancelled
wai-quantum for JavaScript / javascript · linux (push) Has been cancelled
wai-quantum studio and desktop / does this change touch it (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (push) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / does this change touch it (push) Has been cancelled
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has been cancelled
wai-sink-swift native sink package / does this change touch it (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 5m15s
wai cross-architecture byte-equality / does this change touch it (push) Has been cancelled
wai cross-architecture byte-equality / linux-aarch64 (push) Has been cancelled
wai cross-architecture byte-equality / linux-x86_64 (push) Has been cancelled
wai cross-architecture byte-equality / macos-arm64 (reference) (push) Has been cancelled
wai cross-architecture byte-equality / windows-x86_64 (push) Has been cancelled
wai: a refinement over the platform-decoded floor, wai.video.int_refine over a grain-free AV1 base
platform-decode §6 makes the zeroth-condition wai.video.av1 rendition, as the platform
decodes it, a base of the residual refinement layers. A stream every sequence header of
which has film_grain_params_present 0 decodes to the decoding process's frames on every
conforming decoder; a page shows its WebCodecs decoder conforming with a probe (published
SHA-256 of each frame's I420 samples), and the base's RGB8 units are the frames by the JFIF
conversion in 16-bit fixed point, so layers written once against the reference decode reach
the same stage hashes natively and in a page. A stream with film grain anywhere is refused
as a base before it is decoded.

SPEC §5's Base admission admits wai.video.av1 under wai.video.int_refine only where the base
step holds decode-equivalence (codecs::admission_needs_exact_base); staged-delivery §5 and
§6.1 give that base step its tier and units; browser-sink §4.1 and §4.4 say what the probe is
for, and change no route. wai-rs gains staged::av1 (with libdav1d under av1_base),
platform_decode::av1_stream and the grain flag; wai-web gains StageBase.fromAv1, av1Base and
av1Probe and a page that decodes through WebCodecs. The corpus
platform-decode-conformance/grain-free-av1/ holds the stream, its reference decode and probe,
two layers, a stream with film grain and seven cases, checked by wai_av1_refine_vectors,
tests/av1_refine.rs, verify.py (second implementation), the wasm32 replay and the page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 10:46:06 -05:00

214 lines
10 KiB
Rust

//! A grain-free AV1 base refined by `wai.video.int_refine` (`extensions/platform-decode.md`
//! §6): the corpus `platform-decode-conformance/grain-free-av1/`, replayed through
//! `staged::StageSink` from its committed frames.
//!
//! Without a decoder (feature `staged`): every probe digest is the SHA-256 of its frame of
//! `base.i420`; the probe's codec string and size are the stream's; the stage-0 hashes are
//! the frames' `RGB8` units; every case gives its stated reports, units and clip hash; the
//! stream with film grain is refused as a base, and so is the grain-free stream with any
//! sequence header's grain flag set; and frames a platform altered by one sample give
//! other stage hashes. With feature `av1_base`, the reference decode through libdav1d gives
//! `base.i420` byte for byte, and a decode of the stream with film grain is refused before
//! it runs.
use std::path::PathBuf;
use serde_json::Value;
use wai::container::Wai;
use wai::staged::av1::{self, Av1Decoder};
use wai::staged::{tier, ChainConfig, HoldBound, ReceiptEvidence, StageSink};
fn dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../platform-decode-conformance/grain-free-av1")
}
fn read(f: &str) -> Vec<u8> {
std::fs::read(dir().join(f)).unwrap_or_else(|e| panic!("{f}: {e}"))
}
fn json(f: &str) -> Value {
serde_json::from_slice(&read(f)).unwrap_or_else(|e| panic!("{f}: {e}"))
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn decoder(t: &str) -> Av1Decoder {
match t {
"reference" => Av1Decoder::Reference,
"probe-matched" => Av1Decoder::ProbeMatched,
"unprobed" => Av1Decoder::Unprobed,
other => panic!("decoder {other}"),
}
}
const FILES: [&str; 5] = ["base.wai", "base-grain.wai", "layer1.wai", "layer2.wai", "layer1-over-grain.wai"];
fn name_of(h: &[u8; 32]) -> String {
FILES.iter().find(|f| wai::jwp_hash::content_hash(&read(f)) == *h).map(|f| f.to_string()).unwrap_or_else(|| hex(h))
}
#[test]
fn the_probe_is_the_reference_frames() {
let p = json("probe.json");
let frames = read("base.i420");
let base = av1::read_base(&Wai::from_bytes(&read("base.wai")).unwrap().payload).unwrap();
assert_eq!(p["codec"], base.codec.as_str());
assert_eq!((p["codedWidth"].as_u64(), p["codedHeight"].as_u64(), p["frames"].as_u64()), (Some(base.width.into()), Some(base.height.into()), Some(base.frames.into())));
assert_eq!(p["film_grain_params_present"], false);
let fl = base.frame_len();
assert_eq!(frames.len(), fl * base.frames as usize);
let digests: Vec<String> = frames.chunks_exact(fl).map(|f| hex(&av1::frame_sha256(f))).collect();
assert_eq!(p["frame_sha256"], serde_json::json!(digests));
let cat: Vec<u8> = frames.chunks_exact(fl).flat_map(av1::frame_sha256).collect();
assert_eq!(p["clip_sha256"], hex(&wai::pin::sha256(&cat)).as_str());
let c = json("cases.json");
let stage0: Vec<String> = frames.chunks_exact(fl).map(|f| hex(&wai::pin::sha256(&av1::i420_to_rgb8(f, base.width, base.height)))).collect();
assert_eq!(c["stage0_sha256"], serde_json::json!(stage0));
}
#[test]
fn every_case_replays() {
let c = json("cases.json");
let frames = read("base.i420");
let cases = c["cases"].as_array().unwrap();
assert_eq!(cases.len(), 7);
for case in cases {
let name = case["name"].as_str().unwrap();
let e = &case["expect"];
let mut sink = StageSink::new(ChainConfig { bound: HoldBound { max_envelopes: 16, max_age_ms: 1000 }, cost_limit: None, budget: 1 << 30, record_budget: 1 << 20, live: None });
let env = read(case["base"].as_str().unwrap());
let hash = wai::jwp_hash::content_hash(&env);
match av1::base_from_i420(&env, &frames, decoder(case["decoder"].as_str().unwrap()), None) {
Ok((info, units)) => {
assert!(e["base_refusal"].is_null(), "{name}");
assert!(sink.add_base(info, units).is_empty());
}
Err(r) => assert_eq!(e["base_refusal"], r.code(), "{name}"),
}
for (i, (s, want)) in case["steps"].as_array().unwrap().iter().zip(e["steps"].as_array().unwrap()).enumerate() {
let s = s.as_str().unwrap();
let rs = match s.strip_prefix("expire@") {
Some(t) => sink.expire(t.parse().unwrap()),
None => sink.offer(&read(s), 1 + i as u64, ReceiptEvidence::None).unwrap(),
};
let got: Vec<Value> = rs
.iter()
.map(|r| {
serde_json::json!({
"envelope": name_of(&r.content_hash),
"reason": r.envelope.map(|x| x.code()),
"held_for_base": r.held_for_base,
"applied": r.applied,
"held": r.held,
"refused": r.refused.iter().map(|(u, w)| serde_json::json!([u, w.code()])).collect::<Vec<_>>(),
})
})
.collect();
assert_eq!(serde_json::json!(got), want["reports"], "{name} step {i}");
}
match sink.chain(&hash) {
Some(ch) => {
let units: Vec<Value> = (0..ch.n_units())
.map(|u| {
let s = ch.unit(u).unwrap();
serde_json::json!({ "stage": s.stage, "producer": name_of(&s.producer), "tier": tier::token(ch.tier(u)), "sha256": ch.unit_sha256(u).map(|h| hex(&h)) })
})
.collect();
assert_eq!(serde_json::json!(units), e["units"], "{name}");
assert_eq!(serde_json::json!(ch.clip_sha256().map(|h| hex(&h))), e["clip_sha256"], "{name}");
}
None => assert!(e["units"].is_null() && e["clip_sha256"].is_null(), "{name}"),
}
}
// The two decoders at decode-equivalence reach the same stage hashes, and two layers
// reach them in either order.
let by = |n: &str| cases.iter().find(|c| c["name"] == n).unwrap()["expect"]["clip_sha256"].clone();
assert!(by("two-layers").is_string());
assert_eq!(by("two-layers"), by("two-layers-reference"));
assert_eq!(by("two-layers"), by("two-layers-out-of-order"));
assert_ne!(by("two-layers"), by("one-layer"));
}
#[test]
fn grain_anywhere_refuses_the_base() {
let g = Wai::from_bytes(&read("base-grain.wai")).unwrap();
assert_eq!(av1::read_base(&g.payload).unwrap_err().code, "av1_film_grain");
// The grain-free stream with the flag set in its first sequence header: the one bit
// whose flip makes the header read with film grain and nothing else changed.
let w = Wai::from_bytes(&read("base.wai")).unwrap();
let s = wai::platform_decode::av1_stream(&w.payload).unwrap();
assert!(s.grain_free() && !s.sequences.is_empty());
let tu = s.units[0];
// The first unit starts after the framing's 20-byte header and its 4-byte length.
let start = 24;
let mut o = 0usize;
let (body_at, body_len) = loop {
let header = tu[o];
let ext = usize::from(header & 0x04 != 0);
let (mut size, mut k) = (0usize, 0usize);
loop {
let b = tu[o + 1 + ext + k];
size |= usize::from(b & 0x7f) << (7 * k);
k += 1;
if b & 0x80 == 0 {
break;
}
}
let at = o + 1 + ext + k;
if (header >> 3) & 0x0f == 1 {
break (at, size);
}
o = at + size;
};
let mut found = false;
for bit in 0..body_len * 8 {
let mut p = w.payload.clone();
p[start + body_at + bit / 8] ^= 0x80 >> (bit % 8);
let seq = wai::platform_decode::Av1Sequence::parse(&p[start + body_at..start + body_at + body_len]);
if let Ok(q) = seq {
let orig = &s.sequences[0];
if q.film_grain_params_present && q.codec_string() == orig.codec_string() && q.color_range == orig.color_range && q.chroma_sample_position == orig.chroma_sample_position {
assert_eq!(av1::read_base(&p).unwrap_err().code, "av1_film_grain", "bit {bit}");
found = true;
break;
}
}
}
assert!(found, "a bit flip that sets only the grain flag");
}
#[test]
fn an_altered_sample_changes_the_stage_hashes() {
let env = read("base.wai");
let mut frames = read("base.i420");
let (info, units) = av1::base_from_i420(&env, &frames, Av1Decoder::Reference, None).unwrap();
let mut a = StageSink::new(ChainConfig { bound: HoldBound { max_envelopes: 4, max_age_ms: 10 }, cost_limit: None, budget: 1 << 30, record_budget: 1 << 20, live: None });
a.add_base(info.clone(), units);
frames[1234] ^= 1;
let (_, units) = av1::base_from_i420(&env, &frames, Av1Decoder::Reference, None).unwrap();
let mut b = StageSink::new(ChainConfig { bound: HoldBound { max_envelopes: 4, max_age_ms: 10 }, cost_limit: None, budget: 1 << 30, record_budget: 1 << 20, live: None });
b.add_base(info.clone(), units);
for l in ["layer1.wai", "layer2.wai"] {
a.offer(&read(l), 1, ReceiptEvidence::None).unwrap();
b.offer(&read(l), 1, ReceiptEvidence::None).unwrap();
}
let (ca, cb) = (a.chain(&info.content_hash).unwrap(), b.chain(&info.content_hash).unwrap());
assert_eq!(ca.unit(0).unwrap().stage, 2);
assert_ne!(ca.unit_sha256(0), cb.unit_sha256(0), "the layers refine what the decoder gave");
assert_eq!(ca.unit_sha256(1), cb.unit_sha256(1));
}
#[cfg(feature = "av1_base")]
#[test]
fn the_reference_decode_gives_the_committed_frames() {
assert_eq!(av1::decode_i420(&read("base.wai")).unwrap(), read("base.i420"), "libdav1d's frames are base.i420");
let e = av1::decode_i420(&read("base-grain.wai")).unwrap_err();
assert_eq!(e.code(), "av1_film_grain");
let (info, units) = av1::decode_base(&read("base.wai"), None).unwrap();
assert_eq!(info.tier, Some(wai::codecs::DeterminismTier::DecodeEquivalence));
let c = json("cases.json");
let got: Vec<String> = units.iter().map(|u| hex(&wai::pin::sha256(&u.samples.canonical_bytes()))).collect();
assert_eq!(c["stage0_sha256"], serde_json::json!(got));
}