77 lines
3.1 KiB
Rust
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) {
|
|
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");
|
|
}
|