use crate::video_support::env_u32; const DEFAULT_HEVC_JPEG_QUALITY: u32 = 82; const DEFAULT_HEVC_SIZE_DROP_PCT: u32 = 45; const DEFAULT_HEVC_MIN_REFERENCE_BYTES: u32 = 64 * 1024; /// Resolve the JPEG quality used after HEVC decode. /// /// Inputs: optional `LESAVKA_UVC_HEVC_JPEG_QUALITY`, clamped to 1..=100. /// Output: the `jpegenc` quality value. Why: HEVC ingress must become MJPEG /// for the existing UVC gadget path, and a slightly smaller JPEG lowers USB /// and browser pressure without changing the calibrated A/V timing model. pub(super) fn hevc_jpeg_quality() -> u32 { env_u32("LESAVKA_UVC_HEVC_JPEG_QUALITY", DEFAULT_HEVC_JPEG_QUALITY).clamp(1, 100) } /// Decide whether suspicious decoded-frame freezing is enabled. /// /// Inputs: optional `LESAVKA_UVC_HEVC_FREEZE_ON_SIZE_DROP`. Output: true unless /// explicitly disabled. Why: when one damaged decoded MJPEG frame appears, a /// short freeze is less disruptive than showing a grey slab or torn half-frame. pub(super) fn freeze_on_size_drop_enabled() -> bool { std::env::var("LESAVKA_UVC_HEVC_FREEZE_ON_SIZE_DROP") .ok() .map(|value| { let trimmed = value.trim(); !(trimmed.eq_ignore_ascii_case("0") || trimmed.eq_ignore_ascii_case("false") || trimmed.eq_ignore_ascii_case("no") || trimmed.eq_ignore_ascii_case("off")) }) .unwrap_or(true) } /// Resolve the frame-size drop percentage that triggers a freeze. /// /// Inputs: optional `LESAVKA_UVC_HEVC_SIZE_DROP_PCT`, clamped to 1..=95. /// Output: next-frame size percentage of the last good frame. Why: the guard /// should catch sudden damaged-frame collapses while still allowing normal /// bitrate variation from scene motion and encoder decisions. pub(super) fn size_drop_pct() -> u32 { env_u32("LESAVKA_UVC_HEVC_SIZE_DROP_PCT", DEFAULT_HEVC_SIZE_DROP_PCT).clamp(1, 95) } /// Resolve the minimum reference frame size for collapse detection. /// /// Inputs: optional `LESAVKA_UVC_HEVC_MIN_REFERENCE_BYTES`. Output: byte count. /// Why: tiny synthetic or blank frames should not establish a baseline that /// causes later healthy low-detail frames to be frozen. pub(super) fn min_reference_bytes() -> u32 { env_u32( "LESAVKA_UVC_HEVC_MIN_REFERENCE_BYTES", DEFAULT_HEVC_MIN_REFERENCE_BYTES, ) .max(1) } /// Decide whether a decoded HEVC-to-MJPEG frame should be frozen out. /// /// Inputs: byte length of the last successfully spooled decoded MJPEG and the /// next decoded MJPEG. Output: true when the next frame looks like a damaged /// collapse. Why: keeping the last good frame preserves freshness and sync /// better than forwarding a syntactically valid but visually corrupted JPEG. pub(super) fn should_freeze_decoded_mjpeg(previous_bytes: u64, next_bytes: usize) -> bool { if !freeze_on_size_drop_enabled() || previous_bytes < u64::from(min_reference_bytes()) { return false; } let next_bytes = next_bytes as u64; let threshold_bytes = previous_bytes.saturating_mul(u64::from(size_drop_pct())) / 100; next_bytes < threshold_bytes } #[cfg(test)] mod tests { #[test] fn hevc_jpeg_quality_defaults_to_moderate_transport_pressure() { temp_env::with_var_unset("LESAVKA_UVC_HEVC_JPEG_QUALITY", || { assert_eq!(super::hevc_jpeg_quality(), 82); }); temp_env::with_var("LESAVKA_UVC_HEVC_JPEG_QUALITY", Some("101"), || { assert_eq!(super::hevc_jpeg_quality(), 100); }); temp_env::with_var("LESAVKA_UVC_HEVC_JPEG_QUALITY", Some("0"), || { assert_eq!(super::hevc_jpeg_quality(), 1); }); } #[test] fn freeze_guard_catches_large_decoded_frame_collapses() { temp_env::with_vars( [ ("LESAVKA_UVC_HEVC_FREEZE_ON_SIZE_DROP", Some("1")), ("LESAVKA_UVC_HEVC_SIZE_DROP_PCT", Some("45")), ("LESAVKA_UVC_HEVC_MIN_REFERENCE_BYTES", Some("65536")), ], || { assert!(super::should_freeze_decoded_mjpeg(200_000, 80_000)); assert!(!super::should_freeze_decoded_mjpeg(200_000, 110_000)); assert!(!super::should_freeze_decoded_mjpeg(20_000, 1_000)); }, ); } #[test] fn freeze_guard_can_be_disabled_for_diagnostics() { temp_env::with_vars( [ ("LESAVKA_UVC_HEVC_FREEZE_ON_SIZE_DROP", Some("0")), ("LESAVKA_UVC_HEVC_SIZE_DROP_PCT", Some("95")), ("LESAVKA_UVC_HEVC_MIN_REFERENCE_BYTES", Some("1")), ], || { assert!(!super::should_freeze_decoded_mjpeg(200_000, 1_000)); }, ); } }