Skip to content

Commit 407ee31

Browse files
use screen's frame rates for StreamData.framerate
- Read the refresh rate of the captured screen and assign it to the `framerate` of `StreamData`. - Optimize the waiting mechanism for the next frame based on @Drakulix's advice [advice](pop-os#152 (comment)) by prioritizing frame capture and sending it to PipeWire first. Then, calculate the time taken to capture that frame, and if we are ahead of the next frame's capture time, wait for approximately `delay_ns = target_ns - frame_took_ns`.
1 parent 0e95dd6 commit 407ee31

File tree

1 file changed

+45
-27
lines changed

1 file changed

+45
-27
lines changed

src/screencast_thread.rs

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ use pipewire::{
1717
};
1818
use spa_sys::{spa_format_video_raw_parse, spa_video_info_raw};
1919

20+
use crate::{
21+
buffer,
22+
wayland::{CaptureSource, DmabufHelper, Session, WaylandHelper},
23+
};
2024
use std::{
2125
collections::HashMap,
2226
ffi::c_void,
@@ -30,15 +34,9 @@ use wayland_client::{
3034
protocol::{wl_buffer, wl_output, wl_shm},
3135
WEnum,
3236
};
33-
use wayland_protocols::wp::input_timestamps::zv1::client::zwp_input_timestamps_v1;
34-
35-
use crate::{
36-
buffer,
37-
wayland::{CaptureSource, DmabufHelper, Session, WaylandHelper},
38-
};
3937

4038
const TIMESPEC_NSEC_PER_SEC: u32 = 1_000_000_000;
41-
const FPS_MEASURE_PERIOD_SEC: u64 = 5;
39+
const FPS_MEASURE_PERIOD_SEC: f64 = 5.;
4240

4341
pub struct ScreencastThread {
4442
node_id: u32,
@@ -87,6 +85,7 @@ struct FPSLimit {
8785
frame_last_time: Instant,
8886
fps_last_time: Instant,
8987
fps_frame_count: u64,
88+
delay_til_next_frame_ns: u64,
9089
}
9190

9291
impl FPSLimit {
@@ -95,40 +94,46 @@ impl FPSLimit {
9594
frame_last_time: Instant::now(),
9695
fps_last_time: Instant::now(),
9796
fps_frame_count: 0,
97+
delay_til_next_frame_ns: 0,
9898
}
9999
}
100100

101-
fn fps_limit_measure_start(&mut self) {
101+
fn fps_limit_measure_start(&mut self, max_fps: u32) {
102+
if max_fps <= 0 {
103+
return;
104+
}
105+
102106
self.frame_last_time = Instant::now();
103107
}
104108

105109
fn measure_fps(&mut self) {
106110
let now = Instant::now();
107111
self.fps_frame_count += 1;
108-
let elapsed_sec = (now - self.fps_last_time).as_secs();
112+
let elapsed_sec = (now - self.fps_last_time).as_secs_f64();
109113
if elapsed_sec < FPS_MEASURE_PERIOD_SEC {
110114
return;
111115
}
112116

113-
let avg_frames_per_sec = self.fps_frame_count / elapsed_sec;
114-
println!(
115-
"fps_limit: average FPS in the last {} seconds: {}",
116-
avg_frames_per_sec, elapsed_sec
117+
let avg_frames_per_sec = self.fps_frame_count as f64 / elapsed_sec;
118+
log::info!(
119+
"fps_limit: average FPS in the last {:.2} seconds: {:.2}",
120+
avg_frames_per_sec,
121+
elapsed_sec
117122
);
118123
self.fps_frame_count = 0;
119124
self.fps_last_time = now;
120125
}
121126

122-
fn wait_for_next_frame(&mut self, max_fps: u32) {
127+
fn fps_limit_measure_end(&mut self, max_fps: u32) {
128+
if max_fps <= 0 {
129+
self.delay_til_next_frame_ns = 0;
130+
return;
131+
}
123132
self.measure_fps();
124133

125134
let elapsed_ns = self.frame_last_time.elapsed().as_nanos();
126135
let target_ns = (TIMESPEC_NSEC_PER_SEC / max_fps) as u128;
127-
if target_ns > elapsed_ns {
128-
let delay_ns = (target_ns - elapsed_ns) as u64;
129-
// println!("Time til next: {}ns", delay_ns);
130-
std::thread::sleep(Duration::from_nanos(delay_ns));
131-
}
136+
self.delay_til_next_frame_ns = target_ns.saturating_sub(elapsed_ns) as u64;
132137
}
133138
}
134139

@@ -359,12 +364,14 @@ impl StreamData {
359364
fn process(&mut self, stream: &StreamRef) {
360365
let buffer = unsafe { stream.dequeue_raw_buffer() };
361366
if !buffer.is_null() {
362-
// Only wait if it not the first frame
363-
if self.seq > 0 {
364-
// Maybe there's a better way, e.g., using an event loop to wait and get a new frame
365-
self.fps_limit.wait_for_next_frame(self.framerate);
367+
if self.fps_limit.delay_til_next_frame_ns != 0 {
368+
// log::info!(
369+
// "fps_limit: wait {}ns til next frame",
370+
// self.fps_limit.delay_til_next_frame_ns
371+
// );
372+
std::thread::sleep(Duration::from_nanos(self.fps_limit.delay_til_next_frame_ns));
366373
}
367-
374+
self.fps_limit.fps_limit_measure_start(self.framerate);
368375
let wl_buffer = unsafe { &*((*buffer).user_data as *const wl_buffer::WlBuffer) };
369376
let full_damage = &[Rect {
370377
x: 0,
@@ -403,7 +410,7 @@ impl StreamData {
403410
}
404411
}
405412
unsafe { stream.queue_raw_buffer(buffer) };
406-
self.fps_limit.fps_limit_measure_start();
413+
self.fps_limit.fps_limit_measure_end(self.framerate);
407414
self.seq += 1;
408415
}
409416
}
@@ -429,14 +436,25 @@ fn start_stream(
429436

430437
let (node_id_tx, node_id_rx) = oneshot::channel();
431438

439+
// Gets framerate from screen's frame rate, and default to 0 if not available
440+
let framerate = match &capture_source {
441+
CaptureSource::Output(output) => wayland_helper
442+
.output_info(output)
443+
.and_then(|info| info.modes.into_iter().find(|mode| mode.current))
444+
.map_or(0, |mode| mode.refresh_rate as u32 / 1000),
445+
CaptureSource::Toplevel(foreign_toplevel) => wayland_helper
446+
.output_info_toplevel(foreign_toplevel)
447+
.and_then(|info| info.modes.into_iter().find(|mode| mode.current))
448+
.map_or(0, |mode| mode.refresh_rate as u32 / 1000),
449+
_ => 0,
450+
};
451+
432452
let (width, height) =
433453
match block_on(wayland_helper.capture_source_shm(capture_source.clone(), overlay_cursor)) {
434454
Some(frame) => (frame.width, frame.height),
435455
None => return Err(anyhow::anyhow!("failed to use shm capture to get size")),
436456
};
437457

438-
let framerate = 60; // Default framerate. XXX is there a better way?
439-
440458
let dmabuf_helper = wayland_helper.dmabuf();
441459

442460
let stream = pipewire::stream::Stream::new(

0 commit comments

Comments
 (0)