-
Notifications
You must be signed in to change notification settings - Fork 8
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
Screen tearing and input delay #10
Comments
Hi @Advait1306 I'll be available about one day later BRs |
Hi @Advait1306 , Serval steps to improve the streaming quality:
Hope that will be of help to you BRs |
A simple example about the Note that it's also recommended to use one thread to handle input events and one thread to handle output events use anyhow::{Context, Result};
use minifb::{Key, Window, WindowOptions};
use tokio::{self, net::TcpStream};
use tracing::Level;
use vnc::{PixelFormat, Rect, VncConnector, VncEvent, X11Event};
struct CanvasUtils {
window: Window,
video: Vec<u32>,
width: u32,
height: u32,
}
impl CanvasUtils {
fn new() -> Result<Self> {
Ok(Self {
window: Window::new(
"mstsc-rs Remote Desktop in Rust",
800_usize,
600_usize,
WindowOptions::default(),
)
.with_context(|| "Unable to create window".to_string())?,
video: vec![],
width: 800,
height: 600,
})
}
fn init(&mut self, width: u32, height: u32) -> Result<()> {
let mut window = Window::new(
"mstsc-rs Remote Desktop in Rust",
width as usize,
height as usize,
WindowOptions::default(),
)
.with_context(|| "Unable to create window")?;
window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
self.window = window;
self.width = width;
self.height = height;
self.video.resize(height as usize * width as usize, 0);
Ok(())
}
fn draw(&mut self, rect: Rect, data: Vec<u8>) -> Result<()> {
// println!("width: {} height: {}", rect.width, rect.height);
let bytes_per_pixel = 4;
let grouped_pix: Vec<_> = data.chunks_exact(bytes_per_pixel).collect();
let converted_data = grouped_pix
.iter()
.map(|x| u32::from_le_bytes(x[0..bytes_per_pixel].try_into().unwrap()) & 0x00_ff_ff_ff)
.collect::<Vec<_>>();
for y in 0..rect.height as usize {
let start = (rect.y as usize + y) * self.width as usize + rect.x as usize;
let converted_slice =
&converted_data[y * rect.width as usize..(y + 1) * rect.width as usize];
self.video[start..start + rect.width as usize].copy_from_slice(&converted_slice);
}
Ok(())
}
fn flush(&mut self) -> Result<()> {
self.window
.update_with_buffer(&self.video, self.width as usize, self.height as usize)
.with_context(|| "Unable to update screen buffer")?;
Ok(())
}
fn copy(&mut self, dst: Rect, src: Rect) -> Result<()> {
println!("Copy");
let mut tmp = vec![0; src.width as usize * src.height as usize];
let mut tmp_idx = 0;
for y in 0..src.height as usize {
let mut s_idx = (src.y as usize + y) * self.width as usize + src.x as usize;
for _ in 0..src.width {
tmp[tmp_idx] = self.video[s_idx];
tmp_idx += 1;
s_idx += 1;
}
}
tmp_idx = 0;
for y in 0..src.height as usize {
let mut d_idx = (dst.y as usize + y) * self.width as usize + dst.x as usize;
for _ in 0..src.width {
self.video[d_idx] = tmp[tmp_idx];
tmp_idx += 1;
d_idx += 1;
}
}
Ok(())
}
fn close(&self) {}
fn test(&mut self) {
self.init(1920, 1080);
}
fn hande_vnc_event(&mut self, event: VncEvent) -> Result<()> {
match event {
VncEvent::SetResolution(screen) => {
tracing::info!("Resize {:?}", screen);
self.init(screen.width as u32, screen.height as u32)?
}
VncEvent::RawImage(rect, data) => {
self.draw(rect, data)?;
}
VncEvent::Bell => {
tracing::warn!("Bell event got, but ignore it");
}
VncEvent::SetPixelFormat(_) => unreachable!(),
VncEvent::Copy(dst, src) => {
self.copy(dst, src)?;
}
VncEvent::JpegImage(_rect, _data) => {
tracing::warn!("Jpeg event got, but ignore it");
}
VncEvent::SetCursor(rect, data) => {
if rect.width != 0 {
self.draw(rect, data)?;
}
}
VncEvent::Text(string) => {
tracing::info!("Got clipboard message {}", string);
}
_ => tracing::error!("{:?}", event),
}
Ok(())
}
}
struct MouseUtil {
pub mask: u8,
pub x: u16,
pub y: u16,
}
impl MouseUtil {
fn new() -> Self {
Self {
mask: 0,
x: 0,
y: 0,
}
}
fn changed(&mut self, window: &Window) -> bool {
let mut x = 0;
let mut y = 0;
window.get_mouse_pos(minifb::MouseMode::Clamp).map(|mouse| {
// println!("Mouse position: x {} y {}", mouse.0 as u16, mouse.1 as u16);
x = mouse.0 as u16;
y = mouse.1 as u16;
});
// canvas.window.get_scroll_wheel().map(|scroll| {
// println!("scrolling - x {} y {}", scroll.0, scroll.1);
// });
let left_down = window.get_mouse_down(minifb::MouseButton::Left);
// println!("is left down? {}", left_down);
let right_down = window.get_mouse_down(minifb::MouseButton::Right);
// println!("is right down? {}", right_down);
let middle_down = window.get_mouse_down(minifb::MouseButton::Middle);
// println!("is middle down? {}", middle_down);
let mut mask: u8 = 0;
if left_down {
mask |= 1;
}
if middle_down {
mask |= 1 << 1
}
if right_down {
mask |= 1 << 2
}
if (self.x, self.y, self.mask) != (x, y, mask) {
(self.x, self.y, self.mask) = (x, y, mask);
true
} else {
false
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Create tracing subscriber
#[cfg(debug_assertions)]
let subscriber = tracing_subscriber::fmt()
.pretty()
.with_max_level(Level::ERROR)
.finish();
#[cfg(not(debug_assertions))]
let subscriber = tracing_subscriber::fmt()
.pretty()
.with_max_level(Level::INFO)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
let tcp = TcpStream::connect("192.168.1.185:5900").await?;
let vnc = VncConnector::new(tcp)
.set_auth_method(async move { Ok("123456".to_string()) })
.add_encoding(vnc::VncEncoding::Tight)
.add_encoding(vnc::VncEncoding::Zrle)
.add_encoding(vnc::VncEncoding::CopyRect)
.add_encoding(vnc::VncEncoding::Raw)
.allow_shared(true)
.set_pixel_format(PixelFormat::bgra())
.build()?
.try_start()
.await?
.finish()?;
let mut canvas = CanvasUtils::new()?;
let mut mouse = MouseUtil::new();
// canvas.test();
let mut now = std::time::Instant::now();
let mut pressed_keys = Vec::<u32>::new();
loop {
let mut events = Vec::<X11Event>::new();
match vnc.poll_event().await {
Ok(Some(e)) => {
let _ = canvas.hande_vnc_event(e);
}
Ok(None) => (),
Err(e) => {
tracing::error!("{}", e.to_string());
break;
}
}
// canvas
// .window
// .get_keys_pressed(minifb::KeyRepeat::No)
// .iter()
// .for_each(|key| {
// let converted_key = convert_key_to_u32(*key);
// if !pressed_keys.contains(&converted_key) {
// println!("Key pressed: {:?}", key);
// pressed_keys.push(converted_key);
// events.push(X11Event::KeyEvent((converted_key, true).into()))
// }
// });
// canvas.window.get_keys_released().iter().for_each(|key| {
// let converted_key = convert_key_to_u32(*key);
// if pressed_keys.contains(&converted_key) {
// println!("Key released: {:?}", key);
// pressed_keys.retain(|&x| x != converted_key);
// events.push(X11Event::KeyEvent((convert_key_to_u32(*key), false).into()))
// }
// });
if mouse.changed(&canvas.window) {
let _ = vnc
.input(X11Event::PointerEvent(
(mouse.x, mouse.y, mouse.mask).into(),
))
.await;
}
if now.elapsed().as_millis() > 16 {
// Add code for receiver of input events
let _ = canvas.flush();
let _ = vnc.input(X11Event::Refresh).await;
now = std::time::Instant::now();
}
}
canvas.close();
let _ = vnc.close().await;
Ok(())
} |
Hey thank you so much for these improvements. I'll look into implementing these over the weekend and getting back to you with the results. |
Hey sorry for the late updates, I finally transferred the keyboard and button press events to another thread & that improved the performance a lot. Is there a way for me to set the quality of output? |
I guess you want to change the resolution of the vnc client? BRs. |
So when I use noVnc for my client I get an option to set This is the description of that property:
What could be the equivalent in this library? |
Nope, actually from my pov it seems this option only affects the "JPEG" encoding which we currently don't support. BRs |
Okay got it, I was looking for that option. I'll try implementing it myself and raise a PR. If I have any doubts I'll create an issue and we can continue the conversation there. Any links to documentation that can help me? |
Well, it might be not so difficult if you want to have a try.
If any questions, please feel free to ask. BRs. |
And there's one thing that I'm not sure about: The uncompressed image data in JPEG encoding may be rendered in a different way compared with the other encodings. All other encodings have the uncompressed data represent a sequence of 32-bit pixels but the JPEG encoding perhaps represents a jpeg picture, which may need further configuration on the client side. You may test whether |
I made changes to the example given in the library although I'm facing a lot of tearing and input lag issues. I have attached a video as well that you can check it out.
In the draw function I changed the way bytes were written to the video buffer, although that doesn't seem to improve anything.
Screen.Recording.2024-01-19.at.2.16.57.PM.mov
The text was updated successfully, but these errors were encountered: