#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "unit/integration tests"
)]
use super::{D3d12VideoDecoder, DecodedOutput};
use crate::{VideoDecoderConfig, VideoOutputPreference};
use mediaway_common::{
Bytes, CodecKind, GpuDeviceHandle, NativeHandle, PixelFormat, Rational, VideoFrame,
VideoFrameStorage,
};
use mediaway_encoder::windows::WindowsVideoEncoder;
use mediaway_encoder::{VideoEncoder, VideoEncoderConfig, VideoInputPreference};
const WIDTH: u32 = 352;
const HEIGHT: u32 = 288;
fn gradient_nv12_frame(frame_index: u32, pts: i64) -> VideoFrame {
let mut data =
vec![0u8; WIDTH as usize * HEIGHT as usize + WIDTH as usize * HEIGHT as usize / 2];
for y in 0..HEIGHT as usize {
for x in 0..WIDTH as usize {
let value = u8::try_from((x + y + frame_index as usize * 7) % 256).unwrap_or(0);
data[y * WIDTH as usize + x] = value;
}
}
let chroma_base = WIDTH as usize * HEIGHT as usize;
for i in 0..(chroma_base / 2) {
data[chroma_base + i] = 128;
}
VideoFrame {
pts,
duration: 1,
width: WIDTH,
height: HEIGHT,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu {
data: Bytes::from(data),
},
}
}
fn create_d3d12_device() -> Option<windows::Win32::Graphics::Direct3D12::ID3D12Device> {
use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0;
use windows::Win32::Graphics::Direct3D12::{
D3D12CreateDevice, D3D12GetDebugInterface, ID3D12Debug, ID3D12Device,
};
use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1};
let mut debug: Option<ID3D12Debug> = None;
if unsafe { D3D12GetDebugInterface(&raw mut debug) }.is_ok() {
if let Some(debug) = debug {
unsafe { debug.EnableDebugLayer() };
}
}
let factory: IDXGIFactory1 = match unsafe { CreateDXGIFactory1() } {
Ok(f) => f,
Err(e) => {
eprintln!("skip: CreateDXGIFactory1 ({e:?})");
return None;
}
};
let adapter: IDXGIAdapter1 = match unsafe { factory.EnumAdapters1(0) } {
Ok(a) => a,
Err(e) => {
eprintln!("skip: EnumAdapters1 ({e:?})");
return None;
}
};
let mut device: Option<ID3D12Device> = None;
if unsafe { D3D12CreateDevice(&adapter, D3D_FEATURE_LEVEL_11_0, &raw mut device) }.is_err() {
eprintln!("skip: D3D12CreateDevice failed");
return None;
}
device
}
fn dump_d3d12_info_queue(iq: Option<&windows::Win32::Graphics::Direct3D12::ID3D12InfoQueue>) {
use windows::Win32::Graphics::Direct3D12::D3D12_MESSAGE;
let Some(iq) = iq else { return };
let n = unsafe { iq.GetNumStoredMessages() };
for i in 0..n {
let mut len = 0usize;
if unsafe { iq.GetMessage(i, None, &raw mut len) }.is_err() || len == 0 {
continue;
}
let mut buf: Vec<u64> = vec![0; len.div_ceil(8)];
let msg_ptr = buf.as_mut_ptr().cast::<D3D12_MESSAGE>();
if unsafe { iq.GetMessage(i, Some(msg_ptr), &raw mut len) }.is_ok() {
let msg = unsafe { &*msg_ptr };
let desc = unsafe {
std::slice::from_raw_parts(
msg.pDescription,
msg.DescriptionByteLength.saturating_sub(1),
)
};
eprintln!("D3D12 InfoQueue[{i}]: {}", String::from_utf8_lossy(desc));
}
}
unsafe { iq.ClearStoredMessages() };
}
#[test]
#[allow(
clippy::too_many_lines,
reason = "one linear encode-then-decode-then-assert integration sequence; splitting fragments it"
)]
fn h264_decode_idr_and_p_frame_or_skip() {
use windows::core::Interface;
let Some(device) = create_d3d12_device() else {
return;
};
let Some(device_handle) = NativeHandle::new(Interface::as_raw(&device) as usize) else {
eprintln!("skip: null D3D12 device pointer");
return;
};
let info_queue: Option<windows::Win32::Graphics::Direct3D12::ID3D12InfoQueue> =
device.cast().ok();
let enc_cfg = VideoEncoderConfig {
codec: CodecKind::H264,
width: WIDTH,
height: HEIGHT,
time_base: Rational::new(1, 30),
bitrate_bps: 500_000,
pixel_format: PixelFormat::Nv12,
input: VideoInputPreference::CpuUploadOk,
gpu_device: None,
};
let mut encoder = match WindowsVideoEncoder::open(&enc_cfg) {
Ok(e) => e,
Err(e) => {
eprintln!("skip: WindowsVideoEncoder::open failed ({e:?}) — MF unavailable?");
return;
}
};
let mut packets = Vec::new();
for i in 0..8u32 {
let frame = gradient_nv12_frame(i, i64::from(i));
if let Err(e) = encoder.push_frame(&frame) {
eprintln!("skip: encoder push_frame failed ({e:?})");
return;
}
while let Ok(Some(p)) = encoder.poll_packet() {
packets.push(p);
}
}
let _ = encoder.flush();
while let Ok(Some(p)) = encoder.poll_packet() {
packets.push(p);
}
if packets.is_empty() {
eprintln!("skip: encoder produced no packets");
return;
}
let has_non_keyframe = packets.iter().any(|p| !p.is_keyframe);
eprintln!(
"encoded {} packet(s), has_non_keyframe={has_non_keyframe}",
packets.len()
);
let dec_cfg = VideoDecoderConfig {
codec: CodecKind::H264,
width: WIDTH,
height: HEIGHT,
time_base: Rational::new(1, 30),
pixel_format: PixelFormat::Nv12,
output: VideoOutputPreference::CpuFramesOk,
gpu_device: Some(GpuDeviceHandle::DirectX12(device_handle)),
extra_data: Bytes::new(),
};
let mut decoder = match D3d12VideoDecoder::open(&dec_cfg) {
Ok(d) => d,
Err(e) => {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!("skip: D3d12VideoDecoder::open failed ({e:?})");
return;
}
};
let mut decoded_frames = Vec::new();
for (packet_index, packet) in packets.iter().enumerate() {
match decoder.push_packet(packet) {
Ok(()) => {}
Err(e) => {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!(
"skip: push_packet failed on packet {packet_index} ({e:?}, \
is_keyframe={}) — see D3D12 InfoQueue messages above, if any",
packet.is_keyframe
);
return;
}
}
dump_d3d12_info_queue(info_queue.as_ref());
while let Ok(Some(frame)) = decoder.poll_frame() {
decoded_frames.push(frame);
}
}
let _ = decoder.flush();
while let Ok(Some(frame)) = decoder.poll_frame() {
decoded_frames.push(frame);
}
assert!(
!decoded_frames.is_empty(),
"expected at least one decoded frame"
);
let mut any_real_variance = false;
for frame in &decoded_frames {
if let DecodedOutput::Cpu { data } = &frame.output {
assert_eq!(data.len(), (WIDTH * HEIGHT + WIDTH * HEIGHT / 2) as usize);
let first = data[0];
if data.iter().any(|&b| b != first) {
any_real_variance = true;
}
}
}
assert!(
any_real_variance,
"expected genuine pixel variance in at least one decoded frame"
);
eprintln!(
"decoded {} frame(s) from {} packet(s) (has_non_keyframe={has_non_keyframe})",
decoded_frames.len(),
packets.len()
);
}