use std::collections::VecDeque;
use crate::{DecodeError, VideoDecoderConfig, VideoOutputPreference};
use mediaway_common::{Bytes, CodecKind, GpuDeviceHandle, NativeHandle, PixelFormat};
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Graphics::Direct3D12::{
D3D12_COMMAND_LIST_TYPE_COPY, D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE, D3D12_FENCE_FLAG_NONE,
D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_VIDEO_DECODE_READ, ID3D12CommandAllocator, ID3D12CommandQueue,
ID3D12Device, ID3D12Device4, ID3D12Fence, ID3D12GraphicsCommandList, ID3D12Resource,
};
use windows::Win32::Media::MediaFoundation::{
D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_HEIGHT_ALIGNMENT_MULTIPLE_32_REQUIRED,
ID3D12VideoDecodeCommandList1, ID3D12VideoDecoder, ID3D12VideoDecoderHeap, ID3D12VideoDevice,
};
use windows::Win32::System::Threading::CreateEventW;
use windows::core::Interface;
use super::CALLER_HEADROOM;
use super::av1_obu::{ObuType, split_obus};
use super::av1_sequence_header::{SequenceHeader, parse_sequence_header};
use super::dpb::DpbPool;
use super::{av1_frame_header, av1_pic_params, setup, util};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(super) struct Av1RefMeta;
#[derive(Debug)]
pub(crate) enum DecodedOutputAv1 {
Gpu {
resource: NativeHandle,
subresource: u32,
},
Cpu { data: Bytes },
}
#[derive(Debug)]
pub(crate) struct DecodedFrameAv1 {
pub(crate) pts: i64,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) output: DecodedOutputAv1,
}
pub(super) struct SessionAv1 {
pub(super) decoder: ID3D12VideoDecoder,
pub(super) decoder_heap: ID3D12VideoDecoderHeap,
pub(super) decode_queue: ID3D12CommandQueue,
pub(super) decode_allocator: ID3D12CommandAllocator,
pub(super) decode_list: ID3D12VideoDecodeCommandList1,
pub(super) copy_queue: ID3D12CommandQueue,
pub(super) copy_allocator: ID3D12CommandAllocator,
pub(super) copy_list: ID3D12GraphicsCommandList,
pub(super) fence: ID3D12Fence,
pub(super) fence_event: HANDLE,
pub(super) fence_value: u64,
pub(super) dpb: DpbPool<Av1RefMeta>,
pub(super) bitstream_buffer: ID3D12Resource,
pub(super) bitstream_capacity: u64,
pub(super) readback_buffer: ID3D12Resource,
pub(super) width: u32,
pub(super) height: u32,
}
impl Drop for SessionAv1 {
fn drop(&mut self) {
if !self.fence_event.is_invalid() {
let _ = unsafe { windows::Win32::Foundation::CloseHandle(self.fence_event) };
}
}
}
pub(crate) struct D3d12VideoDecoderAv1 {
device: ID3D12Device,
video_device: ID3D12VideoDevice,
device4: ID3D12Device4,
session: Option<SessionAv1>,
active_seq: Option<SequenceHeader>,
output: VideoOutputPreference,
pending: VecDeque<DecodedFrameAv1>,
flushed: bool,
status_report_counter: u32,
}
#[allow(
clippy::non_send_fields_in_send_ty,
reason = "HANDLE wraps a raw pointer with no auto Send impl; this manual impl is \
exactly the intended assertion for it, not an oversight — mirrors D3d12VideoDecoder's \
identical allow"
)]
unsafe impl Send for D3d12VideoDecoderAv1 {}
impl D3d12VideoDecoderAv1 {
pub(crate) fn open(config: &VideoDecoderConfig) -> Result<Self, DecodeError> {
if config.codec != CodecKind::Av1 {
return Err(DecodeError::Unsupported);
}
if config.pixel_format != PixelFormat::Nv12 {
return Err(DecodeError::Unsupported);
}
let Some(GpuDeviceHandle::DirectX12(handle)) = config.gpu_device else {
return Err(DecodeError::InvalidInput);
};
let device = setup::device_from_handle(handle)?;
let video_device: ID3D12VideoDevice =
device.cast().map_err(|_err| DecodeError::Unsupported)?;
let device4: ID3D12Device4 = device.cast().map_err(|_err| DecodeError::Unsupported)?;
Ok(Self {
device,
video_device,
device4,
session: None,
active_seq: None,
output: config.output,
pending: VecDeque::new(),
flushed: false,
status_report_counter: 0,
})
}
fn ensure_session_ready(&mut self, seq: &SequenceHeader) -> Result<(), DecodeError> {
if self.session.is_some() {
return Ok(());
}
let width = seq.max_frame_width;
let mut height = seq.max_frame_height;
let support = super::av1::check_support(&self.video_device, width, height)?;
if support
.ConfigurationFlags
.contains(D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_HEIGHT_ALIGNMENT_MULTIPLE_32_REQUIRED)
{
height = util::align_up_u32(height, 32);
}
let max_dpb_slots = CALLER_HEADROOM + 1;
let (decoder, decoder_heap) =
super::av1::create_decoder(&self.video_device, width, height, max_dpb_slots)?;
let (decode_queue, decode_allocator, decode_list) =
setup::create_command_objects::<ID3D12VideoDecodeCommandList1>(
&self.device,
&self.device4,
D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE,
)?;
let (copy_queue, copy_allocator, copy_list) =
setup::create_command_objects::<ID3D12GraphicsCommandList>(
&self.device,
&self.device4,
D3D12_COMMAND_LIST_TYPE_COPY,
)?;
let fence: ID3D12Fence = unsafe { self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE) }
.map_err(|_err| DecodeError::Backend)?;
let fence_event = unsafe { CreateEventW(None, false, false, None) }
.map_err(|_err| DecodeError::Backend)?;
let texture = setup::create_dpb_texture_array(&self.device, width, height, max_dpb_slots)?;
let dpb = DpbPool::new(texture, max_dpb_slots);
let bitstream_capacity =
u64::from(width) * u64::from(height) + super::BITSTREAM_SAFETY_MARGIN;
let bitstream_buffer = setup::create_linear_buffer(
&self.device,
D3D12_HEAP_TYPE_UPLOAD,
bitstream_capacity,
D3D12_RESOURCE_STATE_VIDEO_DECODE_READ,
)?;
let readback_row_pitch = util::align_up_u32(
width,
windows::Win32::Graphics::Direct3D12::D3D12_TEXTURE_DATA_PITCH_ALIGNMENT,
);
let readback_luma_size = u64::from(readback_row_pitch) * u64::from(height);
let readback_size = readback_luma_size + readback_luma_size / 2;
let readback_buffer = setup::create_linear_buffer(
&self.device,
D3D12_HEAP_TYPE_READBACK,
readback_size,
D3D12_RESOURCE_STATE_COMMON,
)?;
self.session = Some(SessionAv1 {
decoder,
decoder_heap,
decode_queue,
decode_allocator,
decode_list,
copy_queue,
copy_allocator,
copy_list,
fence,
fence_event,
fence_value: 0,
dpb,
bitstream_buffer,
bitstream_capacity,
readback_buffer,
width,
height,
});
Ok(())
}
pub(crate) fn push_packet(
&mut self,
packet: &mediaway_common::Packet,
) -> Result<(), DecodeError> {
if self.flushed {
return Err(DecodeError::Closed);
}
let obus = split_obus(&packet.payload)?;
for obu in obus {
match obu.obu_type {
ObuType::SequenceHeader => {
self.active_seq = Some(parse_sequence_header(obu.payload)?);
}
ObuType::Frame => {
self.decode_frame_obu(obu.payload, packet.pts)?;
}
ObuType::TemporalDelimiter | ObuType::Other(_) => {}
ObuType::FrameHeader | ObuType::TileGroup | ObuType::RedundantFrameHeader => {
return Err(DecodeError::Unsupported);
}
}
}
Ok(())
}
fn decode_frame_obu(&mut self, payload: &[u8], pts: i64) -> Result<(), DecodeError> {
let seq = self.active_seq.ok_or(DecodeError::InvalidInput)?; self.ensure_session_ready(&seq)?;
let (fh, bits_consumed) = av1_frame_header::parse_frame_header(payload, &seq)?;
let header_bytes = bits_consumed.div_ceil(8);
let tile_bytes = payload
.get(header_bytes..)
.ok_or(DecodeError::InvalidInput)?;
if tile_bytes.is_empty() {
return Err(DecodeError::InvalidInput);
}
let Some(session) = self.session.as_mut() else {
return Err(DecodeError::Backend);
};
let output_slot = session.dpb.table_mut().acquire_free_slot()?;
self.status_report_counter = self.status_report_counter.wrapping_add(1);
let mut pic_params =
av1_pic_params::build_pic_params(&seq, &fh, output_slot, self.status_report_counter);
let mut tile = av1_pic_params::build_tile(0, u32::try_from(tile_bytes.len()).unwrap_or(0));
session.decode_frame(tile_bytes, &mut pic_params, &mut tile, output_slot)?;
let width = session.width;
let height = session.height;
let output = match self.output {
VideoOutputPreference::CpuFramesOk => {
let data = session.readback_dpb_slot_to_cpu(output_slot)?;
session.dpb.table_mut().release_if_unused(output_slot);
DecodedOutputAv1::Cpu { data }
}
VideoOutputPreference::ZeroCopyGpu => {
session.dpb.table_mut().mark_handle_outstanding(output_slot);
let raw = windows::core::Interface::as_raw(session.dpb.texture()) as usize;
let resource = NativeHandle::new(raw).ok_or(DecodeError::Backend)?;
DecodedOutputAv1::Gpu {
resource,
subresource: output_slot,
}
}
};
self.pending.push_back(DecodedFrameAv1 {
pts,
width,
height,
output,
});
Ok(())
}
pub(crate) fn release_output(&mut self, subresource: u32) {
if let Some(session) = self.session.as_mut() {
session.dpb.table_mut().release_handle(subresource);
}
}
#[allow(
clippy::unnecessary_wraps,
reason = "mirrors D3d12VideoDecoder::poll_frame's Result shape for a later \
integration pass, even though this stage's poll_frame never actually errors"
)]
pub(crate) fn poll_frame(&mut self) -> Result<Option<DecodedFrameAv1>, DecodeError> {
Ok(self.pending.pop_front())
}
#[allow(
clippy::unnecessary_wraps,
clippy::missing_const_for_fn,
reason = "mirrors D3d12VideoDecoder::flush's Result shape for a later \
integration pass; not meant to be evaluated in const context"
)]
pub(crate) fn flush(&mut self) -> Result<(), DecodeError> {
self.flushed = true;
Ok(())
}
}