@desklink/host 0.1.1 → 0.3.0

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.
Files changed (78) hide show
  1. package/NOTICE +23 -0
  2. package/README.md +107 -55
  3. package/bin/desklink-host.mjs +2 -2
  4. package/dist/bridge.d.ts +1 -0
  5. package/dist/bridge.d.ts.map +1 -1
  6. package/dist/bridge.js +12 -4
  7. package/dist/bridge.js.map +1 -1
  8. package/dist/engineProcess.d.ts.map +1 -1
  9. package/dist/engineProcess.js +1 -0
  10. package/dist/engineProcess.js.map +1 -1
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/keeper.d.ts +52 -0
  16. package/dist/keeper.d.ts.map +1 -0
  17. package/dist/keeper.js +101 -0
  18. package/dist/keeper.js.map +1 -0
  19. package/dist/protocol.d.ts +26 -0
  20. package/dist/protocol.d.ts.map +1 -1
  21. package/dist/protocol.js.map +1 -1
  22. package/dist/resolveEngine.d.ts +3 -3
  23. package/dist/resolveEngine.d.ts.map +1 -1
  24. package/dist/resolveEngine.js +24 -11
  25. package/dist/resolveEngine.js.map +1 -1
  26. package/docs/BOUNDARY.md +20 -8
  27. package/docs/PROTOCOL.md +347 -24
  28. package/engine/Cargo.lock +47 -1
  29. package/engine/Cargo.toml +13 -2
  30. package/engine/build.rs +28 -2
  31. package/engine/native/nvenc_shim.c +453 -0
  32. package/engine/native/vaapi_shim.c +727 -0
  33. package/engine/native/vpx_shim.c +54 -4
  34. package/engine/native/vt_shim.c +448 -0
  35. package/engine/src/capture.rs +75 -55
  36. package/engine/src/capture_mac.rs +24 -14
  37. package/engine/src/convert.rs +286 -61
  38. package/engine/src/encoder.rs +122 -13
  39. package/engine/src/h264.rs +188 -0
  40. package/engine/src/h264_nvenc.rs +308 -0
  41. package/engine/src/h264_openh264.rs +735 -0
  42. package/engine/src/h264_vaapi.rs +263 -0
  43. package/engine/src/h264_vt.rs +368 -0
  44. package/engine/src/keeper.rs +622 -0
  45. package/engine/src/mac.rs +9 -6
  46. package/engine/src/main.rs +64 -4
  47. package/engine/src/peer.rs +887 -42
  48. package/engine/src/protocol.rs +153 -4
  49. package/engine/src/session.rs +1547 -137
  50. package/engine/src/x11.rs +210 -9
  51. package/engine/src/x11_mac.rs +12 -0
  52. package/engine/vendor/libva/COPYING +19 -0
  53. package/engine/vendor/libva/va/va.h +5450 -0
  54. package/engine/vendor/libva/va/va_dec_av1.h +695 -0
  55. package/engine/vendor/libva/va/va_dec_hevc.h +616 -0
  56. package/engine/vendor/libva/va/va_dec_jpeg.h +157 -0
  57. package/engine/vendor/libva/va/va_dec_vp8.h +249 -0
  58. package/engine/vendor/libva/va/va_dec_vp9.h +312 -0
  59. package/engine/vendor/libva/va/va_dec_vvc.h +688 -0
  60. package/engine/vendor/libva/va/va_drm.h +61 -0
  61. package/engine/vendor/libva/va/va_enc_av1.h +1010 -0
  62. package/engine/vendor/libva/va/va_enc_h264.h +659 -0
  63. package/engine/vendor/libva/va/va_enc_hevc.h +958 -0
  64. package/engine/vendor/libva/va/va_enc_jpeg.h +162 -0
  65. package/engine/vendor/libva/va/va_enc_mpeg2.h +308 -0
  66. package/engine/vendor/libva/va/va_enc_vp8.h +350 -0
  67. package/engine/vendor/libva/va/va_enc_vp9.h +639 -0
  68. package/engine/vendor/libva/va/va_fei.h +170 -0
  69. package/engine/vendor/libva/va/va_fei_h264.h +505 -0
  70. package/engine/vendor/libva/va/va_fei_hevc.h +254 -0
  71. package/engine/vendor/libva/va/va_prot.h +525 -0
  72. package/engine/vendor/libva/va/va_version.h +87 -0
  73. package/engine/vendor/libva/va/va_vpp.h +1590 -0
  74. package/engine/vendor/nv-codec-headers/LICENSE +24 -0
  75. package/engine/vendor/nv-codec-headers/README +7 -0
  76. package/engine/vendor/nv-codec-headers/include/ffnvcodec/nvEncodeAPI.h +3825 -0
  77. package/examples/reference-client.html +1 -1
  78. package/package.json +3 -2
