test(server-rc): require stronger mode tuning evidence
This commit is contained in:
parent
18011c2e72
commit
db72307eda
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.19.17"
|
version = "0.19.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1686,7 +1686,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.19.17"
|
version = "0.19.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1698,7 +1698,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.19.17"
|
version = "0.19.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.19.17"
|
version = "0.19.18"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.19.17"
|
version = "0.19.18"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -60,8 +60,10 @@ LESAVKA_SERVER_RC_PROBE_PREBUILD=${LESAVKA_SERVER_RC_PROBE_PREBUILD:-1}
|
|||||||
LESAVKA_SERVER_RC_CONTINUE_ON_FAIL=${LESAVKA_SERVER_RC_CONTINUE_ON_FAIL:-1}
|
LESAVKA_SERVER_RC_CONTINUE_ON_FAIL=${LESAVKA_SERVER_RC_CONTINUE_ON_FAIL:-1}
|
||||||
LESAVKA_SERVER_RC_TUNE_DELAYS=${LESAVKA_SERVER_RC_TUNE_DELAYS:-1}
|
LESAVKA_SERVER_RC_TUNE_DELAYS=${LESAVKA_SERVER_RC_TUNE_DELAYS:-1}
|
||||||
LESAVKA_SERVER_RC_TUNE_CONFIRM=${LESAVKA_SERVER_RC_TUNE_CONFIRM:-1}
|
LESAVKA_SERVER_RC_TUNE_CONFIRM=${LESAVKA_SERVER_RC_TUNE_CONFIRM:-1}
|
||||||
LESAVKA_SERVER_RC_TUNE_MIN_PAIRS=${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS:-3}
|
LESAVKA_SERVER_RC_TUNE_MIN_PAIRS=${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS:-8}
|
||||||
|
LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS=${LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS:-1000}
|
||||||
LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS=${LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS:-80}
|
LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS=${LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS:-80}
|
||||||
|
LESAVKA_SERVER_RC_TUNE_MAX_STEP_US=${LESAVKA_SERVER_RC_TUNE_MAX_STEP_US:-500000}
|
||||||
LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US=${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US:-5000}
|
LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US=${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US:-5000}
|
||||||
|
|
||||||
LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS:-350}
|
LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS:-350}
|
||||||
@ -186,7 +188,9 @@ run_mode_probe() {
|
|||||||
LESAVKA_OUTPUT_DELAY_SAVE=0 \
|
LESAVKA_OUTPUT_DELAY_SAVE=0 \
|
||||||
LESAVKA_OUTPUT_REQUIRE_SYNC_PASS=0 \
|
LESAVKA_OUTPUT_REQUIRE_SYNC_PASS=0 \
|
||||||
LESAVKA_OUTPUT_DELAY_MIN_PAIRS="${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS}" \
|
LESAVKA_OUTPUT_DELAY_MIN_PAIRS="${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS}" \
|
||||||
|
LESAVKA_OUTPUT_DELAY_MAX_ABS_SKEW_MS="${LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS}" \
|
||||||
LESAVKA_OUTPUT_DELAY_MAX_DRIFT_MS="${LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS}" \
|
LESAVKA_OUTPUT_DELAY_MAX_DRIFT_MS="${LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS}" \
|
||||||
|
LESAVKA_OUTPUT_DELAY_MAX_STEP_US="${LESAVKA_SERVER_RC_TUNE_MAX_STEP_US}" \
|
||||||
LESAVKA_OUTPUT_FRESHNESS_MAX_AGE_MS="${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS}" \
|
LESAVKA_OUTPUT_FRESHNESS_MAX_AGE_MS="${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS}" \
|
||||||
LESAVKA_OUTPUT_FRESHNESS_MAX_DRIFT_MS="${LESAVKA_SERVER_RC_FRESHNESS_MAX_DRIFT_MS}" \
|
LESAVKA_OUTPUT_FRESHNESS_MAX_DRIFT_MS="${LESAVKA_SERVER_RC_FRESHNESS_MAX_DRIFT_MS}" \
|
||||||
LESAVKA_OUTPUT_FRESHNESS_MAX_CLOCK_UNCERTAINTY_MS="${LESAVKA_SERVER_RC_FRESHNESS_MAX_CLOCK_UNCERTAINTY_MS}" \
|
LESAVKA_OUTPUT_FRESHNESS_MAX_CLOCK_UNCERTAINTY_MS="${LESAVKA_SERVER_RC_FRESHNESS_MAX_CLOCK_UNCERTAINTY_MS}" \
|
||||||
@ -206,7 +210,9 @@ write_tune_candidate_env() {
|
|||||||
"${mode_result}" \
|
"${mode_result}" \
|
||||||
"${output_env}" \
|
"${output_env}" \
|
||||||
"${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS}" \
|
"${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS}" \
|
||||||
|
"${LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS}" \
|
||||||
"${LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS}" \
|
"${LESAVKA_SERVER_RC_TUNE_MAX_DRIFT_MS}" \
|
||||||
|
"${LESAVKA_SERVER_RC_TUNE_MAX_STEP_US}" \
|
||||||
"${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US}"
|
"${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US}"
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
@ -214,7 +220,15 @@ import pathlib
|
|||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
result_path, output_env_path, min_pairs_raw, max_drift_raw, min_change_raw = sys.argv[1:6]
|
(
|
||||||
|
result_path,
|
||||||
|
output_env_path,
|
||||||
|
min_pairs_raw,
|
||||||
|
max_abs_skew_raw,
|
||||||
|
max_drift_raw,
|
||||||
|
max_step_raw,
|
||||||
|
min_change_raw,
|
||||||
|
) = sys.argv[1:8]
|
||||||
|
|
||||||
|
|
||||||
def env_line(key, value):
|
def env_line(key, value):
|
||||||
@ -239,8 +253,10 @@ def as_float(value, default=0.0):
|
|||||||
result = json.loads(pathlib.Path(result_path).read_text())
|
result = json.loads(pathlib.Path(result_path).read_text())
|
||||||
calibration = result.get("output_delay_calibration") or {}
|
calibration = result.get("output_delay_calibration") or {}
|
||||||
sync = result.get("sync") or {}
|
sync = result.get("sync") or {}
|
||||||
min_pairs = max(1, as_int(min_pairs_raw, 3))
|
min_pairs = max(1, as_int(min_pairs_raw, 8))
|
||||||
|
max_abs_skew_ms = max(1.0, as_float(max_abs_skew_raw, 1000.0))
|
||||||
max_drift_ms = max(0.0, as_float(max_drift_raw, 80.0))
|
max_drift_ms = max(0.0, as_float(max_drift_raw, 80.0))
|
||||||
|
max_step_us = max(1, as_int(max_step_raw, 500_000))
|
||||||
min_change_us = max(0, as_int(min_change_raw, 5_000))
|
min_change_us = max(0, as_int(min_change_raw, 5_000))
|
||||||
current_audio = as_int(result.get("audio_delay_us"), 0)
|
current_audio = as_int(result.get("audio_delay_us"), 0)
|
||||||
current_video = as_int(result.get("video_delay_us"), 0)
|
current_video = as_int(result.get("video_delay_us"), 0)
|
||||||
@ -248,16 +264,30 @@ target_audio = as_int(calibration.get("audio_target_offset_us"), current_audio)
|
|||||||
target_video = as_int(calibration.get("video_target_offset_us"), current_video)
|
target_video = as_int(calibration.get("video_target_offset_us"), current_video)
|
||||||
paired = as_int(calibration.get("paired_event_count"), as_int(sync.get("paired_event_count"), 0))
|
paired = as_int(calibration.get("paired_event_count"), as_int(sync.get("paired_event_count"), 0))
|
||||||
drift_ms = as_float(calibration.get("drift_ms"), as_float(sync.get("drift_ms"), 0.0))
|
drift_ms = as_float(calibration.get("drift_ms"), as_float(sync.get("drift_ms"), 0.0))
|
||||||
|
max_abs_skew_ms_observed = as_float(
|
||||||
|
calibration.get("max_abs_skew_ms"),
|
||||||
|
as_float(sync.get("p95_abs_skew_ms"), 0.0),
|
||||||
|
)
|
||||||
delta_audio = target_audio - current_audio
|
delta_audio = target_audio - current_audio
|
||||||
delta_video = target_video - current_video
|
delta_video = target_video - current_video
|
||||||
|
raw_delta_us = as_int(
|
||||||
|
calibration.get("raw_device_delta_us"),
|
||||||
|
delta_video if abs(delta_video) >= abs(delta_audio) else -delta_audio,
|
||||||
|
)
|
||||||
reasons = []
|
reasons = []
|
||||||
|
|
||||||
if result.get("run_status") != 0:
|
if result.get("run_status") != 0:
|
||||||
reasons.append(f"seed probe exited {result.get('run_status')}")
|
reasons.append(f"seed probe exited {result.get('run_status')}")
|
||||||
if paired < min_pairs:
|
if paired < min_pairs:
|
||||||
reasons.append(f"paired_event_count {paired} < {min_pairs}")
|
reasons.append(f"paired_event_count {paired} < {min_pairs}")
|
||||||
|
if max_abs_skew_ms_observed > max_abs_skew_ms:
|
||||||
|
reasons.append(
|
||||||
|
f"max_abs_skew_ms {max_abs_skew_ms_observed:.1f} > {max_abs_skew_ms:.1f}"
|
||||||
|
)
|
||||||
if abs(drift_ms) > max_drift_ms:
|
if abs(drift_ms) > max_drift_ms:
|
||||||
reasons.append(f"abs(drift_ms) {abs(drift_ms):.1f} > {max_drift_ms:.1f}")
|
reasons.append(f"abs(drift_ms) {abs(drift_ms):.1f} > {max_drift_ms:.1f}")
|
||||||
|
if abs(raw_delta_us) > max_step_us:
|
||||||
|
reasons.append(f"raw delay correction {raw_delta_us:+d}us exceeds {max_step_us}us")
|
||||||
if target_audio < 0 or target_video < 0:
|
if target_audio < 0 or target_video < 0:
|
||||||
reasons.append(
|
reasons.append(
|
||||||
f"direct confirmation delays must be non-negative, got audio={target_audio} video={target_video}"
|
f"direct confirmation delays must be non-negative, got audio={target_audio} video={target_video}"
|
||||||
@ -277,6 +307,8 @@ values = {
|
|||||||
"tune_video_delta_us": delta_video,
|
"tune_video_delta_us": delta_video,
|
||||||
"tune_paired_event_count": paired,
|
"tune_paired_event_count": paired,
|
||||||
"tune_drift_ms": f"{drift_ms:.3f}",
|
"tune_drift_ms": f"{drift_ms:.3f}",
|
||||||
|
"tune_max_abs_skew_ms": f"{max_abs_skew_ms_observed:.3f}",
|
||||||
|
"tune_raw_delta_us": raw_delta_us,
|
||||||
}
|
}
|
||||||
with pathlib.Path(output_env_path).open("w") as handle:
|
with pathlib.Path(output_env_path).open("w") as handle:
|
||||||
for key, value in values.items():
|
for key, value in values.items():
|
||||||
@ -897,6 +929,35 @@ missing = as_int(video.get("estimated_missing_frames"), 0)
|
|||||||
undecodable = as_int(video.get("undecodable_frames"), 0)
|
undecodable = as_int(video.get("undecodable_frames"), 0)
|
||||||
duplicates = as_int(video.get("duplicate_frames"), 0)
|
duplicates = as_int(video.get("duplicate_frames"), 0)
|
||||||
low_rms = as_int(audio_rms.get("low_rms_window_count"), 0)
|
low_rms = as_int(audio_rms.get("low_rms_window_count"), 0)
|
||||||
|
pair_confidences = []
|
||||||
|
for event in report.get("paired_events") or []:
|
||||||
|
if not isinstance(event, dict):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
confidence = float(event.get("confidence"))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if math.isfinite(confidence):
|
||||||
|
pair_confidences.append(confidence)
|
||||||
|
|
||||||
|
|
||||||
|
def median(values, default=0.0):
|
||||||
|
if not values:
|
||||||
|
return default
|
||||||
|
ordered = sorted(values)
|
||||||
|
mid = len(ordered) // 2
|
||||||
|
if len(ordered) % 2:
|
||||||
|
return ordered[mid]
|
||||||
|
return (ordered[mid - 1] + ordered[mid]) / 2.0
|
||||||
|
|
||||||
|
|
||||||
|
activity_start_delta_ms = as_float(report.get("activity_start_delta_ms"), 0.0)
|
||||||
|
first_skew_ms = as_float(report.get("first_skew_ms"), 0.0)
|
||||||
|
activity_pair_disagreement_ms = (
|
||||||
|
activity_start_delta_ms - first_skew_ms
|
||||||
|
if as_int(report.get("paired_event_count"), 0) > 0
|
||||||
|
else 0.0
|
||||||
|
)
|
||||||
|
|
||||||
reasons = []
|
reasons = []
|
||||||
if run_status != 0:
|
if run_status != 0:
|
||||||
@ -945,7 +1006,14 @@ artifact = {
|
|||||||
"p95_abs_skew_ms": as_float(verdict.get("p95_abs_skew_ms"), 0.0),
|
"p95_abs_skew_ms": as_float(verdict.get("p95_abs_skew_ms"), 0.0),
|
||||||
"median_skew_ms": as_float(report.get("median_skew_ms"), 0.0),
|
"median_skew_ms": as_float(report.get("median_skew_ms"), 0.0),
|
||||||
"drift_ms": as_float(report.get("drift_ms"), 0.0),
|
"drift_ms": as_float(report.get("drift_ms"), 0.0),
|
||||||
|
"activity_start_delta_ms": activity_start_delta_ms,
|
||||||
|
"first_skew_ms": first_skew_ms,
|
||||||
|
"activity_pair_disagreement_ms": activity_pair_disagreement_ms,
|
||||||
|
"video_event_count": as_int(report.get("video_event_count"), 0),
|
||||||
|
"audio_event_count": as_int(report.get("audio_event_count"), 0),
|
||||||
"paired_event_count": as_int(report.get("paired_event_count"), 0),
|
"paired_event_count": as_int(report.get("paired_event_count"), 0),
|
||||||
|
"paired_confidence_min": min(pair_confidences) if pair_confidences else 0.0,
|
||||||
|
"paired_confidence_median": median(pair_confidences),
|
||||||
},
|
},
|
||||||
"freshness": {
|
"freshness": {
|
||||||
"status": freshness_status,
|
"status": freshness_status,
|
||||||
@ -977,6 +1045,7 @@ artifact = {
|
|||||||
"decision": calibration.get("decision", "unknown"),
|
"decision": calibration.get("decision", "unknown"),
|
||||||
"target": calibration.get("target", ""),
|
"target": calibration.get("target", ""),
|
||||||
"paired_event_count": as_int(calibration.get("paired_event_count"), 0),
|
"paired_event_count": as_int(calibration.get("paired_event_count"), 0),
|
||||||
|
"min_pairs": as_int(calibration.get("min_pairs"), 0),
|
||||||
"measured_device_skew_ms": as_float(calibration.get("measured_device_skew_ms"), 0.0),
|
"measured_device_skew_ms": as_float(calibration.get("measured_device_skew_ms"), 0.0),
|
||||||
"p95_abs_skew_ms": as_float(calibration.get("p95_abs_skew_ms"), 0.0),
|
"p95_abs_skew_ms": as_float(calibration.get("p95_abs_skew_ms"), 0.0),
|
||||||
"max_abs_skew_ms": as_float(calibration.get("max_abs_skew_ms"), 0.0),
|
"max_abs_skew_ms": as_float(calibration.get("max_abs_skew_ms"), 0.0),
|
||||||
@ -987,6 +1056,9 @@ artifact = {
|
|||||||
"video_offset_adjust_us": as_int(calibration.get("video_offset_adjust_us"), 0),
|
"video_offset_adjust_us": as_int(calibration.get("video_offset_adjust_us"), 0),
|
||||||
"audio_target_offset_us": as_int(calibration.get("audio_target_offset_us"), 0),
|
"audio_target_offset_us": as_int(calibration.get("audio_target_offset_us"), 0),
|
||||||
"video_target_offset_us": as_int(calibration.get("video_target_offset_us"), 0),
|
"video_target_offset_us": as_int(calibration.get("video_target_offset_us"), 0),
|
||||||
|
"raw_device_delta_us": as_int(calibration.get("raw_device_delta_us"), 0),
|
||||||
|
"bounded_device_delta_us": as_int(calibration.get("bounded_device_delta_us"), 0),
|
||||||
|
"max_step_us": as_int(calibration.get("max_step_us"), 0),
|
||||||
"note": calibration.get("note", ""),
|
"note": calibration.get("note", ""),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1037,14 +1109,18 @@ for result in results:
|
|||||||
continue
|
continue
|
||||||
sync = result.get("sync") or {}
|
sync = result.get("sync") or {}
|
||||||
calibration = result.get("output_delay_calibration") or {}
|
calibration = result.get("output_delay_calibration") or {}
|
||||||
confirmed = sync.get("passed") is True
|
required_pairs = calibration.get("min_pairs") or 8
|
||||||
|
confirmed = (
|
||||||
|
sync.get("passed") is True
|
||||||
|
and (sync.get("paired_event_count") or 0) >= required_pairs
|
||||||
|
)
|
||||||
candidate_video = calibration.get("video_target_offset_us")
|
candidate_video = calibration.get("video_target_offset_us")
|
||||||
candidate_audio = calibration.get("audio_target_offset_us")
|
candidate_audio = calibration.get("audio_target_offset_us")
|
||||||
video_delay = result.get("video_delay_us")
|
video_delay = result.get("video_delay_us")
|
||||||
audio_delay = result.get("audio_delay_us")
|
audio_delay = result.get("audio_delay_us")
|
||||||
status = "confirmed" if confirmed else "tested"
|
status = "confirmed" if confirmed else "tested"
|
||||||
candidate_available = (
|
candidate_available = (
|
||||||
calibration.get("decision") in {"ready", "refused"}
|
calibration.get("ready") is True
|
||||||
and (calibration.get("paired_event_count") or 0) > 0
|
and (calibration.get("paired_event_count") or 0) > 0
|
||||||
and isinstance(candidate_video, int)
|
and isinstance(candidate_video, int)
|
||||||
and isinstance(candidate_audio, int)
|
and isinstance(candidate_audio, int)
|
||||||
@ -1069,6 +1145,8 @@ for result in results:
|
|||||||
"sync_status": sync.get("status"),
|
"sync_status": sync.get("status"),
|
||||||
"median_skew_ms": sync.get("median_skew_ms"),
|
"median_skew_ms": sync.get("median_skew_ms"),
|
||||||
"paired_event_count": sync.get("paired_event_count"),
|
"paired_event_count": sync.get("paired_event_count"),
|
||||||
|
"paired_confidence_median": sync.get("paired_confidence_median"),
|
||||||
|
"activity_pair_disagreement_ms": sync.get("activity_pair_disagreement_ms"),
|
||||||
}
|
}
|
||||||
|
|
||||||
summary = {
|
summary = {
|
||||||
@ -1195,6 +1273,13 @@ for result in results:
|
|||||||
f"video_target={calibration.get('video_target_offset_us', 0)}us "
|
f"video_target={calibration.get('video_target_offset_us', 0)}us "
|
||||||
f"audio_target={calibration.get('audio_target_offset_us', 0)}us"
|
f"audio_target={calibration.get('audio_target_offset_us', 0)}us"
|
||||||
)
|
)
|
||||||
|
lines.append(
|
||||||
|
" sync evidence: "
|
||||||
|
f"video_onsets={sync.get('video_event_count', 0)} audio_onsets={sync.get('audio_event_count', 0)} "
|
||||||
|
f"pairs={sync.get('paired_event_count', 0)} "
|
||||||
|
f"pair_conf_median={sync.get('paired_confidence_median', 0.0):.3f} "
|
||||||
|
f"raw_pair_disagreement={sync.get('activity_pair_disagreement_ms', 0.0):+.1f}ms"
|
||||||
|
)
|
||||||
if "seed_video_delay_us" in result or "seed_audio_delay_us" in result:
|
if "seed_video_delay_us" in result or "seed_audio_delay_us" in result:
|
||||||
lines.append(
|
lines.append(
|
||||||
" tuning: "
|
" tuning: "
|
||||||
@ -1223,7 +1308,7 @@ echo " ↪ mode_source=${LESAVKA_SERVER_RC_MODE_SOURCE}"
|
|||||||
echo " ↪ video_delays=${LESAVKA_SERVER_RC_MODE_DELAYS_US}"
|
echo " ↪ video_delays=${LESAVKA_SERVER_RC_MODE_DELAYS_US}"
|
||||||
echo " ↪ audio_delays=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}"
|
echo " ↪ audio_delays=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}"
|
||||||
echo " ↪ capture_stack=${REMOTE_CAPTURE_STACK} audio_source=${REMOTE_AUDIO_SOURCE} pulse_tool=${REMOTE_PULSE_CAPTURE_TOOL} video_mode=${REMOTE_PULSE_VIDEO_MODE}"
|
echo " ↪ capture_stack=${REMOTE_CAPTURE_STACK} audio_source=${REMOTE_AUDIO_SOURCE} pulse_tool=${REMOTE_PULSE_CAPTURE_TOOL} video_mode=${REMOTE_PULSE_VIDEO_MODE}"
|
||||||
echo " ↪ tune_delays=${LESAVKA_SERVER_RC_TUNE_DELAYS} confirm=${LESAVKA_SERVER_RC_TUNE_CONFIRM} min_pairs=${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS} min_change_us=${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US}"
|
echo " ↪ tune_delays=${LESAVKA_SERVER_RC_TUNE_DELAYS} confirm=${LESAVKA_SERVER_RC_TUNE_CONFIRM} min_pairs=${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS} max_abs_skew_ms=${LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS} max_step_us=${LESAVKA_SERVER_RC_TUNE_MAX_STEP_US} min_change_us=${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US}"
|
||||||
echo " ↪ freshness_limit_ms=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS}"
|
echo " ↪ freshness_limit_ms=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS}"
|
||||||
echo " ↪ reconfigure=${LESAVKA_SERVER_RC_RECONFIGURE} strategy=${LESAVKA_SERVER_RC_RECONFIGURE_STRATEGY} allow_gadget_reset=${LESAVKA_SERVER_RC_ALLOW_GADGET_RESET}"
|
echo " ↪ reconfigure=${LESAVKA_SERVER_RC_RECONFIGURE} strategy=${LESAVKA_SERVER_RC_RECONFIGURE_STRATEGY} allow_gadget_reset=${LESAVKA_SERVER_RC_ALLOW_GADGET_RESET}"
|
||||||
echo " ↪ tethys_ready=${LESAVKA_SERVER_RC_WAIT_TETHYS_READY} settle=${LESAVKA_SERVER_RC_TETHYS_SETTLE_SECONDS}s timeout=${LESAVKA_SERVER_RC_TETHYS_READY_TIMEOUT_SECONDS}s preroll_discard=${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}s"
|
echo " ↪ tethys_ready=${LESAVKA_SERVER_RC_WAIT_TETHYS_READY} settle=${LESAVKA_SERVER_RC_TETHYS_SETTLE_SECONDS}s timeout=${LESAVKA_SERVER_RC_TETHYS_READY_TIMEOUT_SECONDS}s preroll_discard=${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}s"
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.19.17"
|
version = "0.19.18"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -738,7 +738,8 @@ fn duration_mul(duration: Duration, count: u64) -> Duration {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
OutputDelayProbeTimeline, ProbeConfig, duration_us, render_audio_chunk, unix_ns_from_start,
|
DARK_FRAME_RGB, OutputDelayProbeTimeline, ProbeConfig, duration_us, probe_color_for_code,
|
||||||
|
render_audio_chunk, unix_ns_from_start,
|
||||||
};
|
};
|
||||||
use crate::camera::{CameraCodec, CameraConfig, CameraOutput};
|
use crate::camera::{CameraCodec, CameraConfig, CameraOutput};
|
||||||
use lesavka_common::lesavka::OutputDelayProbeRequest;
|
use lesavka_common::lesavka::OutputDelayProbeRequest;
|
||||||
@ -787,6 +788,37 @@ mod tests {
|
|||||||
assert!(rms_i16_le(&active) > rms_i16_le(&idle) * 10.0);
|
assert!(rms_i16_le(&active) > rms_i16_le(&idle) * 10.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generated_video_and_audio_share_the_same_event_schedule() {
|
||||||
|
let config = ProbeConfig::from_request(&OutputDelayProbeRequest {
|
||||||
|
duration_seconds: 6,
|
||||||
|
warmup_seconds: 1,
|
||||||
|
pulse_period_ms: 1_000,
|
||||||
|
pulse_width_ms: 120,
|
||||||
|
event_width_codes: "2".to_string(),
|
||||||
|
audio_delay_us: 0,
|
||||||
|
video_delay_us: 0,
|
||||||
|
})
|
||||||
|
.expect("config");
|
||||||
|
let idle_pts = Duration::from_millis(500);
|
||||||
|
let active_pts = Duration::from_secs(1);
|
||||||
|
|
||||||
|
let idle_color = probe_color_for_code(config.event_code_at(idle_pts));
|
||||||
|
let active_color = probe_color_for_code(config.event_code_at(active_pts));
|
||||||
|
let idle_audio = render_audio_chunk(&config, idle_pts, 480);
|
||||||
|
let active_audio = render_audio_chunk(&config, active_pts, 480);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
(idle_color.r, idle_color.g, idle_color.b),
|
||||||
|
(DARK_FRAME_RGB.r, DARK_FRAME_RGB.g, DARK_FRAME_RGB.b)
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
(active_color.r, active_color.g, active_color.b),
|
||||||
|
(DARK_FRAME_RGB.r, DARK_FRAME_RGB.g, DARK_FRAME_RGB.b)
|
||||||
|
);
|
||||||
|
assert!(rms_i16_le(&active_audio) > rms_i16_le(&idle_audio) * 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn timeline_exports_wall_clock_fields_for_freshness() {
|
fn timeline_exports_wall_clock_fields_for_freshness() {
|
||||||
let config = ProbeConfig::from_request(&OutputDelayProbeRequest {
|
let config = ProbeConfig::from_request(&OutputDelayProbeRequest {
|
||||||
|
|||||||
@ -239,7 +239,9 @@ fn server_rc_mode_matrix_validates_advertised_uvc_profiles() {
|
|||||||
"LESAVKA_SERVER_RC_PROBE_PREBUILD=${LESAVKA_SERVER_RC_PROBE_PREBUILD:-1}",
|
"LESAVKA_SERVER_RC_PROBE_PREBUILD=${LESAVKA_SERVER_RC_PROBE_PREBUILD:-1}",
|
||||||
"LESAVKA_SERVER_RC_TUNE_DELAYS=${LESAVKA_SERVER_RC_TUNE_DELAYS:-1}",
|
"LESAVKA_SERVER_RC_TUNE_DELAYS=${LESAVKA_SERVER_RC_TUNE_DELAYS:-1}",
|
||||||
"LESAVKA_SERVER_RC_TUNE_CONFIRM=${LESAVKA_SERVER_RC_TUNE_CONFIRM:-1}",
|
"LESAVKA_SERVER_RC_TUNE_CONFIRM=${LESAVKA_SERVER_RC_TUNE_CONFIRM:-1}",
|
||||||
"LESAVKA_SERVER_RC_TUNE_MIN_PAIRS=${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS:-3}",
|
"LESAVKA_SERVER_RC_TUNE_MIN_PAIRS=${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS:-8}",
|
||||||
|
"LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS=${LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS:-1000}",
|
||||||
|
"LESAVKA_SERVER_RC_TUNE_MAX_STEP_US=${LESAVKA_SERVER_RC_TUNE_MAX_STEP_US:-500000}",
|
||||||
"LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US=${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US:-5000}",
|
"LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US=${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US:-5000}",
|
||||||
"Theia sudo password for %s",
|
"Theia sudo password for %s",
|
||||||
"==> priming remote sudo on ${LESAVKA_SERVER_HOST}",
|
"==> priming remote sudo on ${LESAVKA_SERVER_HOST}",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user