@desklink/host 0.5.15 → 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.
- package/docs/PROTOCOL.md +3 -1
- package/engine/Cargo.lock +1 -1
- package/engine/Cargo.toml +1 -1
- package/engine/src/capture.rs +0 -79
- package/engine/src/encoder.rs +0 -48
- package/engine/src/h264_nvenc.rs +0 -165
- package/engine/src/h264_openh264.rs +0 -245
- package/engine/src/h264_vaapi.rs +0 -97
- package/engine/src/indicator.rs +0 -101
- package/engine/src/input_portal.rs +337 -0
- package/engine/src/main.rs +2 -88
- package/engine/src/observe.rs +0 -33
- package/engine/src/peer.rs +0 -433
- package/engine/src/portal.rs +0 -75
- package/engine/src/session.rs +52 -1063
- package/package.json +3 -3
package/docs/PROTOCOL.md
CHANGED
|
@@ -187,7 +187,9 @@ video itself (see *Encoded sources*): a consumer reads it rather than inferring
|
|
|
187
187
|
support from the engine's version string.
|
|
188
188
|
|
|
189
189
|
`input.mechanism` names the build's portal input path, `inputtino/uinput`,
|
|
190
|
-
where the engine creates its own virtual devices
|
|
190
|
+
where the engine creates its own virtual devices, or `portal-remotedesktop` only when
|
|
191
|
+
`/dev/uinput` is unavailable and the session portal advertises a keyboard (keys
|
|
192
|
+
only; `pointer` and `wheel` are then `false`). A session opened against an X
|
|
191
193
|
display applies input through XTest instead, where the X server applies the
|
|
192
194
|
events and nothing the engine does can reach another session's keyboard.
|
|
193
195
|
`input.layout` is the keymap the engine compiled for typing, taken from the
|
package/engine/Cargo.lock
CHANGED
package/engine/Cargo.toml
CHANGED
package/engine/src/capture.rs
CHANGED
|
@@ -606,82 +606,3 @@ struct StreamState {
|
|
|
606
606
|
|
|
607
607
|
// PipeWire callbacks only touch this from the capture thread.
|
|
608
608
|
unsafe impl Send for StreamState {}
|
|
609
|
-
|
|
610
|
-
#[cfg(test)]
|
|
611
|
-
mod tests {
|
|
612
|
-
use super::*;
|
|
613
|
-
use std::time::Duration;
|
|
614
|
-
|
|
615
|
-
#[test]
|
|
616
|
-
fn layer_mask_keeps_click_center_and_removes_ring_and_edge() {
|
|
617
|
-
let position = Arc::new(Mutex::new(Some((50, 50))));
|
|
618
|
-
let mut mask = IndicatorMask::new(position.clone());
|
|
619
|
-
mask.apply(&mut vec![1; 100 * 100 * 4], 100, 100, 100, 100);
|
|
620
|
-
let mut frame = vec![2; 100 * 100 * 4];
|
|
621
|
-
mask.apply(&mut frame, 100, 100, 100, 100);
|
|
622
|
-
assert_eq!(frame[(50 * 100 + 50) * 4], 2);
|
|
623
|
-
assert_eq!(frame[(50 * 100 + 60) * 4], 1);
|
|
624
|
-
assert_eq!(frame[0], 1);
|
|
625
|
-
*position.lock().unwrap() = Some((75, 50));
|
|
626
|
-
let mut moved = vec![3; 100 * 100 * 4];
|
|
627
|
-
mask.apply(&mut moved, 100, 100, 100, 100);
|
|
628
|
-
assert_eq!(moved[(50 * 100 + 60) * 4], 1, "eased path is masked");
|
|
629
|
-
assert_eq!(moved[(50 * 100 + 75) * 4], 3, "click center remains live");
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
#[test]
|
|
633
|
-
fn dropping_capture_stops_and_joins_its_live_pipewire_loop() {
|
|
634
|
-
// Exercise the real event-loop/channel lifetime without a portal,
|
|
635
|
-
// compositor, screen capture or input device.
|
|
636
|
-
let (quit, stop) = pw::channel::channel();
|
|
637
|
-
let (ready_tx, ready_rx) = mpsc::channel();
|
|
638
|
-
let (ended_tx, ended_rx) = mpsc::channel();
|
|
639
|
-
let thread = std::thread::spawn(move || {
|
|
640
|
-
pw::init();
|
|
641
|
-
let main_loop = pw::main_loop::MainLoopRc::new(None).unwrap();
|
|
642
|
-
let _stop = stop.attach(main_loop.loop_(), {
|
|
643
|
-
let main_loop = main_loop.clone();
|
|
644
|
-
move |_| main_loop.quit()
|
|
645
|
-
});
|
|
646
|
-
let timer = main_loop.loop_().add_timer(move |_| {
|
|
647
|
-
let _ = ready_tx.send(());
|
|
648
|
-
});
|
|
649
|
-
timer
|
|
650
|
-
.update_timer(Some(Duration::from_millis(1)), None)
|
|
651
|
-
.into_result()
|
|
652
|
-
.unwrap();
|
|
653
|
-
main_loop.run();
|
|
654
|
-
});
|
|
655
|
-
let capture = Capture {
|
|
656
|
-
quit,
|
|
657
|
-
thread: Some(thread),
|
|
658
|
-
source: SelectedSource {
|
|
659
|
-
node_id: 0,
|
|
660
|
-
width: 2,
|
|
661
|
-
height: 2,
|
|
662
|
-
position: None,
|
|
663
|
-
source_type: None,
|
|
664
|
-
origin_x: 0,
|
|
665
|
-
origin_y: 0,
|
|
666
|
-
},
|
|
667
|
-
cursor: None,
|
|
668
|
-
geometry: Arc::new(Mutex::new(None)),
|
|
669
|
-
frames: Arc::new(AtomicU64::new(0)),
|
|
670
|
-
dropped: Arc::new(AtomicU64::new(0)),
|
|
671
|
-
encoded_width: 2,
|
|
672
|
-
encoded_height: 2,
|
|
673
|
-
};
|
|
674
|
-
ready_rx
|
|
675
|
-
.recv_timeout(Duration::from_secs(2))
|
|
676
|
-
.expect("the loop is running");
|
|
677
|
-
// Keep a broken join bounded so a missing stop is a failed check rather
|
|
678
|
-
// than a test process that hangs forever.
|
|
679
|
-
std::thread::spawn(move || {
|
|
680
|
-
drop(capture);
|
|
681
|
-
ended_tx.send(()).unwrap();
|
|
682
|
-
});
|
|
683
|
-
ended_rx
|
|
684
|
-
.recv_timeout(Duration::from_secs(2))
|
|
685
|
-
.expect("capture stopped and joined");
|
|
686
|
-
}
|
|
687
|
-
}
|
package/engine/src/encoder.rs
CHANGED
|
@@ -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
|
-
}
|
package/engine/src/h264_nvenc.rs
CHANGED
|
@@ -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
|
-
}
|
package/engine/src/h264_vaapi.rs
CHANGED
|
@@ -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
|
-
}
|