@desklink/host 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -9
- package/dist/protocol.d.ts +9 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/docs/PROTOCOL.md +38 -23
- package/engine/Cargo.lock +2 -1
- package/engine/Cargo.toml +7 -4
- package/engine/src/capture.rs +7 -90
- package/engine/src/cursor.rs +7 -68
- package/engine/src/main.rs +10 -5
- package/engine/src/portal.rs +15 -10
- package/engine/src/protocol.rs +1 -0
- package/engine/src/session.rs +73 -228
- package/engine/src/wl_cursor.rs +367 -0
- package/package.json +3 -3
package/engine/src/session.rs
CHANGED
|
@@ -895,7 +895,7 @@ pub fn capabilities() -> serde_json::Value {
|
|
|
895
895
|
"formats": ["bgrx", "bgra", "rgbx", "rgba"],
|
|
896
896
|
"cursor": "embedded",
|
|
897
897
|
"cursor_modes": ["embedded", "hidden"],
|
|
898
|
-
"cursor_positions": {"x11": true, "
|
|
898
|
+
"cursor_positions": {"x11": true, "wayland": "ext-image-copy-capture-v1"},
|
|
899
899
|
"audio": false,
|
|
900
900
|
},
|
|
901
901
|
"encode": encode_capabilities(),
|
|
@@ -1205,8 +1205,8 @@ impl Session {
|
|
|
1205
1205
|
}
|
|
1206
1206
|
.map_err(|error| {
|
|
1207
1207
|
let message = format!("{error:#}");
|
|
1208
|
-
let code = if message.contains("
|
|
1209
|
-
"
|
|
1208
|
+
let code = if message.contains("cursor-unavailable:") {
|
|
1209
|
+
"cursor-unavailable"
|
|
1210
1210
|
} else {
|
|
1211
1211
|
"source"
|
|
1212
1212
|
};
|
|
@@ -1264,12 +1264,11 @@ impl Session {
|
|
|
1264
1264
|
)
|
|
1265
1265
|
.map_err(|error| SessionError::new("source", format!("{error:#}")))?;
|
|
1266
1266
|
#[cfg(target_os = "linux")]
|
|
1267
|
-
let capture = capture::start(
|
|
1267
|
+
let mut capture = capture::start(
|
|
1268
1268
|
portal,
|
|
1269
1269
|
width,
|
|
1270
1270
|
height,
|
|
1271
1271
|
max_fps,
|
|
1272
|
-
cursor_sink,
|
|
1273
1272
|
sink,
|
|
1274
1273
|
indicator
|
|
1275
1274
|
.as_ref()
|
|
@@ -1281,16 +1280,31 @@ impl Session {
|
|
|
1281
1280
|
)
|
|
1282
1281
|
.map_err(|error| SessionError::new("source", format!("{error:#}")))?;
|
|
1283
1282
|
#[cfg(target_os = "linux")]
|
|
1284
|
-
{
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
)
|
|
1283
|
+
if let Some(cursor_sink) = cursor_sink {
|
|
1284
|
+
// Positions are an addition to hidden capture, never a
|
|
1285
|
+
// condition of it: without them the stream still runs and
|
|
1286
|
+
// the reply says why the receiver gets no cursor.
|
|
1287
|
+
cursor_info["source"] = serde_json::json!(crate::wl_cursor::PROTOCOL);
|
|
1288
|
+
let limitation = match crate::wl_cursor::start(
|
|
1289
|
+
(source.origin_x, source.origin_y),
|
|
1290
|
+
(source_w, source_h),
|
|
1291
|
+
cursor_sink,
|
|
1292
|
+
Duration::from_secs(1),
|
|
1293
|
+
) {
|
|
1294
|
+
Ok((tracker, observed)) => {
|
|
1295
|
+
capture.cursor = Some(tracker);
|
|
1296
|
+
cursor_info["positions"] = serde_json::json!(observed);
|
|
1297
|
+
(!observed).then_some("the compositor has sent no cursor position yet: the cursor is off the captured output, the seat has no pointer, or the compositor withholds positions from this engine")
|
|
1298
|
+
}
|
|
1299
|
+
Err(error) => {
|
|
1300
|
+
log::warn!("hidden cursor capture has no cursor positions: {error:#}");
|
|
1301
|
+
Some("this compositor does not offer ext-image-copy-capture-v1 cursor sessions, so the cursor is hidden and its position is unknown")
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
if let Some(limitation) = limitation {
|
|
1305
|
+
log::warn!("{limitation}");
|
|
1306
|
+
cursor_info["limitation"] = serde_json::json!(limitation);
|
|
1292
1307
|
}
|
|
1293
|
-
cursor_info["positions"] = serde_json::json!(capture.cursor_positions());
|
|
1294
1308
|
}
|
|
1295
1309
|
#[cfg(target_os = "linux")]
|
|
1296
1310
|
if let Some(indicator) = indicator.as_mut() {
|
|
@@ -1319,7 +1333,7 @@ impl Session {
|
|
|
1319
1333
|
let encoder = Encoder::new(
|
|
1320
1334
|
width,
|
|
1321
1335
|
height,
|
|
1322
|
-
bitrate_kbps,
|
|
1336
|
+
RateControl::starting(bitrate_kbps).target,
|
|
1323
1337
|
max_fps,
|
|
1324
1338
|
available_parallelism().min(8) as u32,
|
|
1325
1339
|
)
|
|
@@ -2563,6 +2577,15 @@ struct RateControl {
|
|
|
2563
2577
|
}
|
|
2564
2578
|
|
|
2565
2579
|
impl RateControl {
|
|
2580
|
+
/// Begin below an unknown weak link's capacity, then use the existing
|
|
2581
|
+
/// loss/delay feedback growth to approach the requested ceiling.
|
|
2582
|
+
fn starting(ceiling: u32) -> Self {
|
|
2583
|
+
Self {
|
|
2584
|
+
target: ceiling.min(3000),
|
|
2585
|
+
..Self::new(ceiling)
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2566
2589
|
fn new(ceiling: u32) -> Self {
|
|
2567
2590
|
Self {
|
|
2568
2591
|
ceiling,
|
|
@@ -2659,9 +2682,9 @@ impl SendBudget {
|
|
|
2659
2682
|
}
|
|
2660
2683
|
}
|
|
2661
2684
|
|
|
2662
|
-
/// Whether motion is coded at half size. A full-size frame is slow when
|
|
2663
|
-
///
|
|
2664
|
-
/// newer frame
|
|
2685
|
+
/// Whether motion is coded at half size. A full-size frame is slow when its
|
|
2686
|
+
/// encoding or constrained-link drain time exceeds a frame interval and a
|
|
2687
|
+
/// newer frame or send-budget debt is waiting. Content that
|
|
2665
2688
|
/// arrives slower than the frame cap (30 fps into a 60 fps session) is never
|
|
2666
2689
|
/// slow however long a frame takes, as long as it is done before the next one.
|
|
2667
2690
|
/// Several slow frames that each change much of the picture (a packet over a
|
|
@@ -2677,15 +2700,31 @@ impl SendBudget {
|
|
|
2677
2700
|
struct MotionSize {
|
|
2678
2701
|
half: bool,
|
|
2679
2702
|
run: u32,
|
|
2703
|
+
link_run: u32,
|
|
2680
2704
|
}
|
|
2681
2705
|
|
|
2682
2706
|
impl MotionSize {
|
|
2683
2707
|
const SLOW_RUN: u32 = 5;
|
|
2684
2708
|
const SMALL_RUN: u32 = 30;
|
|
2685
2709
|
|
|
2686
|
-
///
|
|
2710
|
+
/// Persistent send debt needs a quicker response than a transient slow
|
|
2711
|
+
/// encode: every extra full-size motion frame extends the remote queue.
|
|
2712
|
+
fn queued(&mut self, drain: Duration, debt: bool, interval: Duration) {
|
|
2713
|
+
self.link_run = if !self.half && debt && drain > interval {
|
|
2714
|
+
self.link_run + 1
|
|
2715
|
+
} else {
|
|
2716
|
+
0
|
|
2717
|
+
};
|
|
2718
|
+
if self.link_run >= 2 {
|
|
2719
|
+
self.half = true;
|
|
2720
|
+
self.run = 0;
|
|
2721
|
+
self.link_run = 0;
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
/// Account for a motion frame serviced at the current size in `took`; `large`
|
|
2687
2726
|
/// when its packet was over a quarter of the per-frame rate budget, `behind`
|
|
2688
|
-
/// when a newer frame was
|
|
2727
|
+
/// when a newer frame or send-budget debt was waiting once it was coded.
|
|
2689
2728
|
fn coded(&mut self, took: Duration, large: bool, behind: bool, interval: Duration) {
|
|
2690
2729
|
let slow = took > interval && behind;
|
|
2691
2730
|
let (counts, needed) = if self.half {
|
|
@@ -2762,9 +2801,9 @@ fn spawn_pipeline(
|
|
|
2762
2801
|
.name("desklink-encode".into())
|
|
2763
2802
|
.spawn(move || {
|
|
2764
2803
|
let interval = frame_interval(max_fps);
|
|
2765
|
-
let mut rate = RateControl::
|
|
2804
|
+
let mut rate = RateControl::starting(bitrate_kbps);
|
|
2766
2805
|
if let Ok(mut m) = inner.metrics.lock() {
|
|
2767
|
-
m.target_kbps =
|
|
2806
|
+
m.target_kbps = rate.target;
|
|
2768
2807
|
(m.codec, m.encoder) = ("vp9", "libvpx");
|
|
2769
2808
|
}
|
|
2770
2809
|
// Whether motion may be coded at half size: VP9's alone.
|
|
@@ -2944,16 +2983,26 @@ fn spawn_pipeline(
|
|
|
2944
2983
|
Pass::Motion { half: false, .. } | Pass::Keepalive => {}
|
|
2945
2984
|
}
|
|
2946
2985
|
}
|
|
2986
|
+
budget.sent(now, packet.data.len(), rate.target);
|
|
2947
2987
|
match pass {
|
|
2948
2988
|
Pass::Motion { .. } => {
|
|
2949
2989
|
pending = false;
|
|
2950
2990
|
keyframe = false;
|
|
2951
2991
|
refined = false;
|
|
2952
2992
|
still_since = now;
|
|
2953
|
-
let
|
|
2954
|
-
let large = packet.data.len() as u64 * 4 >
|
|
2993
|
+
let frame_budget = rate.target as u64 * 1000 / 8 / max_fps.max(1) as u64;
|
|
2994
|
+
let large = packet.data.len() as u64 * 4 > frame_budget;
|
|
2955
2995
|
if halves {
|
|
2996
|
+
// A fast encoder can still outrun a constrained link.
|
|
2997
|
+
// Include the packet's drain time once the send
|
|
2998
|
+
// budget is holding frames, so sustained motion
|
|
2999
|
+
// reduces its size before a backlog builds.
|
|
3000
|
+
let drain = Duration::from_secs_f64(
|
|
3001
|
+
packet.data.len() as f64 * 8.0 / SendBudget::rate(rate.target),
|
|
3002
|
+
);
|
|
3003
|
+
let link_behind = !budget.wait(Instant::now(), rate.target).is_zero();
|
|
2956
3004
|
size.coded(took, large, frame_rx.waiting(), interval);
|
|
3005
|
+
size.queued(drain, link_behind, interval);
|
|
2957
3006
|
}
|
|
2958
3007
|
}
|
|
2959
3008
|
Pass::Refine => {
|
|
@@ -2964,7 +3013,6 @@ fn spawn_pipeline(
|
|
|
2964
3013
|
}
|
|
2965
3014
|
last_sent = Some(now);
|
|
2966
3015
|
cadence.sent(now);
|
|
2967
|
-
budget.sent(now, packet.data.len(), rate.target);
|
|
2968
3016
|
// A new picture carries the moment it was captured, so the
|
|
2969
3017
|
// receiver paces playout by the desktop's clock rather than by
|
|
2970
3018
|
// encode time; a re-coded one (refinement, keepalive) is new
|
|
@@ -3403,66 +3451,6 @@ mod tests {
|
|
|
3403
3451
|
);
|
|
3404
3452
|
}
|
|
3405
3453
|
|
|
3406
|
-
#[test]
|
|
3407
|
-
fn tile_damage_at_origin_does_not_underflow_or_hide_other_tiles() {
|
|
3408
|
-
assert_eq!(
|
|
3409
|
-
dirty_regions(&[1, 0, 0, 1], &[0; 4], 64, 64),
|
|
3410
|
-
vec![[0, 0, 32, 32], [32, 32, 32, 32]]
|
|
3411
|
-
);
|
|
3412
|
-
}
|
|
3413
|
-
|
|
3414
|
-
#[test]
|
|
3415
|
-
fn frames_that_jitter_around_the_cap_go_at_once_and_a_flood_stays_capped() {
|
|
3416
|
-
let interval = Duration::from_millis(20);
|
|
3417
|
-
let start = Instant::now();
|
|
3418
|
-
let mut cadence = Cadence::new(interval, start);
|
|
3419
|
-
// Captured at the cap, each a few ms early or late: none waits, so
|
|
3420
|
-
// none is delayed or superseded by the next.
|
|
3421
|
-
for (n, jitter) in [0i64, -6, 5, -8, 7, -3, 0, -9, 9, -5].iter().enumerate() {
|
|
3422
|
-
let at = start + interval * n as u32 + Duration::from_millis(9);
|
|
3423
|
-
let at = if *jitter < 0 {
|
|
3424
|
-
at - Duration::from_millis(jitter.unsigned_abs())
|
|
3425
|
-
} else {
|
|
3426
|
-
at + Duration::from_millis(*jitter as u64)
|
|
3427
|
-
};
|
|
3428
|
-
assert_eq!(cadence.wait(at), Duration::ZERO, "frame {n} waited");
|
|
3429
|
-
cadence.sent(at);
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
// A source far above the cap: one second of 1 ms frames sends at most
|
|
3433
|
-
// one frame over the cap.
|
|
3434
|
-
let start = start + Duration::from_secs(10);
|
|
3435
|
-
let mut cadence = Cadence::new(interval, start);
|
|
3436
|
-
let sent = (0..1000)
|
|
3437
|
-
.map(|ms| start + Duration::from_millis(ms))
|
|
3438
|
-
.filter(|&at| {
|
|
3439
|
-
let go = cadence.wait(at).is_zero();
|
|
3440
|
-
if go {
|
|
3441
|
-
cadence.sent(at);
|
|
3442
|
-
}
|
|
3443
|
-
go
|
|
3444
|
-
})
|
|
3445
|
-
.count();
|
|
3446
|
-
assert!((50..=51).contains(&sent), "sent {sent} at a 50 fps cap");
|
|
3447
|
-
}
|
|
3448
|
-
|
|
3449
|
-
#[test]
|
|
3450
|
-
fn a_small_source_is_never_upscaled() {
|
|
3451
|
-
assert_eq!(fit(1000, 600, 1280, 800), (1000, 600));
|
|
3452
|
-
}
|
|
3453
|
-
|
|
3454
|
-
#[test]
|
|
3455
|
-
fn a_large_source_is_fitted_into_the_box_with_even_dimensions() {
|
|
3456
|
-
let (w, h) = fit(2560, 1440, 1280, 800);
|
|
3457
|
-
assert_eq!((w, h), (1280, 720));
|
|
3458
|
-
assert_eq!((w % 2, h % 2), (0, 0));
|
|
3459
|
-
}
|
|
3460
|
-
|
|
3461
|
-
#[test]
|
|
3462
|
-
fn fit_keeps_the_aspect_ratio_when_the_box_is_the_constraint() {
|
|
3463
|
-
assert_eq!(fit(2560, 1440, 1200, 600), (1066, 600));
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
3454
|
#[test]
|
|
3467
3455
|
fn an_encoded_surface_pixel_maps_to_the_sources_own_pixel() {
|
|
3468
3456
|
let source = SelectedSource {
|
|
@@ -4324,149 +4312,6 @@ mod tests {
|
|
|
4324
4312
|
assert!(inner.input.lock().unwrap().is_none());
|
|
4325
4313
|
}
|
|
4326
4314
|
|
|
4327
|
-
#[test]
|
|
4328
|
-
fn a_standing_queue_backs_the_rate_off_to_what_the_path_delivers() {
|
|
4329
|
-
let start = Instant::now();
|
|
4330
|
-
let queued = |ms: u64, delivered_kbps: u32| {
|
|
4331
|
-
Some(PathReport {
|
|
4332
|
-
queue_delay: Duration::from_millis(ms),
|
|
4333
|
-
delivered_kbps,
|
|
4334
|
-
})
|
|
4335
|
-
};
|
|
4336
|
-
let mut rate = RateControl::new(8000);
|
|
4337
|
-
// A burst's worth of queue and no loss moves nothing.
|
|
4338
|
-
assert_eq!(rate.update(&[], queued(15, 3000), start), None);
|
|
4339
|
-
// A standing queue on a 4 Mbps link: 85% of what got through, at once,
|
|
4340
|
-
// with no loss reported at all.
|
|
4341
|
-
assert_eq!(rate.update(&[], queued(80, 4000), start), Some(3400));
|
|
4342
|
-
// Not again until the back-off has had time to show in the feedback.
|
|
4343
|
-
assert_eq!(
|
|
4344
|
-
rate.update(&[], queued(80, 3400), start + Duration::from_millis(100)),
|
|
4345
|
-
None
|
|
4346
|
-
);
|
|
4347
|
-
assert_eq!(
|
|
4348
|
-
rate.update(&[], queued(60, 3400), start + Duration::from_millis(300)),
|
|
4349
|
-
Some(2890)
|
|
4350
|
-
);
|
|
4351
|
-
// A stream that sent little is not taken for a slow link: at most half.
|
|
4352
|
-
assert_eq!(
|
|
4353
|
-
rate.update(&[], queued(60, 100), start + Duration::from_millis(600)),
|
|
4354
|
-
Some(1228)
|
|
4355
|
-
);
|
|
4356
|
-
// Clean loss reports do not raise it while the queue has not drained...
|
|
4357
|
-
assert_eq!(
|
|
4358
|
-
rate.update(&[0], queued(15, 1200), start + Duration::from_secs(2)),
|
|
4359
|
-
None
|
|
4360
|
-
);
|
|
4361
|
-
// ...and do once it has.
|
|
4362
|
-
assert_eq!(
|
|
4363
|
-
rate.update(&[0], queued(2, 1200), start + Duration::from_secs(3)),
|
|
4364
|
-
Some(1390)
|
|
4365
|
-
);
|
|
4366
|
-
// Nothing drops below the floor.
|
|
4367
|
-
let mut rate = RateControl::new(8000);
|
|
4368
|
-
for step in 0..20u64 {
|
|
4369
|
-
rate.update(
|
|
4370
|
-
&[],
|
|
4371
|
-
queued(200, 1),
|
|
4372
|
-
start + DELAY_BACKOFF_EVERY * step as u32,
|
|
4373
|
-
);
|
|
4374
|
-
}
|
|
4375
|
-
assert_eq!(rate.target, 1000);
|
|
4376
|
-
}
|
|
4377
|
-
|
|
4378
|
-
#[test]
|
|
4379
|
-
fn without_transport_feedback_the_rate_follows_loss_alone() {
|
|
4380
|
-
let start = Instant::now();
|
|
4381
|
-
let mut rate = RateControl::new(8000);
|
|
4382
|
-
assert_eq!(rate.update(&[], None, start), None);
|
|
4383
|
-
assert_eq!(rate.update(&[64], None, start), Some(5600));
|
|
4384
|
-
assert_eq!(
|
|
4385
|
-
rate.update(&[64], None, start + Duration::from_millis(500)),
|
|
4386
|
-
None
|
|
4387
|
-
);
|
|
4388
|
-
assert_eq!(
|
|
4389
|
-
rate.update(&[10], None, start + Duration::from_secs(1)),
|
|
4390
|
-
None
|
|
4391
|
-
);
|
|
4392
|
-
assert_eq!(
|
|
4393
|
-
rate.update(&[0], None, start + Duration::from_secs(2)),
|
|
4394
|
-
Some(6112)
|
|
4395
|
-
);
|
|
4396
|
-
}
|
|
4397
|
-
|
|
4398
|
-
#[test]
|
|
4399
|
-
fn a_large_frame_holds_the_next_until_the_link_has_carried_it() {
|
|
4400
|
-
let start = Instant::now();
|
|
4401
|
-
let mut budget = SendBudget::default();
|
|
4402
|
-
// Frames at the target, one per 20 ms at 4000 kbps, never wait, and
|
|
4403
|
-
// neither does the ordinary overshoot of a frame at twice the budget.
|
|
4404
|
-
for n in 0..50u32 {
|
|
4405
|
-
let at = start + Duration::from_millis(20) * n;
|
|
4406
|
-
assert_eq!(budget.wait(at, 4000), Duration::ZERO, "frame {n} waited");
|
|
4407
|
-
budget.sent(at, if n % 5 == 0 { 20_000 } else { 10_000 }, 4000);
|
|
4408
|
-
}
|
|
4409
|
-
// A 100 kB key frame drains in 160 ms (4000 kbps and a quarter): it
|
|
4410
|
-
// goes at once, and the next waits out all of it past the 60 ms burst
|
|
4411
|
-
// allowance.
|
|
4412
|
-
let at = start + Duration::from_secs(1);
|
|
4413
|
-
assert_eq!(budget.wait(at, 4000), Duration::ZERO);
|
|
4414
|
-
budget.sent(at, 100_000, 4000);
|
|
4415
|
-
let wait = budget.wait(at, 4000);
|
|
4416
|
-
assert!(
|
|
4417
|
-
(Duration::from_millis(99)..=Duration::from_millis(101)).contains(&wait),
|
|
4418
|
-
"waited {wait:?}"
|
|
4419
|
-
);
|
|
4420
|
-
assert_eq!(budget.wait(at + wait, 4000), Duration::ZERO);
|
|
4421
|
-
}
|
|
4422
|
-
|
|
4423
|
-
#[test]
|
|
4424
|
-
fn motion_halves_only_when_the_encoder_sets_the_pace_and_does_not_flap() {
|
|
4425
|
-
let interval = Duration::from_millis(16);
|
|
4426
|
-
let (slow, fast) = (Duration::from_millis(30), Duration::from_millis(2));
|
|
4427
|
-
let mut size = MotionSize::default();
|
|
4428
|
-
for _ in 0..MotionSize::SLOW_RUN * 4 {
|
|
4429
|
-
size.coded(slow, false, true, interval);
|
|
4430
|
-
}
|
|
4431
|
-
assert!(
|
|
4432
|
-
!size.half,
|
|
4433
|
-
"slow small frames (typing on a busy host) keep full size"
|
|
4434
|
-
);
|
|
4435
|
-
for _ in 0..MotionSize::SLOW_RUN * 4 {
|
|
4436
|
-
size.coded(slow, true, false, interval);
|
|
4437
|
-
}
|
|
4438
|
-
assert!(
|
|
4439
|
-
!size.half,
|
|
4440
|
-
"a source slower than the cap (30 fps, 30 ms encode) never halves"
|
|
4441
|
-
);
|
|
4442
|
-
for _ in 0..MotionSize::SLOW_RUN - 1 {
|
|
4443
|
-
size.coded(slow, true, true, interval);
|
|
4444
|
-
}
|
|
4445
|
-
size.coded(fast, true, true, interval);
|
|
4446
|
-
assert!(!size.half, "a broken run of slow frames keeps full size");
|
|
4447
|
-
for _ in 0..MotionSize::SLOW_RUN {
|
|
4448
|
-
size.coded(slow, true, true, interval);
|
|
4449
|
-
}
|
|
4450
|
-
assert!(size.half, "a run of slow large frames halves");
|
|
4451
|
-
// The marginal band: half-size frames that are small but still slow,
|
|
4452
|
-
// between large ones. Neither may send it back to full size.
|
|
4453
|
-
for i in 0..MotionSize::SMALL_RUN * 10 {
|
|
4454
|
-
size.coded(slow, i % 7 == 0, true, interval);
|
|
4455
|
-
assert!(size.half, "marginal half-size motion must not flap back");
|
|
4456
|
-
}
|
|
4457
|
-
for _ in 0..MotionSize::SMALL_RUN * 2 {
|
|
4458
|
-
size.coded(fast, true, true, interval);
|
|
4459
|
-
}
|
|
4460
|
-
assert!(size.half, "large half-size frames stay half, however fast");
|
|
4461
|
-
for _ in 0..MotionSize::SMALL_RUN {
|
|
4462
|
-
size.coded(fast, false, true, interval);
|
|
4463
|
-
}
|
|
4464
|
-
assert!(
|
|
4465
|
-
!size.half,
|
|
4466
|
-
"a run of small, fast half-size frames returns to full size"
|
|
4467
|
-
);
|
|
4468
|
-
}
|
|
4469
|
-
|
|
4470
4315
|
#[tokio::test]
|
|
4471
4316
|
async fn the_pipeline_caps_the_rate_codes_the_last_frame_and_refines_a_still_desktop_once() {
|
|
4472
4317
|
let (events, _events_rx) = tokio_mpsc::unbounded_channel();
|