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

165 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.