# ip cameras

<p class="callout info"><strong>Service Summary:</strong> The DrHeck Camera Hub (<code>https://cam.drheck.dev</code>) provides automatic LAN discovery, low-latency live video and MP3 audio streaming, two-way RTSP ONVIF backchannel voice talk, YOLOv8n + hardware AI event capture with a web-viewable timeline and text metadata log, optical 3D click-to-zoom and optional auto-zoom on moving objects, smart floodlight/IR/LED controls, privacy sleep mode, bandwidth adaptation, and a sideloadable Google TV interface for the Reolink PoE IP cameras on <code>10.0.0.0/24</code>. Access is restricted via Authentik ForwardAuth to members of the <code>camera_access</code> group (<code>luke</code>, <code>ikura</code>, and <code>haruka</code>).</p>

## Hardware Inventory & Detected Cameras

Two **Reolink RLC-811A** (`P430`) 4K 8MP PoE Spotlight IP cameras are connected to the `10.0.0.0/24` LAN on interface `eth77` of host `ul` with static DHCP leases on `danknasty`:

| Property | Camera 1 (`RLC-811A (122)`) | Camera 2 (`RLC-811A (123)`) |
| :--- | :--- | :--- |
| **IPv4 Address (Static Lease)** | `10.0.0.122` (`host cam1`) | `10.0.0.123` (`host cam2`) |
| **MAC Address** | `ec:71:db:f1:25:5a` | `ec:71:db:6d:84:ce` |
| **Vendor / OUI** | Reolink (`ec:71:db`) | Reolink (`ec:71:db`) |
| **Model / Item No.** | `RLC-811A` / `P430` | `RLC-811A` / `P430` |
| **Hardware Revision** | `IPC_560B158MP` | `IPC_560B158MP` |
| **Firmware Version** | `v3.1.0.4695_2504301440` | `v3.1.0.4695_2504301440` |
| **Serial Number** | `141484731968753` | `141484731989233` |
| **Ethernet Link** | `100M_FULL` (802.3af PoE) | `100M_FULL` (802.3af PoE) |
| **Optical Zoom Range** | `5X` Motorized (`ZoomPos 0..28`, `FocusPos 0..238`, 3D Box Zoom) | `5X` Motorized (`ZoomPos 0..28`, `FocusPos 0..238`, 3D Box Zoom) |
| **Illumination** | IR Night Vision (`auto`/`close`) + `450 lm` Warm Floodlight (`1..100%`) | IR Night Vision (`auto`/`close`) + `450 lm` Warm Floodlight (`1..100%`) |
| **Audio** | Live Mic (`16 kHz` AAC -> MP3) + Two-Way Backchannel (`8 kHz` G.711 PCMU) + Siren | Live Mic (`16 kHz` AAC -> MP3) + Two-Way Backchannel (`8 kHz` G.711 PCMU) + Siren |
| **Time Sync (NTP)** | `time.google.com:123` (`America/New_York` `EDT`/`EST`) | `time.google.com:123` (`America/New_York` `EDT`/`EST`) |

---

## Camera Services, Network Ports & Internal Operation

On firmware `v3.1.0.4695_2504301440`, Reolink cameras lock down legacy plaintext web/streaming ports out of the box and expose only the proprietary **Baichuan** management port (`9000/tcp`) until explicitly instructed to enable standard streaming protocols.

### 1. Port & Service Matrix (`cmd_id = 37` `GetNetPort` / `cmd_id = 36` `SetNetPort`)

| Service | Protocol / Port | Factory Default | Active State on `ul` | Purpose & Operation |
| :--- | :--- | :--- | :--- | :--- |
| **Baichuan (`server`)** | `9000/tcp` | **Enabled** | **Enabled** | Proprietary binary + XML control, telemetry, snapshot, PTZ/zoom, AI/motion alarm, and configuration bus. |
| **RTSP (`rtsp`)** | `554/tcp`, `554/udp` | Disabled | **Enabled** (Auto-enabled by `cam`) | Real-Time Streaming Protocol serving hardware-encoded `main` (H.265 4K) and `sub` (H.264 360p) video, `16 kHz` AAC microphone audio, and ONVIF Backchannel (`track3` `PCMU/8000`) speaker talkback. |
| **ONVIF (`onvif`)** | `8000/tcp` | Disabled | Disabled (Toggleable) | Open Network Video Interface Forum profile for third-party NVR integration. |
| **RTMP (`rtmp`)** | `1935/tcp` | Disabled | Disabled (Toggleable) | Flash/RTMP H.264 sub-stream (`rtmp://<ip>:1935/bcs/channel0_sub.bcs`). |
| **HTTP (`http`)** | `80/tcp` | Disabled | Disabled (Toggleable) | Embedded camera web UI and `/cgi-bin/api.cgi` JSON endpoint. |
| **HTTPS (`https`)** | `443/tcp` | Disabled | Disabled (Toggleable) | Self-signed TLS embedded camera web UI and `/cgi-bin/api.cgi` JSON endpoint. |

### 2. How the Baichuan Protocol (`9000/tcp`) Works

All discovery, hardware control, and instantaneous snapshots are performed over TCP port `9000` using Reolink's **Baichuan** protocol:

1. **Binary Framing Header**: Every Baichuan packet begins with a 20-byte or 24-byte little-endian binary header starting with magic bytes `0xf0debc0a`, followed by `cmd_id` (uint32), payload length (uint32), `channel_id` (uint8), status code (`200` OK, `400` Bad Request), and payload offset.
2. **Modern Nonce Authentication (`cmd_id = 1`)**:
   - The client sends an initial unauthenticated `cmd_id = 1` request.
   - The camera replies with an XML `<Encryption>` challenge containing a random `<nonce>`.
   - The client computes uppercase MD5 digests of `username + nonce` and `password + nonce` (truncated to 31 characters) and submits the authenticated login frame.
   - Once authenticated, payload bodies are encrypted using AES-128-CFB (or XOR stream cipher depending on message class) keyed from the session nonce.
3. **Complete Baichuan Command IDs Used by `cam.drheck.dev`**:
   - `cmd_id = 80` (`GetDevInfo`): Model (`RLC-811A`), item number (`P430`), firmware version, serial number.
   - `cmd_id = 37` / `36` (`GetNetPort` / `SetNetPort`): Inspects and enables `rtsp` (`554/tcp`), `onvif` (`8000/tcp`), `rtmp` (`1935/tcp`), `http` (`80/tcp`), and `https` (`443/tcp`).
   - `cmd_id = 76` / `75` (`GetLocalLink` / `SetLocalLink`): Reads or writes DHCP vs. static IPv4 settings and MAC address.
   - `cmd_id = 182` (`Snap`): Captures an immediate hardware-encoded JPEG frame (`main` 4K or `sub` `640x360`) directly over the control socket in `~110 ms`.
   - `cmd_id = 56` / `57` (`GetEnc` / `SetEnc`): Reads and updates H.265/H.264 resolution, frame rate (`frame`), target bitrate (`bitRate` in kbps), and audio track embedding (`<audio>1</audio>`) for `mainStream` and `subStream`.
   - `cmd_id = 26` / `25` (`GetImage` / `SetImage`): Reads and updates ISP sensor parameters (`bright`, `contrast`, `saturation`, `hue`, `sharpen`, `DayNight/mode` `auto|color|black&white`, `nr3d`, `mirror`, `flip`).
   - `cmd_id = 44` / `45` (`GetOsd` / `SetOsd`): Reads and updates On-Screen Display overlays, including the Reolink logo watermark (`<OsdChannelName><enWatermark>0|1</enWatermark></OsdChannelName>`), camera name overlay, and timestamp overlay.
   - `cmd_id = 288` (`SetWhiteLed` manual) & `cmd_id = 289` / `290` (`FloodlightTask`): Controls manual spotlight toggle (`0|1`), brightness (`1..100`), automatic lighting mode (`0` Off/Manual, `1` Smart Night AI Trigger, `3` Night Schedule), duration (`60..600s`), and AI trigger classes (`people,vehicle,dog_cat`).
   - `cmd_id = 208` / `209` (`LedState`): Controls Infrared Night Vision LEDs (`<state>auto|close</state>`) and the front power status LED (`<lightState>open|close</lightState>`).
   - `cmd_id = 263` (`AudioAlarmPlay`) & `cmd_id = 232` / `231` (`GetAudioAlarm` / `SetAudioAlarm`): Triggers manual 1x siren or configures automatic siren on AI/motion alarm (`<AudioTask><enable>0|1</enable></AudioTask>`, **disabled `0` by default**).
   - `cmd_id = 264` / `265` (`GetAudioCfg` / `SetAudioCfg`) & `cmd_id = 439` / `440` (`GetAudioNoise` / `SetAudioNoise`): Controls speaker volume (`0..100`) and microphone 3D AI noise reduction (`level 0..3`).
   - `cmd_id = 574` / `575` (`get_privacy_mode` / `set_privacy_mode`) & `cmd_id = 52` / `53` (`GetMask` / `SetMask`): Toggles hardware Privacy / Sleep mode (`<sleepState><sleep>0|1</sleep></sleepState>`) and hardware privacy masks (`<Shelter><enable>0|1</enable></Shelter>`).
   - `cmd_id = 295` (`StartZoomFocus`), `cmd_id = 445` (`Set3DPos`), & `cmd_id = 224` / `225` (`GetAutoFocus` / `SetAutoFocus`): Steps the 5X motorized varifocal lens (`ZoomPos 0..28`, `FocusPos 0..238`), executes 3D area click-to-zoom (`posX`, `posY`, `posWidth`, `posHeight` in `0..1000` normalized coordinates), and locks/unlocks autofocus (`disable 0|1`).
   - `cmd_id = 342` / `343` (`GetAiAlarm` / `SetAiAlarm`) & `cmd_id = 46` / `47` (`GetMdAlarm` / `SetMdAlarm`): Configures per-class on-camera AI sensitivity (`0..100`) and stay-time (`0..8s`) for `people`, `vehicle`, `dog_cat`, `face`, plus general motion sensitivity (`1..50`).
   - `cmd_id = 38` / `39` (`GetNtp` / `SetNtp`), `cmd_id = 104` / `105` (`SystemGeneral`), `cmd_id = 106` / `107` (`Dst`), & `cmd_id = 23` (`reboot`): Configures NTP synchronization (`time.google.com:123`), US Daylight Saving Time, EDT/EST clock sync, and device reboot.

### 3. RTSP Stream Endpoints & ONVIF Two-Way Audio Backchannel (`554/tcp`)

Once `rtspEnable = 1` is set via Baichuan `cmd_id = 36`, each camera serves two RTSP streams plus an ONVIF two-way audio backchannel track:

- **Sub Stream (Low-Latency H.264 + AAC Audio + PCMU Backchannel)**:
  ```text
  rtsp://admin:<password>@10.0.0.122:554/h264Preview_01_sub
  rtsp://admin:<password>@10.0.0.123:554/h264Preview_01_sub
  ```
  - `track1`: **H.264 High Profile** (`yuv420p`, `640x360`, default `10 fps` at `256 kbps`, GOP = `4`, `recvonly`).
  - `track2`: **MPEG4-GENERIC AAC-hbr** (`16 kHz` mono audio, `recvonly`).
  - `track3` (when negotiated with `Require: www.onvif.org/ver20/backchannel`): **G.711 PCMU (`PCMU/8000`, payload type `0`, `sendonly`)** for transmitting two-way voice audio directly to the camera's built-in speaker.
- **Main Stream (4K UHD H.265/HEVC)**:
  ```text
  rtsp://admin:<password>@10.0.0.122:554/h265Preview_01_main
  rtsp://admin:<password>@10.0.0.123:554/h265Preview_01_main
  ```
  Encoded as **HEVC / H.265** (`yuvj420p`, `3840x2160`, default `25 fps` at `6144 kbps`, GOP = `2`) with `16 kHz` mono AAC audio.

---

## Architecture of `cam.drheck.dev`

The web service on host `ul` (`/data/docker/cam`) bridges the isolated IPv4 camera LAN (`10.0.0.0/24`) to an authenticated, browser-compatible HTTPS service at `https://cam.drheck.dev`.

| Component | Configuration on Host `ul` |
| :--- | :--- |
| **Service Directory** | `/data/docker/cam` (`app.py`, `static/index.html`, `static/tv.html`, `tests/app_test.py`) |
| **Persistent State & Models** | `/data/docker/cam/data/cameras.json`, `/data/docker/cam/data/yolov8n.onnx` (`13 MB`), `/data/docker/cam/data/events/` (`events.json` + `<event_id>.jpg`) |
| **Container Name** | `cam` (`cam:latest`, Python 3.12 + FastAPI + `reolink-aio` + `onnxruntime` + `Pillow` + `numpy` + `espeak-ng` + FFmpeg 7.1) |
| **ULA IPv6 Binding** | `[fd77:7777:5010:0:1337:f1ac:d00d:ca30]:8000` on `eth77` |
| **Nginx Reverse Proxy** | `/etc/nginx/sites-available/cam` (`X-Accel-Buffering: no`, `proxy_buffering off`, `X-Authentik-Groups` forwarding) |
| **Authentik SSO & Group** | `ProxyProvider` (`cam-provider`, ForwardAuth) -> `Application` (`cam`) bound to `Group` (`camera_access`) |
| **Backup & Restore** | `/home/luke/git/backups/ul/scripts/backup/cam` & `/home/luke/git/backups/ul/scripts/restore/cam` (`.tar.xz` + `par2 -r10`) |

### Key Subsystems Implemented in `app.py`

1. **Zero-Config Discovery (`discover_cameras`)**:
   - Reads the host kernel ARP table (`/proc/net/arp` mounted read-only at `/host_proc_arp`), matches Reolink OUIs (`ec:71:db`, `9c:8e:cd`), queries all Baichuan settings (`cmd_id` `80`, `37`, `76`, `56`, `26`, `264`, `44`, `289`, `208`, `232`, `439`, `574`, `52`, `224`, `38`, `46`, `342`), and auto-enables RTSP (`554/tcp`) and audio encoding (`<audio>1</audio>`).
2. **Motion & YOLOv8n AI Event Capture + Web-Viewable Timeline (`event_monitor_loop`, `record_camera_event`)**:
   - A background monitor coroutine (`event_monitor_loop`) samples sub-stream snapshots from online, non-sleeping cameras every `3.5 seconds`, computing downscaled `160x90` grayscale pixel frame-differences (`compute_frame_motion`).
   - Whenever motion (`>= 2.2%` of frame pixels) or a manual capture (`POST /api/cameras/{ip}/capture_event`) occurs, `classify_snapshot_yolo` runs the 80-class COCO **YOLOv8n** model (`yolov8n.onnx` via `onnxruntime`, `~62 ms` CPU inference on letterboxed `640x640` input with IoU `0.45` Non-Maximum Suppression).
   - **Clean Unannotated Snapshots + Text Metadata**: The raw JPEG snapshot is saved **without drawn bounding boxes** to `/data/docker/cam/data/events/<event_id>.jpg`, while the detected class labels, confidence percentages (`confidence_pct`), normalized target coordinates (`box_norm`), trigger types (`people`, `vehicle`, `dog_cat`, `motion`, `manual`), frame motion percentage, and `EDT`/`EST` timestamp (`format_edt_timestamp`) are recorded in `/data/docker/cam/data/events/events.json` and displayed in the **Detection Event Timeline & Metadata Log** on `https://cam.drheck.dev/`.
3. **Optional Auto-Zoom on Moving Objects (`apply_auto_zoom_on_box`)**:
   - Each camera has an optional **Auto-Zoom on Moving Objects** toggle (`auto_zoom_motion`, **off by default**).
   - When enabled, detecting a moving person/object or motion region (`[x, y, w, h]` in `0..1000` normalized coordinates) automatically issues Baichuan `cmd_id = 445` (`Set3DPos` `set_ptz_3d_zoom`) to zoom the 5X motorized lens onto the moving target, marks the timeline event with an `Auto-Zoomed` badge, and restores the lens to `baseline_zoom` after `20 seconds` of inactivity.
4. **Two-Way Audio Talk (`POST /api/cameras/{ip}/talk`)**:
   - Supports both **Push-to-Talk (Browser Microphone)** (recording `audio/webm` via `MediaRecorder` in the browser) and **Speak Text (TTS)** (synthesizing speech via `espeak-ng --stdout`).
   - `transcode_to_mulaw` converts the audio stream via `ffmpeg -i pipe:0 -ar 8000 -ac 1 -f mulaw pipe:1` into `8 kHz` G.711 mu-law (`PCMU/8000`), and `stream_rtsp_backchannel_mulaw` negotiates an RTSP Digest session on `554/tcp` with `Require: www.onvif.org/ver20/backchannel`, sets up `track3` (`Transport: RTP/AVP/TCP;unicast;interleaved=0-1`), and transmits `160-byte` (`20 ms`) RTP packets directly to the camera's speaker.

---

## Static DHCP Reservations on `danknasty` & IPv6 Support

### 1. Static DHCP Reservations (`/etc/dhcpd.conf` & `~/git/router_config/dhcpd.conf`)

Both cameras have permanent static DHCP leases configured in `/etc/dhcpd.conf` (and synced to `~/git/router_config/dhcpd.conf`) on `danknasty`:

```text
  host cam1 {
    # RLC-811A (141484731968753)
    hardware ethernet ec:71:db:f1:25:5a;
    fixed-address 10.0.0.122;
  }
  host cam2 {
    # RLC-811A (141484731989233)
    hardware ethernet ec:71:db:6d:84:ce;
    fixed-address 10.0.0.123;
  }
```

### 2. IPv6 Support

- **Camera Hardware (`RLC-811A` Firmware `v3.1.0.4695_2504301440`)**: No native IPv6 stack on the camera SoC (`10.0.0.0/24` IPv4 only).
- **Service Frontend (`cam.drheck.dev`)**: Full dual-stack IPv4 and IPv6 support via Nginx -> `[fd77:7777:5010:0:1337:f1ac:d00d:ca30]:8000`.

---

## Complete Camera Controls & Settings Matrix

All settings are available interactively in `https://cam.drheck.dev/` or via `POST /api/cameras/{ip}/control`:

| Category | Setting | Supported Range / Values | Default on `ul` | Baichuan `cmd_id` | Web UI / API Action |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **Events & AI** | Auto Event Capture to Timeline | `true` / `false` | `true` (Enabled) | Local + `182` | `Auto Event Capture` (`action: "auto_zoom_config"`) |
| **Events & AI** | Manual Event Capture & Classify | Captures JPEG + YOLOv8n text metadata | On-demand | `182` + ONNX | `Capture Event` (`POST /api/cameras/{ip}/capture_event`) |
| **Events & AI** | Person / Vehicle / Pet AI Sensitivity | `0 .. 100` (stayTime `0..8s`) | `60` | `342` / `343` (`SetAiAlarm`) | `Person / Vehicle / Pet AI Sensitivity` (`action: "ai_sensitivity"`) |
| **Events & AI** | Motion Detection Sensitivity | `1 .. 50` | `10` | `46` / `47` (`SetMdAlarm`) | `Motion Sensitivity` (`action: "ai_sensitivity"`) |
| **Optics & PTZ** | Optical Zoom (`ZoomPos`) | `0 .. 28` (`1X .. 5X` optical) | `0` | `295` (`StartZoomFocus`) | `Zoom` slider (`action: "zoom"`) |
| **Optics & PTZ** | Optical Focus (`FocusPos`) | `0 .. 238` | Auto | `295` (`StartZoomFocus`) | `Focus` slider (`action: "focus"`) |
| **Optics & PTZ** | 3D Click-to-Zoom (`Set3DPos`) | `posX, posY, posWidth, posHeight` (`0..1000`) | Interactive | `445` (`set_ptz_3d_zoom`) | `3D Click-Zoom` button (`action: "zoom_3d"`) |
| **Optics & PTZ** | Auto-Zoom on Moving Objects | `false` (Off) or `true` (On) + `baseline_zoom` | `false` (Off) | `445` + `295` | `Auto-Zoom on Moving Objects` (`action: "auto_zoom_config"`) |
| **Optics & PTZ** | Autofocus Lock | `false` (Auto) or `true` (Locked) | `false` (Auto) | `224` / `225` (`SetAutoFocus`) | `Autofocus Lock` (`action: "autofocus"`) |
| **Lighting** | Floodlight Manual Toggle & Brightness | `state: true|false`, `brightness: 1..100` | `Off`, `85%` | `288` (`SetWhiteLed`) | `Spotlight: ON/OFF` & `Light %` (`action: "spotlight"`) |
| **Lighting** | Floodlight Auto Behavior (`FloodlightTask`) | Mode `0` (Manual), `1` (Smart Night AI), `3` (Schedule); Duration `60..600s`; Triggers `people,vehicle,dog_cat` | Mode `1`, `180s` | `289` / `290` (`FloodlightTask`) | `Spotlight Automatic Mode / Triggers / Duration` (`action: "floodlight_config"`) |
| **Lighting** | IR Night Vision LEDs | `auto` (Auto Night IR) or `close` (Off) | `auto` | `208` / `209` (`LedState`) | `IR Night Vision LEDs` (`action: "leds"`) |
| **Lighting** | Front Status Power LED | `open` (On) or `close` (Off / Stealth) | `open` | `208` / `209` (`LedState`) | `Front Status Power LED` (`action: "leds"`) |
| **Lighting** | Day / Night Sensor Mode | `auto`, `color` (24/7 Color), `black&white` | `auto` | `26` / `25` (`SetImage`) | `Day / Night Sensor Mode` (`action: "image"`) |
| **Deterrents & Audio** | Auto-Siren on Detection | `false` (Off) or `true` (On) | **`false` (Off)** | `232` / `231` (`SetAudioAlarm`) | `Auto-Siren on Detection` (`action: "audio_alarm"`) |
| **Deterrents & Audio** | Manual Security Siren | Trigger 1x | Off | `263` (`AudioAlarmPlay`) | `Siren (1x)` (`action: "siren"`) |
| **Deterrents & Audio** | Live Microphone Audio Stream | `16 kHz` AAC -> `64 kbps` MP3 | Enabled | `57` + RTSP | `Audio: Muted / LIVE` (`GET /api/cameras/{ip}/audio.mp3`) |
| **Deterrents & Audio** | Two-Way Audio Talk (Mic & TTS) | Browser `MediaRecorder` or `espeak-ng` -> `8 kHz` G.711 PCMU | On-demand | RTSP `track3` (`554`) | `Push-to-Talk (Mic)` & `Speak Text` (`POST /api/cameras/{ip}/talk`) |
| **Deterrents & Audio** | Mic 3D Noise Reduction | Level `0` (Off), `1` (Low), `2` (Med), `3` (High) | `3` (High) | `439` / `440` (`SetAudioNoise`) | `Mic 3D Noise Reduction` (`action: "audio_noise"`) |
| **Deterrents & Audio** | Speaker Volume | `0 .. 100` (`%`) | `92` | `265` (`SetAudioCfg`) | `Speaker Volume` (`action: "volume"`) |
| **Privacy** | Privacy / Sleep Mode | `false` (Awake) or `true` (Sleeping) | `false` (Awake) | `574` / `575` (`set_privacy_mode`) | `Privacy Sleep` button (`action: "privacy_mode"`) |
| **Privacy** | Hardware Privacy Mask (`Shelter`) | `false` (Off) or `true` (On) | `false` (Off) | `52` / `53` (`SetMask`) | `Hardware Privacy Mask` (`action: "privacy_mask"`) |
| **Image & OSD** | Mounting / Orientation (`mirror`, `flip`) | `Normal`, `180° Upside-Down`, `Flip`, `Mirror` | `Normal` | `25` (`SetImage`) | `Flip 180°` button (`action: "image"`) |
| **Image & OSD** | Reolink Logo Watermark | `false` (Hidden) or `true` (Visible) | `false` (Hidden) | `45` (`SetOsd`) | `Reolink Logo Watermark` (`action: "osd"`) |
| **Time & System** | NTP Server & Clock Sync | `time.google.com:123` (`EDT`/`EST`) | `time.google.com` | `38` / `39` (`SetNtp`) & `105` | `Set time.google.com` & `Sync EDT Clock` (`action: "ntp"`, `action: "sync_time"`) |
| **Time & System** | Reboot Camera | Hardware reboot | On-demand | `23` (`reboot`) | `Reboot Camera` (`action: "reboot"`) |

---

## Bandwidth Adjustments for Unreliable or Slow Internet

When viewing two cameras simultaneously over a slow, high-latency, or lossy internet connection, total downstream bandwidth for $N$ cameras is governed by:

$$B_{\text{total}} = N \times f_{\text{fps}} \times S_{\text{frame}}(W, H, q) \times 8 \quad \text{bits/sec}$$

where $f_{\text{fps}}$ is the frame rate, $W \times H$ is the scaled resolution, and $q \in [2, 24]$ is the FFmpeg MJPEG quantization factor (`-q:v`).

| Preset Mode | Resolution | Frame Rate | JPEG `q` | Avg Frame Size | Per-Camera Bitrate | 2-Camera Total WAN Bitrate | Best For |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| **HD Main Stream** | `1280x720` | `10 fps` | `5` | `~25 KB` | `~2.0 Mbps` | `~4.0 Mbps` | Fast fiber / LAN full-screen inspection |
| **Standard Live (Default)** | `640x360` | `10 fps` | `5` | `~6.2 KB` | `~500 kbps` | `~1.0 Mbps` | Normal broadband / Wi-Fi |
| **Low Bandwidth** | `480x270` | `5 fps` | `10` | `~4.0 KB` | `~160 kbps` | `~320 kbps` | Congested Wi-Fi / mobile hotspot |
| **Ultra-Low WAN** | `320x180` | `2 fps` | `15` | `~3.1 KB` | `~50 kbps` | `~100 kbps` | Throttled cellular / international roaming |
| **Unreliable WAN Snapshot (2s)** | `640x360` | `0.5 fps` (`1 / 2s`) | Native | `~9.0 KB` | `~36 kbps` | `~72 kbps` | High packet loss / TCP reset prone links |
| **Minimal WAN Snapshot (5s)** | `640x360` | `0.2 fps` (`1 / 5s`) | Native | `~9.0 KB` | `~14 kbps` | `~28 kbps` | Extreme low-speed satellite / 2G fallback |

---

## Authentik Access Control (`camera_access` Group) & Google TV App

- **Authorized Group (`camera_access`)**: `luke`, `ikura`, `haruka`. Enforced at both the Nginx ForwardAuth edge (`PolicyBinding` on `cam`) and inside `check_camera_access_headers()` in `app.py`.
- **Google TV Sideloadable App (`dev.drheck.cam`)**:
  - Built APK on `ul`: `/data/docker/cam/googletv_src/cam-googletv.apk`
  - 10-Foot TV Interface: `https://cam.drheck.dev/tv` (authenticated automatically via baked-in 256-bit `TV_AUTH_TOKEN`).
