@desklink/host 0.5.14 → 0.5.16

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.
@@ -0,0 +1,99 @@
1
+ //! `desklink-host diagnose`: the machine facts behind `desklink-host doctor`.
2
+ //!
3
+ //! Every check here is a question, not an action: the X server is connected to
4
+ //! and read, the portal's version properties are fetched, and an encoder codes
5
+ //! one blank 320x240 frame. Nothing opens a portal session, asks for consent,
6
+ //! captures a desktop or creates an input device. `doctor` in the Node CLI adds
7
+ //! the package facts and decides which answer a person needs to act on.
8
+
9
+ use anyhow::Result;
10
+ use serde_json::{json, Value};
11
+
12
+ pub async fn run() -> Result<()> {
13
+ let session = if std::env::var_os("WAYLAND_DISPLAY").is_some() {
14
+ "wayland"
15
+ } else if std::env::var_os("DISPLAY").is_some() {
16
+ "x11"
17
+ } else {
18
+ "none"
19
+ };
20
+ let x11 = if session == "x11" { probe_x11() } else { Value::Null };
21
+ let wayland = if session == "wayland" {
22
+ probe_wayland().await
23
+ } else {
24
+ Value::Null
25
+ };
26
+ // `prepare` rather than `probe`: on a machine with no hardware encoder the
27
+ // engine fetches Cisco's openh264 once at first need, so that is the answer
28
+ // a session would get. It is a bounded download, not a session or a prompt.
29
+ let encoder = crate::h264::prepare().map(|info| {
30
+ json!({ "name": info.encoder, "hardware": info.hardware })
31
+ });
32
+ println!(
33
+ "{}",
34
+ serde_json::to_string(&json!({
35
+ "version": env!("CARGO_PKG_VERSION"),
36
+ "session": session,
37
+ "x11": x11,
38
+ "wayland": wayland,
39
+ "encoder": encoder,
40
+ "uinput": crate::input::probe().is_ok(),
41
+ }))?
42
+ );
43
+ Ok(())
44
+ }
45
+
46
+ /// Connect exactly as the capture backend would, then report the two
47
+ /// extensions it wants and XTest for input.
48
+ fn probe_x11() -> Value {
49
+ match crate::x11::X11Desktop::connect(None) {
50
+ Ok(desktop) => json!({
51
+ "connected": true,
52
+ "display": std::env::var("DISPLAY").ok(),
53
+ "width": desktop.width,
54
+ "height": desktop.height,
55
+ "shm": desktop.capture_path() == "shm",
56
+ "damage": desktop.damage_armed(),
57
+ "xtest": desktop.input_available(),
58
+ }),
59
+ Err(error) => json!({ "connected": false, "error": format!("{error:#}") }),
60
+ }
61
+ }
62
+
63
+ /// Ask the session portal whether its ScreenCast and RemoteDesktop interfaces
64
+ /// are there. Building these proxies reads the interface's `version` property;
65
+ /// it creates no session and cannot prompt. The PipeWire socket is the capture
66
+ /// backend's own half of a screen share.
67
+ async fn probe_wayland() -> Value {
68
+ let screencast = ashpd::desktop::screencast::Screencast::new().await;
69
+ let remote_desktop = ashpd::desktop::remote_desktop::RemoteDesktop::new().await;
70
+ json!({
71
+ "screencast": screencast.is_ok(),
72
+ "screencast_error": screencast.err().map(|error| error.to_string()),
73
+ "remote_desktop": remote_desktop.is_ok(),
74
+ "remote_desktop_error": remote_desktop.err().map(|error| error.to_string()),
75
+ "pipewire": pipewire_socket(),
76
+ })
77
+ }
78
+
79
+ /// Whether the session PipeWire socket is present. A portal screencast hands
80
+ /// back a remote on this socket, so a portal without one cannot deliver frames.
81
+ fn pipewire_socket() -> bool {
82
+ let runtime = std::env::var_os("PIPEWIRE_RUNTIME_DIR")
83
+ .filter(|path| !path.is_empty())
84
+ .or_else(|| std::env::var_os("XDG_RUNTIME_DIR"))
85
+ .map(std::path::PathBuf::from);
86
+ let Some(directory) = runtime else {
87
+ return false;
88
+ };
89
+ let remote = std::env::var("PIPEWIRE_REMOTE")
90
+ .ok()
91
+ .filter(|remote| !remote.is_empty())
92
+ .unwrap_or_else(|| String::from("pipewire-0"));
93
+ let remote = std::path::Path::new(&remote);
94
+ if remote.is_absolute() {
95
+ remote.exists()
96
+ } else {
97
+ directory.join(remote).exists()
98
+ }
99
+ }
@@ -263,51 +263,3 @@ impl std::fmt::Debug for Encoder {
263
263
  )
