H.264 derives its DPB size the same way HEVC did before #96 — from a level ceiling that says what a stream MAY use, not what it needs — and the ceiling saturates at 16 frames, which is 17 hardware slots with the picture in flight. That is the exact arithmetic that cost 720p and 1080p their HEVC. Measured on real encoders (2026-08-07) rather than assumed: H.264 escapes it twice over, and both escapes belong to the encoders, not to the format. encoder level picked VUI restriction NVENC (RTX 5070 Ti, 610.57.04) 3.2/4.2/5.1/5.2 present, buffering 3 VAAPI via libavcodec (RDNA3, 26.0.3) 4.1/4.2/5.1/5.2 present, buffering 1 openh264 (the software rung) 3.2/4.2/5.1/5.2 present, buffering 1 Every one picks a level proportionate to the picture AND states its real need in the VUI bitstream restriction, so the ceiling is never reached and never consulted. Nothing is broken today, and clamping would be wrong: with the restriction present the number IS the stream's own statement, and a stream that genuinely asked for a deep DPB would decode wrong if we shrank it. So this does not change what any stream decodes. It gives the arithmetic one named home (`dpb_limit`, the twin of `h265::dpb_limit`) carrying the evidence and the reasoning, and it adds the signal that was missing: when an SPS carries no restriction AND its level ceiling would demand more slots than mainstream hardware provides, the plan now says so with `PlanWarning::LevelDerivedDpb` instead of a user silently losing the codec the way #96's users silently lost HEVC. It is not an integrity warning — the picture is intact; what fails is opening a session — so `is_integrity_warning` classifies it false. One thing the sweep corrects about how the follow-up was framed: it is SMALL pictures that saturate the ceiling most easily, not 720p specifically. 640x360 at level 3.1 computes 16 as readily as 720p at level 5.0, because the ceiling is MaxDpbMbs divided by the picture's macroblocks. The authored 64x64 test fixtures land there too, which is why they now assert through `picture_warnings`. Guards, as the missing consumer-end half of pf-encode's `rfi_dpb_fits_a_mainstream_vulkan_decoder`: * every_reachable_h264_stream_fits_a_mainstream_slot_pool — the measured (picture, level, declaration) pairs, asserting slots <= 16 * the_level_ceiling_alone_would_reproduce_96_and_is_warned_about — the same resolutions at levels that saturate, pinned WITH the warning * a_proportionate_level_fits_even_without_a_vui_restriction — so neither escape looks like it is doing all the work alone Gates: fmt + clippy -D warnings clean; pf-client-core 167/167; pf-bitstream 84/84; and gpu_parity 8/8 bit-identical to libavcodec on the RTX 5070 Ti, which is the gate that matters for anything touching the bitstream layer.
178 lines
8.6 KiB
Rust
178 lines
8.6 KiB
Rust
//! Which planner warnings mean the PICTURE is damaged (M4 of the native-decode
|
|
//! program).
|
|
//!
|
|
//! The planners emit two very different kinds of thing through one warning
|
|
//! channel, and the split is what a consumer must branch on:
|
|
//!
|
|
//! * **Integrity** — a reference the DPB does not hold, a `frame_num` gap, an AU
|
|
//! whose NALU walk stopped early. The plan was completed with a SUBSTITUTE in
|
|
//! place of something that was lost, so the decoded picture is damaged: its
|
|
//! output must be released unshown and the stream must ask for a re-anchor.
|
|
//! * **Spec-legal envelope signals** — h265's `NonZeroReorder` (the activated SPS
|
|
//! sets `sps_max_num_reorder_pics > 0`) and h264's `Mmco5Rebase`. pf-bitstream
|
|
//! documents both as spec-legal and fully planned; they exist as the field
|
|
//! signal that a punktfunk-host ASSUMPTION broke, not as damage. `NonZeroReorder`
|
|
//! in particular fires on the AU that ACTIVATES an SPS — the opening IDR, and the
|
|
//! fresh IDR at every ABR resolution change — so treating it as concealment would
|
|
//! cost a released-unshown frame plus a keyframe round trip at every
|
|
//! renegotiation, on a stream the planner says it planned correctly.
|
|
//!
|
|
//! It lives here, in the crate the warnings are re-exported from, rather than in
|
|
//! the client that first needed it, because the fault-injection harness
|
|
//! ([`crate::fault`]) has to assert against the SAME predicate the client conceals
|
|
//! on. Two copies of this list would let a test prove detection that production
|
|
//! does not actually perform — the exact shape of the `nb_queries = 0` failure the
|
|
//! program exists to end.
|
|
|
|
use crate::{Av1PlanWarning, H265PlanWarning, PlanWarning};
|
|
|
|
/// Does this H.264 planner warning mean the PICTURE is damaged?
|
|
///
|
|
/// `Mmco5Rebase` does not: the AU carried an MMCO 5 and pf-bitstream planned it in
|
|
/// full (the plan holds the pre-rebase 8.2.1 values; later AUs reference the
|
|
/// rebased ones).
|
|
///
|
|
/// `LevelDerivedDpb` does not either: the picture is intact and fully planned. It
|
|
/// reports that the SPS never declared its DPB depth, so the plan had to size from
|
|
/// A.3.1's level ceiling and the result will not fit a mainstream slot pool — a
|
|
/// property of the STREAM's signalling, which the decoder answers by failing to open
|
|
/// a session, not by showing a damaged frame.
|
|
///
|
|
/// Written as an EXHAUSTIVE match with no wildcard, deliberately. A `matches!` (or
|
|
/// a `_ => false`) makes "damage" the opt-in and silence the default, so a
|
|
/// `PlanWarning` added later — by definition one nobody here has classified —
|
|
/// would be reported as clean and its picture shown. Invisible damage is the bug
|
|
/// this whole program exists to end; the compiler is the only reviewer guaranteed
|
|
/// to be present when that variant is written, so it gets the decision.
|
|
pub fn is_integrity_warning(w: &PlanWarning) -> bool {
|
|
match w {
|
|
PlanWarning::FrameNumGap { .. }
|
|
| PlanWarning::MissingReference { .. }
|
|
| PlanWarning::TruncatedAu { .. } => true,
|
|
PlanWarning::Mmco5Rebase | PlanWarning::LevelDerivedDpb { .. } => false,
|
|
}
|
|
}
|
|
|
|
/// The H.265 twin — the same set pf-bitstream's own `h265` conformance harness
|
|
/// calls integrity, `NonZeroReorder` deliberately excluded (module docs).
|
|
///
|
|
/// Exhaustive for the same reason as [`is_integrity_warning`]: a new H.265 warning
|
|
/// must not be able to mean "damaged" and read as clean.
|
|
pub fn is_integrity_warning_h265(w: &H265PlanWarning) -> bool {
|
|
match w {
|
|
H265PlanWarning::MissingReference { .. } | H265PlanWarning::TruncatedAu { .. } => true,
|
|
H265PlanWarning::NonZeroReorder { .. } => false,
|
|
}
|
|
}
|
|
|
|
/// The AV1 twin (M7). Every variant the AV1 planner has today IS damage, and that
|
|
/// is a fact about the codec rather than an oversight: AV1 puts nothing in this
|
|
/// channel that resembles h265's `NonZeroReorder` or h264's `Mmco5Rebase`. It has
|
|
/// no reorder envelope to report (no bumping process, no `max_num_reorder_pics`)
|
|
/// and no MMCO to rebase — the frame header states the whole reference update
|
|
/// outright — so the only things left to warn about are pictures that went
|
|
/// missing and an OBU walk that stopped early.
|
|
///
|
|
/// `MissingShowExisting` is the one that could be argued, and it is damage: a
|
|
/// `show_existing_frame` naming an empty slot means the picture the STREAM chose
|
|
/// to display was lost upstream. Nothing is displayed for that frame, so the
|
|
/// screen keeps the previous one — exactly the "silently stale picture" state a
|
|
/// re-anchor exists to end.
|
|
///
|
|
/// ⚠ `MissingReference` is classified here for completeness and does NOT normally
|
|
/// reach a consumer through this predicate: [`crate::VkAv1Decoder`] refuses the
|
|
/// whole access unit for it ([`crate::VkDecodeError::MissingReferenceAv1`]),
|
|
/// because AV1's `refs` array is indexed by reference NAME and there is no legal
|
|
/// substitute to write into a hole — a `-1` for a name the frame really references
|
|
/// is a spec violation whose firmware behaviour is undefined. So the AV1 rung
|
|
/// answers a lost reference as a REFUSAL, not as concealment, and it is the
|
|
/// refusal counter that moves. Classifying it as damage here anyway keeps the two
|
|
/// statements consistent for any consumer that does see the warning (and for the
|
|
/// fault harness, which asserts detection against exactly this list).
|
|
///
|
|
/// Exhaustive for the same reason as [`is_integrity_warning`]: a new AV1 warning
|
|
/// must not be able to mean "damaged" and read as clean.
|
|
pub fn is_integrity_warning_av1(w: &Av1PlanWarning) -> bool {
|
|
match w {
|
|
Av1PlanWarning::MissingReference { .. }
|
|
| Av1PlanWarning::MissingShowExisting { .. }
|
|
| Av1PlanWarning::TruncatedAu { .. } => true,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// The split, stated as the pair of lists it is. This test is the contract:
|
|
/// the client drops a frame for everything on the left and shows the picture
|
|
/// for everything on the right, and the fault harness asserts detection
|
|
/// against the very same predicate.
|
|
#[test]
|
|
fn damage_is_a_lost_reference_or_a_short_au_and_nothing_else() {
|
|
for w in [
|
|
PlanWarning::FrameNumGap {
|
|
expected: 4,
|
|
got: 7,
|
|
},
|
|
PlanWarning::MissingReference {
|
|
context: "list0",
|
|
detail: "poc 12".into(),
|
|
},
|
|
PlanWarning::TruncatedAu { offset: 900 },
|
|
] {
|
|
assert!(is_integrity_warning(&w), "{w:?} is damage");
|
|
}
|
|
assert!(
|
|
!is_integrity_warning(&PlanWarning::Mmco5Rebase),
|
|
"an MMCO 5 was planned in FULL — dropping its frame would hitch a \
|
|
correct stream"
|
|
);
|
|
|
|
for w in [
|
|
H265PlanWarning::MissingReference {
|
|
context: "StCurrBefore",
|
|
detail: "poc 12".into(),
|
|
},
|
|
H265PlanWarning::TruncatedAu { offset: 900 },
|
|
] {
|
|
assert!(is_integrity_warning_h265(&w), "{w:?} is damage");
|
|
}
|
|
assert!(
|
|
!is_integrity_warning_h265(&H265PlanWarning::NonZeroReorder {
|
|
max_num_reorder_pics: 1
|
|
}),
|
|
"SPS activation is not damage — it fires on the opening IDR and on \
|
|
every ABR renegotiation's IDR"
|
|
);
|
|
}
|
|
|
|
/// AV1's whole warning vocabulary is damage. Note plainly what this test does
|
|
/// and does not guard, because the two are easy to confuse:
|
|
///
|
|
/// * A NEW variant is caught by the EXHAUSTIVE MATCH in
|
|
/// [`is_integrity_warning_av1`], not here — this loop enumerates the variants
|
|
/// by hand, so a fourth one would simply not appear in it. That is the whole
|
|
/// reason the function is written as a match with no `_` arm.
|
|
/// * What this test does guard is a RECLASSIFICATION: split one of these names
|
|
/// out of the `|` chain and give it a `false` arm — the shape a future
|
|
/// "spec-legal AV1 signal" would arrive in — and the assertion below fires.
|
|
/// `MissingShowExisting` is the one most likely to be argued down that way (a
|
|
/// frame that decoded nothing and displayed nothing reads as harmless), and
|
|
/// reading it as clean would leave the previous picture on the screen with no
|
|
/// re-anchor asked for.
|
|
#[test]
|
|
fn every_av1_warning_is_damage_because_av1_has_no_envelope_signal() {
|
|
for w in [
|
|
Av1PlanWarning::MissingReference {
|
|
slot: 3,
|
|
ref_index: 1,
|
|
},
|
|
Av1PlanWarning::MissingShowExisting { slot: 5 },
|
|
Av1PlanWarning::TruncatedAu { offset: 900 },
|
|
] {
|
|
assert!(is_integrity_warning_av1(&w), "{w:?} is damage");
|
|
}
|
|
}
|
|
}
|