Skip to content

v0.0.3

Choose a tag to compare

@MagicalTux MagicalTux released this 25 Oct 13:13
· 3 commits to master since this release
555fee7

Other

  • §6.5.1 tile sizes helper; README full-stream front-end map
  • complete sequence header + every-frame-type frame header with reference state (§7.20/§7.21), set_frame_refs, setup_shear, tile groups
  • every-frame-type uncompressed header with cross-frame state, §8.6.1 quantizers, §8.8.1 filter levels, superframes
  • full-stream front end — complete slice segment header, POC / RPS marking / ref lists, scaling lists
  • full-stream front end for hardware bridges — complete slice header, POC, frame-store DPB, scaling lists
  • edition 2024, MSRV 1.95
  • iterator-shape the VPS dependency-closure loops for clippy 1.95
  • complete VPS walk (7.3.2.3) + byte-exact writer — multi-layer dependency block, OLS modes 0/1/2 with 7.4.3.3 derivations, PTL list, per-OLS DPB + timing/HRD; VPS fixed point in fuzz
  • H.266 SPS/PPS complete + byte-exact writers, HEVC typed SEI encode incl. BP/PT, AV1 sequence-header + VP9 keyframe writers, fuzz fixed-point roster
  • H.266 SPS/PPS + HEVC typed-SEI/BP/PT + AV1 sequence-header + VP9 header fixed points in parsers target; av1 timing_info zero-clock guard
  • keyframe uncompressed-header writer, byte-exact on the IVF fixture; fix 4-bit misparse (refresh_frame_context pair + raw frame_context_idx were skipped) and re-pin fixture expectations
  • sequence-header OBU writer, byte-exact on the keyframe fixture; lossless timing/colour-description retention + 6.4.2 identity-branch fix
  • typed SEI encoders + HRD-coupled BP/PT decode+encode against a SeiHrdContext (D.2.2/D.2.3 — IRAP CPB params, alt delays, use_alt extension slot, decoding-unit lists)
  • complete PPS walk (7.3.2.5) + byte-exact writer — tile grid + rectangular-slice layout via replayed 6.5.1 derivations, chroma/deblocking blocks, bounded by Table A.2 limits
  • complete SPS walk (7.3.2.4) + byte-exact writer — subpics, DPB, HRD, RPL templates, chroma-QP tables, range extension, opaque VUI block
  • H.266 writer-surface cell — OPI/DCI/APS/AUD/SEI byte-exact, SPS/PPS writers still gated on structural parses
  • OPI + DCI parse + byte-exact writers on a lossless profile_tier_level/GCI walk; fuzz fixed points
  • HEVC writer + H.266 APS/SEI fixed points in parsers target; fix two H.266 SEI truncation soundness holes (zero-fill decode, ue-at-end phantom read); reject reserved max_sub_layers 7
  • SEI framing (7.3.6) + complete Annex-D payload set — BP/PT/DUI with context, nesting, SLI, manifest, prefix indication, CREI; decode + byte-exact encode
  • APS family (7.3.2.6) — ALF + LMCS + scaling-list payloads, parse + byte-exact writers, spec-range enforcement
  • byte-exact VPS/SPS/PPS writers on a lossless parse (PTL constraint/sub-layer bits, raw ST-RPS coding, ordering tables, cprms flags retained); fixture-pinned
  • complete VPS walk (7.3.2.1) — layer sets, timing info, HRD list with cprms inheritance
  • extend parsers target to SEI/metadata/framing + fixed-point invariants; README refresh
  • SEI module (7.3.5 framing + HDR payloads) + fuzz-found CtbLog2SizeY shift-overflow fix
  • Annex-B <-> length-prefixed framing converters
  • metadata OBU family (5.8) — parse + write for all five registered types
  • full SPS/PPS walk — scaling lists, PCM, ST-RPS (7.4.8 derivation), LT-RPS, VUI/HRD, tiles, range extensions
  • byte-exact SPS/PPS writers (parse->write pinned on both fixtures)
  • SEI family — sei_rbsp/sei_message framing + 5 typed payload decoders
  • full SPS/PPS walk — scaling lists (7.3.2.1.1.1) + VUI/HRD (Annex E)
  • untrack Cargo.lock (+anchored .gitignore); neutral wording in provenance comments
  • add CI / crates.io / docs.rs / MIT-license badges
  • refresh to current status, drop per-round changelog cruft
  • add parsers fuzz target; fix H.264/HEVC SPS log2_max panic
  • AV1 §4.10.3 uvlc() + §4.10.4 le(n) — §4.10 descriptor family complete
  • AV1 §4.10.6 su(n) signed integer descriptor
  • ns(n) — AV1 §4.10.7 non-symmetric unsigned
  • peek_bits_u64 — 64-bit non-advancing peek
  • access unit delimiter parse + write
  • neutral wording in ivf.rs module doc
  • write_header + write_frame + write_all — muxer inverse of the demuxer
  • shared ebsp_to_rbsp + new rbsp_to_ebsp inverse
  • SPS-context picture-header parser (ph_pic_order_cnt_lsb + ph_recovery_poc_cnt) plus three new SPS fields
  • structural picture-header prefix parser (7.3.2.7 / 7.3.2.8)
  • i(n), te(v), signed_magnitude, read_bytes + write inverses

Changed

  • edition 2024, MSRV 1.95.

