use std::ffi::c_void;
use std::ptr;
use ffmpeg_next as ffmpeg;
use windows::{
Win32::{
Media::MediaFoundation::{
IMFActivate, IMFAttributes, IMFMediaSource, IMFMediaType, IMFSourceReader,
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, MF_E_NO_MORE_TYPES,
MF_MT_FRAME_RATE, MF_MT_FRAME_SIZE, MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING,
MF_SOURCE_READER_FIRST_VIDEO_STREAM, MF_VERSION, MFCreateAttributes,
MFCreateDeviceSource, MFCreateSourceReaderFromMediaSource, MFEnumDeviceSources,
MFSTARTUP_NOSOCKET, MFShutdown, MFStartup,
},
System::Com::CoTaskMemFree,
},
core::{GUID, HSTRING, PWSTR},
};
use super::super::com::ComApartment;
pub(crate) struct MfRuntime;
impl MfRuntime {
pub(crate) fn new() -> windows::core::Result<Self> {
unsafe { MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET) }?;
Ok(Self)
}
}
impl Drop for MfRuntime {
fn drop(&mut self) {
let _ = unsafe { MFShutdown() };
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MfDevice {
pub id: String,
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MfCaptureFormat {
pub width: u32,
pub height: u32,
pub framerate: ffmpeg::Rational,
}
fn allocated_string(attributes: &IMFAttributes, key: &GUID) -> Option<String> {
let mut value = PWSTR::null();
let mut length = 0u32;
unsafe { attributes.GetAllocatedString(key, &mut value, &mut length) }.ok()?;
let text = unsafe { value.to_string() }.ok();
unsafe { CoTaskMemFree(Some(value.as_ptr() as *const c_void)) };
text
}
fn packed_pair(media_type: &IMFMediaType, key: &GUID) -> windows::core::Result<(u32, u32)> {
let packed = unsafe { media_type.GetUINT64(key) }?;
Ok(((packed >> 32) as u32, packed as u32))
}
pub(crate) fn frame_size(media_type: &IMFMediaType) -> windows::core::Result<(u32, u32)> {
packed_pair(media_type, &MF_MT_FRAME_SIZE)
}
pub(crate) fn frame_rate(media_type: &IMFMediaType) -> windows::core::Result<ffmpeg::Rational> {
let (numerator, denominator) = packed_pair(media_type, &MF_MT_FRAME_RATE)?;
Ok(ffmpeg::Rational::new(numerator as i32, denominator as i32))
}
fn vidcap_attributes(symbolic_link: Option<&str>) -> windows::core::Result<IMFAttributes> {
let mut attributes = None;
unsafe { MFCreateAttributes(&mut attributes, 2) }?;
let attributes = attributes.expect("MFCreateAttributes yields an object whenever it succeeds");
unsafe {
attributes.SetGUID(
&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID,
)?;
if let Some(link) = symbolic_link {
attributes.SetString(
&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&HSTRING::from(link),
)?;
}
}
Ok(attributes)
}
pub(crate) fn list_devices() -> windows::core::Result<Vec<MfDevice>> {
let _apartment = ComApartment::new()?;
let _runtime = MfRuntime::new()?;
let attributes = vidcap_attributes(None)?;
let mut activates: *mut Option<IMFActivate> = ptr::null_mut();
let mut count = 0u32;
unsafe { MFEnumDeviceSources(&attributes, &mut activates, &mut count) }?;
let mut devices = Vec::with_capacity(count as usize);
for index in 0..count as usize {
let Some(activate) = (unsafe { (*activates.add(index)).take() }) else {
continue;
};
let Some(id) = allocated_string(
&activate,
&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
) else {
continue;
};
let name = allocated_string(&activate, &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME)
.map(|name| name.trim().to_owned())
.filter(|name| !name.is_empty())
.unwrap_or_else(|| id.clone());
devices.push(MfDevice { id, name });
}
unsafe { CoTaskMemFree(Some(activates as *const c_void)) };
Ok(devices)
}
pub(crate) fn open_device_source(symbolic_link: &str) -> windows::core::Result<IMFMediaSource> {
let attributes = vidcap_attributes(Some(symbolic_link))?;
unsafe { MFCreateDeviceSource(&attributes) }
}
pub(crate) fn list_formats(
reader: &IMFSourceReader,
) -> windows::core::Result<Vec<MfCaptureFormat>> {
let stream = MF_SOURCE_READER_FIRST_VIDEO_STREAM.0 as u32;
let mut formats: Vec<MfCaptureFormat> = Vec::new();
for index in 0.. {
let media_type = match unsafe { reader.GetNativeMediaType(stream, index) } {
Ok(media_type) => media_type,
Err(error) if error.code() == MF_E_NO_MORE_TYPES => break,
Err(error) => return Err(error),
};
let (Ok((width, height)), Ok(framerate)) =
(frame_size(&media_type), frame_rate(&media_type))
else {
continue;
};
let format = MfCaptureFormat {
width,
height,
framerate,
};
if !formats.contains(&format) {
formats.push(format);
}
}
Ok(formats)
}
pub(crate) fn open_reader(source: &IMFMediaSource) -> windows::core::Result<IMFSourceReader> {
let mut attributes = None;
unsafe { MFCreateAttributes(&mut attributes, 1) }?;
let attributes = attributes.expect("MFCreateAttributes yields an object whenever it succeeds");
unsafe { attributes.SetUINT32(&MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, 1) }?;
unsafe { MFCreateSourceReaderFromMediaSource(source, &attributes) }
}