// Webcam capture pipeline, quality selection, and launcher preview tap support. use anyhow::Context; use gst::prelude::*; use gstreamer as gst; use gstreamer_app as gst_app; use lesavka_common::lesavka::VideoPacket; use std::{ io::Write, path::{Path, PathBuf}, sync::{ Arc, atomic::{AtomicBool, Ordering}, }, thread, time::Duration, }; const CAMERA_PREVIEW_TAP_ENV: &str = "LESAVKA_UPLINK_CAMERA_PREVIEW"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum CameraSourceProfile { Raw, Mjpeg, AutoDecode, } fn env_u32(name: &str, default: u32) -> u32 { std::env::var(name) .ok() .and_then(|v| v.parse::().ok()) .unwrap_or(default) } #[derive(Clone, Copy, Debug)] pub enum CameraCodec { H264, Mjpeg, } #[derive(Clone, Copy, Debug)] pub struct CameraConfig { pub codec: CameraCodec, pub width: u32, pub height: u32, pub fps: u32, } pub struct CameraCapture { #[allow(dead_code)] // kept alive to hold PLAYING state pipeline: gst::Pipeline, sink: gst_app::AppSink, preview_tap_running: Option>, pts_rebaser: crate::live_capture_clock::DurationPacedSourcePtsRebaser, frame_duration_us: u64, } include!("camera/capture_pipeline.rs"); include!("camera/device_selection.rs"); include!("camera/encoder_selection.rs"); include!("camera/source_description.rs"); include!("camera/preview_tap.rs"); include!("camera/bus_and_encoder.rs"); #[cfg(test)] mod tests { use super::{CameraCodec, CameraConfig, resolved_capture_profile}; use serial_test::serial; #[test] #[serial] /// Guards the browser-facing UVC contract against launcher preview overrides. fn negotiated_capture_profile_overrides_launcher_quality_env_by_default() { let cfg = CameraConfig { codec: CameraCodec::Mjpeg, width: 640, height: 480, fps: 20, }; temp_env::with_vars( [ ("LESAVKA_CAM_WIDTH", Some("1280")), ("LESAVKA_CAM_HEIGHT", Some("720")), ("LESAVKA_CAM_FPS", Some("30")), ("LESAVKA_CAM_ALLOW_PROFILE_OVERRIDE", None), ], || assert_eq!(resolved_capture_profile(Some(cfg)), (640, 480, 20)), ); } #[test] #[serial] /// Keeps the explicit lab override available for controlled camera debugging. fn explicit_profile_override_keeps_lab_mode_available() { let cfg = CameraConfig { codec: CameraCodec::Mjpeg, width: 640, height: 480, fps: 20, }; temp_env::with_vars( [ ("LESAVKA_CAM_WIDTH", Some("1280")), ("LESAVKA_CAM_HEIGHT", Some("720")), ("LESAVKA_CAM_FPS", Some("30")), ("LESAVKA_CAM_ALLOW_PROFILE_OVERRIDE", Some("1")), ], || assert_eq!(resolved_capture_profile(Some(cfg)), (1280, 720, 30)), ); } }