Added

  • h264: full-stream front end for hardware decode bridges.
    h264::slice::parse_slice_header walks the complete §7.3.3 slice
    header for I/P/B/SP/SI slices (ref_pic_list_modification,
    pred_weight_table with §7.4.3.2 inferred defaults, dec_ref_pic_marking
    incl. every MMCO, field / MBAFF fields, QP / deblocking) and records
    where slice_data() starts (RBSP bits and the escaped in-NAL bit
    offset). h264::stream::H264Parser keeps SPS/PPS tables, yields
    per-NAL events and detects the first slice of each primary picture
    (§7.4.1.2.4). h264::poc derives picture order counts of types 0/1/2
    (§8.2.1) with their cross-picture state and the MMCO 5 rebasing.
    h264::dpb::Dpb is a frame-store DPB: picture numbers (§8.2.4.1),
    P/B list initialisation for frames and fields incl. the §8.2.4.2.5
    parity interleave, list modification (§8.2.4.3), IDR / sliding-window
    / MMCO 1–6 marking (§8.2.5), gaps in frame_num with non-existing
    frames (§8.2.5.2) and field pairing into one store.
    h264::scaling gives the scaling lists in effect (Table 7-2 fall-back
    rules A/B, Tables 7-3/7-4 defaults, Table 8-13/8-14 scans).
    H264Sps gains max_frame_num / max_pic_order_cnt_lsb /
    pic_width_in_mbs / frame_height_in_mbs / chroma_array_type /
    crop_rect; nal::emulation_prevention_bytes_before /
    rbsp_offset_to_ebsp map RBSP offsets back into escaped NAL units.
    Verified field by field against trace_headers on B-pyramid, weighted
    (luma + chroma), MBAFF, PAFF (long-term, MMCO 1, B fields, mixed
    frame/field), multi-ref, CAVLC-B and scaling-matrix streams, the DPB
    contents after every reference picture against ffmpeg -debug mmco,
    and POC order against container presentation order.
  • hevc: full-stream front end. hevc::slice::parse_slice_segment_header
    walks the complete §7.3.6.1 header for single-layer, non-SCC streams
    — inline or SPS-indexed short-term RPS (inline inter-set prediction
    with delta_idx_minus1), long-term pictures with eq. 7-52
    accumulation, ref_pic_lists_modification(), pred_weight_table()
    with the §7.4.7.3 derived weights / offsets, collocated / TMVP, QP
    offsets, deblocking overrides, entry points, header extension,
    byte_alignment() — completes dependent segments from their
    independent segment, and reports the slice-data byte offset, the
    emulation prevention bytes inside the header and the inline RPS bit
    size. hevc::stream::HevcParser keeps VPS/SPS/PPS tables and yields
    per-NAL events. hevc::dpb::HevcDpb derives NoRaslOutputFlag and
    RASL skipping (§8.1.3), PicOrderCntVal (§8.3.1), the five RPS lists
    with DPB marking (§8.3.2) and RefPicList0/1 (§8.3.4).
    hevc::scaling resolves the scaling lists in effect (Tables 7-5/7-6,
    list prediction, DC values, eq. 7-50 chroma 32x32, §6.5.3 scan).
    Verified against trace_headers on x265 open-GOP B-pyramid (CRA +
    RASL, WPP, two slices), weighted + scaling-list, Main 10, tiles,
    multi-slice and WPP streams, and POC against presentation order.
    hevc::slice::tile_sizes_in_ctbs gives the §6.5.1 tile column
    widths / row heights.
  • vp9: vp9::frame — the uncompressed header of every frame type
    (key, inter with frame_size_with_refs(), intra-only incl. the
    profile 0 colour inference, show_existing_frame) parsed against a
    Vp9State that carries reference slot sizes / colour, loop filter
    deltas and segmentation features across frames with the §7.2
    setup_past_independence() resets; the header reports the values in
    force. Adds the §8.6.1 dc_q / ac_q tables (8/10/12-bit, checked
    against the spec text) with get_qindex() / get_dc_quant() /
    get_ac_quant(), the §8.8.1 LvlLookup table and Annex B
    split_superframe. Verified against trace_headers on 15 streams
    (alt-ref pyramid, superframes, show_existing_frame, intra-only,
    scaled references, resizing, segmentation features, lf deltas,
    frame-parallel, 10-bit, 4:4:4, lossless) and the in-force state
    against the decode traces staged in docs/video/vp9/fixtures/.
  • av1: av1::sequence::parse_sequence_header_obu — the complete
    §5.5 sequence header (every operating point, decoder_model_info(),
    operating parameters, initial display delays). av1::frame — the
    §5.9 uncompressed header for key, inter, intra-only and switch
    frames and show_existing_frame against Av1RefState: frame ids
    with mark_ref_frames(), ref_order_hint[], short reference
    signaling via the §7.8 set_frame_refs process, frame_size_with_refs(),
    superres, uniform / explicit tile info, quantization with matrices,
    segmentation and loop filter deltas from load_previous() or
    setup_past_independence(), delta q / lf, CDEF, loop restoration,
    TX mode, reference select, skip mode frames, warped motion, global
    motion decoded against PrevGmParams, film grain incl.
    load_grain_params(), lossless derivation; Av1RefState::update is
    the §7.20 reference frame update (after the §7.21 load for a shown
    existing key frame). Also setup_shear() (§7.11.3.6 with the
    §7.11.3.7 Div_Lut, checked against the spec text),
    default_gm_params(), LOOP_FILTER_REF_DELTAS_DEFAULT,
    parse_tile_group() and av1::iter_obus. Verified against
    trace_headers on 39 committed streams (and all 140 staged in
    docs/video/av1/fixtures/ locally): short refs, switch frames,
    error resilience, frame ids, show_existing frames, global motion,
    film grain (AR, CSFL, lag 3, predicted), superres + tiles, tile
    groups, segmentation, QM, delta q/lf, LR, CDEF, skip mode, temporal
    / spatial layers, 10/12-bit, monochrome, 4:4:4, SVT-AV1 random access
    with overlays.
  • fuzz: new streams target drives elementary streams through the
    stateful front ends (H.264 parser + POC + DPB, HEVC parser + RPS +
    lists, VP9 superframes + header state, AV1 OBUs + frame header state +
    tile groups); the parsers target also runs the complete H.264 slice
    header and HEVC slice segment header.
  • h266: complete VPS walk (§7.3.2.3) — parse_vps now retains every
    syntax element of video_parameter_set_rbsp() including the
    multi-layer machinery (inter-layer dependency block, OLS modes
    0/1/2 with the §7.4.3.3 TotalNumOlss / NumMultiLayerOlss
    derivations and the mode-2 reference-layer transitive closure, the
    PTL list with per-slot max-tid, per-multi-layer-OLS DPB sizing and
    the OLS timing/HRD list), and write_vps / write_vps_nal are its
    byte-exact inverses. Single-layer VPSs — previously cut short after
    vps_layer_id[0] — now walk their PTL slot, extension bits and
    trailing bits too. VvcVps is the lossless representation
    (layers / ptls / dpb_params / ols_dpbs / timing_hrd
    replace the old prefix-only fields).
  • fuzz: the parsers target now asserts parse→write fixed points for
    the H.266 full-walk SPS/PPS, the HEVC typed SEI families (context
    free via encode_sei_message, and BP/PT against an SPS-VUI HRD
    context recovered from an input prefix), the AV1 sequence header
    and the VP9 keyframe uncompressed header. parse_sequence_header
    additionally rejects zero timing_info clock fields (§6.4.3) so
    the writer inverse holds over the full accepted envelope.
  • vp9: write_uncompressed_header — inverse of
    parse_uncompressed_header over the keyframe envelope (§6.2 incl.
    colour config, loop-filter/quant/segmentation blocks, tile info and
    the byte-alignment padding), byte-exact on the crate's IVF fixture.

