Skip to content

Commit 5ea5e90

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 d8147d2 commit 5ea5e90

File tree

1 file changed

+80
-9
lines changed
  • crates/ironrdp-server/src/encoder

1 file changed

+80
-9
lines changed

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;
@@ -22,6 +25,8 @@ pub(crate) mod rfx;
2225

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

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

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

7078
impl UpdateEncoder {
7179
#[cfg_attr(feature = "__bench", visibility::make(pub))]
7280
pub(crate) fn new(desktop_size: DesktopSize, surface_flags: CmdFlags, codecs: UpdateEncoderCodecs) -> Self {
73-
let bitmap_updater = if surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
81+
let (bitmap_updater, video_updater) = if surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
7482
let mut bitmap = BitmapUpdater::None(NoneHandler);
83+
let mut video = None;
7584

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

80-
bitmap
90+
(bitmap, video)
8191
} else {
82-
BitmapUpdater::Bitmap(BitmapHandler::new())
92+
(BitmapUpdater::Bitmap(BitmapHandler::new()), None)
8393
};
8494

8595
Self {
8696
desktop_size,
8797
framebuffer: None,
8898
bitmap_updater,
99+
video_updater,
100+
region_update_times: HashMap::new(),
89101
}
90102
}
91103

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

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

@@ -200,21 +241,46 @@ impl UpdateEncoder {
200241
}
201242
}
202243

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

261+
#[derive(Copy, Clone, Debug)]
262+
enum HintType {
263+
Image,
264+
Video,
265+
}
266+
267+
#[derive(Debug)]
268+
struct HintRect {
269+
rect: Rect,
270+
hint: HintType,
271+
}
272+
273+
impl HintRect {
274+
fn new(rect: Rect, hint: HintType) -> Self {
275+
Self { rect, hint }
276+
}
277+
}
278+
213279
#[derive(Debug, Default)]
214280
enum State {
215281
Start(DisplayUpdate),
216282
BitmapDiffs {
217-
diffs: Vec<Rect>,
283+
diffs: Vec<HintRect>,
218284
bitmap: BitmapUpdate,
219285
pos: usize,
220286
},
@@ -239,6 +305,8 @@ impl EncoderIter<'_> {
239305
State::Start(update) => match update {
240306
DisplayUpdate::Bitmap(bitmap) => {
241307
let diffs = encoder.bitmap_diffs(&bitmap);
308+
let diffs =
309+
encoder.diff_hints(Instant::now(), usize::from(bitmap.x), usize::from(bitmap.y), diffs);
242310
self.state = State::BitmapDiffs { diffs, bitmap, pos: 0 };
243311
continue;
244312
}
@@ -250,12 +318,14 @@ impl EncoderIter<'_> {
250318
DisplayUpdate::Resize(_) => return None,
251319
},
252320
State::BitmapDiffs { diffs, bitmap, pos } => {
253-
let Some(rect) = diffs.get(pos) else {
321+
let Some(diff) = diffs.get(pos) else {
322+
let diffs = diffs.into_iter().map(|diff| diff.rect).collect::<Vec<_>>();
254323
encoder.bitmap_update_framebuffer(bitmap, &diffs);
255324
self.state = State::Ended;
256325
return None;
257326
};
258-
let Rect { x, y, width, height } = *rect;
327+
328+
let Rect { x, y, width, height } = diff.rect;
259329
let Some(sub) = bitmap.sub(
260330
u16::try_from(x).unwrap(),
261331
u16::try_from(y).unwrap(),
@@ -265,12 +335,13 @@ impl EncoderIter<'_> {
265335
warn!("Failed to extract bitmap subregion");
266336
return None;
267337
};
338+
let hint = diff.hint;
268339
self.state = State::BitmapDiffs {
269340
diffs,
270341
bitmap,
271342
pos: pos + 1,
272343
};
273-
encoder.bitmap(sub).await
344+
encoder.bitmap(sub, hint).await
274345
}
275346
State::Ended => return None,
276347
};

0 commit comments

Comments
 (0)