104 lines
3.4 KiB
Rust
104 lines
3.4 KiB
Rust
// Integration coverage for the MJPEG spool used by UVC output.
|
|
//
|
|
// Scope: write real frame and metadata files through the same spool helper
|
|
// used by direct MJPEG ingress and decoded HEVC ingress.
|
|
// Targets: server/src/video_sinks/mjpeg_spool.rs.
|
|
// Why: both profiles share the UVC helper handoff path, so metadata must keep
|
|
// them separated without changing the atomic frame publication behavior.
|
|
|
|
use std::fs;
|
|
|
|
use serial_test::serial;
|
|
|
|
#[allow(dead_code, clippy::items_after_test_module)]
|
|
mod spool {
|
|
include!(concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/server/src/video_sinks/mjpeg_spool.rs"
|
|
));
|
|
|
|
pub fn write_hevc(
|
|
path: &std::path::Path,
|
|
data: &[u8],
|
|
source_pts_us: u64,
|
|
decoded_pts_us: Option<u64>,
|
|
) -> anyhow::Result<()> {
|
|
spool_mjpeg_frame_with_timing(
|
|
path,
|
|
data,
|
|
Some(MjpegSpoolTiming::hevc_decoded_mjpeg(
|
|
source_pts_us,
|
|
decoded_pts_us,
|
|
)),
|
|
)
|
|
}
|
|
|
|
pub fn write_mjpeg(
|
|
path: &std::path::Path,
|
|
data: &[u8],
|
|
source_pts_us: u64,
|
|
) -> anyhow::Result<()> {
|
|
spool_mjpeg_frame_with_timing(
|
|
path,
|
|
data,
|
|
Some(MjpegSpoolTiming::mjpeg_passthrough(source_pts_us)),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn hevc_decoded_mjpeg_spool_writes_frame_and_timing_metadata() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let frame_path = dir.path().join("uvc-frame.mjpg");
|
|
let log_path = dir.path().join("uvc-frame.jsonl");
|
|
let log_path_text = log_path.to_string_lossy().to_string();
|
|
let frame = [0xff, 0xd8, 0x11, 0x22, 0xff, 0xd9];
|
|
|
|
temp_env::with_vars(
|
|
[
|
|
("LESAVKA_UVC_FRAME_META", Some("1")),
|
|
(
|
|
"LESAVKA_UVC_FRAME_META_LOG_PATH",
|
|
Some(log_path_text.as_str()),
|
|
),
|
|
("LESAVKA_UVC_FRAME_META_PATH", None),
|
|
],
|
|
|| {
|
|
spool::write_hevc(&frame_path, &frame, 42_000, Some(44_000))
|
|
.expect("spool decoded HEVC frame");
|
|
},
|
|
);
|
|
|
|
assert_eq!(fs::read(&frame_path).expect("frame bytes"), frame);
|
|
|
|
let sidecar = frame_path.with_extension("mjpg.meta.json");
|
|
let sidecar_text = fs::read_to_string(sidecar).expect("sidecar metadata");
|
|
let log_text = fs::read_to_string(log_path).expect("metadata log");
|
|
for text in [sidecar_text.as_str(), log_text.as_str()] {
|
|
assert!(text.contains("\"profile\":\"hevc-decoded-mjpeg\""));
|
|
assert!(text.contains("\"bytes\":6"));
|
|
assert!(text.contains("\"source_pts_us\":42000"));
|
|
assert!(text.contains("\"decoded_pts_us\":44000"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn mjpeg_passthrough_spool_keeps_decode_timing_null() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let frame_path = dir.path().join("uvc-frame.mjpg");
|
|
let frame = [0xff, 0xd8, 0x33, 0x44, 0xff, 0xd9];
|
|
|
|
temp_env::with_var("LESAVKA_UVC_FRAME_META", Some("1"), || {
|
|
spool::write_mjpeg(&frame_path, &frame, 55_000).expect("spool MJPEG frame");
|
|
});
|
|
|
|
assert_eq!(fs::read(&frame_path).expect("frame bytes"), frame);
|
|
let sidecar_text =
|
|
fs::read_to_string(frame_path.with_extension("mjpg.meta.json")).expect("sidecar metadata");
|
|
assert!(sidecar_text.contains("\"profile\":\"mjpeg-passthrough\""));
|
|
assert!(sidecar_text.contains("\"source_pts_us\":55000"));
|
|
assert!(sidecar_text.contains("\"decoded_pts_us\":null"));
|
|
}
|