Adaptive bitrate streaming overlay for Frigate NVR. Adds multi-quality streaming for both live and recorded footage with GPU-accelerated transcoding - zero Frigate source modifications required.
- Live streams: Registers lower-resolution stream variants in go2rtc (e.g.
camera_abr_720p). go2rtc only transcodes when a viewer connects, so idle variants cost nothing. - Recordings: Transcodes recording segments on-demand with GPU acceleration, caches the results, and serves them as HLS. Each 10-second segment transcodes in ~1-2 seconds on Intel iGPU.
- Quality selector: A gear icon injected into every video player lets you pick quality. Switching reloads the page with the new setting.
| Tier | Resolution | Bitrate | Use case |
|---|---|---|---|
| Original | Native (e.g. 4K) | Passthrough | LAN / fast connections |
| 1080p | 1920x1080 | 2500k | Broadband |
| 720p | 1280x720 | 1200k | Mobile / moderate |
| 480p | 854x480 | 500k | Slow connections |
These are conservative defaults tuned for security camera footage (mostly static scenes). Configurable in config.yml.
Replace your Frigate image with the frigate-abr image. Everything is baked in.
1. Change your docker-compose.yml (or Portainer stack):
services:
frigate:
image: ghcr.io/007hacky007/frigate-abr:latest # was: ghcr.io/blakeblackshear/frigate:stable
# everything else stays exactly the same2. (Optional) Mount your own ABR config to customize tiers/cache:
volumes:
# ... your existing volumes ...
- ./config-abr.yml:/opt/frigate-abr/config.yml:ro3. Restart:
docker compose up -dThat's it. The image is based on Frigate 0.17.1 with the ABR overlay pre-installed.
Images are built for all Frigate variants:
| Tag | Base image | Use case |
|---|---|---|
latest |
frigate:0.17.1 |
Standard x86_64 (Intel/AMD) |
latest-tensorrt |
frigate:0.17.1-tensorrt |
NVIDIA GPU with TensorRT |
latest-rocm |
frigate:0.17.1-rocm |
AMD GPU with ROCm |
Pinned version tags are also available (e.g. frigate-0.17.1-tensorrt).
To build locally for a specific variant:
git clone https://github.com/007hacky007/frigate-abr.git
cd frigate-abr
# Standard
docker build -t frigate-abr .
# NVIDIA TensorRT
docker build --build-arg FRIGATE_VERSION=0.17.1-tensorrt -t frigate-abr:tensorrt .
# Rockchip
docker build --build-arg FRIGATE_VERSION=0.17.1-rk -t frigate-abr:rk .The sidecar auto-detects your hwaccel preset from Frigate's config. Override in config.yml if needed:
| Hardware | Value |
|---|---|
| NVIDIA | preset-nvidia |
| Intel iGPU (VAAPI) | preset-vaapi |
| Intel iGPU (QSV) | preset-intel-qsv-h264 |
| AMD (VAAPI) | preset-vaapi |
| Rockchip | preset-rkmpp |
| Raspberry Pi | preset-rpi-64-h264 |
| CPU only | default |
For Intel GPUs, VOD transcoding uses QSV (decode + scale + encode entirely on GPU). Live transcoding is handled by go2rtc.
- Open Frigate's web UI.
- A gear icon appears in the top-right corner of each video player.
- Click it to select quality: Original, 1080p, 720p, or 480p.
- For live view - switching quality reconnects to a lower-res go2rtc stream.
- For recordings - segments are transcoded on-demand and cached.
config.yml:
enabled: true
tiers:
- name: "1080p"
width: 1920
height: 1080
bitrate: "2500k"
- name: "720p"
width: 1280
height: 720
bitrate: "1200k"
- name: "480p"
width: 854
height: 480
bitrate: "500k"
cache:
path: /tmp/cache/abr
max_size_gb: 10.0 # LRU eviction when exceeded
ttl_hours: 24 # Cached segments expire after this
max_concurrent_transcodes: 2 # Limits simultaneous GPU transcodes
# Auto-detected from Frigate config. Override if needed:
# hwaccel: preset-nvidia
# gpu: 0| Endpoint | Description |
|---|---|
GET /abr/health |
Health check with version/commit |
GET /abr/config |
Returns tiers, cache stats, enabled state |
GET /abr/stats |
Active transcodes, cache size, hwaccel info |
GET /abr/debug/transcode |
Test single segment transcode with diagnostics |
POST /abr/live/setup |
Manually re-register go2rtc stream variants |
# Check logs for successful startup
docker compose logs frigate | grep "\[ABR\]"
# Check sidecar health (should show version and commit)
curl http://localhost:5000/abr/health
# Check transcoding works
curl http://localhost:5000/abr/debug/transcode?camera=YOUR_CAMERA&quality=480p- S6 oneshot (
abr-patch) patchesnginx.confbefore nginx starts - adds upstream, location blocks, andsub_filterfor JS injection. - S6 longrun (
abr-sidecar) runs a FastAPI service that registers go2rtc stream variants, generates HLS playlists, transcodes segments on-demand, and manages the cache. - Frontend overlay (
inject.js) intercepts XHR/WebSocket requests to rewrite URLs based on the selected quality.
| Symptom | Fix |
|---|---|
| No gear icon on video players | Check docker compose logs frigate | grep ABR for patch errors. |
| Grey/black screen on ABR quality (live) | Firefox autoplay restriction. Click the lock icon in address bar -> Permissions -> Autoplay -> Allow Audio and Video. Chrome works without this. |
| Transcoding fails | Run curl localhost:5000/abr/debug/transcode?camera=YOUR_CAMERA&quality=480p and check ffmpeg_exit_code and ffmpeg_stderr. |
| Cache growing too large | Lower cache.max_size_gb or cache.ttl_hours in config.yml. |
The overlay does not modify any Frigate source files. On Frigate update, the nginx patch re-applies automatically (idempotent). If Frigate changes nginx.conf structure significantly, the sed patterns in abr-patch/run may need updating - the patch logs clearly when it fails.
I tried this first. Frigate already uses nginx-vod-module for HLS playback, so it seemed natural to route ABR requests through it with a different upstream. Two problems killed the approach:
-
vod_upstream_locationcan't be overridden at location level. I added a/vod_abr/location withvod_upstream_location /abr;pointing to the sidecar, but nginx-vod-module ignored it and kept using the server-levelvod_upstream_location /api(Frigate's original API). Both/vod_abr/and/vod/returned identical 3840x2160 HEVC content. -
nginx-vod-module needs the entire manifest upfront. It makes a single subrequest to get a JSON manifest with ALL clip paths, then generates the HLS playlist from that. The sidecar had to transcode ALL 300+ segments before returning the manifest. A 1-hour recording would take 30+ minutes to transcode upfront, and the subrequest would time out long before that.
The solution: bypass nginx-vod-module entirely for ABR. The sidecar generates its own m3u8 playlist and serves MPEG-TS segments on-demand. hls.js requests them one at a time, each transcodes in ~1-2 seconds with QSV, and they're cached after first play.
VAAPI's scale_vaapi filter fails with "Cannot allocate memory" when Frigate is simultaneously using the GPU for object detection. The GPU runs out of surface memory for a second decode+scale+encode pipeline. QSV (Intel Quick Sync) uses a different memory management model (libmfx/oneVPL) and doesn't have this contention issue, even on the same Intel GPU. So the preset-vaapi template maps to QSV decode + vpp_qsv scale + h264_qsv encode for VOD transcoding. Live transcoding is handled by go2rtc separately.
Frigate's MSEPlayer and WebRTCPlayer don't auto-reconnect when WebSockets are closed externally. Their internal state machines have conditions that prevent reconnection. I tried faking visibility changes and closing sockets directly, but the players either ignored it or entered long error-recovery loops. A page reload is the only reliable way to switch quality, and since the setting is stored in localStorage, the new page load picks it up immediately.
MIT