lesavka/client/src/input/camera/source_description.rs

77 lines
2.9 KiB
Rust

/// 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<PathBuf> {
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"
)
}