#![allow(unsafe_code)]
use std::collections::VecDeque;
use crate::{DecodeError, VideoDecoder, VideoDecoderConfig, VideoOutputPreference};
use iso_bmff::bitstream::avc::{annex_b_sequence_header, parse_avc_decoder_config};
use mediaway_common::{
Bytes, GpuBufferHandle, GpuDeviceHandle, NativeHandle, Packet, PixelFormat, StreamInfo,
VideoFrame, VideoFrameStorage, VideoGeometry,
};
use windows::Win32::Graphics::Direct3D11::ID3D11Texture2D;
use windows::Win32::Media::MediaFoundation::{
IMFSample, IMFTransform, MFT_MESSAGE_COMMAND_DRAIN, MFT_MESSAGE_NOTIFY_END_OF_STREAM,
};
use windows::core::Interface;
use super::codec::{is_supported_video_codec, video_subtype};
use super::cpu::{self, open_sw_decoder};
use super::dx11::{self, Dx11Session};
use super::runtime::from_hns;
use super::shared::{
Drain, begin_streaming, configure_decode_types, notify_end_streaming, output_buffer_size,
packet_to_sample, process_one_output, read_output_dimensions,
};
struct GpuFrameHold {
_sample: IMFSample,
texture: ID3D11Texture2D,
subresource: u32,
pts: i64,
duration: u64,
width: u32,
height: u32,
}
impl GpuFrameHold {
fn to_video_frame(&self) -> Result<VideoFrame, DecodeError> {
let texture = NativeHandle::new(Interface::as_raw(&self.texture) as usize)
.ok_or(DecodeError::Backend)?;
Ok(VideoFrame {
pts: self.pts,
duration: self.duration,
width: self.width,
height: self.height,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Gpu(GpuBufferHandle::DirectX11 {
texture,
subresource: self.subresource,
}),
})
}
}
enum PendingFrame {
Gpu(GpuFrameHold),
Cpu(VideoFrame),
}
pub(crate) struct WmfH264Decoder {
transform: IMFTransform,
info: StreamInfo,
time_base_num: u64,
time_base_den: u32,
pending: VecDeque<PendingFrame>,
released: Option<GpuFrameHold>,
flushed: bool,
dx11: Option<Dx11Session>,
output_buf_size: u32,
nal_length_size: Option<u8>,
}
impl WmfH264Decoder {
pub(crate) fn open(config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
validate_common(config)?;
ensure_mf_runtime()?;
match config.output {
VideoOutputPreference::ZeroCopyGpu => Self::open_dx11(config),
VideoOutputPreference::CpuFramesOk => Self::open_cpu(config),
}
}
fn open_dx11(config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
let Some(GpuDeviceHandle::DirectX11(handle)) = config.gpu_device else {
return Err(DecodeError::InvalidInput);
};
let input_subtype = video_subtype(config.codec)?;
let device = dx11::device_from_handle(handle)?;
let (annex_b_extra_data, nal_length_size) = resolve_annex_b_extra_data(&config.extra_data);
let (transform, session) = dx11::open_hw_decoder(
device,
config.width,
config.height,
&annex_b_extra_data,
&input_subtype,
)?;
let output_buf_size = output_buffer_size(&transform)?;
Ok(Self {
transform,
info: stream_info_from(config),
time_base_num: config.time_base.num,
time_base_den: config.time_base.den,
pending: VecDeque::new(),
released: None,
flushed: false,
dx11: Some(session),
output_buf_size,
nal_length_size,
})
}
fn open_cpu(config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
let input_subtype = video_subtype(config.codec)?;
let transform = open_sw_decoder(&input_subtype)?;
let (annex_b_extra_data, nal_length_size) = resolve_annex_b_extra_data(&config.extra_data);
configure_decode_types(
&transform,
config.width,
config.height,
&annex_b_extra_data,
&input_subtype,
)?;
begin_streaming(&transform)?;
let output_buf_size = output_buffer_size(&transform)?;
Ok(Self {
transform,
info: stream_info_from(config),
time_base_num: config.time_base.num,
time_base_den: config.time_base.den,
pending: VecDeque::new(),
released: None,
flushed: false,
dx11: None,
output_buf_size,
nal_length_size,
})
}
fn recycle_surfaces(&mut self) {
self.released = None;
}
fn push_transform_packet(&mut self, packet: &Packet) -> Result<(), DecodeError> {
if let Some(session) = self.dx11.as_mut() {
dx11::drain_events_nonblocking(session)?;
dx11::wait_need_input(session)?;
}
let sample = packet_to_sample(
packet,
self.time_base_num,
self.time_base_den,
self.nal_length_size,
)?;
unsafe { self.transform.ProcessInput(0, &sample, 0) }.map_err(|_| DecodeError::Backend)?;
if let Some(session) = self.dx11.as_mut() {
dx11::consume_need_input(session);
dx11::drain_events_nonblocking(session)?;
}
self.drain_output()?;
Ok(())
}
fn drain_output(&mut self) -> Result<(), DecodeError> {
let provides = self
.dx11
.as_ref()
.is_some_and(|s| s.output_provides_samples);
loop {
if let Some(session) = self.dx11.as_mut() {
dx11::drain_events_nonblocking(session)?;
}
match process_one_output(&self.transform, provides, self.output_buf_size)? {
Drain::Sample(sample) => {
self.adopt_output_sample(sample)?;
}
Drain::NeedMore => break,
Drain::StreamChange => self.apply_stream_change()?,
}
}
Ok(())
}
fn apply_stream_change(&mut self) -> Result<(), DecodeError> {
let (width, height) = read_output_dimensions(&self.transform)?;
if let StreamInfo::Video { geometry, .. } = &mut self.info {
*geometry = VideoGeometry { width, height };
}
let input_subtype = video_subtype(self.info.codec())?;
let (annex_b_extra_data, nal_length_size) =
resolve_annex_b_extra_data(self.info.extra_data());
configure_decode_types(
&self.transform,
width,
height,
&annex_b_extra_data,
&input_subtype,
)?;
self.nal_length_size = nal_length_size;
begin_streaming(&self.transform)?;
self.output_buf_size = output_buffer_size(&self.transform)?;
Ok(())
}
fn adopt_output_sample(&mut self, sample: IMFSample) -> Result<(), DecodeError> {
let pts_hns = unsafe { sample.GetSampleTime() }.unwrap_or(0);
let dur_hns = unsafe { sample.GetSampleDuration() }.unwrap_or(0);
let pts = from_hns(pts_hns, self.time_base_num, self.time_base_den);
let duration =
u64::try_from(from_hns(dur_hns, self.time_base_num, self.time_base_den).max(0))
.unwrap_or(0);
let geometry = self.info.geometry().unwrap_or(VideoGeometry {
width: 0,
height: 0,
});
let width = geometry.width;
let height = geometry.height;
if self.dx11.is_some() {
let (texture, subresource) = dx11::texture_from_output_sample(&sample)?;
self.pending.push_back(PendingFrame::Gpu(GpuFrameHold {
_sample: sample,
texture,
subresource,
pts,
duration,
width,
height,
}));
} else {
let data = cpu::nv12_bytes_from_output_sample(&sample, width, height)?;
self.pending.push_back(PendingFrame::Cpu(VideoFrame {
pts,
duration,
width,
height,
format: PixelFormat::Nv12,
storage: VideoFrameStorage::Cpu { data },
}));
}
Ok(())
}
}
impl VideoDecoder for WmfH264Decoder {
fn stream_info(&self) -> &StreamInfo {
&self.info
}
fn push_packet(&mut self, packet: &Packet) -> Result<(), DecodeError> {
if self.flushed {
return Err(DecodeError::Closed);
}
self.recycle_surfaces();
if packet.is_discard {
return Ok(());
}
self.push_transform_packet(packet)
}
fn poll_frame(&mut self) -> Result<Option<VideoFrame>, DecodeError> {
if self.pending.is_empty() {
self.drain_output()?;
}
let Some(pending) = self.pending.pop_front() else {
return Ok(None);
};
self.recycle_surfaces();
match pending {
PendingFrame::Gpu(hold) => {
let frame = hold.to_video_frame()?;
self.released = Some(hold);
Ok(Some(frame))
}
PendingFrame::Cpu(frame) => Ok(Some(frame)),
}
}
fn flush(&mut self) -> Result<(), DecodeError> {
if self.flushed {
return Ok(());
}
self.recycle_surfaces();
self.flushed = true;
unsafe {
self.transform
.ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0)
.map_err(|_| DecodeError::Backend)?;
self.transform
.ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0)
.map_err(|_| DecodeError::Backend)?;
}
self.drain_output()?;
notify_end_streaming(&self.transform);
Ok(())
}
}
fn validate_common(config: &VideoDecoderConfig) -> Result<(), DecodeError> {
if !is_supported_video_codec(config.codec) {
return Err(DecodeError::Unsupported);
}
if config.pixel_format != PixelFormat::Nv12 {
return Err(DecodeError::Unsupported);
}
if config.time_base.den == 0 {
return Err(DecodeError::InvalidInput);
}
Ok(())
}
#[allow(clippy::missing_const_for_fn, reason = "StreamInfo holds Bytes")]
fn stream_info_from(config: &VideoDecoderConfig) -> StreamInfo {
StreamInfo::Video {
id: 0,
codec: config.codec,
time_base: config.time_base,
geometry: VideoGeometry {
width: config.width,
height: config.height,
},
extra_data: config.extra_data.clone(), }
}
fn ensure_mf_runtime() -> Result<(), DecodeError> {
super::runtime::ensure_mf()
}
fn resolve_annex_b_extra_data(extra_data: &Bytes) -> (Bytes, Option<u8>) {
match parse_avc_decoder_config(extra_data) {
Some(config) => {
let nal_length_size = config.nal_length_size;
(annex_b_sequence_header(&config), Some(nal_length_size))
}
None => (extra_data.clone(), None), }
}