264
264
  }
265
265
  }
266
-
267
- #[cfg(test)]
268
- mod tests {
269
- use super::*;
270
-
271
- fn blank(width: usize, height: usize) -> I420 {
272
- I420 {
273
- width,
274
- height,
275
- data: vec![128u8; width * height + 2 * (width / 2) * (height / 2)],
276
- }
277
- }
278
-
279
- #[test]
280
- fn encodes_successive_frames_and_refuses_a_mismatched_geometry() {
281
- let (w, h) = (64usize, 64usize);
282
- let mut encoder = Encoder::new(w, h, 500, 30, 2).expect("encoder should start");
283
- assert!(!encoder
284
- .encode(&blank(w, h), true, false)
285
- .unwrap()
286
- .data
287
- .is_empty());
288
- assert!(!encoder
289
- .encode(&blank(w, h), false, false)
290
- .unwrap()
291
- .data
292
- .is_empty());
293
- assert!(encoder.encode(&blank(32, 32), false, false).is_err());
294
- }
295
-
296
- #[test]
297
- fn a_half_size_run_starts_and_ends_on_a_key_frame() {
298
- let (w, h) = (1280usize, 720usize);
299
- let mut encoder = Encoder::new(w, h, 2000, 60, 2).expect("encoder should start");
300
- assert!(encoder.encode(&blank(w, h), true, false).unwrap().keyframe);
301
- assert!(!encoder.encode(&blank(w, h), false, false).unwrap().keyframe);
302
- assert!(
303
- encoder.encode(&blank(w, h), false, true).unwrap().keyframe,
304
- "size down"
305
- );
306
- assert!(!encoder.encode(&blank(w, h), false, true).unwrap().keyframe);
307
- assert!(
308
- encoder.refine(&blank(w, h)).unwrap().keyframe,
309
- "size back up"
310
- );
311
- assert!(!encoder.encode(&blank(w, h), false, false).unwrap().keyframe);
312
- }
313
- }
@@ -141,168 +141,3 @@ impl Drop for Encoder {
141
141
  unsafe { dl_nvenc_destroy(self.native) };
142
142
  }
143
143
  }
