Files
craft-libs/crates/video/tests/hevc/main.rs
Dylan M. Taylor e7c3dda7f3 craft-video: AV1 and HEVC take the colour description; AV1 motion search from EffectCraft (#829)
Three-way merge of the changes EffectCraft made to its av1enc and hevcenc
crates after craft-video took them:

- hevc: EncoderConfig carries colour_primaries, transfer_characteristics and
  matrix_coeffs (H.273 code points, BT.709 by default) and the VUI writes them.
- av1: EncoderConfig carries color_primaries, transfer_characteristics and
  matrix_coefficients and color_config writes them.
- av1: motion search keeps a visited set and stops a SAD once it cannot win,
  SATD has fixed-size kernels, and a decision snapshot saves only the block's
  BlockDecoded flags. Output is unchanged.

ffprobe tests check the colour description of both streams.
2026-11-11 16:40:32 -05:00

44 lines
2.2 KiB
Rust

//! ffprobe as the external oracle for what the HEVC parameter sets say.
use std::process::Command;
use craft_video::hevc::{Encoder, EncoderConfig, Frame};
/// The VUI carries the configured colour description, and BT.709 when none is set.
#[test]
fn colour_description_reaches_the_vui() {
let Some(ffprobe) = craft_media_testkit::ffprobe() else { return };
let (w, h) = (64usize, 64usize);
let (y, c) = (vec![100u16; w * h], vec![128u16; w * h / 4]);
for (codes, names) in [(None, ["bt709", "bt709", "bt709"]), (Some([9u8, 16, 9]), ["bt2020", "smpte2084", "bt2020nc"])] {
let mut cfg = EncoderConfig::new(w as u32, h as u32, 30, 1);
if let Some([p, t, m]) = codes {
(cfg.colour_primaries, cfg.transfer_characteristics, cfg.matrix_coeffs) = (p, t, m);
}
let mut enc = Encoder::new(cfg).unwrap();
// Annex B: the parameter sets, then the picture's length-prefixed NAL units.
let mut stream = Vec::new();
for set in enc.parameter_sets() {
stream.extend_from_slice(&[0, 0, 0, 1]);
stream.extend_from_slice(&set);
}
let packet = enc.encode(&Frame { y: &y, u: &c, v: &c, y_stride: w, uv_stride: w / 2 }).unwrap();
let mut rest = packet.data.as_slice();
while let Some((len, tail)) = rest.split_first_chunk::<4>() {
let (nal, tail) = tail.split_at(u32::from_be_bytes(*len) as usize);
stream.extend_from_slice(&[0, 0, 0, 1]);
stream.extend_from_slice(nal);
rest = tail;
}
let path = craft_media_testkit::fixture_subdir("hevc").join(format!("colour-{}-{}.hevc", std::process::id(), codes.is_some()));
std::fs::write(&path, stream).unwrap();
let out = Command::new(&ffprobe).args(["-v", "error", "-show_streams", "-of", "flat"]).arg(&path).output().unwrap();
let _ = std::fs::remove_file(&path);
let s = String::from_utf8_lossy(&out.stdout);
let [p, t, m] = names;
for want in [format!("color_primaries=\"{p}\""), format!("color_transfer=\"{t}\""), format!("color_space=\"{m}\"")] {
assert!(s.contains(&want), "ffprobe output lacks {want}:\n{s}");
}
}
}