Skip to content

Commit 5355d72

Browse files
recording: move display+crop conversion out of capture sources (#1257)
* recording: move display+crop conversion out of capture sources * more anyhow errors * more error handling fixes * fix * Update crates/recording/src/capture_pipeline.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * delay cursor crop calculation --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 54c9c98 commit 5355d72

File tree

4 files changed

+149
-149
lines changed

4 files changed

+149
-149
lines changed

crates/recording/src/capture_pipeline.rs

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ use crate::{
22
feeds::microphone::MicrophoneFeedLock,
33
output_pipeline::*,
44
sources,
5-
sources::screen_capture::{
6-
self, ScreenCaptureConfig, ScreenCaptureFormat, ScreenCaptureTarget,
7-
},
5+
sources::screen_capture::{self, CropBounds, ScreenCaptureFormat, ScreenCaptureTarget},
86
};
7+
use anyhow::anyhow;
98
use cap_timestamp::Timestamps;
10-
use scap_targets::WindowId;
11-
use std::{path::PathBuf, sync::Arc, time::SystemTime};
9+
use scap_targets::bounds::LogicalBounds;
10+
use std::{path::PathBuf, sync::Arc};
1211

1312
pub trait MakeCapturePipeline: ScreenCaptureFormat + std::fmt::Debug + 'static {
1413
async fn make_studio_mode_pipeline(
@@ -127,30 +126,96 @@ pub type ScreenCaptureMethod = screen_capture::CMSampleBufferCapture;
127126
#[cfg(windows)]
128127
pub type ScreenCaptureMethod = screen_capture::Direct3DCapture;
129128

130-
pub async fn create_screen_capture(
131-
capture_target: &ScreenCaptureTarget,
132-
force_show_cursor: bool,
133-
max_fps: u32,
134-
start_time: SystemTime,
135-
system_audio: bool,
136-
#[cfg(windows)] d3d_device: ::windows::Win32::Graphics::Direct3D11::ID3D11Device,
137-
#[cfg(target_os = "macos")] shareable_content: cidre::arc::R<cidre::sc::ShareableContent>,
138-
#[cfg(target_os = "macos")] excluded_windows: Vec<WindowId>,
139-
) -> anyhow::Result<ScreenCaptureConfig<ScreenCaptureMethod>> {
140-
Ok(ScreenCaptureConfig::<ScreenCaptureMethod>::init(
141-
capture_target,
142-
force_show_cursor,
143-
max_fps,
144-
start_time,
145-
system_audio,
146-
#[cfg(windows)]
147-
d3d_device,
148-
#[cfg(target_os = "macos")]
149-
shareable_content,
150-
#[cfg(target_os = "macos")]
151-
excluded_windows,
152-
)
153-
.await?)
129+
pub fn target_to_display_and_crop(
130+
target: &ScreenCaptureTarget,
131+
) -> anyhow::Result<(scap_targets::Display, Option<CropBounds>)> {
132+
use scap_targets::{bounds::*, *};
133+
134+
let display = target
135+
.display()
136+
.ok_or_else(|| anyhow!("Display not found"))?;
137+
138+
let crop_bounds = match target {
139+
ScreenCaptureTarget::Display { .. } => None,
140+
ScreenCaptureTarget::Window { id } => {
141+
let window = Window::from_id(id).ok_or_else(|| anyhow!("Window not found"))?;
142+
143+
#[cfg(target_os = "macos")]
144+
{
145+
let raw_display_bounds = display
146+
.raw_handle()
147+
.logical_bounds()
148+
.ok_or_else(|| anyhow!("No display bounds"))?;
149+
let raw_window_bounds = window
150+
.raw_handle()
151+
.logical_bounds()
152+
.ok_or_else(|| anyhow!("No window bounds"))?;
153+
154+
Some(LogicalBounds::new(
155+
LogicalPosition::new(
156+
raw_window_bounds.position().x() - raw_display_bounds.position().x(),
157+
raw_window_bounds.position().y() - raw_display_bounds.position().y(),
158+
),
159+
raw_window_bounds.size(),
160+
))
161+
}
162+
163+
#[cfg(windows)]
164+
{
165+
let raw_display_position = display
166+
.raw_handle()
167+
.physical_position()
168+
.ok_or_else(|| anyhow!("No display bounds"))?;
169+
let raw_window_bounds = window
170+
.raw_handle()
171+
.physical_bounds()
172+
.ok_or_else(|| anyhow!("No window bounds"))?;
173+
174+
Some(PhysicalBounds::new(
175+
PhysicalPosition::new(
176+
raw_window_bounds.position().x() - raw_display_position.x(),
177+
raw_window_bounds.position().y() - raw_display_position.y(),
178+
),
179+
raw_window_bounds.size(),
180+
))
181+
}
182+
}
183+
ScreenCaptureTarget::Area {
184+
bounds: relative_bounds,
185+
..
186+
} => {
187+
#[cfg(target_os = "macos")]
188+
{
189+
Some(*relative_bounds)
190+
}
191+
192+
#[cfg(windows)]
193+
{
194+
let raw_display_size = display
195+
.physical_size()
196+
.ok_or_else(|| anyhow!("No display bounds"))?;
197+
let logical_display_size = display
198+
.logical_size()
199+
.ok_or_else(|| anyhow!("No display logical size"))?;
200+
Some(PhysicalBounds::new(
201+
PhysicalPosition::new(
202+
(relative_bounds.position().x() / logical_display_size.width())
203+
* raw_display_size.width(),
204+
(relative_bounds.position().y() / logical_display_size.height())
205+
* raw_display_size.height(),
206+
),
207+
PhysicalSize::new(
208+
(relative_bounds.size().width() / logical_display_size.width())
209+
* raw_display_size.width(),
210+
(relative_bounds.size().height() / logical_display_size.height())
211+
* raw_display_size.height(),
212+
),
213+
))
214+
}
215+
}
216+
};
217+
218+
Ok((display, crop_bounds))
154219
}
155220

156221
#[cfg(windows)]

crates/recording/src/instant_recording.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use crate::{
22
RecordingBaseInputs,
3-
capture_pipeline::{MakeCapturePipeline, ScreenCaptureMethod, Stop, create_screen_capture},
3+
capture_pipeline::{
4+
MakeCapturePipeline, ScreenCaptureMethod, Stop, target_to_display_and_crop,
5+
},
46
feeds::microphone::MicrophoneFeedLock,
57
output_pipeline::{self, OutputPipeline},
68
sources::screen_capture::{ScreenCaptureConfig, ScreenCaptureTarget},
79
};
10+
use anyhow::Context as _;
811
use cap_media_info::{AudioInfo, VideoInfo};
912
use cap_project::InstantRecordingMeta;
1013
use cap_utils::ensure_dir;
@@ -293,8 +296,12 @@ pub async fn spawn_instant_recording_actor(
293296
#[cfg(windows)]
294297
let d3d_device = crate::capture_pipeline::create_d3d_device()?;
295298

296-
let screen_source = create_screen_capture(
297-
&inputs.capture_target,
299+
let (display, crop) =
300+
target_to_display_and_crop(&inputs.capture_target).context("target_display_crop")?;
301+
302+
let screen_source = ScreenCaptureConfig::<ScreenCaptureMethod>::init(
303+
display,
304+
crop,
298305
true,
299306
30,
300307
start_time,
@@ -306,7 +313,8 @@ pub async fn spawn_instant_recording_actor(
306313
#[cfg(target_os = "macos")]
307314
inputs.excluded_windows,
308315
)
309-
.await?;
316+
.await
317+
.context("screen capture init")?;
310318

311319
debug!("screen capture: {screen_source:#?}");
312320

crates/recording/src/sources/screen_capture/mod.rs

Lines changed: 10 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,17 @@ impl<TCaptureFormat: ScreenCaptureFormat> Clone for ScreenCaptureConfig<TCapture
253253
#[derive(Clone, Debug)]
254254
pub struct Config {
255255
display: DisplayId,
256-
#[cfg(windows)]
257-
crop_bounds: Option<PhysicalBounds>,
258-
#[cfg(target_os = "macos")]
259-
crop_bounds: Option<LogicalBounds>,
256+
crop_bounds: Option<CropBounds>,
260257
fps: u32,
261258
show_cursor: bool,
262259
}
263260

261+
#[cfg(target_os = "macos")]
262+
pub type CropBounds = LogicalBounds;
263+
264+
#[cfg(windows)]
265+
pub type CropBounds = PhysicalBounds;
266+
264267
impl Config {
265268
pub fn fps(&self) -> u32 {
266269
self.fps
@@ -280,7 +283,8 @@ pub enum ScreenCaptureInitError {
280283
impl<TCaptureFormat: ScreenCaptureFormat> ScreenCaptureConfig<TCaptureFormat> {
281284
#[allow(clippy::too_many_arguments)]
282285
pub async fn init(
283-
target: &ScreenCaptureTarget,
286+
display: scap_targets::Display,
287+
crop_bounds: Option<CropBounds>,
284288
show_cursor: bool,
285289
max_fps: u32,
286290
start_time: SystemTime,
@@ -291,92 +295,10 @@ impl<TCaptureFormat: ScreenCaptureFormat> ScreenCaptureConfig<TCaptureFormat> {
291295
) -> Result<Self, ScreenCaptureInitError> {
292296
cap_fail::fail!("ScreenCaptureSource::init");
293297

294-
let display = target.display().ok_or(ScreenCaptureInitError::NoDisplay)?;
295-
296298
let fps = max_fps.min(display.refresh_rate() as u32);
297299

298-
let crop_bounds = match target {
299-
ScreenCaptureTarget::Display { .. } => None,
300-
ScreenCaptureTarget::Window { id } => {
301-
let window = Window::from_id(id).ok_or(ScreenCaptureInitError::NoWindow)?;
302-
303-
#[cfg(target_os = "macos")]
304-
{
305-
let raw_display_bounds = display
306-
.raw_handle()
307-
.logical_bounds()
308-
.ok_or(ScreenCaptureInitError::NoBounds)?;
309-
let raw_window_bounds = window
310-
.raw_handle()
311-
.logical_bounds()
312-
.ok_or(ScreenCaptureInitError::NoBounds)?;
313-
314-
Some(LogicalBounds::new(
315-
LogicalPosition::new(
316-
raw_window_bounds.position().x() - raw_display_bounds.position().x(),
317-
raw_window_bounds.position().y() - raw_display_bounds.position().y(),
318-
),
319-
raw_window_bounds.size(),
320-
))
321-
}
322-
323-
#[cfg(windows)]
324-
{
325-
let raw_display_position = display
326-
.raw_handle()
327-
.physical_position()
328-
.ok_or(ScreenCaptureInitError::NoBounds)?;
329-
let raw_window_bounds = window
330-
.raw_handle()
331-
.physical_bounds()
332-
.ok_or(ScreenCaptureInitError::NoBounds)?;
333-
334-
Some(PhysicalBounds::new(
335-
PhysicalPosition::new(
336-
raw_window_bounds.position().x() - raw_display_position.x(),
337-
raw_window_bounds.position().y() - raw_display_position.y(),
338-
),
339-
raw_window_bounds.size(),
340-
))
341-
}
342-
}
343-
ScreenCaptureTarget::Area {
344-
bounds: relative_bounds,
345-
..
346-
} => {
347-
#[cfg(target_os = "macos")]
348-
{
349-
Some(*relative_bounds)
350-
}
351-
352-
#[cfg(windows)]
353-
{
354-
let raw_display_size = display
355-
.physical_size()
356-
.ok_or(ScreenCaptureInitError::NoBounds)?;
357-
let logical_display_size = display
358-
.logical_size()
359-
.ok_or(ScreenCaptureInitError::NoBounds)?;
360-
361-
Some(PhysicalBounds::new(
362-
PhysicalPosition::new(
363-
(relative_bounds.position().x() / logical_display_size.width())
364-
* raw_display_size.width(),
365-
(relative_bounds.position().y() / logical_display_size.height())
366-
* raw_display_size.height(),
367-
),
368-
PhysicalSize::new(
369-
(relative_bounds.size().width() / logical_display_size.width())
370-
* raw_display_size.width(),
371-
(relative_bounds.size().height() / logical_display_size.height())
372-
* raw_display_size.height(),
373-
),
374-
))
375-
}
376-
}
377-
};
378-
379300
let output_size = crop_bounds
301+
.clone()
380302
.and_then(|b| {
381303
#[cfg(target_os = "macos")]
382304
{

crates/recording/src/studio_recording.rs

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use crate::{
22
ActorError, MediaError, RecordingBaseInputs, RecordingError,
3-
capture_pipeline::{MakeCapturePipeline, ScreenCaptureMethod, Stop, create_screen_capture},
3+
capture_pipeline::{
4+
MakeCapturePipeline, ScreenCaptureMethod, Stop, target_to_display_and_crop,
5+
},
46
cursor::{CursorActor, Cursors, spawn_cursor_recorder},
57
feeds::{camera::CameraFeedLock, microphone::MicrophoneFeedLock},
68
ffmpeg::{Mp4Muxer, OggMuxer},
79
output_pipeline::{DoneFut, FinishedOutputPipeline, OutputPipeline, PipelineDoneError},
10+
screen_capture::ScreenCaptureConfig,
811
sources::{self, screen_capture},
912
};
1013
use anyhow::{Context as _, anyhow};
@@ -680,20 +683,15 @@ async fn create_segment_pipeline(
680683
custom_cursor_capture: bool,
681684
start_time: Timestamps,
682685
) -> anyhow::Result<Pipeline> {
683-
let display = base_inputs
684-
.capture_target
685-
.display()
686-
.ok_or(CreateSegmentPipelineError::NoDisplay)?;
687-
let crop_bounds = base_inputs
688-
.capture_target
689-
.cursor_crop()
690-
.ok_or(CreateSegmentPipelineError::NoBounds)?;
691-
692686
#[cfg(windows)]
693687
let d3d_device = crate::capture_pipeline::create_d3d_device().unwrap();
694688

695-
let screen_config = create_screen_capture(
696-
&base_inputs.capture_target,
689+
let (display, crop) =
690+
target_to_display_and_crop(&base_inputs.capture_target).context("target_display_crop")?;
691+
692+
let screen_config = ScreenCaptureConfig::<ScreenCaptureMethod>::init(
693+
display,
694+
crop,
697695
!custom_cursor_capture,
698696
120,
699697
start_time.system_time(),
@@ -706,7 +704,7 @@ async fn create_segment_pipeline(
706704
base_inputs.excluded_windows,
707705
)
708706
.await
709-
.unwrap();
707+
.context("screen capture init")?;
710708

711709
let (capture_source, system_audio) = screen_config.to_sources().await?;
712710

@@ -758,21 +756,28 @@ async fn create_segment_pipeline(
758756
.transpose()
759757
.context("microphone pipeline setup")?;
760758

761-
let cursor = custom_cursor_capture.then(move || {
762-
let cursor = spawn_cursor_recorder(
763-
crop_bounds,
764-
display,
765-
cursors_dir.to_path_buf(),
766-
prev_cursors,
767-
next_cursors_id,
768-
start_time,
769-
);
770-
771-
CursorPipeline {
772-
output_path: dir.join("cursor.json"),
773-
actor: cursor,
774-
}
775-
});
759+
let cursor = custom_cursor_capture
760+
.then(move || {
761+
let cursor_crop_bounds = base_inputs
762+
.capture_target
763+
.cursor_crop()
764+
.ok_or(CreateSegmentPipelineError::NoBounds)?;
765+
766+
let cursor = spawn_cursor_recorder(
767+
cursor_crop_bounds,
768+
display,
769+
cursors_dir.to_path_buf(),
770+
prev_cursors,
771+
next_cursors_id,
772+
start_time,
773+
);
774+
775+
Ok::<_, CreateSegmentPipelineError>(CursorPipeline {
776+
output_path: dir.join("cursor.json"),
777+
actor: cursor,
778+
})
779+
})
780+
.transpose()?;
776781

777782
info!("pipeline playing");
778783

0 commit comments

Comments
 (0)