/// Choose the pre-encoder webcam format path. /// /// V4L2 webcams often expose 720p/30 as MJPEG only, so the default accepts /// either raw frames or MJPEG unless the operator explicitly pins a format. fn camera_source_profile(allow_v4l2_auto_decode: bool) -> CameraSourceProfile { if !allow_v4l2_auto_decode { return CameraSourceProfile::Raw; } if std::env::var("LESAVKA_CAM_MJPG").is_ok() { return CameraSourceProfile::Mjpeg; } match std::env::var("LESAVKA_CAM_FORMAT") .ok() .as_deref() .map(str::trim) .map(str::to_ascii_lowercase) .as_deref() { Some("mjpg" | "mjpeg" | "jpeg") => CameraSourceProfile::Mjpeg, Some("raw" | "yuyv" | "yuy2") => CameraSourceProfile::Raw, _ => CameraSourceProfile::AutoDecode, } } /// Build the source-to-raw-video chain consumed by the encoder and preview tap. fn camera_raw_source_chain( src_desc: &str, src_caps: &str, width: u32, height: u32, fps: u32, profile: CameraSourceProfile, ) -> String { match profile { CameraSourceProfile::Raw => format!("{src_desc} ! {src_caps}"), CameraSourceProfile::Mjpeg => format!( "{src_desc} ! \ image/jpeg,width={width},height={height},framerate={fps}/1 ! \ jpegdec ! videoconvert ! videoscale ! videorate ! \ video/x-raw,width={width},height={height},framerate={fps}/1" ), CameraSourceProfile::AutoDecode => format!( "{src_desc} ! \ capsfilter caps=\"{}\" ! \ decodebin ! videoconvert ! videoscale ! videorate ! \ video/x-raw,width={width},height={height},framerate={fps}/1,pixel-aspect-ratio=1/1", camera_auto_decode_caps(width, height, fps) ), } } /// Caps string that lets decodebin negotiate either raw webcam frames or MJPEG. fn camera_auto_decode_caps(width: u32, height: u32, fps: u32) -> String { format!( "video/x-raw,width=(int){width},height=(int){height},framerate=(fraction){fps}/1;image/jpeg,width=(int){width},height=(int){height},framerate=(fraction){fps}/1" ) } fn camera_preview_tap_path() -> Option { std::env::var(CAMERA_PREVIEW_TAP_ENV) .ok() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) .map(PathBuf::from) } fn camera_preview_tap_branch(width: u32, height: u32, fps: u32) -> String { let preview_width = width.clamp(1, i32::MAX as u32); let preview_height = height.clamp(1, i32::MAX as u32); let preview_fps = fps.clamp(1, 60); format!( "videoconvert ! videoscale ! videorate ! \ video/x-raw,format=RGBA,width={preview_width},height={preview_height},framerate={preview_fps}/1,pixel-aspect-ratio=1/1 ! \ appsink name=preview_sink emit-signals=false sync=false max-buffers=1 drop=true" ) }