All checks were successful
SPEC-002 Phase 1 Task 7 (the last), code-reviewed APPROVED, locally verified (cargo fmt + clippy -D warnings exit 0 + cargo test --workspace 89 pass + build). - Encoder trait + factory: RawEncoder (salvaged, UNCHANGED) and H264Encoder, selected by negotiation; factory falls back to raw on H.264 init failure. - Negotiation: agent advertises supports_h264 (MFTEnumEx HW probe, cached) in AgentStatus; server picks the codec via select_video_codec(supports, prefer) and stamps StartStream.video_codec; agent re-guards on local HW. Policy constant DEFAULT_PREFER_H264 = false, so RAW is negotiated for every session today - H.264 stays dormant until live hardware validation (Task 8). - MF H.264 encoder (h264.rs, FIRST-CUT / compile-verified-only): HW encoder MFT, BGRA->NV12 (color.rs, unit-tested), sync drain, fall-back-to-raw on any failure. - Viewer H.264 decoder (decoder.rs, FIRST-CUT): MF decoder on a dedicated COM thread; drops+logs on failure, raw render path untouched. - proto additive: VideoCodec enum, StartStream.video_codec=3, SessionResponse.video_codec=5, AgentStatus.supports_h264=11. - Raw+Zstd path byte-for-byte unchanged; remains the guaranteed default/fallback. Review confirmed unsafe impl Send for H264Encoder is sound (single-owned &mut on the block_on thread; session future never spawned) and every MF failure degrades to raw. H.264 is NOT claimed functional - compile/clippy/build-verified only; live validation + force-IDR + the no-spawn-invariant doc are Task 8 go-live gates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
3.6 KiB
Rust
98 lines
3.6 KiB
Rust
//! 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<bool> = 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<bool> {
|
|
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<windows::Win32::Media::MediaFoundation::IMFActivate> =
|
|
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<IMFActivate> releases the COM reference.
|
|
entry.take();
|
|
}
|
|
windows::Win32::System::Com::CoTaskMemFree(Some(activate_ptr as *const _));
|
|
}
|
|
|
|
Ok(found)
|
|
}
|