144
-
145
- #[cfg(test)]
146
- mod tests {
147
- use super::*;
148
- use crate::h264::Backend;
149
-
150
- /// Fine detail, like text, scrolled sideways by `t`.
151
- fn pattern(width: usize, height: usize, t: usize) -> I420 {
152
- let mut data = vec![128u8; width * height * 3 / 2];
153
- for y in 0..height {
154
- for x in 0..width {
155
- data[y * width + x] = ((x + 4 * t) ^ (3 * y)) as u8;
156
- }
157
- }
158
- I420 {
159
- width,
160
- height,
161
- data,
162
- }
163
- }
164
-
165
- fn nal_types(au: &[u8]) -> Vec<u8> {
166
- let mut types = Vec::new();
167
- let mut i = 0;
168
- while i + 3 < au.len() {
169
- if au[i..i + 3] == [0, 0, 1] {
170
- types.push(au[i + 3] & 0x1f);
171
- i += 3;
172
- } else {
173
- i += 1;
174
- }
175
- }
176
- types
177
- }
178
-
179
- fn open(width: usize, height: usize) -> Option<Encoder> {
180
- match Encoder::open(width, height, 4000, 30) {
181
- Ok(encoder) => Some(encoder),
182
- Err(error) => {
183
- eprintln!("skipping: NVENC unavailable: {error:#}");
184
- None
185
- }
186
- }
187
- }
188
-
189
- #[test]
190
- fn idr_on_request_only_and_refine_is_inter() {
191
- let (w, h) = (640, 480);
192
- let Some(mut encoder) = open(w, h) else {
193
- return;
194
- };
195
- let first = encoder.encode(&pattern(w, h, 0), false).unwrap();
196
- assert!(first.data.starts_with(&[0, 0, 0, 1]), "Annex-B");
197
- assert_eq!(nal_types(&first.data), [7, 8, 5]);
198
- assert!(first.keyframe);
199
-
200
- let next = encoder.encode(&pattern(w, h, 1), false).unwrap();
201
- assert_eq!(nal_types(&next.data), [1]);
202
- assert!(!next.keyframe);
203
-
204
- let forced = encoder.encode(&pattern(w, h, 2), true).unwrap();
205
- assert_eq!(nal_types(&forced.data), [7, 8, 5]);
206
- assert!(forced.keyframe);
207
-
208
- let still = pattern(w, h, 3);
209
- let motion = encoder.encode(&still, false).unwrap();
210
- let refined = encoder.refine(&still).unwrap();
211
- assert_eq!(nal_types(&refined.data), [1]);
212
- assert!(!refined.keyframe);
213
- eprintln!(
214
- "motion frame {} bytes, refinement {} bytes",
215
- motion.data.len(),
216
- refined.data.len()
217
- );
218
-
219
- encoder.set_bitrate(1500).unwrap();
220
- let after = encoder.encode(&pattern(w, h, 4), false).unwrap();
221
- assert_eq!(nal_types(&after.data), [1]);
222
- assert!(!after.keyframe);
223
-
224
- assert!(encoder.encode(&pattern(320, 240, 0), false).is_err());
225
- }
226
-
227
- #[test]
228
- fn the_refinement_ceiling_is_lifted_for_the_motion_after_it() {
229
- let (w, h) = (640, 480);
230
- let Ok(mut encoder) = Encoder::open(w, h, 300, 30) else {
231
- return;
232
- };
233
- // Fresh detail every frame, which no reference predicts: the rate
234
- // control, not motion search, decides these sizes.
235
- let noise = |t: usize| {
236
- let mut frame = pattern(w, h, 0);
237
- for (i, pixel) in frame.data[..w * h].iter_mut().enumerate() {
238
- *pixel = ((i ^ t).wrapping_mul(2_654_435_761) >> 13) as u8;
239
- }
240
- frame
241
- };
242
- let coded = |encoder: &mut Encoder, from: usize| -> usize {
243
- (from..from + 10)
244
- .map(|t| encoder.encode(&noise(t), false).unwrap().data.len())
245
- .sum()
246
- };
247
- coded(&mut encoder, 0);
248
- let before = coded(&mut encoder, 10);
249
- encoder.refine(&noise(19)).unwrap();
250
- let after = coded(&mut encoder, 20);
251
- assert!(
252
- after < before * 2,
253
- "motion after a refinement is back under the rate target: {before} then {after} bytes"
254
- );
255
- }
256
-
257
- #[test]
258
- fn sizes_off_the_macroblock_grid_open_and_code() {
259
- for (w, h) in [(1366, 768), (1918, 1078)] {
260
- let Some(mut encoder) = open(w, h) else {
261
- return;
262
- };
263
- assert!(encoder.encode(&pattern(w, h, 0), false).unwrap().keyframe);
264
- assert!(!encoder.encode(&pattern(w, h, 1), false).unwrap().keyframe);
265
- }
266
- }
267
-
268
- /// Not a check: writes `$DESKLINK_NVENC_DUMP` (60 frames at 1282x722, an
269
- /// IDR at 30 and a refinement at 45) for an external decoder, and prints
270
- /// the mean encode time at 1080p and 4K.
271
- #[test]
272
- #[ignore]
273
- fn dump_and_time() {
274
- use std::time::Instant;
275
- if let Ok(path) = std::env::var("DESKLINK_NVENC_DUMP") {
276
- let (w, h) = (1282, 722);
277
- let Some(mut encoder) = Encoder::open(w, h, 1500, 30).ok() else {
278
- return;
279
- };
280
- let mut out = Vec::new();
281
- for t in 0..60 {
282
- let frame = pattern(w, h, t.min(44));
283
- let coded = match t {
284
- 45 => encoder.refine(&frame),
285
- _ => encoder.encode(&frame, t == 30),
286
- }
287
- .unwrap();
288
- if (44..=46).contains(&t) {
289
- eprintln!("frame {t}: {} bytes", coded.data.len());
290
- }
291
- out.extend_from_slice(&coded.data);
292
- }
293
- std::fs::write(path, out).unwrap();
294
- }
295
- for (w, h) in [(1920, 1080), (3840, 2160)] {
296
- let Some(mut encoder) = Encoder::open(w, h, 20_000, 60).ok() else {
297
- return;
298
- };
299
- let frames: Vec<_> = (0..4).map(|t| pattern(w, h, t)).collect();
300
- encoder.encode(&frames[0], true).unwrap();
301
- let start = Instant::now();
302
- for t in 0..60 {
303
- encoder.encode(&frames[t % 4], false).unwrap();
304
- }
305
- eprintln!("{w}x{h}: {:?} per frame", start.elapsed() / 60);
306
- }
307
- }
308
- }
@@ -488,248 +488,3 @@ fn http_get(path: &str) -> Result<Vec<u8>> {
488
488
  fn hex(bytes: &[u8]) -> String {
489
489
  bytes.iter().map(|b| format!("{b:02x}")).collect()
490
490
  }
491
-
492
- #[cfg(test)]
493
- mod tests {
494
- use super::*;
495
- use crate::h264::Backend;
496
- use openh264_sys2::SLayerBSInfo;
497
-
498
- #[test]
499
- fn malformed_bitstream_headers_are_rejected_before_any_slice() {
500
- fn info(layer_count: c_int, layers: &[SLayerBSInfo], frame_size: c_int) -> SFrameBSInfo {
501
- let mut info = SFrameBSInfo::default();
502
- info.iLayerNum = layer_count;
503
- info.sLayerInfo[..layers.len()].copy_from_slice(layers);
504
- info.iFrameSizeInBytes = frame_size;
505
- info
506
- }
507
- fn layer(nals: &mut [c_int], buf: *mut u8) -> SLayerBSInfo {
508
- SLayerBSInfo {
509
- iNalCount: nals.len() as c_int,
510
- pNalLengthInByte: nals.as_mut_ptr(),
511
- pBsBuf: buf,
512
- ..Default::default()
513
- }
514
- }
515
- let some_buf = NonNull::<u8>::dangling().as_ptr();
516
- // Well-formed headers pass through with their sizes.
517
- let (mut a, mut b) = ([10, 20], [5]);
518
- let ok = info(
519
- 2,
520
- &[layer(&mut a, some_buf), layer(&mut b, some_buf)],
521
- 36,
522
- );
523
- assert_eq!(validated_layer_sizes(&ok).unwrap(), [30, 5]);
524
- // A count past the array is clamped, not read past.
525
- assert_eq!(validated_layer_sizes(&info(500, &[], 0)).unwrap().len(), 128);
526
- assert!(validated_layer_sizes(&info(-1, &[], 0)).unwrap().is_empty());
527
- // Every malformation fails instead of building a slice.
528
- let mut bad = [-1];
529
- assert!(validated_layer_sizes(&info(1, &[layer(&mut bad, some_buf)], 100)).is_err());
530
- let null_lengths = SLayerBSInfo {
531
- iNalCount: 2,
532
- pNalLengthInByte: std::ptr::null_mut(),
533
- pBsBuf: some_buf,
534
- ..Default::default()
535
- };
536
- assert!(validated_layer_sizes(&info(1, &[null_lengths], 100)).is_err());
537
- let mut negative = [10, -3];
538
- assert!(validated_layer_sizes(&info(1, &[layer(&mut negative, some_buf)], 100)).is_err());
539
- let mut no_buf = [10];
540
- assert!(
541
- validated_layer_sizes(&info(1, &[layer(&mut no_buf, std::ptr::null_mut())], 100)).is_err()
542
- );
543
- let mut over = [60, 50];
544
- assert!(validated_layer_sizes(&info(1, &[layer(&mut over, some_buf)], 100)).is_err());
545
- }
546
-
547
- /// A diagonal gradient that moves with `t`, so inter frames carry motion.
548
- fn pattern(width: usize, height: usize, t: usize) -> I420 {
549
- let mut data = vec![128u8; width * height * 3 / 2];
550
- for y in 0..height {
551
- for x in 0..width {
552
- data[y * width + x] = ((x + y + t * 4) % 256) as u8;
553
- }
554
- }
555
- // A moving box, for edges the encoder has to follow.
556
- let (bx, by) = ((t * 7) % (width.saturating_sub(64).max(1)), height / 3);
557
- for y in by..(by + 64).min(height) {
558
- for x in bx..(bx + 64).min(width) {
559
- data[y * width + x] = 16;
560
- }
561
- }
562
- I420 {
563
- width,
564
- height,
565
- data,
566
- }
567
- }
568
-
569
- /// Each NAL unit of an Annex-B access unit, from its header byte on.
570
- fn nals(au: &[u8]) -> Vec<&[u8]> {
571
- let starts: Vec<usize> = (0..au.len().saturating_sub(3))
572
- .filter(|&i| au[i..i + 3] == [0, 0, 1])
573
- .map(|i| i + 3)
574
- .collect();
575
- starts
576
- .iter()
577
- .enumerate()
578
- .map(|(k, &s)| &au[s..starts.get(k + 1).map_or(au.len(), |&n| n - 3)])
579
- .collect()
580
- }
581
-
582
- fn nal_types(au: &[u8]) -> Vec<u8> {
583
- nals(au).iter().map(|n| n[0] & 0x1f).collect()
584
- }
585
-
586
- fn open(width: usize, height: usize) -> Option<Encoder> {
587
- match Encoder::open(width, height, 4000, 30) {
588
- Ok(encoder) => Some(encoder),
589
- Err(error) => {
590
- eprintln!("skipping: openh264 unavailable ({error:#})");
591
- None
592
- }
593
- }
594
- }
595
-
596
- #[test]
597
- fn meets_the_stream_contract() {
598
- let Some(mut encoder) = open(640, 480) else {
599
- return;
600
- };
601
- let first = encoder.encode(&pattern(640, 480, 0), false).unwrap();
602
- assert!(first.keyframe);
603
- assert!(first.data.starts_with(&[0, 0, 0, 1]));
604
- let types = nal_types(&first.data);
605
- let (sps, pps, idr) = (
606
- types.iter().position(|&t| t == 7).unwrap(),
607
- types.iter().position(|&t| t == 8).unwrap(),
608
- types.iter().position(|&t| t == 5).unwrap(),
609
- );
610
- assert!(sps < pps && pps < idr, "{types:?}");
611
- let sps = nals(&first.data)[sps];
612
- assert_eq!(sps[1], 66, "profile_idc must be Baseline");
613
- assert!(sps[2] & 0x40 != 0, "constraint_set1: Constrained Baseline");
614
-
615
- let next = encoder.encode(&pattern(640, 480, 1), false).unwrap();
616
- assert!(!next.keyframe);
617
- assert!(
618
- nal_types(&next.data).iter().all(|&t| t == 1),
619
- "{:?}",
620
- nal_types(&next.data)
621
- );
622
-
623
- let forced = encoder.encode(&pattern(640, 480, 2), true).unwrap();
624
- assert!(forced.keyframe);
625
- let types = nal_types(&forced.data);
626
- assert!(
627
- types.contains(&7) && types.contains(&8) && types.contains(&5),
628
- "{types:?}"
629
- );
630
-
631
- let refined = encoder.refine(&pattern(640, 480, 2)).unwrap();
632
- assert!(!refined.keyframe);
633
- assert!(!nal_types(&refined.data).contains(&5));
634
-
635
- encoder.set_bitrate(1000).unwrap();
636
- let after = encoder.encode(&pattern(640, 480, 3), false).unwrap();
637
- assert!(!after.keyframe);
638
-
639
- // A still frame still produces an access unit: frame skipping is off.
640
- for _ in 0..5 {
641
- assert!(!encoder
642
- .encode(&pattern(640, 480, 3), false)
643
- .unwrap()
644
- .data
645
- .is_empty());
646
- }
647
- assert!(encoder.encode(&pattern(320, 240, 0), false).is_err());
648
- }
649
-
650
- #[test]
651
- fn a_window_switch_is_coded_without_an_unasked_idr() {
652
- let (w, h) = (1280, 720);
653
- let Some(mut encoder) = open(w, h) else {
654
- return;
655
- };
656
- // Four different text-like windows, ten frames each.
657
- let window = |seed: usize| {
658
- let mut frame = pattern(w, h, 0);
659
- for y in 0..h {
660
- for x in 0..w {
661
- let on = (x / (3 + seed) + y / (5 + seed)) % 3 == 0;
662
- frame.data[y * w + x] = if on { 20 } else { 235 };
663
- }
664
- }
665
- frame
666
- };
667
- let idrs: Vec<usize> = (0..40)
668
- .filter(|&t| encoder.encode(&window(t / 10), false).unwrap().keyframe)
669
- .collect();
670
- assert_eq!(idrs, [0], "an IDR only for the first frame");
671
- }
672
-
673
- #[test]
674
- fn codes_sizes_that_are_not_macroblock_multiples() {
675
- for (w, h) in [(1366, 768), (1918, 1078)] {
676
- let Some(mut encoder) = open(w, h) else {
677
- return;
678
- };
679
- assert!(encoder.encode(&pattern(w, h, 0), false).unwrap().keyframe);
680
- assert!(!encoder.encode(&pattern(w, h, 1), false).unwrap().keyframe);
681
- }
682
- }
683
-
684
- /// Writes a clip for out-of-band decoding: `DESKLINK_OPENH264_CLIP=path`.
685
- #[test]
686
- #[ignore]
687
- fn writes_a_clip() {
688
- let path = std::env::var("DESKLINK_OPENH264_CLIP").unwrap();
689
- let (w, h) = (1366, 768);
690
- // Low enough that rate control codes above the refinement ceiling.
691
- let mut encoder = Encoder::open(w, h, 300, 30).expect("openh264 must be in place");
692
- let mut out = Vec::new();
693
- for t in 0..60 {
694
- let frame = pattern(w, h, t);
695
- let au = if t == 40 {
696
- encoder.refine(&pattern(w, h, 39)).unwrap()
697
- } else {
698
- encoder.encode(&frame, t == 30).unwrap()
699
- };
700
- out.extend_from_slice(&au.data);
701
- }
702
- std::fs::write(path, out).unwrap();
703
- }
704
-
705
- /// Average encode time over 60 moving frames.
706
- #[test]
707
- #[ignore]
708
- fn times_large_frames() {
709
- for (w, h) in [(1920, 1080), (3840, 2160)] {
710
- let mut encoder = open(w, h).expect("openh264 must be in place");
711
- let frames: Vec<_> = (0..60).map(|t| pattern(w, h, t)).collect();
712
- let start = Instant::now();
713
- let mut bytes = 0;
714
- for (t, frame) in frames.iter().enumerate() {
715
- bytes += encoder.encode(frame, t == 0).unwrap().data.len();
716
- }
717
- eprintln!(
718
- "{w}x{h}: {:.1} ms/frame, {} KiB/frame",
719
- start.elapsed().as_secs_f64() * 1000.0 / 60.0,
720
- bytes / 60 / 1024
721
- );
722
- }
723
- }
724
-
725
- /// Hits the network: `cargo test fetches -- --ignored` with a scratch
726
- /// `XDG_CACHE_HOME`.
727
- #[test]
728
- #[ignore]
729
- fn fetches_from_cisco() {
730
- assert!(fetch_once());
731
- let path = cached().unwrap();
732
- assert!(path.exists());
733
- assert!(Encoder::open(320, 240, 500, 30).is_ok());
734
- }
735
- }
@@ -164,100 +164,3 @@ impl Drop for Encoder {
164
164
  unsafe { dl_vaapi_destroy(self.native) };
165
165
  }
166
166
  }
