#![cfg_attr(windows, allow(unsafe_code))]
#![cfg_attr(not(windows), deny(unsafe_code))]
#[cfg(not(feature = "video"))]
compile_error!("enable the `video` feature on mediaway-decoder-windows");
use crate::DecodeError;
#[cfg(feature = "video")]
use crate::{VideoDecoder, VideoDecoderConfig};
#[cfg(feature = "video")]
use mediaway_common::VideoFrame;
use mediaway_common::{Bytes, Packet, StreamInfo};
#[cfg(all(windows, feature = "video"))]
mod d3d11_shared_decode_bridge;
#[cfg(all(windows, feature = "video"))]
pub use d3d11_shared_decode_bridge::D3d11SharedDecodeBridge;
#[cfg(all(windows, feature = "video"))]
mod wmf;
#[cfg(all(windows, feature = "video"))]
mod d3d12_video_decode;
#[cfg(windows)]
enum Backend {
H264(wmf::WmfH264Decoder),
MultiCodecCpu(wmf::WmfMultiCodecCpuDecoder),
}
#[cfg(feature = "video")]
pub struct WindowsVideoDecoder {
#[cfg(windows)]
inner: Option<Backend>,
#[cfg(not(windows))]
_priv: (),
}
#[cfg(feature = "video")]
impl WindowsVideoDecoder {
#[cfg(windows)]
pub fn open(config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
use mediaway_common::CodecKind;
let inner = match config.codec {
CodecKind::H264 => Backend::H264(wmf::WmfH264Decoder::open(config)?),
CodecKind::Hevc | CodecKind::Av1 | CodecKind::Vp9 => {
Backend::MultiCodecCpu(wmf::WmfMultiCodecCpuDecoder::open(config)?)
}
_ => return Err(DecodeError::Unsupported),
};
Ok(Self { inner: Some(inner) })
}
#[cfg(not(windows))]
#[allow(
clippy::missing_const_for_fn,
reason = "DecodeError path; non-const when MF open lands on Windows"
)]
pub fn open(_config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
Err(DecodeError::Unsupported)
}
}
#[cfg(feature = "video")]
#[cfg(windows)]
impl VideoDecoder for WindowsVideoDecoder {
fn stream_info(&self) -> &StreamInfo {
#[allow(
clippy::option_if_let_else,
reason = "map_or_else forces 'static vs 'self lifetime clash"
)]
if let Some(backend) = self.inner.as_ref() {
match backend {
Backend::H264(d) => d.stream_info(),
Backend::MultiCodecCpu(d) => d.stream_info(),
}
} else {
closed_stream_info()
}
}
fn push_packet(&mut self, packet: &Packet) -> Result<(), DecodeError> {
match self.inner.as_mut().ok_or(DecodeError::Closed)? {
Backend::H264(d) => d.push_packet(packet),
Backend::MultiCodecCpu(d) => d.push_packet(packet),
}
}
fn poll_frame(&mut self) -> Result<Option<VideoFrame>, DecodeError> {
match self.inner.as_mut().ok_or(DecodeError::Closed)? {
Backend::H264(d) => d.poll_frame(),
Backend::MultiCodecCpu(d) => d.poll_frame(),
}
}
fn flush(&mut self) -> Result<(), DecodeError> {
match self.inner.as_mut().ok_or(DecodeError::Closed)? {
Backend::H264(d) => d.flush(),
Backend::MultiCodecCpu(d) => d.flush(),
}
}
}
#[cfg(feature = "video")]
#[cfg(not(windows))]
impl VideoDecoder for WindowsVideoDecoder {
fn stream_info(&self) -> &StreamInfo {
closed_stream_info()
}
fn push_packet(&mut self, _packet: &Packet) -> Result<(), DecodeError> {
Err(DecodeError::Unsupported)
}
fn poll_frame(&mut self) -> Result<Option<VideoFrame>, DecodeError> {
Ok(None)
}
fn flush(&mut self) -> Result<(), DecodeError> {
Err(DecodeError::Unsupported)
}
}
#[cfg(feature = "video")]
fn closed_stream_info() -> &'static StreamInfo {
use std::sync::OnceLock;
static INFO: OnceLock<StreamInfo> = OnceLock::new();
INFO.get_or_init(|| StreamInfo::Video {
id: 0,
codec: mediaway_common::CodecKind::H264,
time_base: mediaway_common::Rational::new(1, 30),
geometry: mediaway_common::VideoGeometry {
width: 0,
height: 0,
},
extra_data: Bytes::new(),
})
}
#[cfg(all(test, windows, feature = "video"))]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::print_stderr,
reason = "unit tests"
)]
mod tests {
use super::*;
use crate::VideoOutputPreference;
use mediaway_common::{CodecKind, GpuDeviceHandle, NativeHandle, PixelFormat, Rational};
#[test]
fn open_dx11_zero_copy_or_skip() {
use windows::Win32::Foundation::HMODULE;
use windows::Win32::Graphics::Direct3D::D3D_DRIVER_TYPE_HARDWARE;
use windows::Win32::Graphics::Direct3D11::{
D3D11_CREATE_DEVICE_VIDEO_SUPPORT, D3D11_SDK_VERSION, D3D11CreateDevice, ID3D11Device,
};
use windows::core::Interface;
let mut device: Option<ID3D11Device> = None;
let hr = unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
HMODULE::default(),
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
None,
D3D11_SDK_VERSION,
Some(&raw mut device),
None,
None,
)
};
let Some(device) = device else {
eprintln!("skip: D3D11CreateDevice failed ({hr:?})");
return;
};
let device_handle =
NativeHandle::new(Interface::as_raw(&device) as usize).expect("device pointer");
let cfg = VideoDecoderConfig {
codec: CodecKind::H264,
width: 64,
height: 64,
time_base: Rational::new(1, 30),
pixel_format: PixelFormat::Nv12,
output: VideoOutputPreference::ZeroCopyGpu,
gpu_device: Some(GpuDeviceHandle::DirectX11(device_handle)),
extra_data: Bytes::new(),
};
let mut dec = match WindowsVideoDecoder::open(&cfg) {
Ok(d) => d,
Err(e) => {
eprintln!("skip: no HW H.264 decoder MFT / DXGI path ({e:?})");
return;
}
};
dec.flush().expect("flush without packets");
assert!(dec.poll_frame().expect("poll").is_none());
}
}