#![allow(unsafe_code)]
#![allow(clippy::redundant_pub_crate)]
use crate::CaptureError;
use mediaway_common::{
Bytes, CodecKind, GpuBufferHandle, NativeHandle, PixelFormat, StreamInfo, VideoFrame,
VideoFrameStorage, VideoGeometry,
};
use windows::Win32::Graphics::Direct3D11::{ID3D11Device, ID3D11Texture2D};
use windows::Win32::Graphics::Dxgi::{
DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_FRAME_INFO, IDXGIDevice, IDXGIOutput1,
IDXGIOutputDuplication, IDXGIResource,
};
use windows::core::Interface;
const POLL_TIMEOUT_MS: u32 = 16;
pub(crate) struct ExclusiveDuplication {
duplication: IDXGIOutputDuplication,
held: Option<ID3D11Texture2D>,
}
impl ExclusiveDuplication {
pub(crate) fn open(
device_raw: usize,
output_index: u32,
) -> Result<(Self, StreamInfo), CaptureError> {
let raw = device_raw as *mut std::ffi::c_void;
let device_ref =
unsafe { ID3D11Device::from_raw_borrowed(&raw) }.ok_or(CaptureError::InvalidInput)?;
let device: ID3D11Device = device_ref.clone();
let dxgi_device: IDXGIDevice = device.cast().map_err(|_| CaptureError::Backend)?;
let adapter = unsafe { dxgi_device.GetAdapter() }.map_err(|_| CaptureError::Backend)?;
let output =
unsafe { adapter.EnumOutputs(output_index) }.map_err(|_| CaptureError::InvalidInput)?;
let output1: IDXGIOutput1 = output.cast().map_err(|_| CaptureError::Backend)?;
let duplication =
unsafe { output1.DuplicateOutput(&device) }.map_err(|_| CaptureError::AccessDenied)?;
let dup_desc = unsafe { duplication.GetDesc() };
let width = dup_desc.ModeDesc.Width;
let height = dup_desc.ModeDesc.Height;
if width == 0 || height == 0 {
return Err(CaptureError::Backend);
}
let stream_info = StreamInfo::Video {
id: 0,
codec: CodecKind::RawVideo,
time_base: mediaway_common::Rational::new(1, 60),
geometry: VideoGeometry { width, height },
extra_data: Bytes::new(),
};
Ok((
Self {
duplication,
held: None,
},
stream_info,
))
}
pub(crate) fn poll_frame(
&mut self,
geometry: VideoGeometry,
next_pts: &mut i64,
) -> Result<Option<VideoFrame>, CaptureError> {
if self.held.is_some() {
return Err(CaptureError::Backend);
}
let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
let mut desktop_resource: Option<IDXGIResource> = None;
let acquire = unsafe {
self.duplication.AcquireNextFrame(
POLL_TIMEOUT_MS,
&raw mut frame_info,
&raw mut desktop_resource,
)
};
if let Err(e) = acquire {
if e.code() == DXGI_ERROR_WAIT_TIMEOUT {
return Ok(None);
}
return Err(CaptureError::Backend);
}
let Some(desktop_resource) = desktop_resource else {
return Ok(None);
};
let Ok(texture) = desktop_resource.cast::<ID3D11Texture2D>() else {
let _ = unsafe { self.duplication.ReleaseFrame() };
return Err(CaptureError::Backend);
};
let raw_texture_ptr = Interface::as_raw(&texture) as usize;
self.held = Some(texture);
let texture_handle = NativeHandle::new(raw_texture_ptr).ok_or(CaptureError::Backend)?;
let pts = *next_pts;
*next_pts += 1;
Ok(Some(VideoFrame {
pts,
duration: 1,
width: geometry.width,
height: geometry.height,
format: PixelFormat::Bgra8,
storage: VideoFrameStorage::Gpu(GpuBufferHandle::DirectX11 {
texture: texture_handle,
subresource: 0,
}),
}))
}
pub(crate) fn release_frame(&mut self) -> Result<(), CaptureError> {
if self.held.take().is_some() {
unsafe { self.duplication.ReleaseFrame() }.map_err(|_| CaptureError::Backend)?;
}
Ok(())
}
}
impl Drop for ExclusiveDuplication {
fn drop(&mut self) {
let _ = self.release_frame();
}
}