Fixed

  • vp9: the keyframe walk skipped the §6.2 refresh_frame_context /
    frame_parallel_decoding_mode pair and the raw frame_context_idx
    f(2), shifting every later field (loop filter, quant, segmentation,
    tiles, header_size_in_bytes) by four bits. The fixture
    expectations pinned that misparse and are re-pinned to the correct
    walk (base_q 37 / loop-filter 2 / 186-byte compressed header, with
    the encoder explicitly coding the spec-default ref deltas
    [1, 0, -1, -1]). The raw coded index is retained on a new
    coded_frame_context_idx field; the derived frame_context_idx
    stays 0 per the intra reset.
  • av1: write_sequence_header — byte-exact inverse of
    parse_sequence_header over the single-operating-point envelope
    (§5.5 incl. trailing_bits()), pinned byte-exact on the crate's
    keyframe fixture. The parser now retains timing_info(), the
    initial-display-delay pair, the colour-description triple and
    separate_uv_delta_q losslessly, and gains the §6.4.2 identity
    (sRGB 4:4:4) branch — identity streams previously misread a
    color_range bit that the spec never codes there.
  • hevc: typed SEI write direction — encode_sei_message is the
    inverse of decode_sei_message for every context-free family
    (MDCV, CLL, recovery point, ITU-T T.35, user-data-unregistered,
    raw pass-through), and the HRD-coupled pair gains typed decode +
    encode: decode_buffering_period / encode_buffering_period
    (§D.2.2 — IRAP CPB params, NAL/VCL initial-delay lists sized by
    CpbCnt, alt delays, the use_alt_cpb_params_flag
    payload-extension slot) and decode_pic_timing /
    encode_pic_timing (§D.2.3 — frame-field info, AU/DPB delays,
    sub-picture decoding-unit lists with common or per-DU removal
    increments), both against a SeiHrdContext carrying the SPS-VUI
    hrd_parameters() + sub-layer id + frame_field_info_present_flag.
    Hostile num_decoding_units_minus1 counts are bounded by the
    remaining payload.
  • h266: complete PPS walk (§7.3.2.5) — parse_pps now retains every
    syntax element of pic_parameter_set_rbsp() through
    rbsp_trailing_bits() and write_pps / write_pps_nal are its
    byte-exact inverses. New coverage: subpicture id mapping, the tile
    grid (explicit columns/rows + §6.5.1 uniform-fill derivation), the
    rectangular-slice layout (per-slice width/height presence
    conditions driven by a replayed SliceTopLeftTileIdx walk,
    tile-splitting via explicit CTU-row slice heights with the
    NumSlicesInTile advance, raster and out-of-order
    pps_tile_idx_delta_val walks), chroma tool offsets + CU chroma-QP
    offset lists, deblocking control, the in-PH signalling flags and
    verbatim pps_extension_data retention. Loop-driving counts are
    bounded by the Table A.2 level-6.3 limits (MaxTileCols ≤ 30,
    MaxTilesPerAu ≤ 990, MaxSlicesPerAu ≤ 1000).
  • h266: complete SPS walk (§7.3.2.4) — parse_sps now retains every
    syntax element of seq_parameter_set_rbsp() through
    rbsp_trailing_bits() and write_sps / write_sps_nal are its
    byte-exact inverses. New coverage: subpicture layout (geometry
    inference incl. the same-size grid, treated-as-pic / loop-filter
    flags, explicit id mapping), dpb_parameters() (§7.3.4),
    general_timing_hrd_parameters() / ols_timing_hrd_parameters() /
    sublayer_hrd_parameters() (§7.3.5) with CPB schedule lists,
    ref_pic_list_struct() templates (§7.3.10, ST/LT/inter-layer
    entries), chroma-QP mapping tables, all partition / coding-tool
    flags, LADF, virtual boundaries, sps_range_extension(), verbatim
    sps_extension_data retention and the opaque byte-aligned
    vui_payload() block. §7.4 value ranges are enforced (MaxDpbSize,
    MaxSlicesPerAu, hrd_cpb_cnt_minus1 ≤ 31, RPL bounds, …) so
    hostile counts cannot drive unbounded loops. The DPB / HRD / RPLS
    sub-structures live in a new public h266::params module for reuse
    by the VPS walk.

Changed

  • h266: VvcPps is now the lossless full-walk representation —
    presence flags gating unparsed blocks are replaced by Option
    fields (pps_conf_win_offsets, pps_scaling_win_offsets,
    subpic_id_mapping, partition, chroma_tool_offsets,
    deblocking, pps_pic_width_minus_wraparound_offset), and
    parsing no longer stops at pps_subpic_id_mapping_present_flag.
  • h266: VvcSps is now the lossless full-walk representation —
    presence flags that gated unparsed blocks
    (sps_ptl_dpb_hrd_params_present_flag,
    sps_subpic_info_present_flag) are replaced by the corresponding
    Option fields, and subpicture streams no longer return
    Unsupported.
  • h266: OPI + DCI RBSPs — parse_opi / write_opi / write_opi_nal
    (operating point information, §7.3.2.2) and parse_dci /
    write_dci / write_dci_nal (decoding capability information,
    §7.3.2.1 with 1..=16 profile_tier_level(1, 0) entries), both
    byte-exact parse→write inverses. profile_tier_level() parsing is
    now lossless: the general_constraints_info() (§7.3.3.2) content
    bits are retained verbatim on VvcProfileTierLevel (new
    gci_present_flag / gci_bits fields) and a public
    write_profile_tier_level emits the full §7.3.3.1 structure.
    Unretained opi/dci_extension_flag == 1 payloads are refused as
    Unsupported.
  • h266: SEI family — new h266::sei module with the §7.3.2.9 /
    §7.3.6 sei_rbsp()/sei_message() framing (0xFF run-length
    accumulation, declared-size validation, byte-exact framing writers)
    plus typed decode and byte-exact encode for every payload whose
    syntax lives in H.266 Annex D: buffering_period (§D.3,
    self-contained length fields), pic_timing (§D.4) and
    decoding_unit_info (§D.5) against a VvcBufferingPeriod +
    TemporalId context (alt-CPB timing, decoding-unit blocks and the
    §D.4.2/§D.5.2 inferences included), scalable_nesting (§D.6, nested
    messages surfaced raw), subpic_level_info (§D.7), sei_manifest
    (§D.8), sei_prefix_indication (§D.9) and the empty
    constrained_rasl_encoding_indication (§D.10). Payload-alignment
    bits are verified; H.274-delegated payload types surface raw via
    VvcSei::Unknown per §D.2.2.
  • h266: adaptation parameter set family — new h266::aps module with
    the complete §7.3.2.6 adaptation_parameter_set_rbsp() walk and
    all three payload families: ALF (§7.3.2.18 — luma/chroma
    coefficient sets with clipping indices plus both cross-component
    filter banks), LMCS (§7.3.2.19 — piecewise-linear model incl. the
    chroma residual scaling delta) and scaling lists (§7.3.2.20 — all
    28 ids in §6.5.2 up-right-diagonal delta coding with the 64×64
    quadrant skip and copy/pred-mode referencing). Byte-exact writers
    (write_aps / write_aps_nal) round-trip every accepted parse;
    spec ranges are enforced (alf_luma_num_filters_signalled_minus1 ≤ 24, coefficient magnitudes ≤ 128, LMCS bin-index ordering,
    scaling_list_pred_id_delta ≤ maxIdDelta, delta coefficients in
    −128..=127). Reserved aps_params_type values and
    aps_extension_flag == 1 are refused as Unsupported.
  • hevc: byte-exact VPS/SPS/PPS writers — write_vps/write_vps_nal,
    write_sps/write_sps_nal and write_pps/write_pps_nal are full
    inverses of the parsers (§7.3.2.1/§7.3.2.2.1/§7.3.2.3.1 +
    rbsp_trailing_bits(), canonical layer-0/TID-0 NAL headers,
    emulation-prevention encoding), pinned byte-exact against the
    reference fixture's parameter sets and against synthetic streams
    covering scaling lists, PCM, explicit + inter-predicted ST-RPS,
    long-term pics, VUI/HRD, tiles, the range extensions and
    cprms-inherited VPS HRD entries. Hand-built structs are validated
    structurally (gate-flag/Option consistency, list-length vs.
    declared-count checks, §7.4.8 re-derivation of inter-predicted RPS
    resolved vectors); unrepresentable inputs (vps_extension_flag,
    non-zero sps/pps_extension_4bits, multilayer/3D/SCC extensions)
    are refused as Unsupported.
  • hevc: lossless parse retention backing the writers —
    HevcProfileTierLevel now keeps the 48 constraint/reserved bits,
    the inter-flag reserved run and every coded sub-layer profile/level
    entry (HevcSubLayerPtl/HevcSubLayerProfile); VPS/SPS keep the
    raw sub-layer ordering tables (HevcSubLayerOrderingInfo) and
    present flags; HevcShortTermRps records its raw §7.3.7 coding
    (HevcStRpsCoding); VPS entries keep vps_reserved_0xffff_16bits
    and per-entry cprms_present_flag (new HevcVpsHrdEntry replaces
    the (idx, hrd) tuple); SPS/PPS keep *_extension_present_flag +
    *_extension_4bits.