@@ -89,6 +89,26 @@ pub enum SourceRequest {
89
89
  #[serde(default)]
90
90
  display: Option<String>,
91
91
  },
92
+ /// Video the consumer already encoded, fed access unit by access unit with
93
+ /// `session.feed`. The engine has no capture backend, no encoder and no
94
+ /// input backend for this source: it packetizes what it is handed and
95
+ /// forwards control messages back to the consumer, which owns the device.
96
+ Encoded {
97
+ codec: EncodedCodec,
98
+ /// The stream's own pixel size. The consumer is the only party that
99
+ /// knows it, and nothing here is scaled: the client aims at these
100
+ /// pixels and the consumer maps them to its device.
101
+ width: usize,
102
+ height: usize,
103
+ },
104
+ }
105
+
106
+ /// A codec the consumer may feed. Only H.264 is accepted today, because it is
107
+ /// what the device helpers emit and what the clients can already decode.
108
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
109
+ #[serde(rename_all = "lowercase")]
110
+ pub enum EncodedCodec {
111
+ H264,
92
112
  }
93
113
 
94
114
  #[derive(Debug, Deserialize)]
@@ -119,6 +139,15 @@ pub struct OpenParams {
119
139
  /// Show local, capture-excluded feedback for an agent controlling this session.
120
140
  #[serde(default)]
121
141
  pub agent_indicator: bool,
142
+ /// Keep the latest pre-encode frame for `session.frame` and emit
143
+ /// `session.frame.changed`. A consumer that only streams turns it off, and
144
+ /// the engine then spends nothing per frame on either.
145
+ #[serde(default = "default_local_frames")]
146
+ pub local_frames: bool,
147
+ }
148
+
149
+ fn default_local_frames() -> bool {
150
+ true
122
151
  }
123
152
 
124
153
  /// The encode box when the consumer names none: a desktop's own pixels up to
