-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: Improve Media Foundation initialization and error handling #1253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,19 +7,85 @@ use std::{ | |
| use windows::{ | ||
| Win32::{ | ||
| Media::MediaFoundation::{IMFMediaBuffer, MFSTARTUP_FULL, MFStartup}, | ||
| System::WinRT::{RO_INIT_MULTITHREADED, RoInitialize}, | ||
| System::{ | ||
| LibraryLoader::GetModuleHandleW, | ||
| WinRT::{RO_INIT_MULTITHREADED, RoInitialize}, | ||
| }, | ||
| }, | ||
| core::Result, | ||
| core::{Result, PCWSTR}, | ||
| }; | ||
|
|
||
| // This is the value for Win7+ | ||
| pub const MF_VERSION: u32 = 131184; | ||
|
|
||
| /// Custom error type for initialization failures | ||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum InitError { | ||
| #[error("Third-party software conflict detected: {software}. {message}")] | ||
| ThirdPartyConflict { software: String, message: String }, | ||
| #[error("Failed to initialize Windows Runtime: {0}")] | ||
| RoInitializeFailed(windows::core::Error), | ||
| #[error("Failed to initialize Media Foundation: {0}")] | ||
| MFStartupFailed(windows::core::Error), | ||
| } | ||
|
|
||
| /// Detects if Wondershare DemoCreator's hook DLL is loaded in the process. | ||
| /// This DLL is known to cause crashes when interfering with WinRT initialization. | ||
| pub fn detect_wondershare_democreator() -> bool { | ||
| unsafe { | ||
| // Check for CaptureGameHook_64.dll (64-bit version) | ||
| let dll_name = windows::core::w!("CaptureGameHook_64.dll"); | ||
| let handle = GetModuleHandleW(PCWSTR(dll_name.as_ptr())).ok(); | ||
|
|
||
| if handle.is_some() { | ||
| return true; | ||
| } | ||
|
|
||
| // Also check for the 32-bit version just in case | ||
| let dll_name_32 = windows::core::w!("CaptureGameHook.dll"); | ||
| let handle_32 = GetModuleHandleW(PCWSTR(dll_name_32.as_ptr())).ok(); | ||
|
|
||
| handle_32.is_some() | ||
| } | ||
| } | ||
|
|
||
| /// Checks for known incompatible third-party software before initializing. | ||
| /// Returns an error if incompatible software is detected. | ||
| pub fn check_compatibility() -> Result<(), InitError> { | ||
| if detect_wondershare_democreator() { | ||
| return Err(InitError::ThirdPartyConflict { | ||
| software: "Wondershare DemoCreator".to_string(), | ||
| message: "The Wondershare DemoCreator hook DLL (CaptureGameHook_64.dll) is interfering with Cap's screen capture system. Please close Wondershare DemoCreator and try again.".to_string(), | ||
| }); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
Comment on lines
+52
to
+63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chainAvoid redundant pre-check at call sites.
Also applies to: 72-87 🏁 Script executed: #!/usr/bin/env bash
# Find call sites doing both checks; expect to keep only thread_init_checked().
rg -nP --glob '!target' '(check_compatibility\(\).*\n.*thread_init_checked\(\))|thread_init_checked\(\)' -C2Length of output: 2084 [/run_scripts] Based on the script execution results and my analysis, the review comment contains an incorrect assumption. The ripgrep search showed that:
The review comment's suggestion to "remove separate Ignore the review comment—the code is already correctly organized. [incorrect_review_comment] 🏁 Script executed: #!/bin/bash
# Search for ALL calls to check_compatibility() function (not just the definition)
# Exclude the definition at lines 52-63 and look for actual calls
rg -nP --glob '!target' '\bcheck_compatibility\s*\(' --type=rust | grep -v '^\s*pub fn check_compatibility' | grep -v '^\s*//'Length of output: 542 [/run_scripts] 🏁 Script executed: #!/bin/bash
# Show context around the three direct check_compatibility() calls
echo "=== output_pipeline/win.rs around line 68 ==="
sed -n '65,80p' crates/recording/src/output_pipeline/win.rs
echo ""
echo "=== instant_recording.rs around line 293 ==="
sed -n '290,300p' crates/recording/src/instant_recording.rs
echo ""
echo "=== screen_capture/windows.rs around line 189 ==="
sed -n '186,200p' crates/recording/src/sources/screen_capture/windows.rsLength of output: 2066 Remove redundant
🤖 Prompt for AI Agents |
||
|
|
||
| /// Initializes Windows Runtime and Media Foundation for the current thread. | ||
| /// Should be called on threads that use WinRT or Media Foundation APIs. | ||
| pub fn thread_init() { | ||
| let _ = unsafe { RoInitialize(RO_INIT_MULTITHREADED) }; | ||
| let _ = unsafe { MFStartup(MF_VERSION, MFSTARTUP_FULL) }; | ||
| } | ||
|
|
||
| /// Initializes Windows Runtime and Media Foundation for the current thread with error handling. | ||
| /// Returns an error if initialization fails or if incompatible software is detected. | ||
| pub fn thread_init_checked() -> Result<(), InitError> { | ||
| // First check for known incompatible software | ||
| check_compatibility()?; | ||
|
|
||
| // Initialize Windows Runtime | ||
| unsafe { RoInitialize(RO_INIT_MULTITHREADED) } | ||
| .map_err(InitError::RoInitializeFailed)?; | ||
|
|
||
| // Initialize Media Foundation | ||
| unsafe { MFStartup(MF_VERSION, MFSTARTUP_FULL) } | ||
| .map_err(InitError::MFStartupFailed)?; | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| pub trait IMFMediaBufferExt { | ||
| fn lock(&self) -> Result<IMFMediaBufferLock<'_>>; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,7 +64,19 @@ impl Muxer for WindowsMuxer { | |
| let output = output.clone(); | ||
|
|
||
| tasks.spawn_thread("windows-encoder", move || { | ||
| cap_mediafoundation_utils::thread_init(); | ||
| // Check for incompatible third-party software before initializing | ||
| if let Err(e) = cap_mediafoundation_utils::check_compatibility() { | ||
| error!("Third-party software conflict detected: {}", e); | ||
| let _ = ready_tx.send(Err(anyhow!("{}", e))); | ||
| return; | ||
| } | ||
|
|
||
| // Initialize Windows Runtime and Media Foundation with error handling | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainNit: trailing whitespace (if any). Double-check for trailing spaces around the inserted block to keep CI green, similar to the other file. 🏁 Script executed: #!/usr/bin/env bash
rg -nP '\s+$' crates/recording/src/output_pipeline/win.rsLength of output: 19 Remove trailing whitespace from line 73. The verification confirmed trailing whitespace exists on line 73 (the line immediately before the comment). This needs to be cleaned up to keep CI green. 🤖 Prompt for AI Agents |
||
| if let Err(e) = cap_mediafoundation_utils::thread_init_checked() { | ||
| error!("Failed to initialize encoder thread: {}", e); | ||
| let _ = ready_tx.send(Err(anyhow!("{}", e))); | ||
| return; | ||
| } | ||
|
|
||
| let encoder = (|| { | ||
| let mut output = output.lock().unwrap(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -185,7 +185,19 @@ impl output_pipeline::VideoSource for VideoSource { | |||
| let tokio_rt = tokio::runtime::Handle::current(); | ||||
|
|
||||
| ctx.tasks().spawn_thread("d3d-capture-thread", move || { | ||||
| cap_mediafoundation_utils::thread_init(); | ||||
| // Check for incompatible third-party software before initializing | ||||
| if let Err(e) = cap_mediafoundation_utils::check_compatibility() { | ||||
| error!("Third-party software conflict detected: {}", e); | ||||
| let _ = error_tx.send(anyhow!("{}", e)); | ||||
| return; | ||||
| } | ||||
|
|
||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix CI: trailing whitespace. Remove trailing whitespace at Line 194. -
+📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: CI[error] 194-194: left behind trailing whitespace. 🤖 Prompt for AI Agents |
||||
| // Initialize Windows Runtime and Media Foundation with error handling | ||||
| if let Err(e) = cap_mediafoundation_utils::thread_init_checked() { | ||||
| error!("Failed to initialize capture thread: {}", e); | ||||
| let _ = error_tx.send(anyhow!("{}", e)); | ||||
| return; | ||||
| } | ||||
|
|
||||
|
Comment on lines
+188
to
201
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dropped futures: use try_send() instead of send() in blocking thread.
Apply: - let _ = error_tx.send(anyhow!("{}", e));
+ let _ = error_tx.try_send(anyhow!("{}", e));
@@
- let _ = error_tx.send(anyhow!("{}", e));
+ let _ = error_tx.try_send(anyhow!("{}", e));
@@
- let mut error_tx = error_tx.clone();
+ let mut error_tx = error_tx.clone();
move || {
- let _ = error_tx.send(anyhow!("closed"));
+ let _ = error_tx.try_send(anyhow!("closed"));
}Also applies to: 244-250 🧰 Tools🪛 GitHub Actions: CI[error] 194-194: left behind trailing whitespace. 🤖 Prompt for AI Agents |
||||
| // Look up the display and create the GraphicsCaptureItem on this thread to avoid COM threading issues | ||||
| let capture_item = match Display::from_id(&display_id) { | ||||
|
|
||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix
Resulttype clash and make MFStartup idempotent.use windows::core::Resultshadowsstd::result::Result, causing wrong error type in functions returningInitError.MF_E_ALREADY_INITIALIZEDas success to match prior behavior and avoid spurious failures on repeated init.Apply:
Also applies to: 54-64, 72-87, 89-132
🤖 Prompt for AI Agents