From d8b0d739a5ae37674f4053fced975c268ec81989 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 6 Jan 2026 16:19:55 -0300 Subject: [PATCH] client: add MJPG camera input --- client/src/input/camera.rs | 42 +++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/client/src/input/camera.rs b/client/src/input/camera.rs index b87d7d7..fbe1d14 100644 --- a/client/src/input/camera.rs +++ b/client/src/input/camera.rs @@ -31,9 +31,19 @@ impl CameraCapture { None => "/dev/video0".into(), }; - // let (enc, raw_caps) = Self::pick_encoder(); - // (NVIDIA → VA-API → software x264). - let (enc, kf_prop, kf_val) = Self::choose_encoder(); + let use_mjpg = std::env::var("LESAVKA_CAM_MJPG").is_ok() + || std::env::var("LESAVKA_CAM_FORMAT") + .ok() + .map(|v| matches!(v.to_ascii_lowercase().as_str(), "mjpg" | "mjpeg" | "jpeg")) + .unwrap_or(false); + let (enc, kf_prop, kf_val) = if use_mjpg { + ("x264enc", "key-int-max", "30") + } else { + Self::choose_encoder() + }; + if use_mjpg { + tracing::info!("📸 using MJPG source with software encode"); + } tracing::info!("📸 using encoder element: {enc}"); let width = env_u32("LESAVKA_CAM_WIDTH", 1280); let height = env_u32("LESAVKA_CAM_HEIGHT", 720); @@ -72,13 +82,25 @@ impl CameraCapture { // * nvh264enc needs NVMM memory caps; // * vaapih264enc wants system-memory caps; // * x264enc needs the usual raw caps. - let desc = format!( - "v4l2src device={dev} do-timestamp=true ! {src_caps} ! \ - {preenc} {enc} {kf_prop}={kf_val} ! \ - h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! \ - queue max-size-buffers=30 leaky=downstream ! \ - appsink name=asink emit-signals=true max-buffers=60 drop=true" - ); + let desc = if use_mjpg { + format!( + "v4l2src device={dev} do-timestamp=true ! \ + image/jpeg,width={width},height={height} ! \ + jpegdec ! videorate ! video/x-raw,framerate={fps}/1 ! \ + videoconvert ! {enc} {kf_prop}={kf_val} ! \ + h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! \ + queue max-size-buffers=30 leaky=downstream ! \ + appsink name=asink emit-signals=true max-buffers=60 drop=true" + ) + } else { + format!( + "v4l2src device={dev} do-timestamp=true ! {src_caps} ! \ + {preenc} {enc} {kf_prop}={kf_val} ! \ + h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! \ + queue max-size-buffers=30 leaky=downstream ! \ + appsink name=asink emit-signals=true max-buffers=60 drop=true" + ) + }; tracing::info!(%enc, width, height, fps, ?desc, "📸 using encoder element"); let pipeline: gst::Pipeline = gst::parse::launch(&desc)