fix: parse normalized UVC MJPEG caps
This commit is contained in:
parent
eec6c67679
commit
31b828808c
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.25.4"
|
||||
version = "0.25.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1692,7 +1692,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.25.4"
|
||||
version = "0.25.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1704,7 +1704,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.25.4"
|
||||
version = "0.25.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.25.4"
|
||||
version = "0.25.5"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.25.4"
|
||||
version = "0.25.5"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -388,7 +388,7 @@ LESAVKA_UVC_MAXBURST=$(uvc_env_value LESAVKA_UVC_MAXBURST 0)
|
||||
LESAVKA_UVC_BULK=$(uvc_env_value LESAVKA_UVC_BULK 1)
|
||||
LESAVKA_UVC_FRAME_SIZE_GUARD=$(uvc_env_value LESAVKA_UVC_FRAME_SIZE_GUARD 1)
|
||||
LESAVKA_UVC_FRAME_MAX_BYTES=$(uvc_env_value LESAVKA_UVC_FRAME_MAX_BYTES 0)
|
||||
LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC=$(uvc_env_value LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC 4500000)
|
||||
LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC=${LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC:-4500000}
|
||||
LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT=$(uvc_env_value LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT 85)
|
||||
LESAVKA_UVC_STATS_PATH=$(uvc_env_value LESAVKA_UVC_STATS_PATH /run/lesavka-uvc-video-stats.json)
|
||||
EOF
|
||||
|
||||
@ -16,7 +16,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.25.4"
|
||||
version = "0.25.5"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -224,8 +224,10 @@ fn build_direct_mjpeg_normalize_branch(
|
||||
.field("width", width)
|
||||
.field("height", height)
|
||||
.field("pixel-aspect-ratio", gst::Fraction::new(1, 1))
|
||||
.field("colorimetry", "2:4:7:1")
|
||||
.build();
|
||||
let input_parser = gst::ElementFactory::make("jpegparse")
|
||||
.name("direct_mjpeg_normalize_input_parse")
|
||||
.build()?;
|
||||
let decoder = gst::ElementFactory::make("jpegdec").build()?;
|
||||
let decoded_queue = build_hevc_freshness_queue("direct_mjpeg_normalize_decoded_queue")?;
|
||||
let convert = gst::ElementFactory::make("videoconvert").build()?;
|
||||
@ -243,6 +245,9 @@ fn build_direct_mjpeg_normalize_branch(
|
||||
hevc_mjpeg_guard::direct_mjpeg_jpeg_quality() as i32,
|
||||
)
|
||||
.build()?;
|
||||
let output_parser = gst::ElementFactory::make("jpegparse")
|
||||
.name("direct_mjpeg_normalize_output_parse")
|
||||
.build()?;
|
||||
let encoded_caps = gst::ElementFactory::make("capsfilter")
|
||||
.property("caps", &caps_mjpeg)
|
||||
.build()?;
|
||||
@ -260,24 +265,28 @@ fn build_direct_mjpeg_normalize_branch(
|
||||
|
||||
pipeline.add_many([
|
||||
src.upcast_ref(),
|
||||
&input_parser,
|
||||
&decoder,
|
||||
&decoded_queue,
|
||||
&convert,
|
||||
&scale,
|
||||
&raw_capsfilter,
|
||||
&encoder,
|
||||
&output_parser,
|
||||
&encoded_caps,
|
||||
&encoded_queue,
|
||||
sink.upcast_ref(),
|
||||
])?;
|
||||
gst::Element::link_many([
|
||||
src.upcast_ref(),
|
||||
&input_parser,
|
||||
&decoder,
|
||||
&decoded_queue,
|
||||
&convert,
|
||||
&scale,
|
||||
&raw_capsfilter,
|
||||
&encoder,
|
||||
&output_parser,
|
||||
&encoded_caps,
|
||||
&encoded_queue,
|
||||
sink.upcast_ref(),
|
||||
@ -290,7 +299,7 @@ fn add_hevc_mjpeg_spool_branch(
|
||||
pipeline: &gst::Pipeline,
|
||||
width: i32,
|
||||
height: i32,
|
||||
fps: i32,
|
||||
_fps: i32,
|
||||
) -> anyhow::Result<(gst_app::AppSrc, gst_app::AppSink)> {
|
||||
let src = gst::ElementFactory::make("appsrc")
|
||||
.name("dynamic_hevc_mjpeg_src")
|
||||
@ -311,9 +320,7 @@ fn add_hevc_mjpeg_spool_branch(
|
||||
.field("parsed", true)
|
||||
.field("width", width)
|
||||
.field("height", height)
|
||||
.field("framerate", gst::Fraction::new(fps, 1))
|
||||
.field("pixel-aspect-ratio", gst::Fraction::new(1, 1))
|
||||
.field("colorimetry", "2:4:7:1")
|
||||
.build();
|
||||
let h265parse = gst::ElementFactory::make("h265parse")
|
||||
.property("disable-passthrough", true)
|
||||
@ -329,6 +336,9 @@ fn add_hevc_mjpeg_spool_branch(
|
||||
let encoder = gst::ElementFactory::make("jpegenc")
|
||||
.property("quality", hevc_mjpeg_guard::hevc_jpeg_quality() as i32)
|
||||
.build()?;
|
||||
let jpegparse = gst::ElementFactory::make("jpegparse")
|
||||
.name("dynamic_hevc_mjpeg_output_parse")
|
||||
.build()?;
|
||||
let caps = gst::ElementFactory::make("capsfilter")
|
||||
.property("caps", &caps_mjpeg)
|
||||
.build()?;
|
||||
@ -351,6 +361,7 @@ fn add_hevc_mjpeg_spool_branch(
|
||||
&decoded_queue,
|
||||
&convert,
|
||||
&encoder,
|
||||
&jpegparse,
|
||||
&caps,
|
||||
&encoded_queue,
|
||||
sink.upcast_ref(),
|
||||
@ -362,6 +373,7 @@ fn add_hevc_mjpeg_spool_branch(
|
||||
&decoded_queue,
|
||||
&convert,
|
||||
&encoder,
|
||||
&jpegparse,
|
||||
&caps,
|
||||
&encoded_queue,
|
||||
sink.upcast_ref(),
|
||||
|
||||
@ -107,9 +107,7 @@ impl WebcamSink {
|
||||
.field("parsed", true)
|
||||
.field("width", width)
|
||||
.field("height", height)
|
||||
.field("framerate", gst::Fraction::new(fps, 1))
|
||||
.field("pixel-aspect-ratio", gst::Fraction::new(1, 1))
|
||||
.field("colorimetry", "2:4:7:1")
|
||||
.build();
|
||||
src.set_caps(Some(&caps_mjpeg));
|
||||
|
||||
@ -169,7 +167,6 @@ impl WebcamSink {
|
||||
.field("height", height)
|
||||
.field("framerate", gst::Fraction::new(fps, 1))
|
||||
.field("pixel-aspect-ratio", gst::Fraction::new(1, 1))
|
||||
.field("colorimetry", "2:4:7:1")
|
||||
.build();
|
||||
src.set_caps(Some(&caps_mjpeg));
|
||||
|
||||
@ -200,7 +197,6 @@ impl WebcamSink {
|
||||
.field("height", height)
|
||||
.field("framerate", gst::Fraction::new(fps, 1))
|
||||
.field("pixel-aspect-ratio", gst::Fraction::new(1, 1))
|
||||
.field("colorimetry", "2:4:7:1")
|
||||
.build();
|
||||
src.set_caps(Some(&caps_hevc));
|
||||
|
||||
@ -218,6 +214,9 @@ impl WebcamSink {
|
||||
let encoder = gst::ElementFactory::make("jpegenc")
|
||||
.property("quality", hevc_mjpeg_guard::hevc_jpeg_quality() as i32)
|
||||
.build()?;
|
||||
let jpegparse = gst::ElementFactory::make("jpegparse")
|
||||
.name("hevc_mjpeg_output_parse")
|
||||
.build()?;
|
||||
let caps = gst::ElementFactory::make("capsfilter")
|
||||
.property("caps", &caps_mjpeg)
|
||||
.build()?;
|
||||
@ -247,6 +246,7 @@ impl WebcamSink {
|
||||
&decoded_queue,
|
||||
&convert,
|
||||
&encoder,
|
||||
&jpegparse,
|
||||
&caps,
|
||||
&encoded_queue,
|
||||
sink.upcast_ref(),
|
||||
@ -258,6 +258,7 @@ impl WebcamSink {
|
||||
&decoded_queue,
|
||||
&convert,
|
||||
&encoder,
|
||||
&jpegparse,
|
||||
&caps,
|
||||
&encoded_queue,
|
||||
sink.upcast_ref(),
|
||||
@ -281,6 +282,7 @@ impl WebcamSink {
|
||||
&decoder,
|
||||
&convert,
|
||||
&encoder,
|
||||
&jpegparse,
|
||||
&caps,
|
||||
&sink,
|
||||
])?;
|
||||
@ -290,6 +292,7 @@ impl WebcamSink {
|
||||
&decoder,
|
||||
&convert,
|
||||
&encoder,
|
||||
&jpegparse,
|
||||
&caps,
|
||||
&sink,
|
||||
])?;
|
||||
|
||||
@ -226,6 +226,13 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
|
||||
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_BULK 1"));
|
||||
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_SIZE_GUARD 1"));
|
||||
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_MAX_BYTES 0"));
|
||||
assert!(SERVER_INSTALL.contains(
|
||||
"LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC=${LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC:-4500000}"
|
||||
));
|
||||
assert!(
|
||||
!SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC"),
|
||||
"installer should refresh the safer MJPEG byte-budget default instead of preserving an old field-tuned budget forever"
|
||||
);
|
||||
assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT 85"));
|
||||
assert!(
|
||||
SERVER_INSTALL
|
||||
|
||||
@ -45,6 +45,10 @@ const WEBCAM_SINK: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/server/src/video_sinks/webcam_sink.rs"
|
||||
));
|
||||
const WEBCAM_CONSTRUCTOR: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/server/src/video_sinks/webcam_sink/constructor.rs"
|
||||
));
|
||||
const WEBCAM_FRAME_HANDOFF: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/server/src/video_sinks/webcam_sink/frame_handoff.rs"
|
||||
@ -113,6 +117,38 @@ fn installer_keeps_the_native_normalizer_memory_bounded_by_default() {
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_mjpeg_sanitizer_parses_jpeg_caps_after_reencode() {
|
||||
for marker in [
|
||||
"direct_mjpeg_normalize_input_parse",
|
||||
"direct_mjpeg_normalize_output_parse",
|
||||
"dynamic_hevc_mjpeg_output_parse",
|
||||
] {
|
||||
assert!(
|
||||
WEBCAM_SINK.contains(marker),
|
||||
"normalizer/spool branches should preserve JPEG parser marker {marker}"
|
||||
);
|
||||
}
|
||||
assert!(WEBCAM_CONSTRUCTOR.contains("hevc_mjpeg_output_parse"));
|
||||
assert_ordered(
|
||||
WEBCAM_SINK,
|
||||
"direct_mjpeg_normalize_output_parse",
|
||||
".property(\"caps\", &caps_mjpeg)",
|
||||
);
|
||||
let dynamic_parse_pos = WEBCAM_SINK
|
||||
.find("dynamic_hevc_mjpeg_output_parse")
|
||||
.expect("dynamic HEVC spool parser marker");
|
||||
assert!(
|
||||
WEBCAM_SINK[dynamic_parse_pos..].contains(".property(\"caps\", &caps_mjpeg)"),
|
||||
"dynamic HEVC spool capsfilter should sit after jpegparse, not directly after jpegenc"
|
||||
);
|
||||
assert!(
|
||||
!WEBCAM_SINK.contains(".field(\"colorimetry\", \"2:4:7:1\")")
|
||||
&& !WEBCAM_CONSTRUCTOR.contains(".field(\"colorimetry\", \"2:4:7:1\")"),
|
||||
"encoded JPEG parser output advertises bt709-style colorimetry, so hard-coded 2:4:7:1 caps can starve the branch"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opt_in_normalizer_has_rss_fuse_before_per_frame_gstreamer_allocation() {
|
||||
assert!(WEBCAM_FRAME_HANDOFF.contains("current_process_rss_kb"));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user