probe: pause live upstream during synthetic rct runs
This commit is contained in:
parent
5667608707
commit
32b058973e
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.22.55"
|
||||
version = "0.22.56"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1686,7 +1686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.22.55"
|
||||
version = "0.22.56"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1698,7 +1698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.22.55"
|
||||
version = "0.22.56"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.22.55"
|
||||
version = "0.22.56"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.22.55"
|
||||
version = "0.22.56"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ DEFAULT_JPEG_QUALITY = 82
|
||||
HIGH_SPEED_ISOCHRONOUS_MICROFRAMES_PER_SEC = 8000
|
||||
DEFAULT_ISOCHRONOUS_LIMIT_PCT = 85
|
||||
DEFAULT_UVC_MAX_PACKET = 1024
|
||||
DEFAULT_MEDIA_CONTROL_PATH = "/tmp/lesavka-media.control"
|
||||
MARKER_BITS = 32
|
||||
MARKER_COLUMNS = 16
|
||||
CADENCE_REASONS = {"frame_repeat", "frame_gap", "frame_backwards"}
|
||||
@ -53,6 +54,16 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--artifact-dir", default="")
|
||||
parser.add_argument("--remote-rct-dir", default="")
|
||||
parser.add_argument("--remote-inject-dir", default="")
|
||||
parser.add_argument(
|
||||
"--pause-local-live-upstream",
|
||||
action="store_true",
|
||||
help="temporarily write camera=0 to the local Lesavka media control file so a live client does not preempt the synthetic injector",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--media-control-path",
|
||||
default=os.environ.get("LESAVKA_MEDIA_CONTROL", DEFAULT_MEDIA_CONTROL_PATH),
|
||||
help=f"local live-media control file used with --pause-local-live-upstream (default: {DEFAULT_MEDIA_CONTROL_PATH})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--capture-before-inject",
|
||||
action="store_true",
|
||||
@ -155,6 +166,48 @@ def default_artifact_dir(mode: str) -> pathlib.Path:
|
||||
return pathlib.Path("artifacts/synthetic-rct") / f"{safe_mode}-{timestamp()}"
|
||||
|
||||
|
||||
def media_control_with_camera(raw: str | None, enabled: bool) -> str:
|
||||
tokens = raw.split() if raw else []
|
||||
rendered: list[str] = []
|
||||
saw_camera = False
|
||||
saw_microphone = False
|
||||
saw_audio = False
|
||||
for token in tokens:
|
||||
key, sep, _value = token.partition("=")
|
||||
if sep and key == "camera":
|
||||
rendered.append(f"camera={1 if enabled else 0}")
|
||||
saw_camera = True
|
||||
else:
|
||||
rendered.append(token)
|
||||
saw_microphone = saw_microphone or (sep and key in {"microphone", "mic"})
|
||||
saw_audio = saw_audio or (sep and key in {"audio", "speaker"})
|
||||
if not saw_camera:
|
||||
rendered.insert(0, f"camera={1 if enabled else 0}")
|
||||
if not saw_microphone:
|
||||
rendered.append("microphone=1")
|
||||
if not saw_audio:
|
||||
rendered.append("audio=1")
|
||||
return " ".join(rendered) + "\n"
|
||||
|
||||
|
||||
def pause_local_live_upstream(args: argparse.Namespace) -> tuple[pathlib.Path, bytes | None]:
|
||||
path = pathlib.Path(args.media_control_path)
|
||||
original = path.read_bytes() if path.exists() else None
|
||||
raw = original.decode(errors="replace") if original is not None else None
|
||||
path.write_text(media_control_with_camera(raw, False))
|
||||
print(f"paused local live camera upstream via {path}", file=sys.stderr)
|
||||
time.sleep(0.5)
|
||||
return path, original
|
||||
|
||||
|
||||
def restore_local_live_upstream(path: pathlib.Path, original: bytes | None) -> None:
|
||||
if original is None:
|
||||
path.unlink(missing_ok=True)
|
||||
else:
|
||||
path.write_bytes(original)
|
||||
print(f"restored local live media control at {path}", file=sys.stderr)
|
||||
|
||||
|
||||
def run_remote_orchestrated(args: argparse.Namespace) -> int:
|
||||
if (not args.inject_host and not args.local_inject) or not args.rct_host:
|
||||
raise SystemExit(
|
||||
@ -273,6 +326,8 @@ def run_remote_orchestrated(args: argparse.Namespace) -> int:
|
||||
"inject_host": args.inject_host,
|
||||
"local_inject": args.local_inject,
|
||||
"rct_host": args.rct_host,
|
||||
"pause_local_live_upstream": args.pause_local_live_upstream,
|
||||
"media_control_path": args.media_control_path,
|
||||
},
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
@ -332,24 +387,31 @@ def run_remote_orchestrated(args: argparse.Namespace) -> int:
|
||||
|
||||
capture: subprocess.Popen[Any] | None = None
|
||||
diagnosis: list[str] = []
|
||||
if args.capture_before_inject:
|
||||
capture = start_capture()
|
||||
time.sleep(1.0)
|
||||
inject = start_inject()
|
||||
capture_rc, inject_rc = wait_capture_or_inject_exit(capture, inject)
|
||||
else:
|
||||
inject = start_inject()
|
||||
time.sleep(max(0.0, args.inject_warmup_s))
|
||||
inject_rc = inject.poll()
|
||||
if inject_rc is not None:
|
||||
capture_rc = None
|
||||
diagnosis.append(
|
||||
"synthetic uplink exited before capture warmup completed; disconnect the live client or pause upstream webcam before running the isolated probe"
|
||||
)
|
||||
print(f"synthetic uplink exited before capture started rc={inject_rc}", file=sys.stderr)
|
||||
else:
|
||||
paused_control: tuple[pathlib.Path, bytes | None] | None = None
|
||||
try:
|
||||
if args.pause_local_live_upstream:
|
||||
paused_control = pause_local_live_upstream(args)
|
||||
if args.capture_before_inject:
|
||||
capture = start_capture()
|
||||
time.sleep(1.0)
|
||||
inject = start_inject()
|
||||
capture_rc, inject_rc = wait_capture_or_inject_exit(capture, inject)
|
||||
else:
|
||||
inject = start_inject()
|
||||
time.sleep(max(0.0, args.inject_warmup_s))
|
||||
inject_rc = inject.poll()
|
||||
if inject_rc is not None:
|
||||
capture_rc = None
|
||||
diagnosis.append(
|
||||
"synthetic uplink exited before capture warmup completed; disconnect the live client or pause upstream webcam before running the isolated probe"
|
||||
)
|
||||
print(f"synthetic uplink exited before capture started rc={inject_rc}", file=sys.stderr)
|
||||
else:
|
||||
capture = start_capture()
|
||||
capture_rc, inject_rc = wait_capture_or_inject_exit(capture, inject)
|
||||
finally:
|
||||
if paused_control is not None:
|
||||
restore_local_live_upstream(*paused_control)
|
||||
local_capture = artifact_dir / "capture"
|
||||
local_inject = artifact_dir / "inject"
|
||||
if capture is not None:
|
||||
|
||||
@ -16,7 +16,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.22.55"
|
||||
version = "0.22.56"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -41,6 +41,8 @@ fn synthetic_probe_keeps_bundled_network_ingress_and_rct_comparison_markers() {
|
||||
"--capture-before-inject",
|
||||
"--inject-warmup-s",
|
||||
"--capture-finish-grace-s",
|
||||
"--pause-local-live-upstream",
|
||||
"--media-control-path",
|
||||
"--jpeg-quality",
|
||||
"--inject-max-frame-bytes",
|
||||
"--stream-analyze",
|
||||
@ -93,6 +95,9 @@ fn synthetic_probe_keeps_bundled_network_ingress_and_rct_comparison_markers() {
|
||||
"synthetic uplink completed but RCT capture did not finish",
|
||||
"synthetic injector was preempted after sending",
|
||||
"synthetic uplink exited before capture warmup completed",
|
||||
"paused local live camera upstream",
|
||||
"restored local live media control",
|
||||
"media_control_with_camera",
|
||||
"max_lower_mae",
|
||||
"ffmpeg",
|
||||
"v4l2",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user