#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "test modules may unwrap and report hardware-gated skips"
)]
use super::AutoVideoEncoder;
use crate::EncodeError;
use crate::VideoEncoder;
use crate::auto::{AutoVideoEncodeConfig, Backend, BackendSelection, EncodePathClass};
use mediaway_common::{
CodecKind, GpuDeviceHandle, NativeHandle, PixelFormat, Rational, VideoFrame, VideoFrameStorage,
};
fn tiny_h264() -> AutoVideoEncodeConfig {
AutoVideoEncodeConfig::new(CodecKind::H264, 64, 64, Rational::new(1, 30))
}
#[cfg(windows)]
#[cfg(feature = "video")]
#[test]
fn open_cpu_upload_without_device() {
let cfg = tiny_h264();
let enc = AutoVideoEncoder::open(&cfg);
assert!(
enc.is_ok(),
"cpu upload open failed: {:?}",
enc.as_ref().err()
);
if let Ok(enc) = enc {
assert_eq!(enc.path_class(), EncodePathClass::CpuUpload);
assert_eq!(enc.resolved_backend(), Backend::Os);
let info = enc.stream_info();
let geometry = info.geometry().unwrap_or(mediaway_common::VideoGeometry {
width: 0,
height: 0,
});
assert_eq!(geometry.width, 64);
assert_eq!(geometry.height, 64);
}
}
#[cfg(not(windows))]
#[cfg(feature = "video")]
#[test]
fn open_unsupported_off_windows() {
let cfg = tiny_h264();
assert!(matches!(
AutoVideoEncoder::open(&cfg),
Err(EncodeError::Unsupported)
));
}
#[test]
#[cfg(feature = "video")]
fn zero_copy_only_without_device_is_unsupported() {
let mut cfg = tiny_h264();
cfg.max_path_class = EncodePathClass::ZeroCopy;
assert!(matches!(
AutoVideoEncoder::open(&cfg),
Err(EncodeError::Unsupported)
));
}
#[test]
#[cfg(feature = "video")]
fn invalid_dimensions() {
let mut cfg = tiny_h264();
cfg.width = 0;
assert!(matches!(
AutoVideoEncoder::open(&cfg),
Err(EncodeError::InvalidInput)
));
}
#[test]
#[cfg(feature = "video")]
fn directx12_without_gpu_copy_ceiling_is_unsupported() {
let mut cfg = tiny_h264();
let fake = NativeHandle::new(0x1234).expect("nonzero fake handle");
cfg.gpu_device = Some(GpuDeviceHandle::DirectX12(fake));
cfg.max_path_class = EncodePathClass::ZeroCopy;
assert!(matches!(
AutoVideoEncoder::open(&cfg),
Err(EncodeError::Unsupported)
));
}
#[cfg(windows)]
#[cfg(feature = "video")]
#[test]
fn foreign_gpu_device_kind_falls_back_to_cpu_upload() {
let mut cfg = tiny_h264();
let fake = NativeHandle::new(0x1234).expect("nonzero fake handle");
cfg.gpu_device = Some(GpuDeviceHandle::Vulkan(fake));
let enc = AutoVideoEncoder::open(&cfg);
assert!(
enc.is_ok(),
"expected cpu-upload fallback, got {:?}",
enc.as_ref().err()
);
if let Ok(enc) = enc {
assert_eq!(enc.path_class(), EncodePathClass::CpuUpload);
assert_eq!(enc.gpu_copy_target(), None);
assert_eq!(enc.gpu_copy_dx11_frame_handle(), None);
}
}
#[test]
#[cfg(feature = "video")]
fn av1_permissive_ceiling_falls_back_to_software() {
use mediaway_common::Bytes;
let mut cfg = AutoVideoEncodeConfig::new(CodecKind::Av1, 64, 64, Rational::new(1, 30));
cfg.max_path_class = EncodePathClass::Software;
let mut enc = AutoVideoEncoder::open(&cfg).expect("software AV1 fallback should open");
assert_eq!(enc.path_class(), EncodePathClass::Software);
assert_eq!(enc.resolved_backend(), Backend::Software);
let y = vec![128u8; 64 * 64];
let uv = vec![128u8; 32 * 32];
let mut data = y;
data.extend_from_slice(&uv);
data.extend_from_slice(&uv);
let frame = VideoFrame {
pts: 0,
duration: 1,
width: 64,
height: 64,
format: PixelFormat::I420,
storage: VideoFrameStorage::Cpu {
data: Bytes::from(data),
},
};
enc.push_frame(&frame).expect("push_frame");
enc.flush().expect("flush");
while let Ok(Some(_packet)) = enc.poll_packet() {}
}
#[test]
#[cfg(feature = "video")]
fn explicit_software_bypasses_path_class_ceiling() {
let mut cfg = AutoVideoEncodeConfig::new(CodecKind::Av1, 64, 64, Rational::new(1, 30));
cfg.max_path_class = EncodePathClass::ZeroCopy;
cfg.backend = BackendSelection::Explicit(Backend::Software);
let enc = AutoVideoEncoder::open(&cfg);
assert!(
enc.is_ok(),
"Explicit(Software) should open regardless of max_path_class: {:?}",
enc.err()
);
if let Ok(enc) = enc {
assert_eq!(enc.path_class(), EncodePathClass::Software);
assert_eq!(enc.resolved_backend(), Backend::Software);
}
}
#[cfg(windows)]
#[cfg(feature = "video")]
#[test]
fn auto_hardware_only_tries_nvenc_then_quicksync_or_skip() {
let mut cfg = tiny_h264();
cfg.backend = BackendSelection::AutoHardwareOnly;
let enc = match AutoVideoEncoder::open(&cfg) {
Ok(e) => e,
Err(e) => {
eprintln!("skip: no NVENC/QuickSync GPU+driver available ({e:?})");
return;
}
};
assert_eq!(enc.path_class(), EncodePathClass::CpuUpload);
if !matches!(enc.resolved_backend(), Backend::Nvenc | Backend::QuickSync) {
eprintln!(
"skip: neither NVENC nor QuickSync, AutoHardwareOnly fell back to {:?}",
enc.resolved_backend()
);
}
}
#[test]
#[cfg(feature = "video")]
fn explicit_amf_reports_no_backend() {
let mut cfg = tiny_h264();
cfg.backend = BackendSelection::Explicit(Backend::Amf);
assert!(matches!(
AutoVideoEncoder::open(&cfg),
Err(EncodeError::NoBackend)
));
}
#[cfg(windows)]
#[cfg(feature = "video")]
#[test]
fn support_probe_lists_every_backend() {
let rows = super::support(CodecKind::H264);
let backends: Vec<Backend> = rows.iter().map(|r| r.backend).collect();
assert!(backends.contains(&Backend::Os));
assert!(backends.contains(&Backend::Nvenc));
assert!(backends.contains(&Backend::QuickSync));
assert!(backends.contains(&Backend::Amf));
assert!(backends.contains(&Backend::Software));
}