Files
craft-libs/crates/video/tests/apv/alpha.rs
Dylan M. Taylor 7f8a4eed58 craft-video: APV decoder and encoder from FilmCraft (apv) (#865)
FilmCraft's crates/apv at 4bec22bd moves to craft_video::apv: the decoder
(craft_video::apv::decode, feature `apv-decode`), the encoder
(craft_video::apv::encode, feature `apv-encode`), both re-exported at the
module root; `apv` turns on both. Sources, README and tests (ffmpeg oracle,
alpha, mutation and hostile sweeps).

Changes in the move: craft_media::bitstream and craft-media-testkit instead of
FilmCraft's, one test target (tests/apv), crate paths.

New: DecodeOptions::memory_budget (craft_video::budget) refuses a frame whose
samples do not fit before anything is allocated (Error::MemoryBudget).

Fixed, each with a test that fails without it (debug-build overflows):
- EncoderConfig::new multiplied width, height and 30 in u64 without a check.
- Frame::new shifted by the bit depth unchecked (32 and above overflow).
- The public transform functions shifted by a bit depth outside 10..=16.
2026-11-11 17:26:22 -05:00

58 lines
2.1 KiB
Rust

//! Auxiliary alpha frame PBUs (type 27): a valid one becomes the alpha plane, a damaged one is
//! ignored without disturbing the primary picture.
use craft_video::apv::header::PBU_ALPHA_FRAME;
use craft_video::apv::*;
const W: u32 = 64;
const H: u32 = 32;
fn encode(profile: Profile, seed: usize) -> Vec<u8> {
let chroma = profile.default_chroma();
let mut f = Frame::new(W, H, chroma, profile.default_bit_depth(), false);
for (i, s) in f.y.iter_mut().enumerate() {
*s = (64 + (i * seed) % 800) as u16;
}
let mut cfg = EncoderConfig::new(profile, W, H);
cfg.qp = Some(16);
Encoder::with_config(cfg).unwrap().encode(&f).unwrap()
}
/// The first PBU of an access unit written by [`Encoder::encode`] (header and payload).
fn first_pbu(au: &[u8]) -> &[u8] {
let n = u32::from_be_bytes(au[4..8].try_into().unwrap()) as usize;
&au[8..8 + n]
}
/// `primary` with `alpha_pbu` appended as an auxiliary alpha frame PBU.
fn with_alpha(primary: &[u8], alpha_pbu: &[u8]) -> Vec<u8> {
let mut au = primary.to_vec();
au.extend_from_slice(&(alpha_pbu.len() as u32).to_be_bytes());
au.push(PBU_ALPHA_FRAME);
au.extend_from_slice(&alpha_pbu[1..]);
au
}
#[test]
fn auxiliary_alpha_frame_becomes_the_alpha_plane() {
let primary = encode(Profile::P422_10, 37);
let luma = encode(Profile::P400_10, 11);
let expected = decode_frame(&luma).unwrap().y;
let f = decode_frame(&with_alpha(&primary, first_pbu(&luma))).unwrap();
assert_eq!(f.alpha.as_deref(), Some(&expected[..]));
assert_eq!(f.y, decode_frame(&primary).unwrap().y);
}
#[test]
fn truncated_auxiliary_alpha_frame_is_ignored() {
let primary = encode(Profile::P422_10, 37);
let luma = encode(Profile::P400_10, 11);
let pbu = first_pbu(&luma);
// keep the frame header and drop the tile data: the auxiliary frame cannot be decoded
for keep in [pbu.len() / 2, pbu.len() - 1] {
let f = decode_frame(&with_alpha(&primary, &pbu[..keep])).unwrap();
assert!(f.alpha.is_none(), "kept {keep} of {} bytes", pbu.len());
assert_eq!(f.y, decode_frame(&primary).unwrap().y);
}
}