Lightningbeam/lightningbeam-ui/gpu-video-encoder/tests/zerocopy_encode.rs

77 lines
3.1 KiB
Rust

//! Capstone: encode RGBA frames fully zero-copy (GPU render → VAAPI surface → h264_vaapi)
//! and verify the output is real H.264. Skips when VAAPI is unavailable.
#![cfg(target_os = "linux")]
use gpu_video_encoder::encoder::ZeroCopyEncoder;
#[test]
fn zerocopy_encode_h264() {
let (w, h) = (640u32, 480u32);
let out = std::env::temp_dir().join("gpu_video_encoder_zerocopy.mp4");
let _ = std::fs::remove_file(&out);
let mut enc = match ZeroCopyEncoder::new(w, h, 30, 4000, &out, false) {
Ok(e) => e,
Err(e) => {
eprintln!("[zc-encode] unavailable, skipping: {e}");
return;
}
};
// Build one reusable RGBA source texture; update it per frame with a moving pattern.
let device = enc.device();
let src = device.create_texture(&wgpu::TextureDescriptor {
label: Some("rgba"),
size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let n = 30;
for f in 0..n {
let mut rgba = Vec::with_capacity((w * h * 4) as usize);
for y in 0..h {
for x in 0..w {
rgba.push(((x + f * 8) % 256) as u8);
rgba.push(((y + f * 4) % 256) as u8);
rgba.push(((x + y) % 256) as u8);
rgba.push(255);
}
}
enc.queue().write_texture(
wgpu::TexelCopyTextureInfo { texture: &src, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
&rgba,
wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(w * 4), rows_per_image: Some(h) },
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
);
enc.encode_rgba(&src).expect("encode_rgba");
}
enc.finish().expect("finish");
let meta = std::fs::metadata(&out).expect("output .mp4 missing");
eprintln!("[zc-encode] {} frames -> {} bytes mp4 at {}", n, meta.len(), out.display());
assert!(meta.len() > 1000, "implausibly small output");
// ffprobe-verify the container: H.264 stream, right dims, ~n frames.
let o = std::process::Command::new("ffprobe")
.args([
"-hide_banner", "-v", "error", "-count_frames",
"-show_entries", "stream=codec_name,width,height,nb_read_frames",
"-of", "default=noprint_wrappers=1",
])
.arg(&out)
.output()
.expect("run ffprobe");
let s = String::from_utf8_lossy(&o.stdout);
eprintln!("[zc-encode] ffprobe:\n{s}");
assert!(s.contains("codec_name=h264"), "ffprobe didn't see H.264");
assert!(s.contains(&format!("width={w}")), "wrong width");
assert!(s.contains(&format!("height={h}")), "wrong height");
assert!(s.contains(&format!("nb_read_frames={n}")), "expected {n} frames");
eprintln!("[zc-encode] ✅ zero-copy H.264 mp4 encode verified");
}