Changed

  • hevc: the per-sub-layer DPB cap (max_dec_pic_buffering_minus1 ≤ 15,
    §A.4.2) is now enforced on every coded VPS/SPS ordering-info
    entry, not just the highest sub-layer's; vps_max_sub_layers_minus1
    / sps_max_sub_layers_minus1 now reject the reserved value 7
    (§7.4.3.1 / §7.4.3.2.1 pin the range to 0..=6).
  • fuzz: parsers target now asserts the HEVC VPS/SPS/PPS
    parse→write→parse fixed points (writer refusals restricted to the
    documented Unsupported envelopes), the H.266 APS fixed point, the
    H.266 SEI framing fixed point, decode→encode byte fixed points
    for every typed Annex-D payload, and the BP-context PT/DUI pair
    from a split input.

Fixed

  • h266/sei: two fuzz-found truncation soundness holes in the typed SEI
    decoders — (1) a payload shorter than its syntax decoded as zeros
    via the shared reader's zero-fill-past-the-end contract (e.g. an
    empty sei_manifest "decoded" as a zero-entry manifest); the
    payload-alignment check now rejects any over-read; (2) ue(v)
    invoked exactly at the end of a payload returned 0 without
    consuming anything, letting truncated pic_timing /
    decoding_unit_info / nesting payloads decode as if a real zero
    had been coded; all Annex-D ue(v) reads now fail hard at
    end-of-payload. Both breaks were caught by the new decode→encode
    byte fixed points and carry regression tests.

  • h264: complete SPS parse — scaling lists (§7.3.2.1.1.1 scaling_list()
    with UseDefaultScalingMatrixFlag and freeze-after-zero semantics) and
    full VUI/HRD (Annex E §E.1.1/§E.1.2: aspect ratio incl. Extended_SAR,
    overscan, video signal type + colour description, chroma loc, timing
    info, NAL/VCL HRD with CPB schedule entries, low-delay flag,
    pic_struct, bitstream restriction). New types H264ScalingLists,
    H264Vui, H264HrdParameters, H264CpbEntry; helpers
    sample_aspect_ratio() (Table E-1), field_rate()/frame_rate()
    (§E.2.1), bit_rate()/cpb_size() (§E.2.2).

  • h264: PPS scaling lists via new parse_pps_with_sps /
    parse_pps_nal_with_sps (§7.3.2.2 list count depends on the active
    SPS chroma_format_idc); parse_idr_only feeds the SPS context
    through automatically.

  • h264: hostile-input guards — delta_scale range (−128..=127,
    §7.4.2.1.1.1) and cpb_cnt_minus1 ≤ 31 (§E.2.2) bound all loops.

  • h264: SEI parsing — h264::sei module with §7.3.2.3/§7.3.2.3.1
    sei_rbsp()/sei_message() framing (0xFF run-length payloadType /
    payloadSize accumulation, declared-size-vs-actual-bytes validation)
    plus byte-exact framing writers (write_sei_message /
    write_sei_rbsp). Typed decoders: buffering_period (§D.1.2, HRD
    field widths from the active SPS), pic_timing (§D.1.3 incl. the full
    clock-timestamp ladder, NumClockTS per Table D-1, reserved
    pic_struct rejected), user_data_registered_itu_t_t35 (§D.1.6),
    user_data_unregistered (§D.1.7), recovery_point (§D.1.8); every
    other type surfaced raw per §D.2.1.

  • h264: byte-exact SPS/PPS writers — write_sps/write_sps_nal and
    write_pps/write_pps_nal are full inverses of the parsers
    (§7.3.2.1.1 / §7.3.2.2 + rbsp_trailing_bits(), NAL wrapping with
    emulation-prevention insertion). parse→write reproduces both h264
    fixtures' SPS and PPS RBSP bytes exactly. Parser now also retains
    the POC-type-1 offset fields (offset_for_non_ref_pic,
    offset_for_top_to_bottom_field, offsets_for_ref_frame) and the
    coded scaling-list count, and bounds
    num_ref_frames_in_pic_order_cnt_cycle to 0..=255 (§7.4.2.1.1).
    PPS records high_profile_tail_present so the optional §7.3.2.2
    tail round-trips bit-for-bit.

  • hevc: SPS/PPS walk driven to the extension flags — every former
    Unsupported refusal except SCC/multilayer/3D payloads now parses:
    scaling_list_data (§7.3.4 with §7.4.5 range checks), PCM block,
    short-term RPS with the full §7.4.8 inter-set-prediction derivation
    (7-59..7-71) resolved to DeltaPocS0/S1 form, long-term RPS,
    VUI/HRD (§E.2.1/§E.2.2/§E.2.3 incl. sub-pic HRD fields and
    per-sub-layer CPB schedules), SPS/PPS range extensions
    (§7.3.2.2.2/§7.3.2.3.2), PPS tile grid + WPP. New types
    HevcScalingListData, HevcShortTermRps, HevcVui,
    HevcHrdParameters, HevcCpbEntry, HevcTiles,
    Hevc{Sps,Pps}RangeExtension, HevcDefaultDisplayWindow; helpers
    sample_aspect_ratio() (Table E.1), picture_rate() (§E.3.1),
    num_negative_pics()/num_positive_pics()/num_delta_pocs()
    (7-63/7-64/7-71).

  • hevc: hostile-input bounds — sps_max_dec_pic_buffering_minus1 ≤ 15
    (§A.4.2 MaxDpbSize cap), num_short_term_ref_pic_sets ≤ 64,
    num_long_term_ref_pics_sps ≤ 32 (§7.4.3.2.1), cpb_cnt_minus1 ≤ 31
    (§E.3.2), chroma_qp_offset_list_len_minus1 ≤ 5 (§7.4.3.3.2),
    tile column/row counts checked against remaining payload bits.

  • av1: metadata OBU family (§5.8) — parse_metadata_obu /
    write_metadata_obu covering HDR CLL (§5.8.3), HDR MDCV (§5.8.4),
    scalability incl. the full scalability_structure() (§5.8.5/§5.8.6,
    SCALABILITY_SS), ITU-T T.35 (§5.8.2 with the last-non-zero-byte
    trailing rule enforced both ways) and timecode (§5.8.7 ladder);
    reserved/user-private types surfaced raw per the §5.8.1 note. All
    five families round-trip through the writer, including end-to-end
    framing through write_obu/read_obu.

  • nal: framing converters — split_length_prefixed,
    annex_b_to_length_prefixed, length_prefixed_to_annex_b re-frame
    elementary streams between Annex-B start-code form (ITU-T Annex B)
    and 1..4-byte big-endian length-prefixed form (ISO base-media
    sample framing) without touching NAL bodies. Declared lengths are
    validated against actual bytes; oversize NALs that cannot fit the
    prefix width are refused.

  • hevc: SEI module (hevc::sei) — §7.3.5 sei_message() framing
    (prefix type 39 + suffix type 40 NALs both accepted) with byte-exact
    framing writers, plus typed decoders for ITU-T T.35 (§D.2.6),
    user_data_unregistered (§D.2.7), recovery_point (§D.2.8, signed
    recovery_poc_cnt), mastering_display_colour_volume (§D.2.28, type
    137) and content_light_level_info (§D.2.35, type 144); everything
    else surfaced raw per §D.3.1.

  • fuzz: parsers target now drives H.264 SEI (context-free + SPS-coupled
    decode), HEVC SEI, AV1 metadata, and the framing converters at every
    prefix width, and asserts parse→write→parse fixed points (H.264
    SPS/PPS/SEI, HEVC SEI, AV1 metadata, length-prefixed framing) on
    every successful parse.

  • hevc: VPS walk completed through vps_extension_flag (§7.3.2.1) —
    base-layer flags, sub-layer ordering info (DPB/reorder/latency,
    §A.4.2 cap enforced), vps_max_layer_id + layer-set inclusion
    bitmasks (vps_num_layer_sets_minus1 ≤ 1023 enforced), timing info,
    and the HRD list with the §7.4.3.1 cprms_present_flag == 0
    common-info inheritance from the previous entry.

