use std::{
fs,
path::{Path, PathBuf},
process::Command,
time::{SystemTime, UNIX_EPOCH},
};
const WIDTH: usize = 96;
const HEIGHT: usize = 64;
const FRAMES: usize = 1;
struct Scratch(PathBuf);
impl Scratch {
fn new() -> Self {
let stamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let path =
std::env::temp_dir().join(format!("nc-cli-keyqp-{}-{stamp}", std::process::id()));
fs::create_dir(&path).unwrap();
Self(path)
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn key_bytes(scratch: &Path, input: &Path, codec: &str, qp: u32, offset: i32) -> u64 {
let stream = scratch.join(format!("{codec}{offset}.bin"));
let size = |n: usize| n.to_string();
let output = Command::new(env!("CARGO_BIN_EXE_nanocodec"))
.args([
"encode",
"--codec",
codec,
"--input",
input.to_str().unwrap(),
])
.args(["--output", stream.to_str().unwrap()])
.args(["--width", &size(WIDTH), "--height", &size(HEIGHT)])
.args(["--fps", "30", "--frames", &size(FRAMES)])
.args([
"--qp",
&qp.to_string(),
"--key-qp-offset",
&offset.to_string(),
])
.output()
.unwrap();
assert!(
output.status.success(),
"{codec} --key-qp-offset {offset}: {}",
String::from_utf8_lossy(&output.stderr)
);
fs::metadata(&stream).unwrap().len()
}
#[test]
fn the_offset_changes_every_codecs_key_quantizer() {
let scratch = Scratch::new();
let input = scratch.0.join("source.yuv");
let bytes: Vec<u8> = (0..FRAMES * WIDTH * HEIGHT * 3 / 2)
.map(|i| ((i * 29 + (i / WIDTH) * 11 + i / (WIDTH * HEIGHT) * 23) % 251) as u8)
.collect();
fs::write(&input, bytes).unwrap();
for (codec, qp, step) in [
("h264", 30, 6),
("hevc", 30, 6),
("vvc", 32, 6),
("av1", 120, 40),
("vp9", 120, 40),
("av2", 120, 40),
] {
let coarser = key_bytes(&scratch.0, &input, codec, qp, -step);
let level = key_bytes(&scratch.0, &input, codec, qp, 0);
let finer = key_bytes(&scratch.0, &input, codec, qp, step);
assert!(
coarser < level && level < finer,
"{codec}: key bytes {coarser} / {level} / {finer} at offsets -{step} / 0 / {step}"
);
}
}