#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "unit tests"
)]
use crate::{VideoEncoder, VideoEncoderConfig, VideoInputPreference};
use mediaway_common::{
Bytes, CodecKind, GpuDeviceHandle, NativeHandle, PixelFormat, Rational, VideoFrame,
VideoFrameStorage,
};
use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0;
use windows::Win32::Graphics::Direct3D12::{
D3D12_MESSAGE, D3D12CreateDevice, D3D12GetDebugInterface, ID3D12Debug, ID3D12Device,
ID3D12InfoQueue,
};
use windows::Win32::Graphics::Dxgi::{CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1};
use windows::core::Interface;
use super::D3d12VideoEncoder;
const WIDTH: u32 = 176;
const HEIGHT: u32 = 144;
const WIDTH_HEVC: u32 = 256;
const HEIGHT_HEVC: u32 = 192;
const WIDTH_AV1: u32 = WIDTH_HEVC;
const HEIGHT_AV1: u32 = HEIGHT_HEVC;
fn nal_unit_types(payload: &[u8]) -> Vec<u8> {
let mut types = Vec::new();
let mut i = 0usize;
while i + 3 < payload.len() {
let is_start_code_3 = payload[i] == 0 && payload[i + 1] == 0 && payload[i + 2] == 1;
let is_start_code_4 = i + 4 < payload.len()
&& payload[i] == 0
&& payload[i + 1] == 0
&& payload[i + 2] == 0
&& payload[i + 3] == 1;
if is_start_code_4 {
types.push(payload[i + 4] & 0x1F);
i += 5;
} else if is_start_code_3 {
types.push(payload[i + 3] & 0x1F);
i += 4;
} else {
i += 1;
}
}
types
}
fn nal_unit_types_hevc(payload: &[u8]) -> Vec<u8> {
let mut types = Vec::new();
let mut i = 0usize;
while i + 3 < payload.len() {
let is_start_code_3 = payload[i] == 0 && payload[i + 1] == 0 && payload[i + 2] == 1;
let is_start_code_4 = i + 4 < payload.len()
&& payload[i] == 0
&& payload[i + 1] == 0
&& payload[i + 2] == 0
&& payload[i + 3] == 1;
if is_start_code_4 && i + 5 < payload.len() {
types.push((payload[i + 4] >> 1) & 0x3F);
i += 6;
} else if is_start_code_3 && i + 4 < payload.len() {
types.push((payload[i + 3] >> 1) & 0x3F);
i += 5;
} else {
i += 1;
}
}
types
}
fn nv12_frame(pts: i64) -> VideoFrame {
nv12_frame_sized(pts, WIDTH, HEIGHT)
}
fn nv12_frame_sized(pts: i64, width: u32, height: u32) -> VideoFrame {
let len = (width as usize) * (height as usize) + (width as usize) * (height as usize) / 2;
let data = vec![128u8; len];
VideoFrame {
pts,
duration: 1,
width,
height,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu {
data: Bytes::from(data),
},
}
}
fn open_real_d3d12_device() -> Option<ID3D12Device> {
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;
}
if device.is_none() {
eprintln!("skip: null D3D12 device");
}
device
}
fn dump_d3d12_info_queue(iq: Option<&ID3D12InfoQueue>) {
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]
fn d3d12_native_h264_encode_or_skip() {
let Some(device) = open_real_d3d12_device() else {
return;
};
let Some(handle) = NativeHandle::new(Interface::as_raw(&device) as usize) else {
eprintln!("skip: null D3D12 device pointer");
return;
};
let info_queue: Option<ID3D12InfoQueue> = device.cast().ok();
let 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: Some(GpuDeviceHandle::DirectX12(handle)),
};
let mut enc = match D3d12VideoEncoder::open(&cfg) {
Ok(e) => e,
Err(e) => {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!(
"skip: D3d12VideoEncoder::open failed ({e:?}) — no D3D12 H.264 video-encode \
support on this device/driver?"
);
return;
}
};
let mut packets = 0usize;
for i in 0..3i64 {
let frame = nv12_frame(i);
if let Err(e) = enc.push_frame(&frame) {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!("skip: push_frame failed ({e:?})");
return;
}
let packet = match enc.poll_packet() {
Ok(Some(p)) => p,
Ok(None) => {
eprintln!("skip: no packet after push_frame {i}");
return;
}
Err(e) => {
eprintln!("skip: poll_packet failed ({e:?})");
return;
}
};
assert!(!packet.payload.is_empty(), "packet {i} payload is empty");
assert!(packet.is_keyframe, "packet {i} should be an IDR keyframe");
let types = nal_unit_types(&packet.payload);
assert!(
types.contains(&7),
"packet {i} missing SPS NAL (type 7); found types {types:?}"
);
assert!(
types.contains(&5),
"packet {i} missing IDR slice NAL (type 5); found types {types:?}"
);
packets += 1;
}
enc.flush().expect("flush");
eprintln!("d3d12 native h264 encode ok: {packets} packets, all with real SPS+IDR Annex-B NALs");
}
#[test]
fn d3d12_native_hevc_encode_or_skip() {
let Some(device) = open_real_d3d12_device() else {
return;
};
let Some(handle) = NativeHandle::new(Interface::as_raw(&device) as usize) else {
eprintln!("skip: null D3D12 device pointer");
return;
};
let info_queue: Option<ID3D12InfoQueue> = device.cast().ok();
let cfg = VideoEncoderConfig {
codec: CodecKind::Hevc,
width: WIDTH_HEVC,
height: HEIGHT_HEVC,
time_base: Rational::new(1, 30),
bitrate_bps: 500_000,
pixel_format: PixelFormat::Nv12,
input: VideoInputPreference::CpuUploadOk,
gpu_device: Some(GpuDeviceHandle::DirectX12(handle)),
};
let mut enc = match D3d12VideoEncoder::open(&cfg) {
Ok(e) => e,
Err(e) => {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!(
"skip: D3d12VideoEncoder::open (HEVC) failed ({e:?}) — no D3D12 HEVC video-encode \
support on this device/driver?"
);
return;
}
};
let mut packets = 0usize;
for i in 0..3i64 {
let frame = nv12_frame_sized(i, WIDTH_HEVC, HEIGHT_HEVC);
if let Err(e) = enc.push_frame(&frame) {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!("skip: push_frame (HEVC) failed ({e:?})");
return;
}
let packet = match enc.poll_packet() {
Ok(Some(p)) => p,
Ok(None) => {
eprintln!("skip: no packet after push_frame {i} (HEVC)");
return;
}
Err(e) => {
eprintln!("skip: poll_packet (HEVC) failed ({e:?})");
return;
}
};
assert!(!packet.payload.is_empty(), "packet {i} payload is empty");
assert!(packet.is_keyframe, "packet {i} should be an IDR keyframe");
let types = nal_unit_types_hevc(&packet.payload);
assert!(
types.contains(&32),
"packet {i} missing VPS NAL (type 32); found types {types:?}"
);
assert!(
types.contains(&33),
"packet {i} missing SPS NAL (type 33); found types {types:?}"
);
assert!(
types.contains(&34),
"packet {i} missing PPS NAL (type 34); found types {types:?}"
);
assert!(
types.contains(&19) || types.contains(&20),
"packet {i} missing IDR slice NAL (type 19/20); found types {types:?}"
);
packets += 1;
}
enc.flush().expect("flush");
eprintln!(
"d3d12 native hevc encode ok: {packets} packets, all with real VPS+SPS+PPS+IDR Annex-B NALs"
);
}
fn obu_types(payload: &[u8]) -> Vec<u8> {
let mut types = Vec::new();
let mut i = 0usize;
while i < payload.len() {
let header = payload[i];
let obu_type = (header >> 3) & 0x0F;
let has_size_field = (header >> 1) & 1 == 1;
let has_extension = (header >> 2) & 1 == 1;
i += 1;
if has_extension {
i += 1;
}
if !has_size_field || i >= payload.len() {
break;
}
let mut size = 0u64;
let mut shift = 0u32;
loop {
if i >= payload.len() {
return types;
}
let b = payload[i];
i += 1;
size |= u64::from(b & 0x7f) << shift;
shift += 7;
if b & 0x80 == 0 {
break;
}
}
types.push(obu_type);
let Ok(size) = usize::try_from(size) else {
break;
};
i += size;
}
types
}
#[test]
fn d3d12_native_av1_encode_or_skip() {
let Some(device) = open_real_d3d12_device() else {
return;
};
let Some(handle) = NativeHandle::new(Interface::as_raw(&device) as usize) else {
eprintln!("skip: null D3D12 device pointer");
return;
};
let info_queue: Option<ID3D12InfoQueue> = device.cast().ok();
let cfg = VideoEncoderConfig {
codec: CodecKind::Av1,
width: WIDTH_AV1,
height: HEIGHT_AV1,
time_base: Rational::new(1, 30),
bitrate_bps: 500_000,
pixel_format: PixelFormat::Nv12,
input: VideoInputPreference::CpuUploadOk,
gpu_device: Some(GpuDeviceHandle::DirectX12(handle)),
};
let mut enc = match D3d12VideoEncoder::open(&cfg) {
Ok(e) => e,
Err(e) => {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!(
"skip: D3d12VideoEncoder::open (AV1) failed ({e:?}) — no D3D12 AV1 video-encode \
support on this device/driver?"
);
return;
}
};
let mut packets = 0usize;
for i in 0..3i64 {
let frame = nv12_frame_sized(i, WIDTH_AV1, HEIGHT_AV1);
if let Err(e) = enc.push_frame(&frame) {
dump_d3d12_info_queue(info_queue.as_ref());
eprintln!("skip: push_frame (AV1) failed ({e:?})");
return;
}
let packet = match enc.poll_packet() {
Ok(Some(p)) => p,
Ok(None) => {
eprintln!("skip: no packet after push_frame {i} (AV1)");
return;
}
Err(e) => {
eprintln!("skip: poll_packet (AV1) failed ({e:?})");
return;
}
};
assert!(!packet.payload.is_empty(), "packet {i} payload is empty");
assert!(packet.is_keyframe, "packet {i} should be a key frame");
let types = obu_types(&packet.payload);
assert!(
types.contains(&2),
"packet {i} missing temporal delimiter OBU (type 2); found types {types:?}"
);
assert!(
types.contains(&1),
"packet {i} missing sequence header OBU (type 1); found types {types:?}"
);
assert!(
types.contains(&6),
"packet {i} missing frame OBU (type 6); found types {types:?}"
);
packets += 1;
}
enc.flush().expect("flush");
eprintln!("d3d12 native av1 encode ok: {packets} packets, all with real TD+SeqHdr+Frame OBUs");
}
#[test]
fn d3d12_av1_encode_codec_probe() {
use windows::Win32::Media::MediaFoundation::{
D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC, D3D12_FEATURE_VIDEO_ENCODER_CODEC,
D3D12_VIDEO_ENCODER_CODEC_AV1, ID3D12VideoDevice3,
};
use windows::core::BOOL;
let Some(device) = open_real_d3d12_device() else {
return;
};
let Ok(video_device) = device.cast::<ID3D12VideoDevice3>() else {
eprintln!("skip: no ID3D12VideoDevice3 on this device");
return;
};
let mut support = D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC {
NodeIndex: 0,
Codec: D3D12_VIDEO_ENCODER_CODEC_AV1,
IsSupported: BOOL::default(),
};
let hr = unsafe {
video_device.CheckFeatureSupport(
D3D12_FEATURE_VIDEO_ENCODER_CODEC,
std::ptr::from_mut(&mut support).cast(),
u32::try_from(std::mem::size_of::<D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC>())
.unwrap_or(u32::MAX),
)
};
match hr {
Ok(()) => eprintln!(
"d3d12 av1 encode codec probe: IsSupported={}",
support.IsSupported.as_bool()
),
Err(e) => eprintln!("d3d12 av1 encode codec probe: CheckFeatureSupport failed ({e:?})"),
}
}