Fixed

  • hevc: fuzz-found panic — a hostile SPS with out-of-range coding-block
    log2 fields drove 1u32 << CtbLog2SizeY past 31 in the
    slice-segment-address width computation (and silently truncated
    through the u8 struct fields). The SPS parser now rejects
    CtbLog2SizeY outside 4..=6 (§A.3 profile conformance) and the
    slice-header parser bounds the shift defensively for
    caller-constructed SPS structs.
  • H.264 / HEVC SPS parsers now reject an out-of-range
    log2_max_frame_num_minus4 (H.264) and
    log2_max_pic_order_cnt_lsb_minus4 (H.264 / HEVC) with
    InvalidData. The respective specs (H.264 §7.4.2.1.1,
    H.265 §7.4.3.2.1) constrain both to 0..=12, so the dependent
    frame_num / pic_order_cnt_lsb fields are at most 16 bits. A
    malformed SPS carrying a larger value previously slipped through SPS
    parsing and then drove BitReader::u(n > 32) inside
    parse_slice_header_minimal, panicking the host application on a
    hostile fixture. Both fields are now validated at the SPS parse site,
    mirroring the existing H.266 bound on
    sps_log2_max_pic_order_cnt_lsb_minus4. Found by the new parsers
    fuzz target.

Added

  • parsers fuzz target (fuzz/fuzz_targets/parsers.rs) — a second
    panic-hardening harness covering the per-codec header parsers (the
    layer above the BitReader / BitWriter primitives the reader
    target already hammers). It drives every context-free parse_* entry
    point of the H.264, HEVC, H.266, MPEG-2, VC-1, VP8 and VP9 modules at
    several input-derived byte offsets, and feeds the context-dependent
    slice / picture / entry-point parsers an SPS / PPS / sequence-header
    context recovered from a prefix of the same input. The invariant under
    test is purely "no panic, no overflow, no out-of-bounds, no unbounded
    loop on any byte sequence." It immediately surfaced the SPS-width
    panic fixed above.

  • BitReader::uvlc() and BitWriter::write_uvlc(value) — AV1 §4.10.3
    unsigned variable-length code, completing the §4.10 descriptor family
    alongside the le(n) sibling below, leb128 (§4.10.5), su(n)
    (§4.10.6) and ns(n) (§4.10.7). Below u32::MAX the bit layout
    coincides with the H.26x ue(v) Exp-Golomb code, but the descriptors
    diverge at the top of the range: ue(v) rejects 32+ leading zeros as
    a syntax error while uvlc() saturates to u32::MAX, consuming
    the zero run through its terminating 1 bit and reading no suffix.
    Both directions are therefore total — every u32 encodes (the
    saturation code is 32 zeros + the done bit, 33 bits) and every input
    decodes, so neither side returns a Result. This promotes the
    private read_uvlc helper previously buried in av1.rs (used by
    timing_info()'s num_ticks_per_picture_minus_1) to the shared
    descriptor surface, and fixes a positional divergence from the spec
    in that helper: it stopped after the 32nd leading zero, whereas
    §4.10.3 keeps consuming the run through the terminating bit — on a
    (hypothetical) stream with a 33+-zero run every subsequent field
    would have been misread. The shared reader follows the spec loop
    exactly; a dedicated test pins the post-saturation bit position with
    a sentinel field packed directly behind a 40-zero run.

  • BitReader::le(n) and BitWriter::write_le(value, n) — AV1 §4.10.4
    unsigned little-endian n-byte number (the descriptor AV1 uses
    for byte-aligned tile-size fields, tile_size_minus_1 coded as
    le(TileSizeBytes)). n counts bytes, not bits, and is capped at 8
    so the result fits a u64; n == 0 is the trivial zero-bit read.
    The implementation is the spec's literal composition of f(8) reads
    assembled least-significant-byte first, so it is position-agnostic
    (the spec only employs it byte-aligned; callers wanting strictness
    can assert byte_aligned() first). The writer rejects n > 8 and
    any value that does not fit in n bytes, with nothing appended on
    rejection.

    Test delta for the pair: 9 reader + 6 writer unit tests (known-value
    tables, the §4.10.3 saturation + long-run positional pins,
    end-of-stream run termination, little-endian assembly, unaligned
    f(8)-composition equivalence, past-the-end zero padding, rejection
    paths), 6 new roundtrip_props.rs invariants (uvlc round-trip over
    boundary + random values, uvlc/ue bit-layout agreement below
    saturation in both encode directions, the 33-bit saturation-code
    position, le round-trips across every width × bit offset, le vs
    read_bytes little-endian agreement, writer rejection hygiene), and
    the reader fuzz harness now drives uvlc() / le(n) on attacker
    bytes every opcode-tape iteration plus a structured uvlc+le
    writer→reader round-trip stage.

  • BitReader::su(n) and BitWriter::write_su(value, n) — AV1 §4.10.6
    signed integer descriptor. Reads n bits as an unsigned f(n) value,
    then reinterprets the top bit as a sign per the spec arithmetic
    (signMask = 1 << (n - 1); if (value & signMask) value -= 2 * signMask)
    — two's-complement sign extension of an n-bit field. This is the same
    numeric mapping as the H.26x i(n) descriptor but is surfaced
    separately so AV1 parsers can cite §4.10.6 directly; AV1 uses it for
    delta_q = su(1 + 6) (§5.9.13) and the global-motion parameter reads
    (§5.9.24). Accepts widths 1..=32; n == 32 round-trips the full
    i32 range including i32::MIN/i32::MAX. The reader rejects
    n == 0 (the signMask shift is undefined for a zero-bit signed
    field) and n > 32; the writer rejects the same widths plus any value
    outside the representable -(2^(n-1)) .. 2^(n-1) - 1 range (no silent
    truncation, so the round-trip is total for accepted inputs).

    Five new reader unit tests pin the spec's two's-complement decode for
    every raw pattern at widths 1..=8, the §4.10.6 arithmetic example
    (su(4) of 0b1011 → -5), the full-width-32 i32 endpoints, the
    n == 1 {0, -1} alphabet, and the n == 0 / n > 32 rejections.
    Six new writer unit tests mirror the full i8 round-trip, the spec
    example inverse (-5 → 0b1011), out-of-range and zero/oversize-width
    rejections, the full-i32-at-width-32 round-trip, and an exhaustive
    1..=16 × full-range round-trip through the writer/reader pair. Three
    new roundtrip_props.rs invariants extend that to a 1500-value random
    round-trip per width 1..=32, a per-width out-of-range rejection
    sweep, and a cross-check that su(n) and i(n) agree numerically at
    every bit offset for widths up to 24 (so a future divergence in either
    decoder is caught). The reader fuzz harness now also drives su(n) on
    attacker bytes every opcode-tape iteration and adds a structured
    write_su → su round-trip on attacker-derived (value, n) pairs.

  • BitReader::ns(n) and BitWriter::write_ns(value, n) — AV1 §4.10.7
    non-symmetric unsigned integer descriptor. Outputs values in range
    0..n, emitting FloorLog2(n) bits for the lower part of the range
    and FloorLog2(n) + 1 bits for the upper part. Used by the AV1
    syntax for tile sizes (width_in_sbs_minus_1, height_in_sbs_minus_1)
    and film-grain subexp_final_bits (§5.9.15 / §5.9.30). The reader
    rejects n == 0 (the spec only defines ns(n) for n >= 1), caps
    n at 1 << 30 so the internal arithmetic always fits in u32,
    and short-circuits n == 1 to return 0 without consuming any bits
    (the trivial single-value alphabet). The writer enforces
    value < n, the same n == 0 and envelope rejections, and the
    same zero-bit emission for n == 1. Powers of two are handled per
    the spec (w = FloorLog2(n) + 1 keeps a redundant high bit;
    decoders that compute w from n - 1 would otherwise undershoot
    by one — patched explicitly).

    Six new in-module reader unit tests pin the spec's n = 5 byte
    layout (codes 00, 01, 10, 110, 111), confirm n == 1 consumes
    zero bits, exercise the power-of-two branch on n = 4, walk the
    n = 3 two-bit-for-everyone-but-zero pattern, and reject n == 0
    / n above the supported envelope. Five new writer unit tests
    mirror the reader's spec-table pinning, the zero-bit emission for
    n == 1, rejection of value >= n, rejection of n == 0 and
    oversized n, and an exhaustive 1..=33 × 0..n round-trip
    through the writer/reader pair. Four new roundtrip_props.rs
    invariants extend that to n up to 257 exhaustively (spanning the
    power-of-two boundary at 256), a 600-pair mixed-n packing test
    that catches alignment bugs the per-value tests miss, a check that
    ns(1) is invisible when sandwiched between two u(8) fields,
    and the writer's rejection-paths-leave-buffer-clean contract. The
    reader fuzz harness now also drives ns(n) on attacker bytes
    every opcode-tape iteration and adds a structured write_ns →
    ns round-trip on attacker-derived (value, n) pairs.

  • BitReader::peek_bits_u64(n) — 64-bit counterpart of the existing
    peek_bits, symmetric with BitReader::u64. Inspects up to 64 bits
    ahead of the current bit_pos without advancing it, with the same
    past-the-end "zero bits" contract as u64. Borrows the reader
    &self so callers can peek without losing other borrows. Useful for
    parsers that need to inspect a wide marker (e.g. AV1's
    reference_frame_id u(v) class fields up to 16 bits, leb128-aware
    look-aheads on a 64-bit horizon) before deciding whether to commit
    to a branch. Five new in-module unit tests cover the bit-zero case
    matching u64(64), every (offset, width) pair on a 16-byte buffer
    for widths 0..=64, past-end zero-padding (both fully past-end and
    partial straddle), agreement with peek_bits for widths up to 32,
    and the width-zero no-op. Three new roundtrip_props.rs invariants
    exercise the new primitive: a 120-iteration randomised
    every-offset-every-width parity check against u64 (asserting
    peek_bits agreement for n ≤ 32 and the past-end-low-bits-zero
    contract), an empty-reader panic-hardening assertion across all
    widths 0..=64, and a 400-iteration writer ↔ peek round-trip across
    random (value, width) field sequences. The reader fuzz harness's
    opcode tape now also calls peek_bits_u64 on every iteration so
    attacker bytes exercise the new surface.

  • Access unit delimiter (AUD) parse + write for H.264, HEVC and H.266.
    Each codec module now exposes a public NAL-level entry point and its
    inverse:

    • h264::parse_aud_nal / h264::write_aud_nal (§7.3.2.4 /
      §7.4.2.4): surfaces the 3-bit primary_pic_type and verifies the
      rbsp_trailing_bits() marker. The writer accepts the full u(3)
      envelope (0..=7) and rejects oversized values as
      [BitstreamError::InvalidData]. Constant
      H264_PRIMARY_PIC_TYPE_MAX = 7 documents the envelope.
    • hevc::parse_aud_nal / hevc::write_aud_nal (§7.3.2.5 /
      §7.4.3.5): surfaces the 3-bit pic_type field on a
      two-byte-NAL-header AUD. Per the H.265 spec's
      "Decoders … shall ignore reserved values" clause, the parser
      accepts the full u(3) range (0..=7) — including the reserved
      values 3..=7 — and returns them verbatim. The writer mirrors the
      same envelope so reserved values round-trip. Three documented
      pic_type constants (HEVC_PIC_TYPE_I_ONLY = 0,
      HEVC_PIC_TYPE_P_OR_I = 1, HEVC_PIC_TYPE_B_P_OR_I = 2) plus
      HEVC_PIC_TYPE_MAX = 7.
    • h266::parse_aud / h266::write_aud (§7.3.2.10 / §7.4.3.10):
      surfaces both aud_irap_or_gdr_flag u(1) and aud_pic_type u(3)
      on a VvcAccessUnitDelimiter struct. Same reserved-value contract
      as HEVC: 3..=7 round-trip unchanged. Writer rejects fields
      outside their u(1) / u(3) envelopes as
      [BitstreamError::InvalidData]. Constants AUD_PIC_TYPE_I_ONLY,
      AUD_PIC_TYPE_P_OR_I, AUD_PIC_TYPE_B_P_OR_I and
      AUD_PIC_TYPE_MAX = 7 document the envelope.

    Each writer emits the canonical NAL header (H.264: nal_ref_idc = 0
    / type 9; HEVC: layer_id = 0 / tid_plus1 = 1 / type 35; H.266:
    layer_id = 0 / tid_plus1 = 1 / type 20) followed by a 1-byte
    RBSP packing the signalled field(s) plus the rbsp_trailing_bits()
    marker. The single-byte RBSP cannot trigger the
    0x00 0x00 0x0{0..3} start-code-emulation triple, so the encoded
    EBSP is byte-identical to the RBSP and no
    emulation-prevention stuffing is required.

    Twenty-five new in-module AUD tests cover the round-trip across
    every u(3) value, the canonical byte layout for representative
    inputs, reserved-value pass-through (HEVC + H.266), out-of-range
    field rejection on the writer side, wrong-NAL-type rejection,
    truncated-input rejection, header-only-NAL rejection, and
    missing-stop-bit rejection. Five new roundtrip_props.rs
    invariants exercise the writer / parser pair across the full u(3)
    range for each codec (with 0..=255 oversized-value rejection on
    the writer side), and pin the exact emitted byte layout for three
    representative inputs (one per codec).

  • BitWriter::write_rbsp_trailing_bits() — the exact inverse of
    [BitReader::read_rbsp_trailing_bits]. Writes a single
    rbsp_stop_one_bit (= 1) followed by enough alignment_zero_bit
    values to push the bit cursor to the next byte boundary. Three new
    bit_writer unit tests cover the byte-boundary canonical case
    (writer at bit 0 -> emits 0x80), the
    start-at-every-bit-position-then-reader-accepts-marker invariant
    (prefix_bits ∈ 0..8), and the seven-payload-bits-fill-one-byte
    case (0xff is a valid AUD RBSP). The H.264, HEVC and H.266 AUD
    writers above use this helper to terminate their 1-byte RBSPs.

  • IVF muxer (ivf::write_header, ivf::write_frame, ivf::write_all)
    — the inverse of the existing demuxer (parse_header, parse_frame,
    parse_all). write_header emits a 32-byte DKIF global header
    exactly matching the reader's strict fixed-byte checks (version = 0
    LE u16, header_len = 32 LE u16, zeroed reserved tail). write_frame
    appends a 12-byte per-frame header (LE u32 size + LE u64 timestamp)
    followed by the payload bytes. write_all is a convenience that
    emits the global header plus an iterator of (timestamp, payload)
    frames in one allocation. Each writer returns the
    (start, end) byte range covering the appended block so callers
    composing a larger container can locate the new bytes. write_frame
    validates payload.len() <= IVF_FRAME_PAYLOAD_MAX (the u32 wire
    envelope) and write_all does the same pre-flight sweep across every
    frame, leaving the output buffer untouched on rejection — mirroring
    the rejection contract on av1::write_obu / av1::write_leb128.
    Three new public byte-length constants document the wire-format
    envelope: IVF_HEADER_LEN = 32, IVF_FRAME_HEADER_LEN = 12,
    IVF_FRAME_PAYLOAD_MAX = u32::MAX as usize. Ten new ivf unit
    tests cover the lone-header round-trip, prefix-preserving append,
    reserved-tail zeroing, single-frame and empty-payload round-trips,
    multi-frame write_all ↔ parse_all, the empty-frame-list case,
    documented byte-offset placement, in-order concatenation of two
    frames, and the boundary constant pinning. Five new
    roundtrip_props.rs invariants exercise the writer: a 5000-iteration
    randomised global-header round-trip across four FourCCs and every
    u16/u32 field width, a 2000-iteration frame round-trip with payload
    lengths in 0..=4096, a 200-stream multi-frame write_all
    round-trip with 0..=16 frames per stream and per-frame payload
    lengths up to 255 bytes, a 200-iteration "returned-range locates
    appended block" check on top of a hand-written prefix, and a
    fixed-byte-layout pin that matches the reader's strict checks.
    The fuzz/reader.rs target now also (a) calls parse_header,
    parse_frame and parse_all on attacker bytes (panic-hardening),
    and (b) drives write_all + an incremental write_header +
    write_frame sequence from attacker-derived fields and asserts the
    two paths produce byte-identical output that parse_all recovers.

  • New nal module hosting the shared
    emulation_prevention_three_byte (0x03) byte-level helpers used by
    every NAL-framed codec in the crate. nal::ebsp_to_rbsp (the
    stripper) is identical to the byte-identical copies previously
    carried by h264, hevc and h266; those three modules now
    re-export the shared definition so a future drift can no longer
    affect just one codec. nal::rbsp_to_ebsp is the inverse — the
    encoder-side inserter that takes an RBSP built through BitWriter
    and emits a wire-framed EBSP. It escapes every 0x00 0x00 X triple
    with X ∈ {0x00, 0x01, 0x02, 0x03} per ITU-T H.264 §7.4.1.1 /
    H.265 §7.4.1.1 / H.266 §7.4.2.1, and appends the trailing escape
    guard the spec mandates when the RBSP ends on a 0x00 0x00
    suffix. Eleven new nal unit tests cover canonical triple
    stripping, every escaped third-byte value (0x00..=0x03), the
    unescaped third-byte boundary at 0x04, longer interior zero runs
    whose escapes overlap, the trailing-zero-guard round-trip, the
    unaffected-bytes pass-through, and the empty-input identity. Three
    new roundtrip_props.rs invariants: a 11-length × 200-iteration
    random RBSP round-trip through the inverse pair plus 14
    hand-constructed edge cases (empty, lone 0x00, three-zero clusters,
    the all-zeros sequence at lengths 8 and 9, interior triple-zero
    windows); a "no forbidden window" scan asserting the framed EBSP
    contains no 0x00 0x00 0x00 or 0x00 0x00 0x01 triple at any
    offset; and a re-export identity check that the three codec
    modules' ebsp_to_rbsp produce byte-identical output to the shared
    nal::ebsp_to_rbsp on a representative fixture.

