Skip to content

Commit 8a9ae2d

Browse files
committed
feat(server): add simple heuristic to detect video regions
If a region is updated more than 5x/seconds, use a video updater for encoding. For now, this is RemoteFx, but it will allow future tweaking. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent 877211c commit 8a9ae2d

1 file changed

Lines changed: 80 additions & 9 deletions

File tree

  • crates/ironrdp-server/src/encoder

crates/ironrdp-server/src/encoder/mod.rs

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use core::fmt;
22
use core::num::NonZeroU16;
3+
use core::time::Duration;
4+
use std::collections::HashMap;
5+
use std::time::Instant;
36

47
use anyhow::{Context, Result};
58
use ironrdp_acceptor::DesktopSize;
@@ -23,6 +26,8 @@ pub(crate) mod rfx;
2326

2427
pub(crate) use fast_path::*;
2528

29+
const VIDEO_HINT_FPS: usize = 5;
30+
2631
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2732
#[repr(u8)]
2833
enum CodecId {
@@ -58,35 +63,42 @@ pub(crate) struct UpdateEncoder {
5863
desktop_size: DesktopSize,
5964
framebuffer: Option<Framebuffer>,
6065
bitmap_updater: BitmapUpdater,
66+
video_updater: Option<BitmapUpdater>,
67+
region_update_times: HashMap<Rect, Vec<Instant>>,
6168
}
6269

6370
impl fmt::Debug for UpdateEncoder {
6471
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6572
f.debug_struct("UpdateEncoder")
6673
.field("bitmap_update", &self.bitmap_updater)
74+
.field("video_updater", &self.video_updater)
6775
.finish()
6876
}
6977
}
7078

7179
impl UpdateEncoder {
7280
#[cfg_attr(feature = "__bench", visibility::make(pub))]
7381
pub(crate) fn new(desktop_size: DesktopSize, surface_flags: CmdFlags, codecs: UpdateEncoderCodecs) -> Self {
74-
let bitmap_updater = if surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
82+
let (bitmap_updater, video_updater) = if surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
7583
let mut bitmap = BitmapUpdater::None(NoneHandler);
84+
let mut video = None;
7685

7786
if let Some((algo, id)) = codecs.remotefx {
7887
bitmap = BitmapUpdater::RemoteFx(RemoteFxHandler::new(algo, id, desktop_size));
88+
video = Some(bitmap.clone());
7989
}
8090

81-
bitmap
91+
(bitmap, video)
8292
} else {
83-
BitmapUpdater::Bitmap(BitmapHandler::new())
93+
(BitmapUpdater::Bitmap(BitmapHandler::new()), None)
8494
};
8595

8696
Self {
8797
desktop_size,
8898
framebuffer: None,
8999
bitmap_updater,
100+
video_updater,
101+
region_update_times: HashMap::new(),
90102
}
91103
}
92104

@@ -153,6 +165,35 @@ impl UpdateEncoder {
153165
Ok(UpdateFragmenter::new(UpdateCode::PositionPointer, encode_vec(&pos)?))
154166
}
155167

168+
// This is a very naive heuristic for detecting video regions
169+
// based on the number of updates in the last second.
170+
// Feel free to improve it! :)
171+
fn diff_hints(&mut self, now: Instant, off_x: usize, off_y: usize, regions: Vec<Rect>) -> Vec<HintRect> {
172+
// keep the updates from the last second
173+
for (_region, ts) in self.region_update_times.iter_mut() {
174+
ts.retain(|ts| now - *ts < Duration::from_millis(1000));
175+
}
176+
self.region_update_times.retain(|_, times| !times.is_empty());
177+
178+
let mut diffs = Vec::new();
179+
for rect in regions {
180+
let rect_root = rect.clone().add_xy(off_x, off_y);
181+
let entry = self.region_update_times.entry(rect_root).or_default();
182+
entry.push(now);
183+
184+
let hint = if entry.len() >= VIDEO_HINT_FPS {
185+
HintType::Video
186+
} else {
187+
HintType::Image
188+
};
189+
190+
let diff = HintRect::new(rect, hint);
191+
diffs.push(diff);
192+
}
193+
194+
diffs
195+
}
196+
156197
fn bitmap_diffs(&mut self, bitmap: &BitmapUpdate) -> Vec<Rect> {
157198
// TODO: we may want to make it optional for servers that already provide damaged regions
158199
const USE_DIFFS: bool = true;
@@ -202,21 +243,46 @@ impl UpdateEncoder {
202243
}
203244
}
204245

