Keep screencast running through size changes

This commit is contained in:
Ivan Molodetskikh
2024-06-21 11:05:28 +03:00
parent 198b5a502d
commit 589e5a600c
2 changed files with 101 additions and 47 deletions
+11 -2
View File
@@ -3271,9 +3271,18 @@ impl Niri {
continue; continue;
} }
if cast.size != size { if cast.size.get() != size {
debug!("stopping screencast due to output size change"); if cast.pending_size.get() != size {
debug!("output size changed, updating stream size");
if let Err(err) = cast.set_size(size) {
warn!("error updating stream size, stopping screencast: {err:?}");
casts_to_stop.push(cast.session_id); casts_to_stop.push(cast.session_id);
}
} else {
debug!("stream size still hasn't changed, skipping frame");
}
// Even in the successful case, we'll need to wait till the size actually changes.
continue; continue;
} }
+77 -32
View File
@@ -48,7 +48,9 @@ pub struct Cast {
_listener: StreamListener<()>, _listener: StreamListener<()>,
pub is_active: Rc<Cell<bool>>, pub is_active: Rc<Cell<bool>>,
pub output: Output, pub output: Output,
pub size: Size<i32, Physical>, pub size: Rc<Cell<Size<i32, Physical>>>,
pub pending_size: Rc<Cell<Size<i32, Physical>>>,
pub refresh: u32,
pub cursor_mode: CursorMode, pub cursor_mode: CursorMode,
pub last_frame_time: Duration, pub last_frame_time: Duration,
pub min_time_between_frames: Rc<Cell<Duration>>, pub min_time_between_frames: Rc<Cell<Duration>>,
@@ -115,12 +117,13 @@ impl PipeWire {
} }
} }
}; };
let redraw_ = redraw.clone();
let mode = output.current_mode().unwrap(); let mode = output.current_mode().unwrap();
let size = mode.size; let size = mode.size;
let transform = output.current_transform(); let transform = output.current_transform();
let size = transform.transform_size(size); let size = transform.transform_size(size);
let refresh = mode.refresh; let refresh = mode.refresh as u32;
let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new()) let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new())
.context("error creating Stream")?; .context("error creating Stream")?;
@@ -130,6 +133,8 @@ impl PipeWire {
let is_active = Rc::new(Cell::new(false)); let is_active = Rc::new(Cell::new(false));
let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO)); let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO));
let dmabufs = Rc::new(RefCell::new(HashMap::new())); let dmabufs = Rc::new(RefCell::new(HashMap::new()));
let pending_size = Rc::new(Cell::new(size));
let size = Rc::new(Cell::new(Size::from((0, 0))));
let listener = stream let listener = stream
.add_local_listener_with_user_data(()) .add_local_listener_with_user_data(())
@@ -180,6 +185,8 @@ impl PipeWire {
}) })
.param_changed({ .param_changed({
let min_time_between_frames = min_time_between_frames.clone(); let min_time_between_frames = min_time_between_frames.clone();
let size = size.clone();
let pending_size = pending_size.clone();
move |stream, (), id, pod| { move |stream, (), id, pod| {
let id = ParamType::from_raw(id); let id = ParamType::from_raw(id);
trace!(?id, "pw stream: param_changed"); trace!(?id, "pw stream: param_changed");
@@ -206,6 +213,10 @@ impl PipeWire {
format.parse(pod).unwrap(); format.parse(pod).unwrap();
trace!("pw stream: got format = {format:?}"); trace!("pw stream: got format = {format:?}");
assert_eq!(pending_size.get().w as u32, format.size().width);
assert_eq!(pending_size.get().h as u32, format.size().height);
size.set(pending_size.get());
let max_frame_rate = format.max_framerate(); let max_frame_rate = format.max_framerate();
// Subtract 0.5 ms to improve edge cases when equal to refresh rate. // Subtract 0.5 ms to improve edge cases when equal to refresh rate.
let min_frame_time = Duration::from_secs_f64( let min_frame_time = Duration::from_secs_f64(
@@ -270,7 +281,8 @@ impl PipeWire {
.add_buffer({ .add_buffer({
let dmabufs = dmabufs.clone(); let dmabufs = dmabufs.clone();
let stop_cast = stop_cast.clone(); let stop_cast = stop_cast.clone();
move |_stream, (), buffer| { let size = size.clone();
move |stream, (), buffer| {
trace!("pw stream: add_buffer"); trace!("pw stream: add_buffer");
unsafe { unsafe {
@@ -279,6 +291,8 @@ impl PipeWire {
assert!((*spa_buffer).n_datas > 0); assert!((*spa_buffer).n_datas > 0);
assert!((*spa_data).type_ & (1 << DataType::DmaBuf.as_raw()) > 0); assert!((*spa_data).type_ & (1 << DataType::DmaBuf.as_raw()) > 0);
let size = size.get();
let bo = match gbm.create_buffer_object::<()>( let bo = match gbm.create_buffer_object::<()>(
size.w as u32, size.w as u32,
size.h as u32, size.h as u32,
@@ -311,6 +325,12 @@ impl PipeWire {
assert!(dmabufs.borrow_mut().insert(fd, dmabuf).is_none()); assert!(dmabufs.borrow_mut().insert(fd, dmabuf).is_none());
} }
// During size re-negotiation, the stream sometimes just keeps running, in
// which case we may need to force a redraw once we got a newly sized buffer.
if dmabufs.borrow().len() == 1 && stream.state() == StreamState::Streaming {
redraw_();
}
} }
}) })
.remove_buffer({ .remove_buffer({
@@ -331,7 +351,58 @@ impl PipeWire {
.register() .register()
.unwrap(); .unwrap();
let object = pod::object!( let object = make_video_params(pending_size.get(), refresh);
let mut buffer = vec![];
let mut params = [make_pod(&mut buffer, object)];
stream
.connect(
Direction::Output,
None,
StreamFlags::DRIVER | StreamFlags::ALLOC_BUFFERS,
&mut params,
)
.context("error connecting stream")?;
let cast = Cast {
session_id,
stream,
_listener: listener,
is_active,
output,
size,
pending_size,
refresh,
cursor_mode,
last_frame_time: Duration::ZERO,
min_time_between_frames,
dmabufs,
};
Ok(cast)
}
}
impl Cast {
pub fn set_size(&self, size: Size<i32, Physical>) -> anyhow::Result<()> {
if self.pending_size.get() == size {
return Ok(());
}
let _span = tracy_client::span!("Cast::set_size");
self.size.set(Size::from((0, 0)));
self.pending_size.set(size);
let object = make_video_params(size, self.refresh);
let mut buffer = vec![];
let mut params = [make_pod(&mut buffer, object)];
self.stream
.update_params(&mut params)
.context("error updating stream params")
}
}
fn make_video_params(size: Size<i32, Physical>, refresh: u32) -> pod::Object {
pod::object!(
SpaTypes::ObjectParamFormat, SpaTypes::ObjectParamFormat,
ParamType::EnumFormat, ParamType::EnumFormat,
pod::property!(FormatProperties::MediaType, Id, MediaType::Video), pod::property!(FormatProperties::MediaType, Id, MediaType::Video),
@@ -361,42 +432,16 @@ impl PipeWire {
Range, Range,
Fraction, Fraction,
Fraction { Fraction {
num: refresh as u32, num: refresh,
denom: 1000 denom: 1000
}, },
Fraction { num: 1, denom: 1 }, Fraction { num: 1, denom: 1 },
Fraction { Fraction {
num: refresh as u32, num: refresh,
denom: 1000 denom: 1000
} }
), ),
);
let mut buffer = vec![];
let mut params = [make_pod(&mut buffer, object)];
stream
.connect(
Direction::Output,
None,
StreamFlags::DRIVER | StreamFlags::ALLOC_BUFFERS,
&mut params,
) )
.context("error connecting stream")?;
let cast = Cast {
session_id,
stream,
_listener: listener,
is_active,
output,
size,
cursor_mode,
last_frame_time: Duration::ZERO,
min_time_between_frames,
dmabufs,
};
Ok(cast)
}
} }
fn make_pod(buffer: &mut Vec<u8>, object: pod::Object) -> &Pod { fn make_pod(buffer: &mut Vec<u8>, object: pod::Object) -> &Pod {