Files
vigilar/docs/superpowers/specs/2026-04-05-camera-stream-simulator-design.md
adlee-was-taken 475c7b097b docs(specs): camera stream simulator design
Dev-only tooling to loop phone-recorded mp4s as local RTSP streams
(MediaMTX + ffmpeg) so the real camera pipeline can be exercised
without physical cameras.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:11:54 -04:00

6.3 KiB
Raw Blame History

Camera Stream Simulator — Design

Status: Approved Date: 2026-04-05 Scope: Development tooling only. Not shipped to operators.

Purpose

Loop phone-recorded video files as local RTSP streams so Vigilar's real camera pipeline (OpenCV capture, motion detection, HLS, recording, reconnect logic) can be exercised end-to-end without physical cameras. The operator records 35 minutes of footage from each angle on a phone, drops the files into a videos/ directory, and runs a single script that serves them as rtsp://127.0.0.1:8554/<camera_id>.

Primary use case: manual dev testing with 45 real clips. Secondary use case: stress testing with up to 32 distinct clips.

Non-Goals

  • Not a production feature. Never invoked in deployed installs.
  • Not a pytest fixture. Manual workflow only.
  • No per-stream jitter, audio, non-mp4 inputs, or CLI flags.
  • No Python rewrite or vigilar sim subcommand.

Architecture

One bash script, one helper binary (MediaMTX), one ffmpeg process per stream.

scripts/sim_cameras.sh
        │
        ├── starts: mediamtx (127.0.0.1:8554)    ← single static Go binary
        ├── reads:  config/vigilar.toml          ← discovers camera ids
        ├── for each [[cameras]] block:
        │     expects videos/<camera_id>.mp4
        │     spawns: ffmpeg -stream_loop -1 -re -i videos/<id>.mp4 \
        │                    -c copy -f rtsp rtsp://127.0.0.1:8554/<id>
        ├── writes: config/vigilar.sim.toml      ← copy of real config,
        │                                          rtsp_urls rewritten
        └── foreground; Ctrl-C kills all children

Operator workflow:

$ ./scripts/sim_cameras.sh
  → serving 5 streams on 127.0.0.1:8554
  → wrote config/vigilar.sim.toml
$ vigilar start --config config/vigilar.sim.toml   # in another terminal

Strict 1:1 mapping — each simulated stream corresponds to exactly one video file, named after the camera id in vigilar.toml. No file reuse, no cycling.

Components

scripts/sim_cameras.sh

Bash, no Python dependencies. Responsibilities:

  1. Resolve paths: $REPO/config/vigilar.toml, $REPO/videos/, $REPO/.sim/mediamtx, $REPO/.sim/logs/.
  2. Check prerequisites: ffmpeg on PATH, mediamtx binary present, videos/ directory exists.
  3. Parse camera ids from vigilar.toml by grepping ^id = "..." lines inside [[cameras]] blocks. No TOML parser — tolerable because the config format is stable and this is dev tooling.
  4. Verify videos/<id>.mp4 exists for every camera id. List all missing files in one error message, then exit 1.
  5. Launch mediamtx in the background, writing its log to .sim/logs/mediamtx.log. Wait ~500ms, then verify the RTSP port (8554) is listening (ss -lnt or /dev/tcp).
  6. Launch one ffmpeg process per camera, backgrounded, with stdout/stderr redirected to .sim/logs/<id>.log. Track PIDs in a bash array.
  7. Generate config/vigilar.sim.toml: copy the real TOML, rewrite each rtsp_url = "..." line to rtsp://127.0.0.1:8554/<id> using sed scoped per camera block.
  8. Print a summary: each served URL, the sim config path, and "Press Ctrl-C to stop".
  9. Install a trap on EXIT/INT/TERM that kills mediamtx and every ffmpeg child PID, then removes .sim/. Idempotent.
  10. wait on child PIDs so the script blocks until Ctrl-C.

scripts/download_mediamtx.sh

Parallel to the existing scripts/download_model.sh. Downloads the MediaMTX static binary for the current architecture from the official GitHub release into .sim/mediamtx, verifies it is executable, and exits. Run once per machine.

videos/ (gitignored)

Operator drops <camera_id>.mp4 files matching camera ids in config/vigilar.toml. Format is mp4 only; other extensions are rejected.

.sim/ (gitignored)

Working directory, recreated each run:

  • .sim/mediamtx — the static binary (installed by download_mediamtx.sh)
  • .sim/logs/mediamtx.log
  • .sim/logs/<camera_id>.log

config/vigilar.sim.toml (gitignored)

Generated artifact. Overwritten on every run. Never hand-edited. The real config/vigilar.toml is read-only from the simulator's perspective.

MediaMTX

Single static Go binary. Default configuration is sufficient — listens on RTSP port 8554, accepts rtsp://127.0.0.1:8554/<path> publishes from ffmpeg. No custom yaml required.

Error Handling

Fail loud and early. No silent fallbacks.

Condition Behavior
ffmpeg not on PATH Print install hint, exit 1
.sim/mediamtx missing Print "run scripts/download_mediamtx.sh", exit 1
videos/ missing or any videos/<id>.mp4 missing List all missing files, exit 1
MediaMTX fails to bind 8554 Dump .sim/logs/mediamtx.log tail, exit 1
ffmpeg child dies mid-run Log to stderr, leave others running. Vigilar's reconnect logic handles the gap — this is a feature, not a bug.
Ctrl-C / script death trap kills mediamtx + all ffmpeg PIDs, removes .sim/

Acceptance Criteria

Manual verification — this is dev tooling, not production code.

  1. Drop 5 mp4s matching camera ids into videos/, run ./scripts/sim_cameras.sh. 5 URLs print.
  2. ffprobe rtsp://127.0.0.1:8554/<id> succeeds for each stream.
  3. In another terminal: vigilar start --config config/vigilar.sim.toml. The web UI shows 5 camera tiles with live HLS. Waving at phone footage triggers motion events in the event log.
  4. Kill one ffmpeg process manually. Vigilar marks that camera as disconnected; reconnect loop engages. Restart the same ffmpeg invocation → stream recovers in the UI.
  5. Ctrl-C the sim script. pgrep ffmpeg and pgrep mediamtx both empty. .sim/ removed.
  6. Scale test: populate 32 dummy mp4s matching 32 camera blocks. Script starts cleanly; CPU remains within reason (subjective; no hard target).

Automated Check

One light test: tests/unit/test_sim_script.py runs bash -n scripts/sim_cameras.sh (syntax check) and, if shellcheck is available, runs it as well. No integration tests — the simulator exists to support manual testing.

File Inventory

New files:

  • scripts/sim_cameras.sh
  • scripts/download_mediamtx.sh
  • tests/unit/test_sim_script.py

Modified files:

  • .gitignore — add videos/, .sim/, config/vigilar.sim.toml

No changes to vigilar/ package code. The simulator is fully external.