205-
async fn bitmap(&mut self, bitmap: BitmapUpdate) -> Result<UpdateFragmenter> {
246+
async fn bitmap(&mut self, bitmap: BitmapUpdate, hint: HintType) -> Result<UpdateFragmenter> {
247+
let updater = match hint {
248+
HintType::Image => &self.bitmap_updater,
249+
HintType::Video => {
250+
trace!(?bitmap, "Encoding with video hint");
251+
self.video_updater.as_ref().unwrap_or(&self.bitmap_updater)
252+
}
253+
};
206254
// Clone to satisfy spawn_blocking 'static requirement
207255
// this should be cheap, even if using bitmap, since vec![] will be empty
208-
let mut updater = self.bitmap_updater.clone();
256+
let mut updater = updater.clone();
209257
tokio::task::spawn_blocking(move || time_warn!("Encoding bitmap", 10, updater.handle(&bitmap)))
210258
.await
211259
.unwrap()
212260
}
213261
}
214262

263+
#[derive(Copy, Clone, Debug)]
264+
enum HintType {
265+
Image,
266+
Video,
267+
}
268+
269+
#[derive(Debug)]
270+
struct HintRect {
271+
rect: Rect,
272+
hint: HintType,
273+
}
274+
275+
impl HintRect {
276+
fn new(rect: Rect, hint: HintType) -> Self {
277+
Self { rect, hint }
278+
}
279+
}
280+
215281
#[derive(Debug, Default)]
216282
enum State {
217283
Start(DisplayUpdate),
218284
BitmapDiffs {
219-
diffs: Vec<Rect>,
285+
diffs: Vec<HintRect>,
220286
bitmap: BitmapUpdate,
221287
pos: usize,
222288
},
@@ -241,6 +307,8 @@ impl EncoderIter<'_> {
241307
State::Start(update) => match update {
242308
DisplayUpdate::Bitmap(bitmap) => {
243309
let diffs = encoder.bitmap_diffs(&bitmap);
310+
let diffs =
311+
encoder.diff_hints(Instant::now(), usize::from(bitmap.x), usize::from(bitmap.y), diffs);
244312
self.state = State::BitmapDiffs { diffs, bitmap, pos: 0 };
245313
continue;
246314
}
@@ -252,12 +320,14 @@ impl EncoderIter<'_> {
252320
DisplayUpdate::Resize(_) => return None,
253321
},
254322
State::BitmapDiffs { diffs, bitmap, pos } => {
255-
let Some(rect) = diffs.get(pos) else {
323+
let Some(diff) = diffs.get(pos) else {
324+
let diffs = diffs.into_iter().map(|diff| diff.rect).collect::<Vec<_>>();
256325
encoder.bitmap_update_framebuffer(bitmap, &diffs);
257326
self.state = State::Ended;
258327
return None;
259328
};
260-
let Rect { x, y, width, height } = *rect;
329+
330+
let Rect { x, y, width, height } = diff.rect;
261331
let Some(sub) = bitmap.sub(
262332
u16::try_from(x).unwrap(),
263333
u16::try_from(y).unwrap(),
@@ -267,12 +337,13 @@ impl EncoderIter<'_> {
267337
warn!("Failed to extract bitmap subregion");
268338
return None;
269339
};
340+
let hint = diff.hint;
270341
self.state = State::BitmapDiffs {
271342
diffs,
272343
bitmap,
273344
pos: pos + 1,
274345
};
275-
encoder.bitmap(sub).await
346+
encoder.bitmap(sub, hint).await
276347
}
277348
State::Ended => return None,
278349
};

0 commit comments

Comments
 (0)