use std::path::Path;
fn test_media(name: &str) -> Option<Vec<u8>> {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("test_media")
.join(name);
std::fs::read(&path).ok()
}
fn try_decode_matrix(file: &str, label: &str) -> (bool, usize, Option<String>) {
let Some(data) = test_media(file) else {
eprintln!(" SKIP {}: {} not present", label, file);
return (false, 0, None);
};
let demuxed = match container::demux::demux(&data) {
Ok(d) => d,
Err(e) => {
eprintln!(" SKIP {}: demux failed: {}", label, e);
return (false, 0, Some(format!("demux: {e}")));
}
};
eprintln!(
" {} demux: codec={} {}x{} {} samples, head: {:02x?}",
label,
demuxed.codec,
demuxed.info.width,
demuxed.info.height,
demuxed.samples.len(),
&demuxed
.samples
.first()
.map(|s| &s[..s.len().min(32)])
.unwrap_or(&[]),
);
let mut decoder = codec::decode::nvdec::NvdecDecoder::new(demuxed.info.clone(), 0);
for sample in &demuxed.samples {
if let Err(e) = decoder.push_sample(sample) {
eprintln!(" {} push_sample failed: {:#}", label, e);
return (true, 0, Some(format!("{e:#}")));
}
}
if let Err(e) = decoder.finish() {
eprintln!(" {} finish failed: {:#}", label, e);
return (true, 0, Some(format!("{e:#}")));
}
let mut count = 0usize;
while let Some(_f) = decoder.decode_next().expect("decode_next") {
count += 1;
if count >= 30 {
break;
}
}
eprintln!(" {} decoded {} frames", label, count);
(true, count, None)
}
#[test]
fn nvdec_exoplayer_main_nal_dump() {
let Some(data) = test_media("exoplayer_h264_main_720p.mp4") else {
eprintln!("SKIP: exoplayer_h264_main_720p.mp4 not present");
return;
};
let demuxed = match container::demux::demux(&data) {
Ok(d) => d,
Err(e) => {
eprintln!("SKIP: demux failed: {e}");
return;
}
};
eprintln!("Total samples: {}", demuxed.samples.len());
let mut saw_pps = false;
let mut idr_samples = Vec::new();
for (i, s) in demuxed.samples.iter().enumerate() {
let mut nals = Vec::new();
let mut j = 0;
while j + 4 < s.len() {
if s[j] == 0 && s[j + 1] == 0 && s[j + 2] == 0 && s[j + 3] == 1 {
nals.push(s[j + 4] & 0x1F);
j += 4;
} else if s[j] == 0 && s[j + 1] == 0 && s[j + 2] == 1 {
nals.push(s[j + 3] & 0x1F);
j += 3;
} else {
j += 1;
}
}
if nals.contains(&8) {
saw_pps = true;
}
if nals.contains(&5) {
idr_samples.push(i);
}
if i < 20 {
eprintln!(" sample {}: {} bytes, NALs={:?}", i, s.len(), nals);
}
}
eprintln!(
"\nPPS (type=8) seen: {}",
if saw_pps { "YES" } else { "NO" }
);
eprintln!(
"IDR (type=5) count: {} (first 10 indices: {:?})",
idr_samples.len(),
&idr_samples[..idr_samples.len().min(10)]
);
eprintln!(
"Total samples: {}, IDR: {}, non-IDR: {}",
demuxed.samples.len(),
idr_samples.len(),
demuxed.samples.len() - idr_samples.len()
);
}
#[test]
fn nvdec_matrix_all_codecs() {
let gpus = codec::gpu::detect_gpus();
if !gpus
.iter()
.any(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
{
eprintln!("SKIP: no NVIDIA GPU");
return;
}
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_writer(std::io::stderr)
.try_init();
eprintln!("--- NVDEC matrix on {} ---", gpus[0].name);
let h264_baseline = try_decode_matrix("exoplayer_h264_baseline_480p.mp4", "H.264 Baseline");
let h264_main = try_decode_matrix("exoplayer_h264_main_720p.mp4", "H.264 Main");
let h264_high = try_decode_matrix("jellyfin_h264_high_l40_1080p_24fps.mp4", "H.264 High");
let h264_bbb = try_decode_matrix(
"bigbuck_bunny_8bit_750kbps_720p_60.0fps_h264.mp4",
"H.264 BBB (High 4:2:2)",
);
let hevc_bbb = try_decode_matrix(
"bigbuck_bunny_8bit_750kbps_720p_60.0fps_hevc.mp4",
"HEVC BBB",
);
let hevc_main = try_decode_matrix("jellyfin_hevc_main_1080p_24fps.mp4", "HEVC Main 1080p");
let hevc_main10 =
try_decode_matrix("jellyfin_hevc_main10_1080p_24fps.mp4", "HEVC Main10 1080p");
let hevc_main10_hdr = try_decode_matrix(
"jellyfin_hevc_main10_hdr10_1080p_24fps.mp4",
"HEVC Main10 HDR10",
);
let vp9 = try_decode_matrix(
"bigbuck_bunny_8bit_750kbps_720p_60.0fps_vp9.mkv",
"VP9 (MKV)",
);
let av1 = try_decode_matrix("jellyfin_av1_main_1080p_24fps.mp4", "AV1");
let vp8_webm = try_decode_matrix("vp8_webm_480p.webm", "VP8 (WebM)");
let mpeg2_ts = try_decode_matrix("mpeg2_ts_720p.ts", "MPEG-2 (TS)");
let mpeg4_avi = try_decode_matrix("xvid_divx_480p.avi", "MPEG-4 Part 2 (AVI XVID)");
let divx_avi = try_decode_matrix(
"ffmpeg_divx5_mpeg4part2_test_no_b_frames.avi",
"MPEG-4 Part 2 (AVI DIVX no-B)",
);
let prores_422 = try_decode_matrix("prores_422_720p.mov", "ProRes 422 HQ (apch)");
let prores_4444 = try_decode_matrix("ffmpeg_prores_4444_ap4h_fcp.mov", "ProRes 4444 (ap4h)");
eprintln!("\n=== MATRIX ===");
for (label, r) in [
("H.264 Baseline ", &h264_baseline),
("H.264 Main ", &h264_main),
("H.264 High ", &h264_high),
("H.264 BBB (High 4:2:2) ", &h264_bbb),
("HEVC BBB ", &hevc_bbb),
("HEVC Main 1080p ", &hevc_main),
("HEVC Main10 1080p ", &hevc_main10),
("HEVC Main10 HDR10 ", &hevc_main10_hdr),
("VP9 MKV ", &vp9),
("AV1 ", &av1),
("VP8 (WebM) ", &vp8_webm),
("MPEG-2 (TS) ", &mpeg2_ts),
("MPEG-4 Part 2 (AVI XVID) ", &mpeg4_avi),
("MPEG-4 Part 2 (AVI DIVX no-B)", &divx_avi),
("ProRes 422 HQ (apch) ", &prores_422),
("ProRes 4444 (ap4h) ", &prores_4444),
] {
eprintln!(" {} → ran={} frames={:>4} err={:?}", label, r.0, r.1, r.2);
}
}
#[test]
fn nvdec_decodes_h264_big_buck_bunny() {
let gpus = codec::gpu::detect_gpus();
println!("detected {} GPU(s):", gpus.len());
for g in &gpus {
println!(" - {:?} idx={} name={}", g.vendor, g.index, g.name);
}
let Some(dev) = gpus
.iter()
.find(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
else {
eprintln!("SKIP: no NVIDIA GPU detected");
return;
};
let Some(data) = test_media("bigbuck_bunny_8bit_750kbps_720p_60.0fps_h264.mp4") else {
eprintln!("SKIP: test_media/ not populated");
return;
};
let demuxed = container::demux::demux(&data).expect("demux");
println!(
"demux: codec={} {}x{} @ {:.2}fps, {} samples",
demuxed.codec,
demuxed.info.width,
demuxed.info.height,
demuxed.info.frame_rate,
demuxed.samples.len()
);
for (i, s) in demuxed.samples.iter().enumerate().take(5) {
let mut nal_types = Vec::new();
let mut j = 0;
while j + 4 < s.len() && nal_types.len() < 8 {
if s[j] == 0 && s[j + 1] == 0 && s[j + 2] == 0 && s[j + 3] == 1 {
nal_types.push(s[j + 4] & 0x1F);
j += 4;
} else if s[j] == 0 && s[j + 1] == 0 && s[j + 2] == 1 {
nal_types.push(s[j + 3] & 0x1F);
j += 3;
} else {
j += 1;
}
}
println!("sample {}: {} bytes, nal_types={:?}", i, s.len(), nal_types);
}
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_writer(std::io::stderr)
.try_init();
let mut decoder = codec::decode::nvdec::NvdecDecoder::new(demuxed.info.clone(), dev.index);
for sample in &demuxed.samples {
if let Err(e) = decoder.push_sample(sample) {
panic!("push_sample failed: {e:#}");
}
}
match decoder.finish() {
Ok(()) => {}
Err(e) => {
if let Some(nvdec_err) = e.downcast_ref::<codec::decode::nvdec::NvdecError>() {
match nvdec_err {
codec::decode::nvdec::NvdecError::UnsupportedChroma { label, .. } => {
println!("NVDEC correctly rejected {}: {}", label, nvdec_err);
return;
}
codec::decode::nvdec::NvdecError::UnsupportedPixelFormat { bit_depth } => {
println!("NVDEC correctly rejected {}-bit: {}", bit_depth, nvdec_err);
return;
}
codec::decode::nvdec::NvdecError::UnsupportedByHardware { reason } => {
println!("NVDEC correctly rejected (hardware caps): {}", reason);
return;
}
}
}
panic!("NvdecDecoder finish failed with non-typed error: {e:#}");
}
}
let mut count = 0usize;
while let Some(_frame) = decoder.decode_next().expect("decode_next") {
count += 1;
if count >= 60 {
break;
}
}
println!("decoded {} frames", count);
assert!(count > 0, "NVDEC produced zero frames");
}
use codec::decode::nvdec::{NvdecError, deinterleave_p016_to_yuv420p10le, validate_format};
#[test]
fn test_nvdec_rejects_422_chroma_with_typed_error() {
let err = validate_format(
2, 0,
1920, 1080,
)
.expect("4:2:2 must surface a typed reject");
match err {
NvdecError::UnsupportedChroma {
chroma_format,
label,
width,
height,
} => {
assert_eq!(chroma_format, 2);
assert_eq!(label, "4:2:2");
assert_eq!(width, 1920);
assert_eq!(height, 1080);
}
other => panic!("expected UnsupportedChroma, got {other:?}"),
}
let msg = format!("{err}");
assert!(
msg.contains("4:2:2"),
"Display must mention chroma label: {msg}"
);
assert!(
msg.contains("1920x1080"),
"Display must carry resolution: {msg}"
);
}
#[test]
fn test_nvdec_rejects_444_chroma_with_typed_error() {
let err = validate_format(
3, 0,
3840, 2160,
)
.expect("4:4:4 must surface a typed reject");
match err {
NvdecError::UnsupportedChroma {
chroma_format,
label,
width,
height,
} => {
assert_eq!(chroma_format, 3);
assert_eq!(label, "4:4:4");
assert_eq!(width, 3840);
assert_eq!(height, 2160);
}
other => panic!("expected UnsupportedChroma, got {other:?}"),
}
}
#[test]
fn test_nvdec_accepts_420_at_standard_bit_depths() {
assert!(validate_format(1, 0, 1280, 720).is_none());
assert!(validate_format(1, 2, 1920, 1080).is_none());
assert!(validate_format(1, 4, 1920, 1080).is_none());
}
#[test]
fn test_nvdec_rejects_14bit_with_typed_error() {
let err = validate_format(1, 6, 1920, 1080).expect("14-bit must reject");
match err {
NvdecError::UnsupportedPixelFormat { bit_depth } => {
assert_eq!(bit_depth, 14);
}
other => panic!("expected UnsupportedPixelFormat, got {other:?}"),
}
}
#[test]
fn test_p016_deinterleave_round_trip() {
let w: usize = 4;
let h: usize = 2;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let uv_pairs = cw * ch;
let y_values_10bit: [u16; 8] = [0x001, 0x040, 0x080, 0x0FF, 0x100, 0x200, 0x3FF, 0x0AB];
let uv_u_10bit: [u16; 2] = [0x123, 0x234];
let uv_v_10bit: [u16; 2] = [0x345, 0x1FE];
let mut p016 = Vec::with_capacity((w * h + uv_pairs * 2) * 2);
for &y in &y_values_10bit {
let hi = y << 6;
p016.extend_from_slice(&hi.to_le_bytes());
}
for i in 0..uv_pairs {
let u = uv_u_10bit[i] << 6;
let v = uv_v_10bit[i] << 6;
p016.extend_from_slice(&u.to_le_bytes());
p016.extend_from_slice(&v.to_le_bytes());
}
let planar = deinterleave_p016_to_yuv420p10le(&p016, w, h);
let y_bytes = w * h * 2;
let u_bytes = uv_pairs * 2;
let v_bytes = uv_pairs * 2;
assert_eq!(
planar.len(),
y_bytes + u_bytes + v_bytes,
"output layout mismatch"
);
for (i, &expected) in y_values_10bit.iter().enumerate() {
let lo = planar[i * 2];
let hi = planar[i * 2 + 1];
let got = u16::from_le_bytes([lo, hi]);
assert_eq!(got, expected, "Y[{i}] mismatch");
}
for i in 0..uv_pairs {
let base = y_bytes + i * 2;
let got = u16::from_le_bytes([planar[base], planar[base + 1]]);
assert_eq!(got, uv_u_10bit[i], "U[{i}] mismatch");
}
for i in 0..uv_pairs {
let base = y_bytes + u_bytes + i * 2;
let got = u16::from_le_bytes([planar[base], planar[base + 1]]);
assert_eq!(got, uv_v_10bit[i], "V[{i}] mismatch");
}
}
#[test]
fn test_p016_deinterleave_odd_height_preserves_last_uv_row() {
let w: usize = 4;
let h: usize = 3;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
assert_eq!(ch, 2, "test setup: h=3 → ceil(h/2) = 2");
let uv_pairs = cw * ch;
let mut p016 = Vec::new();
for i in 0..(w * h) {
let v = ((i as u16) + 1) << 6;
p016.extend_from_slice(&v.to_le_bytes());
}
let u_vals: [u16; 4] = [0x010, 0x020, 0x030, 0x3FE];
let v_vals: [u16; 4] = [0x101, 0x202, 0x303, 0x1AB];
for i in 0..uv_pairs {
let u = u_vals[i] << 6;
let v = v_vals[i] << 6;
p016.extend_from_slice(&u.to_le_bytes());
p016.extend_from_slice(&v.to_le_bytes());
}
let planar = deinterleave_p016_to_yuv420p10le(&p016, w, h);
let y_bytes = w * h * 2;
let u_bytes = uv_pairs * 2;
let last_u_base = y_bytes + (uv_pairs - 1) * 2;
let got_last_u = u16::from_le_bytes([planar[last_u_base], planar[last_u_base + 1]]);
assert_eq!(got_last_u, u_vals[uv_pairs - 1], "last U row dropped");
let last_v_base = y_bytes + u_bytes + (uv_pairs - 1) * 2;
let got_last_v = u16::from_le_bytes([planar[last_v_base], planar[last_v_base + 1]]);
assert_eq!(got_last_v, v_vals[uv_pairs - 1], "last V row dropped");
}
#[test]
fn test_pts_propagated_through_callback_for_b_frame_sequence() {
use codec::decode::nvdec::NvdecDecoder;
use codec::frame::{ColorMetadata, ColorSpace, PixelFormat, StreamInfo};
let w = 16u32;
let h = 16u32;
let y_bytes = (w * h) as usize;
let uv_bytes = (w * h / 2) as usize; let frame_bytes = y_bytes + uv_bytes;
let pts_values: [u64; 6] = [0, 20, 40, 60, 80, 120];
let frames: Vec<(Vec<u8>, u32, u32, u8, u64)> = pts_values
.iter()
.map(|&pts| (vec![0u8; frame_bytes], w, h, 0, pts))
.collect();
let info = StreamInfo {
codec: "h264".into(),
width: w,
height: h,
frame_rate: 30.0,
duration: 1.0,
pixel_format: PixelFormat::Yuv420p,
color_space: ColorSpace::Bt709,
total_frames: pts_values.len() as u64,
bitrate: 1_000_000,
color_metadata: ColorMetadata::default(),
};
let mut dec = NvdecDecoder::test_new_from_frames(frames, info);
let mut drained = Vec::new();
while let Some(f) = dec.decode_next().expect("decode_next") {
drained.push(f.pts);
}
assert_eq!(
drained, pts_values,
"PTS must round-trip through DecodedFrame → VideoFrame without synthetic counter"
);
}
#[test]
fn nvdec_h264_real_sample_does_not_segfault() {
let gpus = codec::gpu::detect_gpus();
let Some(dev) = gpus
.iter()
.find(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
else {
eprintln!("SKIP: no NVIDIA GPU detected");
return;
};
eprintln!("GPU: {} idx={}", dev.name, dev.index);
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::WARN)
.with_writer(std::io::stderr)
.try_init();
let candidates: &[&str] = &[
"jellyfin_h264_high_l40_1080p_24fps.mp4",
"exoplayer_h264_main_720p.mp4",
];
let mut any_decoded = false;
let mut any_present = false;
for fname in candidates {
let Some(data) = test_media(fname) else {
eprintln!(" skip {}: not present", fname);
continue;
};
any_present = true;
let demuxed = match container::demux::demux(&data) {
Ok(d) => d,
Err(e) => {
eprintln!(" skip {}: demux failed: {e}", fname);
continue;
}
};
eprintln!(
" feed {}: {}x{} {} samples",
fname,
demuxed.info.width,
demuxed.info.height,
demuxed.samples.len()
);
let mut decoder = codec::decode::nvdec::NvdecDecoder::new(demuxed.info.clone(), dev.index);
for sample in &demuxed.samples {
if let Err(e) = decoder.push_sample(sample) {
eprintln!(" {} push_sample error (no crash): {e:#}", fname);
continue;
}
}
match decoder.finish() {
Ok(()) => {
let mut n = 0usize;
while let Some(_f) = decoder.decode_next().expect("decode_next") {
n += 1;
if n >= 5 {
break;
}
}
eprintln!(" {} decoded {} frames (no crash)", fname, n);
if n > 0 {
any_decoded = true;
}
}
Err(e) => {
eprintln!(" {} decode error (no crash): {e:#}", fname);
}
}
}
if !any_present {
eprintln!("SKIP: no 4:2:0 H.264 samples present in test_media/");
return;
}
assert!(
any_decoded,
"NVDEC produced 0 frames on every 4:2:0 H.264 sample — regression"
);
}
#[test]
fn nvdec_robust_against_garbage_input() {
let gpus = codec::gpu::detect_gpus();
let Some(dev) = gpus
.iter()
.find(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
else {
eprintln!("SKIP: no NVIDIA GPU detected");
return;
};
eprintln!("GPU: {} idx={}", dev.name, dev.index);
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::WARN)
.with_writer(std::io::stderr)
.try_init();
let info = codec::frame::StreamInfo {
codec: "h264".into(),
width: 1920,
height: 1080,
frame_rate: 30.0,
duration: 1.0,
pixel_format: codec::frame::PixelFormat::Yuv420p,
color_space: codec::frame::ColorSpace::Bt709,
total_frames: 1,
bitrate: 1_000_000,
color_metadata: codec::frame::ColorMetadata::default(),
};
fn run_one(info: &codec::frame::StreamInfo, gpu: u32, samples: Vec<Vec<u8>>) -> Option<String> {
let mut dec = codec::decode::nvdec::NvdecDecoder::new(info.clone(), gpu);
for s in &samples {
if let Err(e) = dec.push_sample(s) {
return Some(format!("push: {e}"));
}
}
if let Err(e) = dec.finish() {
return Some(format!("finish: {e}"));
}
None
}
eprintln!(
" empty-sample-list: {:?}",
run_one(&info, dev.index, Vec::new())
);
eprintln!(
" one-empty-sample: {:?}",
run_one(&info, dev.index, vec![Vec::new()])
);
eprintln!(
" one-byte-sample: {:?}",
run_one(&info, dev.index, vec![vec![0u8]])
);
eprintln!(
" all-zero-1KiB: {:?}",
run_one(&info, dev.index, vec![vec![0u8; 1024]])
);
let mut garbage = Vec::with_capacity(4096);
let mut state: u32 = 0xDEADBEEF;
for _ in 0..4096 {
state = state.wrapping_mul(1664525).wrapping_add(1013904223);
garbage.push((state >> 24) as u8);
}
eprintln!(
" random-garbage-4KiB: {:?}",
run_one(&info, dev.index, vec![garbage])
);
let fake_annex_b: Vec<u8> = vec![
0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, ];
eprintln!(
" synthetic-annex-b: {:?}",
run_one(&info, dev.index, vec![fake_annex_b])
);
}
#[test]
fn nvdec_streaming_dispatch_init_smoke() {
let gpus = codec::gpu::detect_gpus();
let Some(dev) = gpus
.iter()
.find(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
else {
eprintln!("SKIP: no NVIDIA GPU");
return;
};
eprintln!("GPU: {} idx={}", dev.name, dev.index);
let info = codec::frame::StreamInfo {
codec: "h264".into(),
width: 1920,
height: 1080,
frame_rate: 30.0,
duration: 1.0,
pixel_format: codec::frame::PixelFormat::Yuv420p,
color_space: codec::frame::ColorSpace::Bt709,
total_frames: 1,
bitrate: 1_000_000,
color_metadata: codec::frame::ColorMetadata::default(),
};
let mut decoder = codec::decode::create_decoder("h264", info)
.expect("create_decoder must not fail on NVIDIA host");
decoder.push_sample(&[]).expect("empty push must succeed");
assert!(decoder.decode_next().expect("decode_next").is_none());
decoder.finish().expect("finish");
while let Some(_f) = decoder.decode_next().expect("decode_next drain") {
}
}
#[test]
fn nvdec_streaming_emits_frames_before_finish() {
let gpus = codec::gpu::detect_gpus();
let Some(_dev) = gpus
.iter()
.find(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
else {
eprintln!("SKIP: no NVIDIA GPU");
return;
};
let Some(data) = test_media("jellyfin_h264_high_l40_1080p_24fps.mp4") else {
eprintln!("SKIP: jellyfin_h264_high_l40_1080p_24fps.mp4 not present");
return;
};
let demuxed = match container::demux::demux(&data) {
Ok(d) => d,
Err(e) => {
eprintln!("SKIP: demux failed: {e}");
return;
}
};
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::WARN)
.with_writer(std::io::stderr)
.try_init();
let mut decoder = codec::decode::create_decoder(&demuxed.codec, demuxed.info.clone())
.expect("create_decoder must engage NVDEC streaming on NVIDIA host");
let mut first_frame_at: Option<usize> = None;
let mut frames_pre_finish: usize = 0;
for (idx, sample) in demuxed.samples.iter().enumerate() {
decoder.push_sample(sample).expect("push_sample");
while let Some(_f) = decoder.decode_next().expect("decode_next") {
if first_frame_at.is_none() {
first_frame_at = Some(idx);
}
frames_pre_finish += 1;
}
if frames_pre_finish >= 30 {
break;
}
}
eprintln!(
"Streaming: first frame at sample={:?}, {} frames emitted pre-finish",
first_frame_at, frames_pre_finish
);
assert!(
frames_pre_finish > 0,
"NvdecStreamingDecoder must emit frames per push (got 0 pre-finish)"
);
assert!(
first_frame_at.is_some_and(|i| i < demuxed.samples.len() / 2),
"First frame should appear in the first half of the stream, got {:?}",
first_frame_at
);
}
#[test]
fn nvdec_streaming_peak_rss_under_200mib() {
let gpus = codec::gpu::detect_gpus();
let Some(dev) = gpus
.iter()
.find(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
else {
eprintln!("SKIP: no NVIDIA GPU");
return;
};
let Some(data) = test_media("jellyfin_h264_high_l40_1080p_24fps.mp4") else {
eprintln!("SKIP: jellyfin_h264_high_l40_1080p_24fps.mp4 not present");
return;
};
let demuxed = match container::demux::demux(&data) {
Ok(d) => d,
Err(e) => {
eprintln!("SKIP: demux failed: {e}");
return;
}
};
eprintln!("GPU: {} idx={}", dev.name, dev.index);
eprintln!(
"Demux: {}x{}, {} samples available",
demuxed.info.width,
demuxed.info.height,
demuxed.samples.len()
);
let baseline = current_rss_bytes();
eprintln!("Baseline RSS: {} MiB", baseline / (1024 * 1024));
let mut decoder = codec::decode::create_decoder(&demuxed.codec, demuxed.info.clone())
.expect("create_decoder");
let n = demuxed.samples.len().min(1000);
let mut peak_rss = baseline;
let mut frames: usize = 0;
for sample in demuxed.samples.iter().take(n) {
decoder.push_sample(sample).expect("push_sample");
while let Some(_f) = decoder.decode_next().expect("decode_next") {
frames += 1;
}
let rss = current_rss_bytes();
if rss > peak_rss {
peak_rss = rss;
}
}
decoder.finish().expect("finish");
while let Some(_f) = decoder.decode_next().expect("decode_next drain") {
frames += 1;
}
let final_rss = current_rss_bytes();
if final_rss > peak_rss {
peak_rss = final_rss;
}
let delta = peak_rss.saturating_sub(baseline);
let limit_bytes: u64 = 200 * 1024 * 1024;
eprintln!(
"Pumped {} samples → {} decoded frames; peak RSS {} MiB ({} MiB above baseline). \
Limit: {} MiB.",
n,
frames,
peak_rss / (1024 * 1024),
delta / (1024 * 1024),
limit_bytes / (1024 * 1024),
);
assert!(
delta <= limit_bytes,
"Streaming NVDEC peak RSS delta {} MiB > 200 MiB limit (regression — \
decoder is buffering frames between pushes)",
delta / (1024 * 1024)
);
assert!(frames > 0, "Pumped {} samples, got zero decoded frames", n);
}
#[test]
fn nvdec_streaming_jellyfin_h264_full_decode() {
nvdec_streaming_h264_full_decode(
"jellyfin_h264_high_l40_1080p_24fps.mp4",
Some(1797),
"Squad-12 baseline",
);
}
#[test]
fn nvdec_streaming_h264_4k_screen_recording_full_decode() {
nvdec_streaming_h264_full_decode(
"screen_4k_h264_main_l51.mp4",
Some(179),
"production segfault repro 2026-05-01",
);
}
fn nvdec_streaming_h264_full_decode(fname: &str, expected_frames: Option<usize>, label: &str) {
let gpus = codec::gpu::detect_gpus();
let Some(dev) = gpus
.iter()
.find(|g| g.vendor == codec::gpu::GpuVendor::Nvidia)
else {
eprintln!("SKIP: no NVIDIA GPU ({label})");
return;
};
let Some(data) = test_media(fname) else {
eprintln!("SKIP: {fname} not present");
return;
};
let demuxed = match container::demux::demux(&data) {
Ok(d) => d,
Err(e) => {
eprintln!("SKIP: demux failed: {e}");
return;
}
};
eprintln!(
"GPU: {} | Sample: {fname} | demux {}x{}, {} samples",
dev.name,
demuxed.info.width,
demuxed.info.height,
demuxed.samples.len()
);
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::WARN)
.with_writer(std::io::stderr)
.try_init();
let mut decoder = codec::decode::create_decoder(&demuxed.codec, demuxed.info.clone())
.expect("create_decoder must engage NVDEC streaming on NVIDIA host");
let mut frames: usize = 0;
for sample in &demuxed.samples {
decoder.push_sample(sample).expect("push_sample");
while let Some(_f) = decoder.decode_next().expect("decode_next") {
frames += 1;
}
}
decoder.finish().expect("finish");
while let Some(_f) = decoder.decode_next().expect("decode_next drain") {
frames += 1;
}
eprintln!(
"Decoded {} frames from {} samples ({label})",
frames,
demuxed.samples.len()
);
if let Some(expected) = expected_frames {
assert_eq!(
frames, expected,
"Frame count regression: got {} expected {} ({label})",
frames, expected
);
}
}
#[cfg(target_os = "linux")]
fn current_rss_bytes() -> u64 {
let Ok(s) = std::fs::read_to_string("/proc/self/status") else {
return 0;
};
for line in s.lines() {
if let Some(rest) = line.strip_prefix("VmHWM:") {
let kb: u64 = rest
.trim()
.trim_end_matches("kB")
.trim()
.parse()
.unwrap_or(0);
return kb * 1024;
}
}
0
}
#[cfg(target_os = "macos")]
fn current_rss_bytes() -> u64 {
0
}
#[cfg(target_os = "windows")]
fn current_rss_bytes() -> u64 {
#[repr(C)]
#[allow(non_snake_case)]
struct ProcessMemoryCounters {
cb: u32,
PageFaultCount: u32,
PeakWorkingSetSize: usize,
WorkingSetSize: usize,
QuotaPeakPagedPoolUsage: usize,
QuotaPagedPoolUsage: usize,
QuotaPeakNonPagedPoolUsage: usize,
QuotaNonPagedPoolUsage: usize,
PagefileUsage: usize,
PeakPagefileUsage: usize,
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetCurrentProcess() -> *mut core::ffi::c_void;
}
#[link(name = "psapi")]
unsafe extern "system" {
fn GetProcessMemoryInfo(
process: *mut core::ffi::c_void,
counters: *mut ProcessMemoryCounters,
cb: u32,
) -> i32;
}
unsafe {
let mut pmc: ProcessMemoryCounters = core::mem::zeroed();
pmc.cb = core::mem::size_of::<ProcessMemoryCounters>() as u32;
let h = GetCurrentProcess();
if GetProcessMemoryInfo(h, &mut pmc, pmc.cb) != 0 {
pmc.PeakWorkingSetSize as u64
} else {
0
}
}
}