//! Hardware video-encode capability detection (Task 7). //! //! Probes Windows Media Foundation for a HARDWARE H.264 encoder MFT at startup. //! The result is cached and advertised to the server in `AgentStatus.supports_h264` //! so the server can negotiate the codec (see `StartStream.video_codec`). //! //! Detection is intentionally cheap and side-effect-free: it only ENUMERATES the //! available encoder MFTs (it does not create or initialize one). A `true` result //! means a hardware H.264 encoder was advertised by the OS; it does NOT guarantee //! the encoder will successfully initialize at stream time — the H.264 encoder //! still falls back to raw on any init/feed failure. //! //! On non-Windows targets, or if MF is unavailable, this reports `false`. use std::sync::OnceLock; /// Cached capability result. Detection runs at most once per process. static SUPPORTS_H264: OnceLock = OnceLock::new(); /// Return whether this machine has a hardware H.264 encoder, detecting once and /// caching the result. Safe to call repeatedly and from any thread. pub fn supports_hardware_h264() -> bool { *SUPPORTS_H264.get_or_init(detect_hardware_h264) } /// Run the actual detection. Separated so the cached accessor stays trivial. fn detect_hardware_h264() -> bool { let supported = detect_inner(); if supported { tracing::info!("Hardware H.264 encoder detected (Media Foundation)"); } else { tracing::info!("No hardware H.264 encoder detected; raw+Zstd only"); } supported } #[cfg(windows)] fn detect_inner() -> bool { // Enumerate hardware H.264 encoder MFTs. This is a read-only probe; it does // not init D3D, COM apartments persistently, or create the encoder. match unsafe { enumerate_hardware_h264() } { Ok(found) => found, Err(e) => { tracing::warn!("H.264 capability probe failed: {e:#}; assuming no HW encoder"); false } } } #[cfg(not(windows))] fn detect_inner() -> bool { false } #[cfg(windows)] unsafe fn enumerate_hardware_h264() -> anyhow::Result { use windows::Win32::Media::MediaFoundation::{ MFMediaType_Video, MFTEnumEx, MFVideoFormat_H264, MFT_CATEGORY_VIDEO_ENCODER, MFT_ENUM_FLAG_HARDWARE, MFT_ENUM_FLAG_SORTANDFILTER, MFT_ENUM_FLAG_TRANSCODE_ONLY, MFT_REGISTER_TYPE_INFO, }; // We only specify the OUTPUT type (H.264); input is left unconstrained so the // probe matches encoders regardless of their preferred input subtype. let output_type = MFT_REGISTER_TYPE_INFO { guidMajorType: MFMediaType_Video, guidSubtype: MFVideoFormat_H264, }; let mut activate_ptr: *mut Option = std::ptr::null_mut(); let mut count: u32 = 0; // MFTEnumEx does not itself require MFStartup for a pure enumeration, but we // guard with a Result so any HRESULT failure degrades to "no HW encoder". MFTEnumEx( MFT_CATEGORY_VIDEO_ENCODER, MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_TRANSCODE_ONLY, None, // input type: any Some(&output_type as *const _), &mut activate_ptr, &mut count, )?; // Release every returned IMFActivate, then free the array CoTaskMemAlloc'd by MF. let found = count > 0; if !activate_ptr.is_null() { let slice = std::slice::from_raw_parts_mut(activate_ptr, count as usize); for entry in slice.iter_mut() { // Dropping the Option releases the COM reference. entry.take(); } windows::Win32::System::Com::CoTaskMemFree(Some(activate_ptr as *const _)); } Ok(found) }