use crate::upstream_media_runtime::UpstreamMediaRuntime; use anyhow::{Context, Result}; use chrono::Utc; use lesavka_common::lesavka::{ CalibrationAction, CalibrationRequest, CalibrationState as ProtoCalibrationState, }; use std::path::PathBuf; use std::sync::{Arc, Mutex}; mod mode_env; mod profile_offsets; use mode_env::{current_uvc_mode, lookup_mode_offset_us}; pub use profile_offsets::{ FACTORY_HEVC_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_AUDIO_OFFSET_US, FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_OPUS_AUDIO_OFFSET_US, FACTORY_HEVC_OPUS_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_OPUS_VIDEO_OFFSET_US, FACTORY_HEVC_PCM_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_PCM_AUDIO_OFFSET_US, FACTORY_HEVC_PCM_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_PCM_VIDEO_OFFSET_US, FACTORY_HEVC_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_VIDEO_OFFSET_1280X720_20_US, FACTORY_HEVC_VIDEO_OFFSET_1280X720_30_US, FACTORY_HEVC_VIDEO_OFFSET_1920X1080_20_US, FACTORY_HEVC_VIDEO_OFFSET_1920X1080_30_US, FACTORY_HEVC_VIDEO_OFFSET_US, FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_AUDIO_OFFSET_US, FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_OPUS_AUDIO_OFFSET_US, FACTORY_MJPEG_OPUS_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1280X720_20_US, FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1280X720_30_US, FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1920X1080_20_US, FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1920X1080_30_US, FACTORY_MJPEG_OPUS_VIDEO_OFFSET_US, FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_PCM_AUDIO_OFFSET_US, FACTORY_MJPEG_PCM_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_PCM_VIDEO_OFFSET_US, FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_VIDEO_OFFSET_1280X720_20_US, FACTORY_MJPEG_VIDEO_OFFSET_1280X720_30_US, FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_20_US, FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US, FACTORY_MJPEG_VIDEO_OFFSET_US, }; use profile_offsets::{ configured_profile_offset_us, current_profile, factory_audio_mode_offsets_us, factory_audio_scalar_offset_us, factory_video_mode_offsets_us, factory_video_scalar_offset_us, normalize_calibration_profile, }; const LEGACY_FACTORY_MJPEG_AUDIO_OFFSET_US: i64 = -45_000; const PREVIOUS_FACTORY_MJPEG_AUDIO_OFFSET_US: i64 = 720_000; const PREVIOUS_TUNED_MJPEG_AUDIO_OFFSET_US: i64 = 1_260_000; const PREVIOUS_FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = 0; const PREVIOUS_DELAYED_FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = 350_000; const PREVIOUS_BROWSER_FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = 130_000; const PREVIOUS_SCALAR_FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = 170_000; const PREVIOUS_OVERSHOT_FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = 1_090_000; const FACTORY_CONFIDENCE: &str = "factory"; const PREVIOUS_OFFSET_LIMIT_US: i64 = 500_000; const OFFSET_LIMIT_US: i64 = 1_500_000; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CalibrationMedia { Audio, Video, } #[derive(Debug, Clone, PartialEq, Eq)] struct CalibrationSnapshot { profile: String, factory_audio_offset_us: i64, factory_video_offset_us: i64, default_audio_offset_us: i64, default_video_offset_us: i64, active_audio_offset_us: i64, active_video_offset_us: i64, source: String, confidence: String, updated_at: String, detail: String, } #[derive(Debug)] pub struct CalibrationStore { path: PathBuf, runtime: Arc, state: Mutex, } impl CalibrationStore { /// Keeps `load` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. pub fn load(runtime: Arc) -> Self { let path = calibration_path(); let state = std::fs::read_to_string(&path) .ok() .map(|raw| migrate_legacy_snapshot(parse_snapshot(&raw))) .unwrap_or_else(snapshot_from_env); runtime.set_playout_offsets(state.active_video_offset_us, state.active_audio_offset_us); Self { path, runtime, state: Mutex::new(state), } } pub fn current(&self) -> ProtoCalibrationState { self.state .lock() .expect("calibration mutex poisoned") .to_proto() } /// Keeps `apply` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. pub fn apply(&self, request: CalibrationRequest) -> Result { let mut state = self.state.lock().expect("calibration mutex poisoned"); let action = CalibrationAction::try_from(request.action).unwrap_or(CalibrationAction::Unspecified); match action { CalibrationAction::Unspecified => {} CalibrationAction::RestoreDefault => { state.active_audio_offset_us = state.default_audio_offset_us; state.active_video_offset_us = state.default_video_offset_us; state.source = "default".to_string(); state.confidence = "saved-default".to_string(); state.detail = "restored saved upstream A/V calibration".to_string(); touch(&mut state); } CalibrationAction::RestoreFactory => { *state = factory_snapshot_from_env(format!( "restored release-shipped {} upstream calibration", current_profile() )); } CalibrationAction::AdjustActive => { state.active_audio_offset_us = clamp_offset( state .active_audio_offset_us .saturating_add(request.audio_delta_us), ); state.active_video_offset_us = clamp_offset( state .active_video_offset_us .saturating_add(request.video_delta_us), ); state.source = "manual".to_string(); state.confidence = "manual".to_string(); state.detail = format!( "manual upstream A/V calibration nudge: audio {:+.1}ms, video {:+.1}ms", request.audio_delta_us as f64 / 1000.0, request.video_delta_us as f64 / 1000.0 ); touch(&mut state); } CalibrationAction::BlindEstimate => { state.active_audio_offset_us = clamp_offset( state .active_audio_offset_us .saturating_add(request.audio_delta_us), ); state.active_video_offset_us = clamp_offset( state .active_video_offset_us .saturating_add(request.video_delta_us), ); state.source = "blind".to_string(); state.confidence = "estimated".to_string(); state.detail = if request.note.trim().is_empty() { format!( "blind estimate applied from relay telemetry: delivery skew {:.1}ms, enqueue skew {:.1}ms", request.observed_delivery_skew_ms, request.observed_enqueue_skew_ms ) } else { request.note }; touch(&mut state); } CalibrationAction::SaveActiveAsDefault => { state.default_audio_offset_us = state.active_audio_offset_us; state.default_video_offset_us = state.active_video_offset_us; state.source = "default".to_string(); state.confidence = "measured".to_string(); state.detail = "saved current upstream A/V calibration as site default".to_string(); touch(&mut state); } } self.runtime .set_playout_offsets(state.active_video_offset_us, state.active_audio_offset_us); persist_snapshot(&self.path, &state)?; Ok(state.to_proto()) } /// Keeps `apply_transient_blind_estimate` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. pub fn apply_transient_blind_estimate( &self, audio_delta_us: i64, video_delta_us: i64, observed_delivery_skew_ms: f32, observed_enqueue_skew_ms: f32, note: impl Into, ) -> ProtoCalibrationState { let mut state = self.state.lock().expect("calibration mutex poisoned"); state.active_audio_offset_us = clamp_offset(state.active_audio_offset_us.saturating_add(audio_delta_us)); state.active_video_offset_us = clamp_offset(state.active_video_offset_us.saturating_add(video_delta_us)); state.source = "blind".to_string(); state.confidence = "runtime-estimated".to_string(); let note = note.into(); state.detail = if note.trim().is_empty() { format!( "transient blind estimate from relay telemetry: delivery skew {:.1}ms, enqueue skew {:.1}ms", observed_delivery_skew_ms, observed_enqueue_skew_ms ) } else { note }; touch(&mut state); self.runtime .set_playout_offsets(state.active_video_offset_us, state.active_audio_offset_us); state.to_proto() } } impl CalibrationSnapshot { /// Keeps `to_proto` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. fn to_proto(&self) -> ProtoCalibrationState { ProtoCalibrationState { profile: self.profile.clone(), factory_audio_offset_us: self.factory_audio_offset_us, factory_video_offset_us: self.factory_video_offset_us, default_audio_offset_us: self.default_audio_offset_us, default_video_offset_us: self.default_video_offset_us, active_audio_offset_us: self.active_audio_offset_us, active_video_offset_us: self.active_video_offset_us, source: self.source.clone(), confidence: self.confidence.clone(), updated_at: self.updated_at.clone(), detail: self.detail.clone(), } } } pub fn calibration_path() -> PathBuf { std::env::var("LESAVKA_CALIBRATION_PATH") .ok() .filter(|path| !path.trim().is_empty()) .map(PathBuf::from) .unwrap_or_else(|| PathBuf::from("/var/lib/lesavka/calibration.toml")) } /// Resolve the startup playout offset for the current calibration profile. /// /// Inputs: media kind plus process env. Output: microsecond offset selected /// from profile-specific env or factory maps. Why: the upstream runtime starts /// before the persisted calibration store is loaded, so it still needs the same /// camera/audio profile fork used by the durable calibration state. pub fn configured_playout_offset_us(media: CalibrationMedia) -> i64 { let mode = current_uvc_mode(); let profile = current_profile(); let (media_name, factory_map, factory_scalar, stale) = match media { CalibrationMedia::Audio => ( "AUDIO", factory_audio_mode_offsets_us(&profile), factory_audio_scalar_offset_us(&profile), is_stale_audio_offset_us as fn(i64) -> bool, ), CalibrationMedia::Video => ( "VIDEO", factory_video_mode_offsets_us(&profile), factory_video_scalar_offset_us(&profile), is_stale_video_offset_us as fn(i64) -> bool, ), }; configured_profile_offset_us(&profile, media_name, mode.as_deref(), stale) .or_else(|| { mode.as_deref() .and_then(|mode| lookup_mode_offset_us(factory_map, mode)) }) .unwrap_or(factory_scalar) } /// Keeps `snapshot_from_env` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. fn snapshot_from_env() -> CalibrationSnapshot { let mode = current_uvc_mode(); let profile = current_profile(); let factory_audio_mode_offsets_us = factory_audio_mode_offsets_us(&profile); let factory_video_mode_offsets_us = factory_video_mode_offsets_us(&profile); let factory_audio_scalar_offset_us = factory_audio_scalar_offset_us(&profile); let factory_video_scalar_offset_us = factory_video_scalar_offset_us(&profile); let factory_audio_offset_us = mode .as_deref() .and_then(|mode| lookup_mode_offset_us(factory_audio_mode_offsets_us, mode)) .unwrap_or(factory_audio_scalar_offset_us); let factory_video_offset_us = mode .as_deref() .and_then(|mode| lookup_mode_offset_us(factory_video_mode_offsets_us, mode)) .unwrap_or(factory_video_scalar_offset_us); let env_audio = configured_profile_offset_us(&profile, "AUDIO", mode.as_deref(), is_stale_audio_offset_us); let env_video = configured_profile_offset_us(&profile, "VIDEO", mode.as_deref(), is_stale_video_offset_us); let default_audio_offset_us = env_audio.unwrap_or(factory_audio_offset_us); let default_video_offset_us = env_video.unwrap_or(factory_video_offset_us); let source = if env_audio.is_some() || env_video.is_some() { "env".to_string() } else { "factory".to_string() }; let confidence = if source == "factory" { FACTORY_CONFIDENCE.to_string() } else { "configured".to_string() }; CalibrationSnapshot { profile, factory_audio_offset_us, factory_video_offset_us, default_audio_offset_us, default_video_offset_us, active_audio_offset_us: default_audio_offset_us, active_video_offset_us: default_video_offset_us, source, confidence, updated_at: Utc::now().to_rfc3339(), detail: "loaded upstream A/V calibration defaults".to_string(), } } /// Keeps `parse_snapshot` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. fn parse_snapshot(raw: &str) -> CalibrationSnapshot { let fallback = snapshot_from_env(); let value = |key: &str| -> Option { raw.lines().find_map(|line| { let trimmed = line.trim(); let (left, right) = trimmed.split_once('=')?; (left.trim() == key).then(|| right.trim().trim_matches('"').to_string()) }) }; let number = |key: &str, default: i64| -> i64 { value(key) .and_then(|raw| raw.parse::().ok()) .map(clamp_offset) .unwrap_or(default) }; CalibrationSnapshot { profile: value("profile").unwrap_or(fallback.profile), factory_audio_offset_us: fallback.factory_audio_offset_us, factory_video_offset_us: fallback.factory_video_offset_us, default_audio_offset_us: number( "default_audio_offset_us", fallback.default_audio_offset_us, ), default_video_offset_us: number( "default_video_offset_us", fallback.default_video_offset_us, ), active_audio_offset_us: number("active_audio_offset_us", fallback.active_audio_offset_us), active_video_offset_us: number("active_video_offset_us", fallback.active_video_offset_us), source: value("source").unwrap_or(fallback.source), confidence: value("confidence").unwrap_or(fallback.confidence), updated_at: value("updated_at").unwrap_or(fallback.updated_at), detail: value("detail").unwrap_or(fallback.detail), } } /// Keeps `migrate_legacy_snapshot` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapshot { let current_profile = current_profile(); let stored_profile = normalize_calibration_profile(&state.profile); let source_allows_migration = matches!(state.source.as_str(), "factory" | "env"); let confidence_allows_migration = matches!(state.confidence.as_str(), "factory" | "configured"); let detail_allows_profile_migration = state .detail .contains("loaded upstream A/V calibration defaults") || state.detail.contains("restored release-shipped"); if stored_profile != current_profile && source_allows_migration && confidence_allows_migration && detail_allows_profile_migration { let mut replacement = factory_snapshot_from_env(format!( "migrated factory upstream A/V calibration profile from {} to {}", stored_profile, current_profile )); replacement.detail = format!( "migrated factory upstream A/V calibration profile from {} to {}", stored_profile, replacement.profile ); return replacement; } let clamped_previous_baseline = matches!( state.default_audio_offset_us, PREVIOUS_OFFSET_LIMIT_US | OFFSET_LIMIT_US ) && state .detail .contains("loaded upstream A/V calibration defaults"); let untouched_legacy_audio = (is_stale_audio_offset_us(state.default_audio_offset_us) || state.default_audio_offset_us == state.factory_audio_offset_us || clamped_previous_baseline) && state.active_audio_offset_us == state.default_audio_offset_us; let untouched_legacy_video = is_stale_video_offset_us(state.default_video_offset_us) && state.active_video_offset_us == state.default_video_offset_us; if stored_profile == current_profile && source_allows_migration && confidence_allows_migration && untouched_legacy_audio && untouched_legacy_video { let old_audio_offset_us = state.default_audio_offset_us; let old_video_offset_us = state.default_video_offset_us; state.default_audio_offset_us = state.factory_audio_offset_us; state.active_audio_offset_us = state.factory_audio_offset_us; state.default_video_offset_us = state.factory_video_offset_us; state.active_video_offset_us = state.factory_video_offset_us; state.profile = current_profile; state.source = "factory".to_string(); state.confidence = FACTORY_CONFIDENCE.to_string(); state.detail = format!( "migrated legacy MJPEG upstream A/V baseline from audio {:+.1}ms/video {:+.1}ms to audio {:+.1}ms/video {:+.1}ms", old_audio_offset_us as f64 / 1000.0, old_video_offset_us as f64 / 1000.0, state.factory_audio_offset_us as f64 / 1000.0, state.factory_video_offset_us as f64 / 1000.0 ); touch(&mut state); } state } /// Builds a persisted calibration snapshot from the current profile factory. /// /// Inputs: human-readable audit detail. Output: calibration state whose active /// and default offsets are reset to the profile-specific factory values. /// Why: restore/migration paths must not accidentally revive stale MJPEG /// offsets after the server is running an HEVC decode-to-MJPEG profile. fn factory_snapshot_from_env(detail: impl Into) -> CalibrationSnapshot { let mut state = snapshot_from_env(); state.default_audio_offset_us = state.factory_audio_offset_us; state.default_video_offset_us = state.factory_video_offset_us; state.active_audio_offset_us = state.factory_audio_offset_us; state.active_video_offset_us = state.factory_video_offset_us; state.source = "factory".to_string(); state.confidence = FACTORY_CONFIDENCE.to_string(); state.detail = detail.into(); touch(&mut state); state } /// Keeps `persist_snapshot` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. fn persist_snapshot(path: &PathBuf, state: &CalibrationSnapshot) -> Result<()> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) .with_context(|| format!("creating calibration directory {}", parent.display()))?; } std::fs::write(path, serialize_snapshot(state)) .with_context(|| format!("writing calibration state {}", path.display())) } /// Keeps `serialize_snapshot` explicit because it sits on calibration state, where persisted and factory offsets must stay auditable. /// Inputs are the typed parameters; output is the return value or side effect. fn serialize_snapshot(state: &CalibrationSnapshot) -> String { format!( "profile=\"{}\"\ndefault_audio_offset_us={}\ndefault_video_offset_us={}\nactive_audio_offset_us={}\nactive_video_offset_us={}\nsource=\"{}\"\nconfidence=\"{}\"\nupdated_at=\"{}\"\ndetail=\"{}\"\n", escape_value(&state.profile), state.default_audio_offset_us, state.default_video_offset_us, state.active_audio_offset_us, state.active_video_offset_us, escape_value(&state.source), escape_value(&state.confidence), escape_value(&state.updated_at), escape_value(&state.detail), ) } fn touch(state: &mut CalibrationSnapshot) { state.updated_at = Utc::now().to_rfc3339(); } fn is_stale_audio_offset_us(offset: i64) -> bool { matches!( offset, LEGACY_FACTORY_MJPEG_AUDIO_OFFSET_US | PREVIOUS_FACTORY_MJPEG_AUDIO_OFFSET_US | PREVIOUS_TUNED_MJPEG_AUDIO_OFFSET_US ) } fn is_stale_video_offset_us(offset: i64) -> bool { matches!( offset, PREVIOUS_FACTORY_MJPEG_VIDEO_OFFSET_US | PREVIOUS_DELAYED_FACTORY_MJPEG_VIDEO_OFFSET_US | PREVIOUS_BROWSER_FACTORY_MJPEG_VIDEO_OFFSET_US | PREVIOUS_SCALAR_FACTORY_MJPEG_VIDEO_OFFSET_US | PREVIOUS_OVERSHOT_FACTORY_MJPEG_VIDEO_OFFSET_US ) } fn clamp_offset(value: i64) -> i64 { value.clamp(-OFFSET_LIMIT_US, OFFSET_LIMIT_US) } fn escape_value(value: &str) -> String { value.replace('\\', "\\\\").replace('"', "\\\"") } #[cfg(test)] #[path = "calibration/tests/mod.rs"] mod tests;