167
-
168
- #[cfg(test)]
169
- mod tests {
170
- use super::*;
171
- use crate::h264::Backend;
172
-
173
- /// A diagonal gradient shifted by `t`, so successive frames differ.
174
- fn pattern(width: usize, height: usize, t: usize) -> I420 {
175
- let mut data = vec![128u8; width * height * 3 / 2];
176
- for y in 0..height {
177
- for x in 0..width {
178
- data[y * width + x] = ((x + y + 4 * t) & 0xff) as u8;
179
- }
180
- }
181
- I420 {
182
- width,
183
- height,
184
- data,
185
- }
186
- }
187
-
188
- /// NAL unit types in Annex-B order.
189
- fn nal_types(data: &[u8]) -> Vec<u8> {
190
- let mut types = Vec::new();
191
- let mut i = 0;
192
- while i + 3 < data.len() {
193
- if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
194
- types.push(data[i + 3] & 0x1f);
195
- i += 3;
196
- } else {
197
- i += 1;
198
- }
199
- }
200
- types
201
- }
202
-
203
- fn open(width: usize, height: usize) -> Option<Encoder> {
204
- match Encoder::open(width, height, 4000, 30) {
205
- Ok(encoder) => Some(encoder),
206
- Err(error) => {
207
- eprintln!("skipping: VA-API H.264 unavailable: {error:#}");
208
- None
209
- }
210
- }
211
- }
212
-
213
- fn slices(types: &[u8]) -> Vec<u8> {
214
- types
215
- .iter()
216
- .copied()
217
- .filter(|t| !matches!(t, 6 | 9))
218
- .collect()
219
- }
220
-
221
- #[test]
222
- fn codes_idr_then_p_frames_and_refines() {
223
- let (w, h) = (640, 480);
224
- let Some(mut encoder) = open(w, h) else {
225
- return;
226
- };
227
- let first = encoder.encode(&pattern(w, h, 0), false).unwrap();
228
- assert!(first.keyframe);
229
- assert!(first.data.starts_with(&[0, 0, 0, 1]));
230
- assert_eq!(slices(&nal_types(&first.data))[..3], [7, 8, 5]);
231
- let next = encoder.encode(&pattern(w, h, 1), false).unwrap();
232
- assert!(!next.keyframe);
233
- assert_eq!(slices(&nal_types(&next.data)), [1]);
234
- let forced = encoder.encode(&pattern(w, h, 2), true).unwrap();
235
- assert!(forced.keyframe);
236
- assert_eq!(slices(&nal_types(&forced.data))[..3], [7, 8, 5]);
237
- let motion = encoder.encode(&pattern(w, h, 3), false).unwrap();
238
- let refined = encoder.refine(&pattern(w, h, 3)).unwrap();
239
- assert!(!refined.keyframe);
240
- assert_eq!(slices(&nal_types(&refined.data)), [1]);
241
- eprintln!(
242
- "motion frame {} bytes, refinement {} bytes",
243
- motion.data.len(),
244
- refined.data.len()
245
- );
246
- encoder.set_bitrate(1000).unwrap();
247
- let slower = encoder.encode(&pattern(w, h, 4), false).unwrap();
248
- assert!(!slower.keyframe);
249
- assert_eq!(slices(&nal_types(&slower.data)), [1]);
250
- assert!(encoder.encode(&pattern(320, 240, 5), false).is_err());
251
- }
252
-
253
- #[test]
254
- fn codes_sizes_off_the_macroblock_grid() {
255
- for (w, h) in [(1366, 768), (1918, 1078)] {
256
- let Some(mut encoder) = open(w, h) else {
257
- return;
258
- };
259
- assert!(encoder.encode(&pattern(w, h, 0), false).unwrap().keyframe);
260
- assert!(!encoder.encode(&pattern(w, h, 1), false).unwrap().keyframe);
261
- }
262
- }
263
- }