#![cfg(test)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "unit tests"
)]
use super::*;
use mediaway_common::{Bytes, CodecKind, Rational, VideoFrameStorage};
use mediaway_encoder::windows::WindowsVideoEncoder;
use mediaway_encoder::{VideoEncoder, VideoEncoderConfig, VideoInputPreference};
use windows::Win32::Media::MediaFoundation::{
IMFActivate, MFMediaType_Video, MFT_CATEGORY_VIDEO_DECODER, MFT_ENUM_FLAG,
MFT_ENUM_FLAG_SORTANDFILTER, MFT_ENUM_FLAG_SYNCMFT, MFT_FRIENDLY_NAME_Attribute,
MFT_REGISTER_TYPE_INFO, MFTEnumEx, MFVideoFormat_AV1, MFVideoFormat_HEVC, MFVideoFormat_NV12,
MFVideoFormat_VP90,
};
use windows::Win32::System::Com::CoTaskMemFree;
use windows::core::PWSTR;
const WIDTH: u32 = 64;
const HEIGHT: u32 = 64;
#[test]
fn list_decoder_mfts_for_each_codec() {
super::super::runtime::ensure_mf().expect("MF runtime init");
for (name, subtype) in [
("HEVC", MFVideoFormat_HEVC),
("AV1", MFVideoFormat_AV1),
("VP9", MFVideoFormat_VP90),
] {
let all = enum_decoder_mft_names(subtype, false);
let sync = enum_decoder_mft_names(subtype, true);
eprintln!("{name}: any-flag decoder MFTs = {all:?}");
eprintln!("{name}: MFT_ENUM_FLAG_SYNCMFT decoder MFTs = {sync:?}");
}
}
fn enum_decoder_mft_names(subtype: windows::core::GUID, sync_only: bool) -> Vec<String> {
let input = MFT_REGISTER_TYPE_INFO {
guidMajorType: MFMediaType_Video,
guidSubtype: subtype,
};
let output = MFT_REGISTER_TYPE_INFO {
guidMajorType: MFMediaType_Video,
guidSubtype: MFVideoFormat_NV12,
};
let flags = if sync_only {
MFT_ENUM_FLAG(MFT_ENUM_FLAG_SYNCMFT.0 | MFT_ENUM_FLAG_SORTANDFILTER.0)
} else {
MFT_ENUM_FLAG_SORTANDFILTER
};
let mut activates: *mut Option<IMFActivate> = std::ptr::null_mut();
let mut count = 0u32;
let hr = unsafe {
MFTEnumEx(
MFT_CATEGORY_VIDEO_DECODER,
flags,
Some(std::ptr::from_ref(&input)),
Some(std::ptr::from_ref(&output)),
&raw mut activates,
&raw mut count,
)
};
if hr.is_err() || activates.is_null() {
return Vec::new();
}
let mut names = Vec::new();
for i in 0..count as usize {
let activate = unsafe { (*activates.add(i)).take() };
if let Some(activate) = activate {
names.push(friendly_name(&activate).unwrap_or_else(|| "<unnamed>".to_owned()));
}
}
unsafe {
CoTaskMemFree(Some(activates.cast_const().cast()));
}
names
}
fn friendly_name(activate: &IMFActivate) -> Option<String> {
let mut raw = PWSTR::null();
let mut len = 0u32;
unsafe {
activate.GetAllocatedString(&MFT_FRIENDLY_NAME_Attribute, &raw mut raw, &raw mut len)
}
.ok()?;
if raw.is_null() {
return None;
}
let name = unsafe { raw.to_string() }.ok();
unsafe {
CoTaskMemFree(Some(raw.0.cast()));
}
name
}
fn gradient_nv12(width: u32, height: u32) -> Bytes {
let w = width as usize;
let h = height as usize;
let mut out = vec![0u8; w * h + w * (h / 2)];
for y in 0..h {
for x in 0..w {
out[y * w + x] = u8::try_from((x * 255) / w.max(1)).unwrap_or(u8::MAX);
}
}
for i in 0..(w * (h / 2)) {
out[w * h + i] = 128;
}
Bytes::from(out)
}
fn has_variance(data: &[u8]) -> bool {
data.iter().any(|&b| b != data[0])
}
fn ffmpeg_av1_ivf(width: u32, height: u32) -> Option<Vec<u8>> {
let output = std::process::Command::new("ffmpeg")
.args([
"-y",
"-v",
"error",
"-f",
"lavfi",
"-i",
&format!("testsrc=size={width}x{height}:rate=30:d=1"),
"-c:v",
"libaom-av1",
"-frames:v",
"1",
"-strict",
"experimental",
"-f",
"ivf",
"pipe:1",
])
.output()
.ok()?;
if !output.status.success() || output.stdout.is_empty() {
return None;
}
Some(output.stdout)
}
fn parse_ivf_frame_payloads(data: &[u8]) -> Vec<Vec<u8>> {
if data.len() < 32 || &data[0..4] != b"DKIF" {
return Vec::new();
}
let mut frames = Vec::new();
let mut off = 32usize;
while off + 12 <= data.len() {
let size =
u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
off += 12;
if off + size > data.len() {
break;
}
frames.push(data[off..off + size].to_vec());
off += size;
}
frames
}
#[test]
fn decode_real_ffmpeg_av1_bitstream_or_skip() {
let Some(ivf) = ffmpeg_av1_ivf(WIDTH, HEIGHT) else {
eprintln!("skip: system ffmpeg (libaom-av1) unavailable — optional oracle, see ADR-0002");
return;
};
let payloads = parse_ivf_frame_payloads(&ivf);
assert!(
!payloads.is_empty(),
"ffmpeg produced an IVF file with zero frames"
);
let dec_cfg = VideoDecoderConfig {
codec: CodecKind::Av1,
width: WIDTH,
height: HEIGHT,
time_base: Rational::new(1, 30),
pixel_format: PixelFormat::Nv12,
output: VideoOutputPreference::CpuFramesOk,
gpu_device: None,
extra_data: Bytes::new(),
};
let mut decoder = match WmfMultiCodecCpuDecoder::open(&dec_cfg) {
Ok(d) => d,
Err(e) => {
eprintln!(
"skip: no CPU AV1 decoder MFT ({e:?}) — negative finding, see docs/roadmap.md"
);
return;
}
};
for (i, payload) in payloads.iter().enumerate() {
let pts = i64::try_from(i).unwrap_or(0);
let packet = Packet {
stream_id: 0,
pts,
dts: pts,
duration: 1,
is_keyframe: i == 0,
is_discard: false,
payload: Bytes::from(payload.clone()),
};
if let Err(e) = decoder.push_packet(&packet) {
eprintln!(
"skip: decoder push_packet on real ffmpeg AV1 data ({e:?}) — see \
docs/roadmap.md for the AYUV-not-NV12 negotiation finding"
);
return;
}
}
if let Err(e) = decoder.flush() {
eprintln!(
"skip: decoder flush on real ffmpeg AV1 data ({e:?}) — see docs/roadmap.md for \
the AYUV-not-NV12 negotiation finding"
);
return;
}
let mut frames_out = Vec::new();
while let Some(f) = decoder.poll_frame().expect("decoder poll_frame") {
frames_out.push(f);
}
assert!(
!frames_out.is_empty(),
"expected at least one decoded frame from a real ffmpeg-encoded AV1 stream"
);
for f in &frames_out {
assert_eq!(f.width, WIDTH);
assert_eq!(f.height, HEIGHT);
let VideoFrameStorage::Cpu { data } = &f.storage else {
unreachable!("expected VideoFrameStorage::Cpu");
};
assert!(
has_variance(&data[..(WIDTH * HEIGHT) as usize]),
"decoded luma plane has no variance — testsrc pattern should decode to real \
varying content, not a zeroed/garbage buffer"
);
}
eprintln!(
"AV1 real ffmpeg round trip OK — {} packet(s) in, {} frame(s) out",
payloads.len(),
frames_out.len()
);
}
fn encode_one_codec_cpu(codec: CodecKind) -> Option<(Vec<Packet>, Bytes)> {
let enc_cfg = VideoEncoderConfig {
codec,
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 {codec:?}: no CPU encoder MFT ({e:?})");
return None;
}
};
let frame = VideoFrame {
pts: 0,
duration: 1,
width: WIDTH,
height: HEIGHT,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu {
data: gradient_nv12(WIDTH, HEIGHT),
},
};
if let Err(e) = encoder.push_frame(&frame) {
eprintln!("skip {codec:?}: encoder push_frame failed ({e:?})");
return None;
}
encoder.flush().expect("encoder flush");
let mut packets = Vec::new();
while let Some(p) = encoder.poll_packet().expect("encoder poll_packet") {
packets.push(p);
}
if packets.is_empty() {
eprintln!("skip {codec:?}: encoder produced zero packets");
return None;
}
let extra_data = encoder.stream_info().extra_data().clone(); Some((packets, extra_data))
}
fn decode_and_verify_cpu(codec: CodecKind, packets: &[Packet], extra_data: Bytes) {
let nv12_len = (WIDTH * HEIGHT + WIDTH * HEIGHT / 2) as usize;
let dec_cfg = VideoDecoderConfig {
codec,
width: WIDTH,
height: HEIGHT,
time_base: Rational::new(1, 30),
pixel_format: PixelFormat::Nv12,
output: VideoOutputPreference::CpuFramesOk,
gpu_device: None,
extra_data,
};
let mut decoder = match WmfMultiCodecCpuDecoder::open(&dec_cfg) {
Ok(d) => d,
Err(e) => {
eprintln!(
"skip {codec:?}: no CPU decoder MFT ({e:?}) — negative finding, see docs/roadmap.md"
);
return;
}
};
for packet in packets {
if let Err(e) = decoder.push_packet(packet) {
eprintln!("skip {codec:?}: decoder push_packet failed ({e:?})");
return;
}
}
if let Err(e) = decoder.flush() {
eprintln!("skip {codec:?}: decoder flush failed ({e:?})");
return;
}
let mut frames_out = Vec::new();
loop {
match decoder.poll_frame() {
Ok(Some(f)) => frames_out.push(f),
Ok(None) => break,
Err(e) => {
eprintln!("skip {codec:?}: decoder poll_frame failed ({e:?})");
break;
}
}
}
if frames_out.is_empty() {
eprintln!("skip {codec:?}: decoder produced zero frames");
return;
}
for f in &frames_out {
assert_eq!(f.width, WIDTH, "{codec:?} decoded width");
assert_eq!(f.height, HEIGHT, "{codec:?} decoded height");
let VideoFrameStorage::Cpu { data } = &f.storage else {
unreachable!("{codec:?}: expected VideoFrameStorage::Cpu");
};
assert!(
data.len() >= nv12_len,
"{codec:?}: decoded buffer too small ({} < {nv12_len})",
data.len()
);
assert!(
has_variance(&data[..(WIDTH * HEIGHT) as usize]),
"{codec:?}: decoded luma plane has no variance — looks like a zeroed/garbage \
buffer, not a real decoded gradient frame"
);
}
eprintln!(
"{codec:?}: real CPU decode OK — {} packet(s) in, {} frame(s) out, first frame \
luma[0..8]={:?}",
packets.len(),
frames_out.len(),
match &frames_out[0].storage {
VideoFrameStorage::Cpu { data } => &data[..8.min(data.len())],
_ => &[],
}
);
}
#[test]
fn encode_then_decode_hevc_av1_vp9_cpu_round_trip_or_skip() {
for codec in [CodecKind::Hevc, CodecKind::Av1, CodecKind::Vp9] {
let Some((packets, extra_data)) = encode_one_codec_cpu(codec) else {
continue;
};
decode_and_verify_cpu(codec, &packets, extra_data);
}
}