@@ -133,8 +162,10 @@ fn default_max_height() -> usize {
133
162
  fn default_bitrate() -> u32 {
134
163
  0
135
164
  }
165
+ /// A still desktop costs nothing at 60: frames are only produced on change,
166
+ /// so consumers get the smooth path without naming a rate.
136
167
  fn default_fps() -> u32 {
137
- 30
168
+ 60
138
169
  }
139
170
 
140
171
  #[derive(Debug, Deserialize)]
@@ -154,6 +185,44 @@ pub struct FrameParams {
154
185
  pub region: Option<[usize; 4]>,
155
186
  }
156
187
 
188
+ /// One access unit of a consumer-encoded stream.
189
+ ///
190
+ /// Base64 inside the same JSON line keeps one ordered channel and one fd: one
191
+ /// second of a 2 Mbit/s stream costs about 1 ms to decode here and 0.09 ms to
192
+ /// encode on the consumer's side, against the 5 % of a core that would have
193
+ /// justified a second fd (see `docs/PROTOCOL.md`, "Encoded sources").
194
+ #[derive(Debug, Deserialize)]
195
+ pub struct FeedParams {
196
+ pub session_id: String,
197
+ /// True for an IDR access unit. The first one also carries the SPS whose
198
+ /// profile-level-id the offer negotiates.
199
+ pub keyframe: bool,
200
+ pub data_b64: String,
201
+ }
202
+
203
+ /// The largest access unit this protocol accepts, once decoded. A 4K key frame
204
+ /// at a quality worth sending is a few hundred kilobytes; this refuses a request
205
+ /// that is not a picture rather than acting as a rate limit.
206
+ pub const MAX_ACCESS_UNIT_BYTES: usize = 8 * 1024 * 1024;
207
+
208
+ impl FeedParams {
209
+ /// The access unit's own bytes, or why this request is not one.
210
+ pub fn access_unit(&self) -> std::result::Result<Vec<u8>, String> {
211
+ let data = base64::Engine::decode(
212
+ &base64::engine::general_purpose::STANDARD,
213
+ self.data_b64.as_bytes(),
214
+ )
215
+ .map_err(|error| format!("data_b64 is not base64: {error}"))?;
216
+ if data.len() > MAX_ACCESS_UNIT_BYTES {
217
+ return Err(format!(
218
+ "an access unit is {} bytes; the limit is {MAX_ACCESS_UNIT_BYTES}",
219
+ data.len()
220
+ ));
221
+ }
222
+ Ok(data)
223
+ }
224
+ }
225
+
157
226
  #[derive(Debug, Deserialize)]
158
227
  pub struct SessionRef {
159
228
  pub session_id: String,
@@ -202,7 +271,7 @@ pub struct ClipboardParams {
202
271
  /// `seq` is the client's own ordering. The engine refuses a repeat or a lower
203
272
  /// value rather than re-applying an action, so a replayed frame cannot move the
204
273
  /// pointer twice.
205
- #[derive(Debug, Deserialize)]
274
+ #[derive(Debug, Deserialize, Serialize)]
206
275
  #[serde(tag = "kind", rename_all = "snake_case")]
207
276
  pub enum ControlMessage {
208
277
  Pointer {
@@ -274,7 +343,7 @@ impl ControlMessage {
274
343
  }
275
344
  }
276
345
 
277
- #[derive(Debug, Clone, Copy, Deserialize)]
346
+ #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
278
347
  #[serde(rename_all = "snake_case")]
279
348
  pub enum PointerPhase {
280
349
  Move,
@@ -308,6 +377,7 @@ pub enum ControlReply<'a> {
308
377
  },
309
378
  Revoked {
310
379
  reason: &'a str,
380
+ code: &'a str,
311
381
  },
312
382
  }
313
383
 
@@ -355,6 +425,84 @@ mod tests {
355
425
  ));
356
426
  }
357
427
 
428
+ #[test]
429
+ fn an_encoded_source_names_its_codec_and_its_own_size() {
430
+ let request: SourceRequest = serde_json::from_value(serde_json::json!({
431
+ "kind": "encoded",
432
+ "codec": "h264",
433
+ "width": 486,
434
+ "height": 1080
435
+ }))
436
+ .unwrap();
437
+ assert!(matches!(
438
+ request,
439
+ SourceRequest::Encoded {
440
+ codec: EncodedCodec::H264,
441
+ width: 486,
442
+ height: 1080
443
+ }
444
+ ));
445
+ }
446
+
447
+ #[test]
448
+ fn an_unknown_encoded_codec_is_refused_rather_than_guessed() {
449
+ assert!(serde_json::from_value::<SourceRequest>(serde_json::json!({
450
+ "kind": "encoded",
451
+ "codec": "av1",
452
+ "width": 486,
453
+ "height": 1080
454
+ }))
455
+ .is_err());
456
+ }
457
+
458
+ #[test]
459
+ fn a_feed_request_is_one_access_unit_with_its_own_keyframe_flag() {
460
+ let params: FeedParams =
461
+ serde_json::from_str(r#"{"session_id":"s","keyframe":true,"data_b64":"AAAA"}"#)
462
+ .unwrap();
463
+ assert_eq!(params.session_id, "s");
464
+ assert!(params.keyframe, "a feed says whether its unit is an IDR");
465
+ assert_eq!(params.data_b64, "AAAA");
466
+ assert_eq!(params.access_unit().unwrap(), vec![0x00, 0x00, 0x00]);
467
+ }
468
+
469
+ #[test]
470
+ fn a_feed_request_that_is_not_base64_is_refused() {
471
+ let params = FeedParams {
472
+ session_id: String::from("s"),
473
+ keyframe: false,
474
+ data_b64: String::from("not base64!!"),
475
+ };
476
+ assert!(params.access_unit().is_err());
477
+ }
478
+
479
+ /// The reason this protocol carries bytes in base64 rather than adding a
480
+ /// second inherited fd: at 2 Mbit/s it costs a fraction of a per cent of a
481
+ /// core, against a whole second channel to keep ordered and bounded. The
482
+ /// bound below is deliberately loose — it is a sanity check on the order of
483
+ /// magnitude, not a benchmark, so it does not flake on a loaded machine.
484
+ #[test]
485
+ fn decoding_a_two_megabit_stream_is_nowhere_near_a_core() {
486
+ // One second of a 2 Mbit/s stream, in access units.
487
+ let unit: Vec<u8> = (0..8_000u32).map(|index| index as u8).collect();
488
+ let encoded = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &unit);
489
+ let params = FeedParams {
490
+ session_id: String::from("s"),
491
+ keyframe: false,
492
+ data_b64: encoded,
493
+ };
494
+ let started = std::time::Instant::now();
495
+ for _ in 0..(250_000 / unit.len()) {
496
+ assert_eq!(params.access_unit().unwrap().len(), unit.len());
497
+ }
498
+ let elapsed = started.elapsed();
499
+ assert!(
500
+ elapsed < std::time::Duration::from_millis(500),
501
+ "a second of 2 Mbit/s base64 took {elapsed:?}, which is far past 5 % of a core",
502
+ );
503
+ println!("a second of 2 Mbit/s stream decoded in {elapsed:?}");
504
+ }
505
+
358
506
  #[test]
359
507
  fn open_defaults_keep_the_desktops_own_pixels_up_to_4k() {
360
508
  let params: OpenParams =
@@ -365,7 +513,8 @@ mod tests {
365
513
  params.bitrate_kbps, 0,
366
514
  "the engine sizes the rate to the surface"
367
515
  );
368
- assert_eq!(params.max_fps, 30);
516
+ assert_eq!(params.max_fps, 60);
517
+ assert!(params.local_frames, "session.frame works unless turned off");
369
518
  assert!(
370
519
  !params.agent_indicator,
371
520
  "human control has no overlay by default"