calibration: bake per-mode RC delays

This commit is contained in:
Brad Stein 2026-05-06 03:59:20 -03:00
parent 40287aca33
commit 1b3b8c2cbb
11 changed files with 408 additions and 65 deletions

6
Cargo.lock generated
View File

@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lesavka_client"
version = "0.19.28"
version = "0.19.29"
dependencies = [
"anyhow",
"async-stream",
@ -1686,7 +1686,7 @@ dependencies = [
[[package]]
name = "lesavka_common"
version = "0.19.28"
version = "0.19.29"
dependencies = [
"anyhow",
"base64",
@ -1698,7 +1698,7 @@ dependencies = [
[[package]]
name = "lesavka_server"
version = "0.19.28"
version = "0.19.29"
dependencies = [
"anyhow",
"base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.19.28"
version = "0.19.29"
edition = "2024"
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.19.28"
version = "0.19.29"
edition = "2024"
build = "build.rs"

View File

@ -263,6 +263,7 @@ from `LESAVKA_CLIENT_PKI_SSH_SOURCE` over SSH. Runtime clients require the insta
| `LESAVKA_TOUCHPAD_SCALE` | input routing/clipboard override |
| `LESAVKA_UAC_DEV` | server hardware/device override |
| `LESAVKA_UAC_SESSION_CLOCK_ALIGN` | server audio sink clock-alignment override; `0` is the host-validated default |
| `LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US` | server upstream per-UVC-mode UAC output-path map, e.g. `1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0` |
| `LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US` | server upstream output-path override; v2 uses it as the explicit UAC handoff delay relative to the shared client capture clock |
| `LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS` | server upstream sync override; how long video may wait past its nominal due time for UAC audio to reach the matching timestamp, defaults to `350` |
| `LESAVKA_UPSTREAM_BUNDLED_PLAYOUT_DELAY_MS` | compatibility alias for `LESAVKA_UPSTREAM_V2_PLAYOUT_DELAY_MS` |
@ -274,6 +275,7 @@ from `LESAVKA_CLIENT_PKI_SSH_SOURCE` over SSH. Runtime clients require the insta
| `LESAVKA_UPSTREAM_TIMING_TRACE` | upstream capture/rebase trace override for sync debugging |
| `LESAVKA_UPSTREAM_V2_MAX_LIVE_AGE_MS` | v2 bundled webcam freshness ceiling; bundles already older than this are dropped as one unit, defaults to `1000` |
| `LESAVKA_UPSTREAM_V2_PLAYOUT_DELAY_MS` | v2 optional common playout slack after sync offsets; defaults to `20` and is reduced when needed to protect the live-age budget |
| `LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US` | server upstream per-UVC-mode output-path map; shipped MJPEG defaults are `1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952` |
| `LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US` | server upstream output-path override; v2 uses it as the explicit UVC handoff delay relative to the shared client capture clock, defaults to the calibrated MJPEG/UVC offset |
| `LESAVKA_UPLINK_CAMERA_PREVIEW` | client media capture/playback override |
| `LESAVKA_UPLINK_MIC_LEVEL` | client media capture/playback override |

View File

@ -15,25 +15,68 @@ INSTALL_SERVER_BIND_ADDR=${LESAVKA_INSTALL_SERVER_BIND_ADDR:-0.0.0.0:50051}
LESAVKA_TLS_DIR=${LESAVKA_TLS_DIR:-/etc/lesavka/pki}
LESAVKA_CLIENT_BUNDLE=${LESAVKA_CLIENT_BUNDLE:-/etc/lesavka/lesavka-client-pki.tar.gz}
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=170000
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952
LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-45000
PREVIOUS_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=720000
PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=1260000
PREVIOUS_ZERO_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=0
PREVIOUS_DELAYED_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=350000
PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=130000
PREVIOUS_SCALAR_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=170000
PREVIOUS_OVERSHOT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=1090000
lookup_mode_offset_us() {
local mode=$1
local offset_map=$2
local entry key value
IFS=',' read -r -a offset_entries <<<"${offset_map}"
for entry in "${offset_entries[@]}"; do
key=${entry%%=*}
value=${entry#*=}
if [[ "${key}" == "${mode}" && "${value}" =~ ^-?[0-9]+$ ]]; then
printf '%s\n' "${value}"
return 0
fi
done
return 1
}
default_uvc_mode() {
local width=${LESAVKA_UVC_WIDTH:-1280}
local height=${LESAVKA_UVC_HEIGHT:-720}
local fps=${LESAVKA_UVC_FPS:-30}
printf '%sx%s@%s\n' "${width}" "${height}" "${fps}"
}
default_mjpeg_upstream_audio_playout_offset_us() {
local mode
mode=$(default_uvc_mode)
lookup_mode_offset_us "${mode}" "$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US" \
|| printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"
}
default_mjpeg_upstream_video_playout_offset_us() {
local mode
mode=$(default_uvc_mode)
lookup_mode_offset_us "${mode}" "$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US" \
|| printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"
}
resolve_upstream_audio_playout_offset_us() {
local default_offset_us
default_offset_us=$(default_mjpeg_upstream_audio_playout_offset_us)
if [[ -n ${LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} ]]; then
printf '%s\n' "$LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"
return 0
fi
if [[ ${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} == "$LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US" ]]; then
echo "⚠️ migrating stale upstream audio playout offset to the 0.17 freshness-first planner default." >&2
echo "⚠️ migrating stale upstream audio playout offset to the per-mode MJPEG/UAC default." >&2
echo " Use LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US to intentionally keep an older value." >&2
printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"
printf '%s\n' "$default_offset_us"
return 0
fi
@ -42,19 +85,22 @@ resolve_upstream_audio_playout_offset_us() {
return 0
fi
printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"
printf '%s\n' "$default_offset_us"
}
resolve_upstream_video_playout_offset_us() {
local default_offset_us
default_offset_us=$(default_mjpeg_upstream_video_playout_offset_us)
if [[ -n ${LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} ]]; then
printf '%s\n' "$LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"
return 0
fi
if [[ ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_ZERO_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_DELAYED_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_OVERSHOT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" ]]; then
echo "⚠️ migrating stale upstream video playout offset to the direct UVC/UAC MJPEG sync center." >&2
if [[ ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_ZERO_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_DELAYED_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_SCALAR_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" || ${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-} == "$PREVIOUS_OVERSHOT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US" ]]; then
echo "⚠️ migrating stale upstream video playout offset to the per-mode direct UVC/UAC MJPEG sync center." >&2
echo " Use LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US to intentionally keep an older value." >&2
printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"
printf '%s\n' "$default_offset_us"
return 0
fi
@ -63,7 +109,7 @@ resolve_upstream_video_playout_offset_us() {
return 0
fi
printf '%s\n' "$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"
printf '%s\n' "$default_offset_us"
}
manifest_package_version() {
@ -1004,6 +1050,8 @@ fi
printf 'LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS=%s\n' "${LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS:-350}"
printf 'LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS=%s\n' "${LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS:-1000}"
printf 'LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS=%s\n' "${LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS:-60000}"
printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "$(resolve_upstream_audio_playout_offset_us)"
printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "$(resolve_upstream_video_playout_offset_us)"
printf 'LESAVKA_UPSTREAM_PAIR_SLACK_US=%s\n' "${LESAVKA_UPSTREAM_PAIR_SLACK_US:-80000}"

View File

@ -32,9 +32,9 @@ SSH_OPTS=${SSH_OPTS:-"-o BatchMode=yes -o ConnectTimeout=30"}
LESAVKA_SERVER_RC_CORE_WEBCAM_MODES=${LESAVKA_SERVER_RC_CORE_WEBCAM_MODES:-1280x720@20,1280x720@30,1920x1080@20,1920x1080@30}
LESAVKA_SERVER_RC_MODES=${LESAVKA_SERVER_RC_MODES:-${LESAVKA_SERVER_RC_CORE_WEBCAM_MODES}}
LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US:-${LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US:-0}}
LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US:-170000}
LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US:-135090}
LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US:-1280x720@20=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1280x720@30=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1920x1080@20=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1920x1080@30=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US}}
LESAVKA_SERVER_RC_MODE_DELAYS_US=${LESAVKA_SERVER_RC_MODE_DELAYS_US:-1280x720@20=170000,1280x720@30=170000,1920x1080@20=170000,1920x1080@30=170000}
LESAVKA_SERVER_RC_MODE_DELAYS_US=${LESAVKA_SERVER_RC_MODE_DELAYS_US:-1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952}
LESAVKA_SERVER_RC_MODE_DISCOVERY_SIZES=${LESAVKA_SERVER_RC_MODE_DISCOVERY_SIZES:-1280x720,1920x1080}
LESAVKA_SERVER_RC_MODE_DISCOVERY_FPS=${LESAVKA_SERVER_RC_MODE_DISCOVERY_FPS:-20,30}
LESAVKA_SERVER_RC_MODE_DISCOVERY_INCLUDE_REGEX=${LESAVKA_SERVER_RC_MODE_DISCOVERY_INCLUDE_REGEX:-Logitech|BRIO|C9[0-9]+|HD UVC WebCam|USB2[.]0 HD|Integrated Camera|Webcam|Camera}
@ -979,6 +979,8 @@ reconfigure_server_mode() {
local width=$2
local height=$3
local fps=$4
local audio_delay_us=${5:-$(lookup_audio_delay_us "${mode}")}
local video_delay_us=${6:-$(lookup_video_delay_us "${mode}")}
[[ "${LESAVKA_SERVER_RC_RECONFIGURE}" != "0" ]] || return 0
echo "==> reconfiguring ${LESAVKA_SERVER_HOST} UVC gadget for ${mode}"
@ -987,6 +989,8 @@ reconfigure_server_mode() {
LESAVKA_UVC_WIDTH="${width}" \
LESAVKA_UVC_HEIGHT="${height}" \
LESAVKA_UVC_FPS="${fps}" \
LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US="${audio_delay_us}" \
LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US="${video_delay_us}" \
bash -c "${LESAVKA_SERVER_RC_RECONFIGURE_COMMAND}"
return 0
fi
@ -1009,7 +1013,11 @@ reconfigure_server_mode() {
"${LESAVKA_SERVER_RC_ALLOW_GADGET_RESET}" \
"${LESAVKA_SERVER_RC_FORCE_GADGET_REBUILD}" \
"${LESAVKA_SERVER_RC_RECONFIGURE_SETTLE_SECONDS}" \
"${LESAVKA_SERVER_RC_RECONFIGURE_VERBOSE}" <<'REMOTE_RECONFIGURE'
"${LESAVKA_SERVER_RC_RECONFIGURE_VERBOSE}" \
"${audio_delay_us}" \
"${video_delay_us}" \
"${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}" \
"${LESAVKA_SERVER_RC_MODE_DELAYS_US}" <<'REMOTE_RECONFIGURE'
set -euo pipefail
mode=$1
width=$2
@ -1021,6 +1029,10 @@ allow_gadget_reset=$7
force_gadget_rebuild=$8
settle_seconds=$9
verbose=${10}
audio_delay_us=${11}
video_delay_us=${12}
audio_delay_map=${13}
video_delay_map=${14}
set_env_value() {
local file=$1
@ -1068,6 +1080,10 @@ set_env_value /etc/lesavka/server.env LESAVKA_UVC_WIDTH "${width}"
set_env_value /etc/lesavka/server.env LESAVKA_UVC_HEIGHT "${height}"
set_env_value /etc/lesavka/server.env LESAVKA_UVC_FPS "${fps}"
set_env_value /etc/lesavka/server.env LESAVKA_UVC_INTERVAL "${interval}"
set_env_value /etc/lesavka/server.env LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US "${audio_delay_map}"
set_env_value /etc/lesavka/server.env LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US "${video_delay_map}"
set_env_value /etc/lesavka/server.env LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US "${audio_delay_us}"
set_env_value /etc/lesavka/server.env LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US "${video_delay_us}"
set_env_value /etc/lesavka/uvc.env LESAVKA_UVC_CODEC "${codec}"
set_env_value /etc/lesavka/uvc.env LESAVKA_UVC_WIDTH "${width}"
@ -2282,7 +2298,7 @@ for mode in "${modes[@]}"; do
mkdir -p "${mode_dir}"
echo "==> mode ${mode} run ${mode_run_index} repeat ${repeat_index}/${LESAVKA_SERVER_RC_REPEAT_COUNT}: video_delay_us=${video_delay_us} audio_delay_us=${audio_delay_us}"
reconfigure_server_mode "${mode}" "${width}" "${height}" "${fps}"
reconfigure_server_mode "${mode}" "${width}" "${height}" "${fps}" "${audio_delay_us}" "${video_delay_us}"
wait_tethys_media_ready "${mode}" "${width}" "${height}" "${fps}"
if [[ "${LESAVKA_SERVER_RC_SIGNAL_READY}" != "0" && "${LESAVKA_SERVER_RC_SIGNAL_READY_MODE}" != "separate" ]]; then

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.19.28"
version = "0.19.29"
edition = "2024"
autobins = false

View File

@ -10,16 +10,25 @@ use lesavka_common::lesavka::{
use crate::upstream_media_runtime::UpstreamMediaRuntime;
pub const FACTORY_MJPEG_AUDIO_OFFSET_US: i64 = 0;
// Direct UVC/UAC output-delay probes against the lab RC target put the
// server-to-target sync center near 170ms for MJPEG/UVC video. This is an
// output-path compensation, not a freshness buffer.
pub const FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = 170_000;
pub const FACTORY_MJPEG_VIDEO_OFFSET_1280X720_20_US: i64 = 162_659;
pub const FACTORY_MJPEG_VIDEO_OFFSET_1280X720_30_US: i64 = 135_090;
pub const FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_20_US: i64 = 160_045;
pub const FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US: i64 = 127_952;
pub const FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US: &str =
"1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0";
pub const FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US: &str =
"1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952";
// Direct UVC/UAC output-delay probes against the lab RC target showed a
// per-mode sync center for MJPEG/UVC video. This is output-path compensation,
// not a freshness buffer. The scalar fallback follows the default UVC mode.
pub const FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = FACTORY_MJPEG_VIDEO_OFFSET_1280X720_30_US;
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 PROFILE: &str = "mjpeg";
const FACTORY_CONFIDENCE: &str = "factory";
@ -208,10 +217,29 @@ pub fn calibration_path() -> PathBuf {
}
fn snapshot_from_env() -> CalibrationSnapshot {
let env_audio = env_i64("LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US");
let env_video = env_i64("LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US");
let default_audio_offset_us = env_audio.unwrap_or(FACTORY_MJPEG_AUDIO_OFFSET_US);
let default_video_offset_us = env_video.unwrap_or(FACTORY_MJPEG_VIDEO_OFFSET_US);
let mode = current_uvc_mode();
let factory_audio_offset_us = mode
.as_deref()
.and_then(|mode| lookup_mode_offset_us(FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, mode))
.unwrap_or(FACTORY_MJPEG_AUDIO_OFFSET_US);
let factory_video_offset_us = mode
.as_deref()
.and_then(|mode| lookup_mode_offset_us(FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US, mode))
.unwrap_or(FACTORY_MJPEG_VIDEO_OFFSET_US);
let env_audio = configured_offset_us(
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US",
mode.as_deref(),
is_stale_audio_offset_us,
);
let env_video = configured_offset_us(
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US",
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US",
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 {
@ -224,8 +252,8 @@ fn snapshot_from_env() -> CalibrationSnapshot {
};
CalibrationSnapshot {
profile: PROFILE.to_string(),
factory_audio_offset_us: FACTORY_MJPEG_AUDIO_OFFSET_US,
factory_video_offset_us: FACTORY_MJPEG_VIDEO_OFFSET_US,
factory_audio_offset_us,
factory_video_offset_us,
default_audio_offset_us,
default_video_offset_us,
active_audio_offset_us: default_audio_offset_us,
@ -254,8 +282,8 @@ fn parse_snapshot(raw: &str) -> CalibrationSnapshot {
};
CalibrationSnapshot {
profile: value("profile").unwrap_or(fallback.profile),
factory_audio_offset_us: FACTORY_MJPEG_AUDIO_OFFSET_US,
factory_video_offset_us: FACTORY_MJPEG_VIDEO_OFFSET_US,
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,
@ -282,21 +310,12 @@ fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapsho
) && state
.detail
.contains("loaded upstream A/V calibration defaults");
let untouched_legacy_audio = (matches!(
state.default_audio_offset_us,
FACTORY_MJPEG_AUDIO_OFFSET_US
| LEGACY_FACTORY_MJPEG_AUDIO_OFFSET_US
| PREVIOUS_FACTORY_MJPEG_AUDIO_OFFSET_US
| PREVIOUS_TUNED_MJPEG_AUDIO_OFFSET_US
) || clamped_previous_baseline)
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 = matches!(
state.default_video_offset_us,
PREVIOUS_FACTORY_MJPEG_VIDEO_OFFSET_US
| PREVIOUS_DELAYED_FACTORY_MJPEG_VIDEO_OFFSET_US
| PREVIOUS_BROWSER_FACTORY_MJPEG_VIDEO_OFFSET_US
| PREVIOUS_OVERSHOT_FACTORY_MJPEG_VIDEO_OFFSET_US
) && state.active_video_offset_us == state.default_video_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 state.profile == PROFILE
&& source_allows_migration
&& confidence_allows_migration
@ -305,18 +324,18 @@ fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapsho
{
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 = FACTORY_MJPEG_AUDIO_OFFSET_US;
state.active_audio_offset_us = FACTORY_MJPEG_AUDIO_OFFSET_US;
state.default_video_offset_us = FACTORY_MJPEG_VIDEO_OFFSET_US;
state.active_video_offset_us = FACTORY_MJPEG_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.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,
FACTORY_MJPEG_AUDIO_OFFSET_US as f64 / 1000.0,
FACTORY_MJPEG_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);
}
@ -351,6 +370,67 @@ fn touch(state: &mut CalibrationSnapshot) {
state.updated_at = Utc::now().to_rfc3339();
}
fn configured_offset_us(
mode_map_name: &str,
scalar_name: &str,
mode: Option<&str>,
is_stale_scalar: impl Fn(i64) -> bool,
) -> Option<i64> {
mode.and_then(|mode| env_mode_offset_us(mode_map_name, mode))
.or_else(|| env_i64(scalar_name).filter(|offset| !is_stale_scalar(*offset)))
}
fn current_uvc_mode() -> Option<String> {
env_mode("UVC_MODE")
.or_else(|| env_mode("LESAVKA_UVC_MODE"))
.or_else(|| {
let width = env_u32("LESAVKA_UVC_WIDTH")?;
let height = env_u32("LESAVKA_UVC_HEIGHT")?;
let fps = env_u32("LESAVKA_UVC_FPS")
.or_else(|| {
env_u32("LESAVKA_UVC_INTERVAL")
.and_then(|interval| (interval > 0).then_some(10_000_000 / interval))
})?
.max(1);
Some(format!("{width}x{height}@{fps}"))
})
.or_else(|| {
let width = env_u32("LESAVKA_CAM_WIDTH")?;
let height = env_u32("LESAVKA_CAM_HEIGHT")?;
let fps = env_u32("LESAVKA_CAM_FPS")?.max(1);
Some(format!("{width}x{height}@{fps}"))
})
}
fn env_mode(name: &str) -> Option<String> {
std::env::var(name).ok().and_then(|value| {
let trimmed = value.trim();
let valid = trimmed.split_once('@').and_then(|(size, fps)| {
let (width, height) = size.split_once('x')?;
width.parse::<u32>().ok()?;
height.parse::<u32>().ok()?;
fps.parse::<u32>().ok()?;
Some(())
});
valid.map(|()| trimmed.to_string())
})
}
fn env_mode_offset_us(name: &str, mode: &str) -> Option<i64> {
std::env::var(name)
.ok()
.and_then(|map| lookup_mode_offset_us(&map, mode))
}
fn lookup_mode_offset_us(map: &str, mode: &str) -> Option<i64> {
map.split(',').find_map(|entry| {
let (key, value) = entry.trim().split_once('=')?;
(key.trim() == mode)
.then(|| value.trim().parse::<i64>().ok().map(clamp_offset))
.flatten()
})
}
fn env_i64(name: &str) -> Option<i64> {
std::env::var(name)
.ok()
@ -358,6 +438,32 @@ fn env_i64(name: &str) -> Option<i64> {
.map(clamp_offset)
}
fn env_u32(name: &str) -> Option<u32> {
std::env::var(name)
.ok()
.and_then(|value| value.trim().parse::<u32>().ok())
}
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)
}
@ -377,6 +483,18 @@ mod tests {
[
("LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US", None::<&str>),
("LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US", None::<&str>),
(
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
None::<&str>,
),
(
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US",
None::<&str>,
),
("LESAVKA_UVC_WIDTH", None::<&str>),
("LESAVKA_UVC_HEIGHT", None::<&str>),
("LESAVKA_UVC_FPS", None::<&str>),
("LESAVKA_UVC_INTERVAL", None::<&str>),
],
|| {
let state = snapshot_from_env();
@ -387,6 +505,98 @@ mod tests {
);
}
#[test]
fn default_snapshot_uses_uvc_mode_factory_calibration() {
temp_env::with_vars(
[
("LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US", None::<&str>),
("LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US", None::<&str>),
(
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
None::<&str>,
),
(
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US",
None::<&str>,
),
("LESAVKA_UVC_WIDTH", Some("1920")),
("LESAVKA_UVC_HEIGHT", Some("1080")),
("LESAVKA_UVC_FPS", Some("30")),
("LESAVKA_UVC_INTERVAL", None::<&str>),
],
|| {
let state = snapshot_from_env();
assert_eq!(
state.default_video_offset_us,
FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US
);
assert_eq!(
state.factory_video_offset_us,
FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US
);
assert_eq!(state.source, "factory");
},
);
}
#[test]
fn mode_offset_map_overrides_stale_scalar_offset() {
temp_env::with_vars(
[
("LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US", None::<&str>),
("LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US", Some("170000")),
(
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
None::<&str>,
),
(
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US",
Some("1280x720@20=123456"),
),
("LESAVKA_UVC_WIDTH", Some("1280")),
("LESAVKA_UVC_HEIGHT", Some("720")),
("LESAVKA_UVC_FPS", Some("20")),
("LESAVKA_UVC_INTERVAL", None::<&str>),
],
|| {
let state = snapshot_from_env();
assert_eq!(state.default_video_offset_us, 123_456);
assert_eq!(state.source, "env");
assert_eq!(state.confidence, "configured");
},
);
}
#[test]
fn stale_scalar_video_offset_falls_back_to_mode_factory() {
temp_env::with_vars(
[
("LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US", None::<&str>),
("LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US", Some("170000")),
(
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
None::<&str>,
),
(
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US",
None::<&str>,
),
("LESAVKA_UVC_WIDTH", Some("1920")),
("LESAVKA_UVC_HEIGHT", Some("1080")),
("LESAVKA_UVC_FPS", Some("20")),
("LESAVKA_UVC_INTERVAL", None::<&str>),
],
|| {
let state = snapshot_from_env();
assert_eq!(
state.default_video_offset_us,
FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_20_US
);
assert_eq!(state.source, "factory");
},
);
}
#[test]
fn store_persists_manual_adjustments_and_updates_runtime() {
let file = NamedTempFile::new().expect("temp calibration file");
@ -535,7 +745,7 @@ mod tests {
runtime.playout_offsets(),
(FACTORY_MJPEG_VIDEO_OFFSET_US, 0)
);
assert!(state.detail.contains("to audio +0.0ms/video +170.0ms"));
assert!(state.detail.contains("to audio +0.0ms/video +135.1ms"));
});
}

View File

@ -7,9 +7,12 @@ use std::time::Duration;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use tokio::time::Instant;
use crate::calibration::{
FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_AUDIO_OFFSET_US,
FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_VIDEO_OFFSET_US,
};
const TIMING_WINDOW_CAPACITY: usize = 240;
const FACTORY_MJPEG_AUDIO_OFFSET_US: i64 = 0;
const FACTORY_MJPEG_VIDEO_OFFSET_US: i64 = 1_090_000;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UpstreamMediaKind {
@ -727,17 +730,68 @@ fn upstream_playout_delay() -> Duration {
}
fn playout_offset_us(kind: UpstreamMediaKind) -> i64 {
let name = match kind {
UpstreamMediaKind::Camera => "LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US",
UpstreamMediaKind::Microphone => "LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US",
let (scalar_name, mode_map_name, factory_map, factory_offset_us) = match kind {
UpstreamMediaKind::Camera => (
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US",
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US",
FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US,
FACTORY_MJPEG_VIDEO_OFFSET_US,
),
UpstreamMediaKind::Microphone => (
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US",
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US,
FACTORY_MJPEG_AUDIO_OFFSET_US,
),
};
let mode = current_uvc_mode();
mode.as_deref()
.and_then(|mode| env_mode_offset_us(mode_map_name, mode))
.or_else(|| env_i64(scalar_name))
.or_else(|| {
mode.as_deref()
.and_then(|mode| lookup_mode_offset_us(factory_map, mode))
})
.unwrap_or(factory_offset_us)
}
fn current_uvc_mode() -> Option<String> {
let width = env_u32("LESAVKA_UVC_WIDTH")?;
let height = env_u32("LESAVKA_UVC_HEIGHT")?;
let fps = env_u32("LESAVKA_UVC_FPS")
.or_else(|| {
env_u32("LESAVKA_UVC_INTERVAL")
.and_then(|interval| (interval > 0).then_some(10_000_000 / interval))
})?
.max(1);
Some(format!("{width}x{height}@{fps}"))
}
fn env_mode_offset_us(name: &str, mode: &str) -> Option<i64> {
std::env::var(name)
.ok()
.and_then(|map| lookup_mode_offset_us(&map, mode))
}
fn lookup_mode_offset_us(map: &str, mode: &str) -> Option<i64> {
map.split(',').find_map(|entry| {
let (key, value) = entry.trim().split_once('=')?;
(key.trim() == mode)
.then(|| value.trim().parse::<i64>().ok())
.flatten()
})
}
fn env_i64(name: &str) -> Option<i64> {
std::env::var(name)
.ok()
.and_then(|value| value.trim().parse::<i64>().ok())
.unwrap_or(match kind {
UpstreamMediaKind::Camera => FACTORY_MJPEG_VIDEO_OFFSET_US,
UpstreamMediaKind::Microphone => FACTORY_MJPEG_AUDIO_OFFSET_US,
})
}
fn env_u32(name: &str) -> Option<u32> {
std::env::var(name)
.ok()
.and_then(|value| value.trim().parse::<u32>().ok())
}
fn apply_offset(instant: Instant, offset_us: i64) -> Instant {

View File

@ -378,9 +378,9 @@ fn server_rc_mode_matrix_validates_advertised_uvc_profiles() {
"LESAVKA_SERVER_RC_MODES=${LESAVKA_SERVER_RC_MODES:-${LESAVKA_SERVER_RC_CORE_WEBCAM_MODES}}",
"LESAVKA_SERVER_REPO=${LESAVKA_SERVER_REPO:-auto}",
"LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US:-${LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US:-0}}",
"LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US:-170000}",
"LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US:-135090}",
"LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US:-1280x720@20=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1280x720@30=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1920x1080@20=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1920x1080@30=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US}}",
"LESAVKA_SERVER_RC_MODE_DELAYS_US=${LESAVKA_SERVER_RC_MODE_DELAYS_US:-1280x720@20=170000,1280x720@30=170000,1920x1080@20=170000,1920x1080@30=170000}",
"LESAVKA_SERVER_RC_MODE_DELAYS_US=${LESAVKA_SERVER_RC_MODE_DELAYS_US:-1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952}",
"LESAVKA_SERVER_RC_MODE_DISCOVERY_SIZES=${LESAVKA_SERVER_RC_MODE_DISCOVERY_SIZES:-1280x720,1920x1080}",
"LESAVKA_SERVER_RC_MODE_DISCOVERY_FPS=${LESAVKA_SERVER_RC_MODE_DISCOVERY_FPS:-20,30}",
"LESAVKA_SERVER_RC_MODE_DISCOVERY_INCLUDE_REGEX=${LESAVKA_SERVER_RC_MODE_DISCOVERY_INCLUDE_REGEX:-Logitech|BRIO|C9[0-9]+|HD UVC WebCam|USB2[.]0 HD|Integrated Camera|Webcam|Camera}",

View File

@ -21,6 +21,8 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
"LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS=%s",
"LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS=%s",
"LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS=%s",
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s",
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=%s",
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=%s",
"LESAVKA_UPSTREAM_PAIR_SLACK_US=%s",
@ -56,7 +58,13 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS:-1000}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS:-60000}"));
assert!(SERVER_INSTALL.contains("DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0"));
assert!(SERVER_INSTALL.contains("DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=170000"));
assert!(SERVER_INSTALL.contains("DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090"));
assert!(SERVER_INSTALL.contains(
"DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0"
));
assert!(SERVER_INSTALL.contains(
"DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952"
));
assert!(
SERVER_INSTALL.contains("resolve_upstream_video_playout_offset_us"),
"video offset should be resolved through stale-baseline migration logic"
@ -72,6 +80,9 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
assert!(
SERVER_INSTALL.contains("PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=130000")
);
assert!(
SERVER_INSTALL.contains("PREVIOUS_SCALAR_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=170000")
);
assert!(
SERVER_INSTALL.contains("PREVIOUS_OVERSHOT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=1090000")
);
@ -84,12 +95,14 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
"install-specific video offset override should bypass stale ambient runtime env"
);
assert!(
SERVER_INSTALL.contains("migrating stale upstream audio playout offset to the 0.17 freshness-first planner default"),
SERVER_INSTALL.contains(
"migrating stale upstream audio playout offset to the per-mode MJPEG/UAC default"
),
"installer should not preserve old MJPEG/UVC sync baselines accidentally"
);
assert!(
SERVER_INSTALL.contains(
"migrating stale upstream video playout offset to the direct UVC/UAC MJPEG sync center"
"migrating stale upstream video playout offset to the per-mode direct UVC/UAC MJPEG sync center"
),
"installer should not preserve old video delay baselines accidentally"
);