Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions crates/camera-mediafoundation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ use std::{
ops::{Deref, DerefMut},
os::windows::ffi::OsStringExt,
slice::from_raw_parts,
sync::mpsc::{Receiver, Sender, channel},
sync::{
Arc,
mpsc::{Receiver, Sender, channel},
},
time::Duration,
};
use tracing::error;
use windows::Win32::{
Foundation::{S_FALSE, *},
Media::MediaFoundation::*,
System::{
Com::{CLSCTX_INPROC_SERVER, CoCreateInstance, CoInitialize},
Com::{CLSCTX_INPROC_SERVER, CoCreateInstance, CoInitialize, CoTaskMemFree},
Performance::QueryPerformanceCounter,
},
};
Expand Down Expand Up @@ -73,6 +76,19 @@ impl DeviceSourcesIterator {
}
}

impl Drop for DeviceSourcesIterator {
fn drop(&mut self) {
// MFEnumDeviceSources allocates the IMFActivate array with CoTaskMemAlloc;
// the caller owns both the array and each element's reference.
unsafe {
for i in 0..self.count as usize {
std::ptr::drop_in_place(self.devices.add(i));
}
CoTaskMemFree(Some(self.devices.cast()));
}
}
}

impl Iterator for DeviceSourcesIterator {
type Item = Device;

Expand Down Expand Up @@ -102,17 +118,40 @@ impl Iterator for DeviceSourcesIterator {
};

return Some(Device {
media_source,
media_source: media_source.clone(),
shutdown_guard: Arc::new(MediaSourceGuard(media_source)),
activate: device.clone(),
});
}
}
}

/// Enforces Media Foundation's documented teardown contract:
/// `IMFMediaSource::Shutdown()` must be called before the final COM release.
///
/// Without it, teardown only happens when the refcount hits zero, on whatever
/// thread drops the last reference — which races the KS proxy's registered
/// threadpool waits and kills the process with a non-catchable 0xC000070A
/// (handle closed while a threadpool wait is still registered on it).
struct MediaSourceGuard(IMFMediaSource);

impl Drop for MediaSourceGuard {
fn drop(&mut self) {
// Shutting down an already-shut-down source returns an error; that's fine.
unsafe {
let _ = self.0.Shutdown();
}
}
}

#[derive(Clone)]
pub struct Device {
activate: IMFActivate,
pub media_source: IMFMediaSource,
media_source: IMFMediaSource,
/// Shared owner of the media source's shutdown. `Shutdown()` fires when the
/// last clone of this `Device` (and any `CaptureHandle` borrowing the
/// source) drops.
shutdown_guard: Arc<MediaSourceGuard>,
}

#[derive(thiserror::Error, Debug)]
Expand Down Expand Up @@ -333,7 +372,11 @@ impl Device {
wait_for_event(&event_rx, CaptureEngineEventVariant::PreviewStarted)
.map_err(|v| StartCapturingError::StartPreview(v.into()))?;

Ok(CaptureHandle { engine, event_rx })
Ok(CaptureHandle {
engine,
event_rx,
_source_guard: self.shutdown_guard.clone(),
})
}
}
}
Expand Down Expand Up @@ -368,6 +411,9 @@ fn retry_on_invalid_request<T>(
pub struct CaptureHandle {
event_rx: Receiver<CaptureEngineEvent>,
engine: IMFCaptureEngine,
/// Keeps the media source's shutdown deferred until capture ends, even if
/// the originating `Device` is dropped while capturing.
_source_guard: Arc<MediaSourceGuard>,
}

impl CaptureHandle {
Expand Down