diff --git a/Cargo.lock b/Cargo.lock index ea4f7c0..7c9625d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.14.44" +version = "0.14.45" dependencies = [ "anyhow", "async-stream", @@ -1676,7 +1676,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.14.44" +version = "0.14.45" dependencies = [ "anyhow", "base64", @@ -1688,7 +1688,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.14.44" +version = "0.14.45" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index 0f5bd93..b583a46 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.14.44" +version = "0.14.45" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index d7c7f22..f31d174 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.14.44" +version = "0.14.45" edition = "2024" build = "build.rs" diff --git a/server/Cargo.toml b/server/Cargo.toml index aac309a..189f44b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,7 +10,7 @@ bench = false [package] name = "lesavka_server" -version = "0.14.44" +version = "0.14.45" edition = "2024" autobins = false diff --git a/server/src/video_sinks/webcam_sink.rs b/server/src/video_sinks/webcam_sink.rs index ab14c7a..f9462ba 100644 --- a/server/src/video_sinks/webcam_sink.rs +++ b/server/src/video_sinks/webcam_sink.rs @@ -38,6 +38,21 @@ fn uvc_sink_session_clock_align_enabled() -> bool { .unwrap_or(true) } +fn uvc_mjpeg_v4l2sink_io_mode() -> String { + let value = std::env::var("LESAVKA_UVC_MJPEG_IO_MODE").unwrap_or_else(|_| "rw".to_string()); + let trimmed = value.trim().to_ascii_lowercase(); + match trimmed.as_str() { + "auto" | "rw" | "mmap" | "userptr" | "dmabuf" | "dmabuf-import" => trimmed, + _ => { + warn!( + value, + "invalid LESAVKA_UVC_MJPEG_IO_MODE; falling back to rw" + ); + "rw".to_string() + } + } +} + impl WebcamSink { /// Build a new webcam sink pipeline. /// @@ -126,6 +141,9 @@ impl WebcamSink { let sink = gst::ElementFactory::make("v4l2sink") .property("device", uvc_dev) .build()?; + // The control helper keeps the gadget node open for UVC requests. + // RW mode avoids the MMAP/REQBUFS path that conflicts with that fd. + sink.set_property_from_str("io-mode", &uvc_mjpeg_v4l2sink_io_mode()); if clock_align_enabled { crate::media_timing::enable_sink_clock_sync(&sink); } else if sink.has_property("sync", None) { @@ -255,4 +273,19 @@ mod tests { assert!(super::uvc_sink_session_clock_align_enabled()); }); } + + #[test] + fn mjpeg_uvc_sink_defaults_to_rw_io_mode_with_safe_override() { + temp_env::with_var_unset("LESAVKA_UVC_MJPEG_IO_MODE", || { + assert_eq!(super::uvc_mjpeg_v4l2sink_io_mode(), "rw"); + }); + + temp_env::with_var("LESAVKA_UVC_MJPEG_IO_MODE", Some("mmap"), || { + assert_eq!(super::uvc_mjpeg_v4l2sink_io_mode(), "mmap"); + }); + + temp_env::with_var("LESAVKA_UVC_MJPEG_IO_MODE", Some("not-real"), || { + assert_eq!(super::uvc_mjpeg_v4l2sink_io_mode(), "rw"); + }); + } }