From 9df524d8820bab40dbee4efc3bb374bf753d425e Mon Sep 17 00:00:00 2001 From: "seer-by-sentry[bot]" <157164994+seer-by-sentry[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:34:18 +0000 Subject: [PATCH 1/4] recording: Improve error handling for hardware encoder failures on Windows --- crates/recording/src/output_pipeline/win.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/recording/src/output_pipeline/win.rs b/crates/recording/src/output_pipeline/win.rs index 4845f5a541..53851241f1 100644 --- a/crates/recording/src/output_pipeline/win.rs +++ b/crates/recording/src/output_pipeline/win.rs @@ -130,7 +130,7 @@ impl Muxer for WindowsMuxer { either::Left((mut encoder, mut muxer)) => { trace!("Running native encoder"); let mut first_timestamp = None; - encoder + if let Err(e) = encoder .run( Arc::new(AtomicBool::default()), || { @@ -157,7 +157,13 @@ impl Muxer for WindowsMuxer { Ok(()) }, ) - .unwrap(); + { + error!( + "Hardware encoder failed with error: {} (code: {:?}). This may be due to hardware limitations or driver issues.", + e.message(), + e.code() + ); + } } either::Right(mut encoder) => { while let Ok(Some((frame, time))) = video_rx.recv() { From 9b59611437c1d96d40189bbfc7ea80839f3ccd4d Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 23 Oct 2025 19:41:39 +0800 Subject: [PATCH 2/4] allow returning errors from spawn_thread --- crates/recording/src/output_pipeline/core.rs | 20 ++++++++--- crates/recording/src/output_pipeline/win.rs | 36 +++++++------------ .../src/sources/screen_capture/windows.rs | 18 ++++------ crates/scap-direct3d/src/lib.rs | 6 ++++ 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/crates/recording/src/output_pipeline/core.rs b/crates/recording/src/output_pipeline/core.rs index b60777eec9..767e588aa9 100644 --- a/crates/recording/src/output_pipeline/core.rs +++ b/crates/recording/src/output_pipeline/core.rs @@ -138,19 +138,26 @@ impl TaskPool { )); } - pub fn spawn_thread(&mut self, name: &'static str, cb: impl FnOnce() + Send + 'static) { + pub fn spawn_thread( + &mut self, + name: &'static str, + cb: impl FnOnce() -> anyhow::Result<()> + Send + 'static, + ) { let span = error_span!("", task = name); let (done_tx, done_rx) = oneshot::channel(); std::thread::spawn(move || { let _guard = span.enter(); trace!("Task started"); - cb(); - let _ = done_tx.send(()); + let _ = done_tx.send(cb()); info!("Task finished"); }); self.0.push(( name, - tokio::spawn(done_rx.map_err(|_| anyhow!("Cancelled"))), + tokio::spawn( + done_rx + .map_err(|_| anyhow!("Cancelled")) + .map(|v| v.and_then(|v| v)), + ), )); } } @@ -481,7 +488,10 @@ async fn configure_audio( setup_ctx.tasks().spawn_thread("audio-mixer", { let stop_flag = stop_flag.clone(); - move || audio_mixer.run(audio_tx, ready_tx, stop_flag) + move || { + audio_mixer.run(audio_tx, ready_tx, stop_flag); + Ok(()) + } }); let _ = ready_rx .await diff --git a/crates/recording/src/output_pipeline/win.rs b/crates/recording/src/output_pipeline/win.rs index 53851241f1..0e5f29c74b 100644 --- a/crates/recording/src/output_pipeline/win.rs +++ b/crates/recording/src/output_pipeline/win.rs @@ -1,6 +1,5 @@ use crate::{AudioFrame, AudioMuxer, Muxer, TaskPool, VideoMuxer, screen_capture}; -use anyhow::Context; -use anyhow::anyhow; +use anyhow::{Context, anyhow}; use cap_enc_ffmpeg::AACEncoder; use cap_media_info::{AudioInfo, VideoInfo}; use futures::channel::oneshot; @@ -130,7 +129,7 @@ impl Muxer for WindowsMuxer { either::Left((mut encoder, mut muxer)) => { trace!("Running native encoder"); let mut first_timestamp = None; - if let Err(e) = encoder + encoder .run( Arc::new(AtomicBool::default()), || { @@ -157,13 +156,7 @@ impl Muxer for WindowsMuxer { Ok(()) }, ) - { - error!( - "Hardware encoder failed with error: {} (code: {:?}). This may be due to hardware limitations or driver issues.", - e.message(), - e.code() - ); - } + .context("run native encoder") } either::Right(mut encoder) => { while let Ok(Some((frame, time))) = video_rx.recv() { @@ -179,20 +172,17 @@ impl Muxer for WindowsMuxer { use scap_ffmpeg::AsFFmpeg; - if let Err(e) = - frame - .as_ffmpeg() - .context("frame as_ffmpeg") - .and_then(|frame| { - encoder - .queue_frame(frame, time, &mut output) - .context("queue_frame") - }) - { - error!("{e}"); - return; - } + frame + .as_ffmpeg() + .context("frame as_ffmpeg") + .and_then(|frame| { + encoder + .queue_frame(frame, time, &mut output) + .context("queue_frame") + })?; } + + Ok(()) } } }); diff --git a/crates/recording/src/sources/screen_capture/windows.rs b/crates/recording/src/sources/screen_capture/windows.rs index f856304da5..6b3ece6db7 100644 --- a/crates/recording/src/sources/screen_capture/windows.rs +++ b/crates/recording/src/sources/screen_capture/windows.rs @@ -197,15 +197,13 @@ impl output_pipeline::VideoSource for VideoSource { } Err(e) => { error!("Failed to create GraphicsCaptureItem on capture thread: {}", e); - let _ = error_tx.send(anyhow!("Failed to create GraphicsCaptureItem: {}", e)); - return; + return Err(anyhow!("Failed to create GraphicsCaptureItem: {}", e)); } } } None => { error!("Display not found for ID: {:?}", display_id); - let _ = error_tx.send(anyhow!("Display not found for ID: {:?}", display_id)); - return; + return Err(anyhow!("Display not found for ID: {:?}", display_id)); } }; @@ -246,15 +244,13 @@ impl output_pipeline::VideoSource for VideoSource { } Err(e) => { error!("Failed to create D3D capturer: {}", e); - let _ = error_tx.send(e.into()); - return; + return Err(e.into()); } }; let Ok(VideoControl::Start(reply)) = ctrl_rx.recv() else { error!("Failed to receive Start control message - channel disconnected"); - let _ = error_tx.send(anyhow!("Control channel disconnected before Start")); - return; + return Err(anyhow!("Control channel disconnected before Start")); }; tokio_rt.spawn( @@ -279,16 +275,16 @@ impl output_pipeline::VideoSource for VideoSource { } if reply.send(start_result).is_err() { error!("Failed to send start result - receiver dropped"); - return; + return Ok(()); } let Ok(VideoControl::Stop(reply)) = ctrl_rx.recv() else { trace!("Failed to receive Stop control message - channel disconnected (expected during shutdown)"); - return; + return Ok(()); }; if reply.send(capturer.stop().map_err(Into::into)).is_err() { - return; + return Ok(()); } drop(drop_guard) diff --git a/crates/scap-direct3d/src/lib.rs b/crates/scap-direct3d/src/lib.rs index e69b36c927..17c9f1b5bc 100644 --- a/crates/scap-direct3d/src/lib.rs +++ b/crates/scap-direct3d/src/lib.rs @@ -384,6 +384,12 @@ impl Capturer { } } +impl Drop for Capturer { + fn drop(&mut self) { + let _ = self.stop(); + } +} + pub struct Frame { width: u32, height: u32, From c799d8615297ab472bd71f078007be5e561df337 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 23 Oct 2025 20:23:39 +0800 Subject: [PATCH 3/4] some fixes --- crates/recording/src/output_pipeline/win.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/recording/src/output_pipeline/win.rs b/crates/recording/src/output_pipeline/win.rs index 0e5f29c74b..63a913b446 100644 --- a/crates/recording/src/output_pipeline/win.rs +++ b/crates/recording/src/output_pipeline/win.rs @@ -114,14 +114,14 @@ impl Muxer for WindowsMuxer { Ok(encoder) => { if ready_tx.send(Ok(())).is_err() { error!("Failed to send ready signal - receiver dropped"); - return; + return Ok(()); } encoder } Err(e) => { error!("Encoder setup failed: {}", e); - let _ = ready_tx.send(Err(e)); - return; + let _ = ready_tx.send(Err(e.into())); + return Err(e.into()); } }; From b2c4f2082cb9338f12a1404d8b966aa443651dff Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 23 Oct 2025 20:33:13 +0800 Subject: [PATCH 4/4] fix build --- crates/recording/src/output_pipeline/win.rs | 8 ++++---- crates/recording/src/sources/screen_capture/windows.rs | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/recording/src/output_pipeline/win.rs b/crates/recording/src/output_pipeline/win.rs index 63a913b446..30cd843b3d 100644 --- a/crates/recording/src/output_pipeline/win.rs +++ b/crates/recording/src/output_pipeline/win.rs @@ -57,7 +57,7 @@ impl Muxer for WindowsMuxer { .transpose()?; let output = Arc::new(Mutex::new(output)); - let (ready_tx, ready_rx) = oneshot::channel(); + let (ready_tx, ready_rx) = oneshot::channel::>(); { let output = output.clone(); @@ -119,9 +119,9 @@ impl Muxer for WindowsMuxer { encoder } Err(e) => { - error!("Encoder setup failed: {}", e); - let _ = ready_tx.send(Err(e.into())); - return Err(e.into()); + error!("Encoder setup failed: {:#}", e); + let _ = ready_tx.send(Err(anyhow!("{e}"))); + return Err(anyhow!("{e}")); } }; diff --git a/crates/recording/src/sources/screen_capture/windows.rs b/crates/recording/src/sources/screen_capture/windows.rs index 6b3ece6db7..d14eb84f6b 100644 --- a/crates/recording/src/sources/screen_capture/windows.rs +++ b/crates/recording/src/sources/screen_capture/windows.rs @@ -287,7 +287,9 @@ impl output_pipeline::VideoSource for VideoSource { return Ok(()); } - drop(drop_guard) + drop(drop_guard); + + Ok(()) }); ctx.tasks().spawn("d3d-capture", async move {