-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Reflects actual deployed state as of 2026-07-25 (v0.01.21).
┌─────────────────────────────────────────────────────────────────────────┐
│ Docker Compose (nvr-net) │
│ │
│ ┌──────────┐ RTSP sub ┌─────────────────┐ │
│ │ IP Cams │───────────────→│ AI Engine │ Redis pub/sub │
│ │ (11× │ │ YOLOv8n ONNX │──────────────────┐ │
│ │ Dahua) │ │ FrameSampler │ nvr:motion │ │
│ │ │ │ MOG2 gate │ nvr:events │ │
│ └────┬─────┘ └─────────────────┘ │ │
│ │ │ │
│ │ RTSP sub ┌──────────────┐ ▼ │
│ ├──────────────────────────→│ Stream Mgr │ ┌────────────────┐ │
│ │ │ FFmpeg relay │ │ Redis │ │
│ │ │ libx264 │ │ pub/sub/cache │ │
│ │ │ circuit brkr │ └────────┬───────┘ │
│ │ └──────┬───────┘ │ │
│ │ │ RTSP libx264 │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ MediaMTX │ │ │
│ │ │ LL-HLS │ │ │
│ │ │ RTSP→HLS │ │ │
│ │ └──────┬───────┘ │ │
│ │ │ HLS │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ Browser │ │ │
│ │ │ hls.js │ │ │
│ │ └──────────────┘ │ │
│ │ │ │
│ │ RTSP sub ┌──────────────────┐ │ │
│ ├─────────────────────→│ Recording Engine │←─────────────┘ │
│ │ │ -c:v copy FFmpeg │ motion trigger │
│ │ │ 300s MP4 segs │ │
│ │ │ MotionRecorder │ │
│ │ │ SegmentCatalog │ │
│ │ │ CircularRetention│ │
│ │ │ DiskAnalytics │ │
│ │ └────────┬─────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ │ │ Disk │ │
│ │ │ /data/recordings │ │
│ │ └────────┬─────────┘ │
│ │ │ HTTP Range │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ ┌────┴──────────────────────│ FastAPI (nvr-api)│ │
│ │ │ REST + WS │ │
│ │ └────────┬─────────┘ │
│ │ │ SQL │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ │ │ PostgreSQL 16 │ │
│ │ │ (nvr-db) │ │
│ │ └──────────────────┘ │
│ └── cameras table, recordings, events, system_config │
└─────────────────────────────────────────────────────────────────────────┘
| Container | Image | CPU Limit | Memory Limit | Ports |
|---|---|---|---|---|
nvr-db |
timescale/timescaledb:2.16-pg16 | 2 | 2G | 5432 |
nvr-redis |
redis:7-alpine | — | — | 6379 |
nvr-api |
python:3.13-slim | 1 | 1G | 8000 |
nvr-web |
node:22-slim (Vite dev) | — | — | 3000 |
nvr-stream-manager |
python:3.13-slim (+FFmpeg 5.1) | 4 | 4G | 8001 |
nvr-mediamtx |
bluenviron/mediamtx:1.19.2 | — | — | 8554, 8888, 9997 |
nvr-recording-engine |
python:3.13-slim (+FFmpeg 5.1) | 2 | 2G | — |
nvr-ai-engine |
python:3.13-slim (+ONNX) | 2 | 4G | — |
ffmpeg -rtsp_transport tcp -timeout 10000000 \
-i rtsp://<username>:<password>@camera:554/Streaming/Channels/102 \
-c:v libx264 -preset ultrafast -tune zerolatency -g 5 \
-b:v 1000k -maxrate 1000k -bufsize 2000k \
-c:a aac -b:a 64k -ac 1 -pkt_size 1200 \
-f rtsp -rtsp_transport tcp \
rtsp://nvr-mediamtx:8554/{camera_id}_subNotes:
- Always
libx264transcode (Dahua H.264 FU-A packet ordering causes MediaMTX HLS errors with-c:v copy) - Sub-stream bitrate: 1000k; Main-stream: 4000k
- GOP 5 (ultra-short for LL-HLS)
- Circuit breaker: 15s→120s cooldown;
returncode=0bypasses breaker - Idle reaper: stops relay after 600s with zero readers
ffmpeg -rtsp_transport tcp -timeout 15000000 \
-i rtsp://<username>:<password>@camera:554/Streaming/Channels/102 \
-c:v copy \ # Zero video CPU
-c:a aac -b:a 64k \ # Lightweight audio re-encode
-f segment -segment_format mp4 \
-segment_format_options movflags=+faststart \
-segment_time 300 -segment_atclocktime 1 \
-reset_timestamps 1 -strftime 1 \
/data/recordings/{camera_id}/%Y/%m/%d/%Y%m%d_%H%M%S.mp4Notes:
-
-c:v copy— no video transcode (very low CPU) -
Codec normalization via ffprobe before FFmpeg spawns:
- HEVC →
-c:v libx264 -preset ultrafast -crf 20 -pix_fmt yuv420p - H.264 →
-bsf:v h264_metadata=video_full_range_flag=0(normalizesyuvj420p color_range=pc→yuv420p tv)
- HEVC →
- 300s segments with
+faststart(enables seeking before download complete) - Motion-only mode: FFmpeg starts/stops based on Redis
nvr:motionstate
RTSP Sub-stream (0.5 FPS, 640px width)
│
▼
┌───────────────────┐
│ FrameSampler │ OpenCV cap.read()
│ per camera │ cv2.CAP_PROP_BUFFERSIZE=1
└────────┬──────────┘
│
▼
┌───────────────────┐
│ MOG2 Motion Gate │ history=500, detectShadows=False
│ (OpenCV) │ countNonZero > 500 pixels → motion
│ │ Threshold: low=40, med=25, high=16
└────────┬──────────┘
│ motion detected
▼
┌───────────────────┐
│ YOLOv8n ONNX │ Shared singleton session
│ (ONNX Runtime) │ Intra-op threads: 1
│ INPUT_SIZE=640 │ CPUExecutionProvider
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Post-processing │ Transpose (1,84,8400) → per-class argmax
│ │ NMS (IoU=0.45), confidence clamp [0.05, 0.95]
│ Letterbox unscale│
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Zone Filter │ cv2.pointPolygonTest on bbox bottom-center
│ Confidence check │ Only objects inside zones pass
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Position Dedup │ Static object: 1 event/5min
│ │ Moved (>0.10 normalized): immediate event
│ Min event gap: 5s │ per class
└────────┬──────────┘
┌────┴────┐
▼ ▼
Events JPEG
Table Snapshot
id (UUID PK), name, ip_address, stream_main_uri, stream_sub_uri, encrypted_password, recording_mode (motion/continuous/never/disabled), ai_enabled, ai_objects (JSON), ai_zones (JSON), ai_sensitivity, ai_min_confidence, motion_source, status, connection_error, location_id ←
id (UUID), camera_id ←, file_path, file_size_bytes, duration_seconds, start_time, end_time, recording_type (continuous/motion/event), has_audio, codec, resolution, is_corrupt
id (UUID), camera_id ←, event_type, severity, start_time, end_time, metadata (JSON), snapshot_path, is_acknowledged
key (VARCHAR PK), value (JSONB), description, updated_at
| Context | Base Cooldown | Max Cooldown | Formula |
|---|---|---|---|
| Stream relay | 15s | 120s | min(15 × 2^trips, 120) |
| Recording recorder | 60s | 600s | min(60 × 2^trips, 600) |
| AI frame sampler | 5s | 120s | min(5 × 2^trips, 120) |
-
returncode=0(clean exit) → breaker NOT tripped, immediate reconnect up to 5 attempts -
returncode≠0→ breaker tripped, up to 3 attempts with transport fallback
Every 5 minutes: check disk usage. If ≥85% full or <2GB free → delete oldest segments first (files <10min protected). Also age-based (7 days). Max 500 deletes/run.
-
FFmpeg 5.1.9 —
-stimeoutnot supported; use-timeout -
libx264transcode always for stream relay — Dahua FU-A packet ordering causes HLS errors -
-c:v copyfor recording — zero video CPU since it writes to disk directly - Codec auto-detection + normalization — HEVC→H.264, H.264 full-range→tv range
- Sub-stream for AI — 640px width, 0.5 FPS reduces inference load dramatically
-
Motion-only recording — all cameras use
recording_mode='motion' -
No hls.js in RecordingPlayer — native
<video>progressive MP4 only; avoids CSP unsafe-eval conflict