Skip to content

Architecture

wiki-sync-bot edited this page Jul 26, 2026 · 1 revision

Architecture

Reflects actual deployed state as of 2026-07-25 (v0.01.21).

Service Topology

┌─────────────────────────────────────────────────────────────────────────┐
│                          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                     │
└─────────────────────────────────────────────────────────────────────────┘

Containers

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 Pipelines

Stream Relay (Stream Manager → MediaMTX)

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}_sub

Notes:

  • Always libx264 transcode (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=0 bypasses breaker
  • Idle reaper: stops relay after 600s with zero readers

Recording Engine (Direct to Disk)

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.mp4

Notes:

  • -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 (normalizes yuvj420p color_range=pcyuv420p tv)
  • 300s segments with +faststart (enables seeking before download complete)
  • Motion-only mode: FFmpeg starts/stops based on Redis nvr:motion state

AI Pipeline

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

Database Schema (Key Tables)

cameras

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 ←

recordings

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

events

id (UUID), camera_id ←, event_type, severity, start_time, end_time, metadata (JSON), snapshot_path, is_acknowledged

system_config

key (VARCHAR PK), value (JSONB), description, updated_at

Resilience Patterns

Circuit Breaker

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

Circular Retention

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.

Key Technical Decisions

  1. FFmpeg 5.1.9-stimeout not supported; use -timeout
  2. libx264 transcode always for stream relay — Dahua FU-A packet ordering causes HLS errors
  3. -c:v copy for recording — zero video CPU since it writes to disk directly
  4. Codec auto-detection + normalization — HEVC→H.264, H.264 full-range→tv range
  5. Sub-stream for AI — 640px width, 0.5 FPS reduces inference load dramatically
  6. Motion-only recording — all cameras use recording_mode='motion'
  7. No hls.js in RecordingPlayer — native <video> progressive MP4 only; avoids CSP unsafe-eval conflict

Clone this wiki locally