Changed

  • h264::ebsp_to_rbsp, hevc::ebsp_to_rbsp and h266::ebsp_to_rbsp
    are now pub use re-exports of nal::ebsp_to_rbsp. Behaviour is
    bit-identical to the previous private copies (which were already
    byte-equivalent across the three modules); the function signature
    is unchanged so every existing caller (tests/h264_idr.rs,
    h264::parse_sps_nal, hevc::parse_sps_nal, the H.266 SPS / PPS /
    picture-header routes, etc.) continues to type-check without
    modification. The module-level rustdoc on each codec now points at
    the shared helper.

  • H.266 / VVC SPS parser extended past sps_bitdepth_minus8 (ITU-T
    H.266 §7.3.2.4). Three new structural fields are surfaced on
    VvcSps: sps_entropy_coding_sync_enabled_flag u(1) (the WPP
    enable HW bridges need for the per-picture VA-API / Vulkan
    parameter), sps_entry_point_offsets_present_flag u(1), and
    sps_log2_max_pic_order_cnt_lsb_minus4 u(4). The new field drives
    the bit-width of ph_pic_order_cnt_lsb (7.3.2.8) via
    MaxPicOrderCntLsb = 1 << (sps_log2_max_pic_order_cnt_lsb_minus4 + 4), surfaced as the VvcSps::poc_lsb_width() and
    VvcSps::max_pic_order_cnt_lsb() convenience accessors plus a new
    public constant SPS_LOG2_MAX_PIC_ORDER_CNT_LSB_MINUS4_MAX = 12
    (the spec envelope dictated by MaxPicOrderCntLsb ≤ 2^16). Values
    outside 0..=12 return BitstreamError::InvalidData.

  • H.266 / VVC SPS-context picture-header parser
    parse_picture_header_with_sps(nal_body, &VvcSps) (ITU-T H.266
    §7.3.2.8). Extends the context-free parse_picture_header() by
    threading the active SPS through, so the parser can resolve the
    ph_pic_order_cnt_lsb u(v) width (from
    sps_log2_max_pic_order_cnt_lsb_minus4 + 4) and decode that field
    plus the conditional ph_recovery_poc_cnt ue(v) that follows when
    ph_gdr_pic_flag = 1. Two new optional fields on VvcPictureHeader
    surface those values (ph_pic_order_cnt_lsb: Option<u32>,
    ph_recovery_poc_cnt: Option<u32>); both are Some(_) only on the
    SPS-context path and None on the context-free
    parse_picture_header() path. Everything past
    ph_recovery_poc_cnt (the NumExtraPhBits array, the
    sps_poc_msb_cycle_flag block, every ALF / LMCS / scaling-list /
    virtual-boundary / RPL / partition-constraint / deblocking /
    QP-delta sub-block gated on later sps_* / pps_* flags) stays
    out of scope.

  • Nine new h266 unit tests cover (a) IRAP picture with 4-bit POC LSB
    decoding through the SPS context, (b) a GDR picture exercising the
    ph_recovery_poc_cnt path with an 8-bit POC LSB, (c) a non-IRAP
    picture with inter_allowed = 0 confirming the inferred-intra
    branch survives the SPS-context route, (d) a 16-bit POC LSB at the
    spec maximum (sps_log2_max_pic_order_cnt_lsb_minus4 = 12,
    poc_lsb = 0xffff), (e) wrong-NAL-type rejection on
    parse_picture_header_with_sps, (f) truncated input rejection on
    the SPS-context entry, (g) oversized-ph_pic_parameter_set_id
    rejection through the same entry, (h) the new SPS envelope check
    (sps_log2_max_pic_order_cnt_lsb_minus4 = 13 → InvalidData) using
    the existing 1080p fixture with one byte rewritten, and (i) an
    independent SPS round-trip through BitWriter that asserts the
    three new fields decode to their written values. Plus one new
    tests/h266_nal_walker.rs integration test driving a synthetic
    Annex-B VPS + SPS + PPS + PH + IDR_W_RADL AU end-to-end through
    split_annex_b → parse_nal_header → parse_sps →
    parse_picture_header_with_sps and asserting the recovered
    ph_pic_order_cnt_lsb.

  • One new tests/roundtrip_props.rs invariant: a 1092-path
    exhaustive sweep that drives parse_picture_header_with_sps
    through every legal POC LSB width
    (sps_log2_max_pic_order_cnt_lsb_minus4 = 0..=12) with both an
    IRAP and a GDR picture per width, exercising endpoint values
    (0 and 2^width - 1) and 40 random mid-range values per width.

  • H.266 / VVC picture-header structural-prefix parser (ITU-T H.266
    §7.3.2.7 / §7.3.2.8). New parse_picture_header() decodes a PH NAL
    body (NAL_TYPE_PH) through ph_pic_parameter_set_id — the always-
    present prefix a HW bridge needs to classify the picture for
    random-access entry-point selection:
    ph_gdr_or_irap_pic_flag u(1), ph_non_ref_pic_flag u(1),
    optional ph_gdr_pic_flag u(1), ph_inter_slice_allowed_flag u(1),
    optional ph_intra_slice_allowed_flag u(1) and
    ph_pic_parameter_set_id ue(v). Parsing stops there because the next
    field (ph_pic_order_cnt_lsb) is u(v) with a width derived from
    the active SPS (sps_log2_max_pic_order_cnt_lsb_minus4 + 4 per
    §7.4.3.4 / §7.4.3.8) which a context-free parser cannot resolve
    yet — routing that SPS context through the parser is deferred to a
    later round. ph_pic_parameter_set_id > 63 (above the
    pps_pic_parameter_set_id u(6) envelope from §7.4.3.5) returns
    BitstreamError::InvalidData. The result struct (VvcPictureHeader)
    exposes is_irap(), is_gdr() and intra_slice_allowed()
    convenience accessors that resolve the §7.4.3.8 inference rules
    (ph_intra_slice_allowed_flag is inferred to 1 when not signalled).
    A new public constant PH_PIC_PARAMETER_SET_ID_MAX = 63 documents
    the validated envelope.

  • Nine new h266 unit tests cover (a) an IRAP picture
    (gdr_or_irap = 1, gdr_pic = 0), (b) a GDR non-reference picture
    (gdr_or_irap = 1, gdr_pic = 1, non_ref = 1), (c) a non-IRAP
    inter-only picture (no ph_gdr_pic_flag, intra = 0), (d) a
    picture with inter_allowed = 0 exercising the inferred-intra path,
    (e) wrong-NAL-type rejection, (f) truncated input, (g)
    oversized-ph_pic_parameter_set_id rejection, (h) emulation-
    prevention-byte stripping via ebsp_to_rbsp and (i) the 2x2x{1,2}x2x
    {1,2} Cartesian product of every legal signalled/inferred flag
    combination. Plus one new tests/h266_nal_walker.rs integration
    test walking a synthetic VPS + SPS + PPS + PH + IDR_W_RADL Annex-B
    access unit through split_annex_b → parse_nal_header →
    parse_picture_header.

  • Two new tests/roundtrip_props.rs invariants: a 1152-path exhaustive
    sweep that crosses every signalled/inferred flag combination with
    every legal ph_pic_parameter_set_id (0..=63), and a boundary-
    rejection sweep across the 16 values immediately above the spec
    maximum.

  • BitReader::i(n) — read n MSB-first bits and interpret as a two's
    complement signed integer (H.264 §7.2 / H.265 §7.2 / H.266 §7.2
    i(n) descriptor). Accepts widths in 1..=32; the full-width case
    (n == 32) round-trips the entire i32 range including i32::MIN
    and i32::MAX. Past-the-end bits remain zero per the reader's
    documented over-read contract.

  • BitWriter::write_i(value, n) — exact inverse of the new reader.
    Refuses any value outside the representable
    -(2^(n-1)) .. 2^(n-1) - 1 range (no silent truncation), so the
    round-trip contract is total for accepted inputs.

  • BitReader::signed_magnitude(n) / BitWriter::write_signed_magnitude
    — read/write a signed value as n magnitude bits followed by a 1-bit
    sign (1 = negative), the layout used by VP9's
    loop_filter_ref_deltas and a number of legacy headers. Widths in
    1..=31; canonical encoding of zero is sign=0, so a hand-crafted
    "negative zero" input decodes back to positive zero (the writer
    picks the canonical form on re-emission).

  • BitReader::te(x_max) / BitWriter::write_te(value, x_max) —
    H.264 §9.1.2 truncated Exp-Golomb. When x_max == 1 the code
    collapses to a single bit equal to 1 - value; for larger x_max
    the helpers delegate to ue / write_ue. x_max == 0 is rejected
    (no spec-defined code) and the writer additionally enforces
    value <= x_max.

  • BitReader::read_bytes(n) / BitWriter::write_bytes(bytes) —
    byte-aligned slice helpers. Both reject unaligned positions with
    InvalidData and the reader returns UnexpectedEnd on a short
    buffer without advancing the cursor, so failures are recoverable.

  • Five new property-style invariants in roundtrip_props.rs exercising
    every new helper: i(n) round-trip for every width 1..=32 (1500
    random values per width), the out-of-range rejection sweep for every
    width 1..=31, signed_magnitude round-trip for every width 1..=31
    (1500 random values per width) plus the negative-zero canonicalisation
    invariant, te(v) round-trip across a handful of representative
    x_max values, and the read_bytes / write_bytes round-trip with
    interleaved bit fields. Plus a dozen new unit tests in the
    bit_reader / bit_writer modules covering the boundary cases
    (full 8-bit enumeration of i(n) decoding, rejection paths, etc.).

  • av1::write_obu — the inverse of av1::read_obu. Builds a
    fully-framed OBU (obu_header [obu_extension_header] obu_size payload)
    from an ObuHeader + payload byte slice and appends it to a
    caller-provided Vec<u8>, returning the (start, end) byte range
    covering the framed OBU. Round-trip contract: feeding start back
    through read_obu reproduces the same ObuHeader, a payload range
    whose length matches payload.len(), and next_offset == end. The
    emitter validates every spec-required bit-field width up-front (obu_type
    ≤ 15, temporal_id ≤ 7, spatial_id ≤ 3, payload size ≤
    LEB128_MAX) and refuses non-zero temporal_id/spatial_id paired
    with extension_flag=0 (which the reader would silently zero), so
    the inverse pair is total. LOBF's obu_has_size_field=1 requirement
    is enforced. Buffer is left untouched on rejection. Three new public
    width-bound constants (OBU_TYPE_MAX, OBU_TEMPORAL_ID_MAX,
    OBU_SPATIAL_ID_MAX) document the field widths. Ten new av1 unit
    tests cover the empty-TD canonical form, payload-size round-trips at
    the 1-byte / 2-byte size-field boundaries (0, 1, 16, 127, 128, 1024),
    the full Cartesian product of legal (temporal_id, spatial_id)
    pairs through the extension byte, append-without-clobber, every
    validation rejection path (size-field-clear, oversized obu_type,
    oversized temporal_id/spatial_id, non-zero IDs without extension
    flag), multi-byte leb128 size emission, the max-legal-IDs corner,
    and a two-OBU concatenation walk.

  • Three new roundtrip_props.rs invariants exercising write_obu: a
    400-iteration random-payload round-trip against read_obu across all
    seven canonical OBU types and the LCG-shuffled extension-byte ID
    space; a validator that every documented rejection path returns
    InvalidData with the buffer untouched; and a 32-OBU concatenation
    that walks the entire synthetic temporal unit back through read_obu
    in lock-step, asserting the next_offset chain parse_obu_stream
    itself depends on.

  • The fuzz/reader.rs target now hammers write_obu alongside the
    other writer surfaces — pulling shape bytes out of attacker input to
    drive (obu_type, extension_flag, temporal_id, spatial_id, payload_len), asserting either write_obu → read_obu == identity
    on accepted inputs or a buffer-preserving clean error on rejected
    ones.