This project emulates UniFi Protect cameras so that non-Ubiquiti hardware (or purely virtual cameras) can participate in the Protect ecosystem.
Current capabilities
- UDP discovery responder (port 10001) and HTTPS adoption endpoint (port 443)
- Authenticated WebSocket handshake on 7442 with driver plumbing (Null + Amcrest snapshots)
- Driver framework with
NullDriverdemo output andAmcrestDriversnapshot support
Work in progress
- Command/event bridge over 7442/7550 (settings sync, telemetry, talkback)
- Streaming pipeline: uPFLV video/audio + metadata on the camera-initiated 7550 WSS channel
- Additional hardware drivers (Reolink, Hikvision, etc.)
Drivers live under Unifi/drivers/ and are selected through camera_data/settings.json (camera.type). Use "null" for the built-in test pattern or "amcrest" with ip, user, pass, etc., to exercise a real device while the rest of the driver matrix is being fleshed out.
Due to significant changes in UniFi Protect, this project is being completely rewritten. While the core functionality will remain the same, several improvements are planned — including better support for adoption, motion events, and PTZ controls.
- About
- Roadmap
- Starting the Dev Container
- Documentation
- UniFi Protect Discovery & Adoption Process
- High-Level Program Tree
- Network Surfaces
- UniFi Camera FLV & extendedFlv Stream Format
| Step | Direction | Port | Protocol | Purpose | Replicated |
|---|---|---|---|---|---|
| 1 | Controller → Camera | 10001 | UDP | Discovery | ✅ |
| 2 | Controller → Camera | 443 | HTTPS | Adoption command (manage) | ✅ |
| 3 | Camera → Controller | 7442 | WSS | Authenticated WebSocket handshake | ✅ |
| 4 | Controller → Camera | 7442 | WSS | Sends settings + adoption details | 🚧 |
| 5 | Camera ↔ Controller | 7550 | WSS (extendedFlv + commands) | Metadata/video/audio uplink, SPS/config downlink (in development) | 🚧 |
To get started:
- Clone the repository using Git to a folder on a Linux machine with Docker installed.
- Remote into the machine and open the folder using Visual Studio Code.
- Create a custom Docker network using macvlan (see instructions below).
- When prompted, allow VS Code to build and open the dev container. You can also rebuild the container at any time by pressing Ctrl+Shift+P and selecting "Dev Containers: Rebuild Container".
- Once the container is running and UniFi Protect is active on the same network, a discovery message should be recieved automatically. After the script responds to the discovery message (within 10–20 seconds), a new device should appear in UniFi Protect, ready for adoption.
Note: Before launching the dev container, you need to create a custom Docker network using macvlan:
docker network create -d macvlan \
--subnet=192.168.0.0/24 \
--gateway=192.168.0.1 \
-o parent=lan0 \
lan_net
Replace lan0 with your actual network interface name (e.g., eth0, enp3s0, etc.).
View the documentation at https://unifi-cam-proxy.com
See docs/wss_manager.md for the WSS manager overview and message-handling plan.
The rewrite mirrors the real UniFi Protect handshake in five stages. Each stage is handled by a dedicated component inside Unifi/entry.py.
| Stage | Triggered By | Transport / Port | Entry Point | Purpose |
|---|---|---|---|---|
| 1. Discovery | Protect broadcasts every few seconds | UDP / 10001 | DiscoveryResponder.start() (Unifi/discovery_responder.py) |
Reply with camera identity TLVs so the controller can list the device as “Ready to Adopt.” |
| 2. Adoption request | User clicks Adopt in Protect | HTTPS / 443 | VerboseAPIServer.start() (Unifi/api_server.py) |
Receive /api/1.2/manage, persist credentials, and hand off controller token to the WSS manager. |
| 3. AVClient handshake | Camera thread receives token event | WSS / 7442 | WssManager.start() (Unifi/wss_manager.py) |
Perform TLS WebSocket login, negotiate features, and fetch configs. |
| 4. Command/event bus (WIP) | Camera initiates after handshake | WSS / 7550 | WssManager secondary socket (Unifi/wss_manager.py) |
Bidirectional uPFLV transport: metadata/video/audio travel up, control frames down. |
| 5. Streaming (WIP) | Controller requests live view | WSS / 7550 | Camera drivers (Unifi/drivers/*) |
Encode extendedFlv (video/audio + Script tags) once instructed over the 7550 channel. |
- Initiated by Protect; every broadcast contains
0x01 0x00 0x0000. DiscoveryResponderlistens on a raw socket and replies immediately with a TLV payload describing MAC, firmware, feature bits, and optional extras (seeUnifi/discovery_responder.pyfor exact layout).- Adoption state is stored in
camera_data/settings.json, so subsequent boots can skip discovery ifcanAdoptis false.
- When the operator clicks Adopt, Protect performs
POST /api/1.2/manageagainst the camera. VerboseAPIServerterminates TLS (self-signed cert in repo), verifies credentials, and stores the management payload. The payload includes thetoken, controller hostnames, and user-provided username/password.- A
threading.Event(token_event) notifies the rest of the program that the controller token is ready.
WssManagerwaits ontoken_event, then opens a secure WebSocket to the controller host delivered in the adoption payload.- During this stage the manager:
- Sends the “hello” document defined in
HELLO_FEATURES. - Receives baseline settings (video, ISP, motion, etc.).
- Dispatches those payloads to the active camera driver (
NullDriver,AmcrestDriver, etc.) so each driver can apply or emulate them.
- Sends the “hello” document defined in
- Token refreshes or reconnects reuse the same flow; the manager loops until
stop_eventis set.
- After the AVClient handshake, the camera opens a second secure socket to the controller on port 7550.
- The protocol matches Protect’s uPFLV channel:
- Camera → Controller: FLV header, AMF metadata (
onMetaData,onClockSync), video tags (AVC sequence headers + H.264 NALUs), and optional AAC audio. - Controller → Camera: lightweight binary frames prefixed with
DE 19 16 75 50carrying stream start/stop commands, heartbeats, and clock-sync values.
- Camera → Controller: FLV header, AMF metadata (
- No RTSP/HTTP is involved; everything is raw TCP/WebSocket framing around standard FLV tags.
- Loss of controller heartbeats should cause the camera to tear down the session and stop streaming.
- Protect expects extendedFlv over the same 7550 socket, so there is no RTSP relay.
- Drivers can choose how to generate frames:
NullDriverfabricates JPEGs for snapshots and will eventually synthesize video frames for testing.- Hardware drivers (e.g., Amcrest) can ingest vendor RTSP or SDK feeds locally, transcode with FFmpeg, and push the resulting H.264/AAC payloads into the WSS stream helpers.
- See the “UniFi Camera FLV & extendedFlv Stream Format” section below for the exact tag layout Protect expects.
Unifi/entry.py
├─ CameraSettings() # loads/saves camera_data/settings.json
├─ DiscoveryResponder.start() # UDP 10001 responder (thread)
├─ VerboseAPIServer.start() # HTTPS /api server (thread)
├─ start_upload_server() # optional upload shim for Protect firmware updates
└─ WssManager.start() # owns token wait + both WSS connections
├─ build_camera_driver(...) # Null / Amcrest / future drivers
├─ driver.get_snapshot_jpeg() # used for adoption UI + diagnostics
├─ driver.apply_*_settings() # invoked when Protect sends settings
└─ streaming helpers # package extendedFlv metadata/audio/video on 7550
| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 10001 | UDP | Controller → Camera | Discovery broadcast + TLV response |
| 443 | HTTPS | Controller → Camera | /api/1.2/manage adoption endpoint |
| 7442 | WSS | Camera → Controller | Primary AVClient handshake (token auth + settings delivery) |
| 7550 | WSS (WIP) | Camera ↔ Controller | uPFLV stream channel (camera-initiated: metadata/video/audio up, control/SPS down) |
To inspect local sockets during development:
ss -tnp | grep 'replace_with_your_camera_IP'
Note: UniFi OS often binds these listeners on
tcp6. When/proc/sys/net/ipv6/bindv6onlyis0, IPv4 cameras can still connect via IPv4-mapped addresses (::ffff:192.168.x.y). Otherwise expose dedicated IPv4 listeners.
This document explains the structure of UniFi Protect's camera video stream format. Protect wraps standard FLV tags in a tiny “uP” header before handing control to the FLV parser, so cameras must prefix every connection with the UniFi signature.
| Bytes | Field | Description |
|---|---|---|
| 0x00-0x09 | DE 19 16 15 47 17 DE 19 16 75 50 |
UniFi “uPFLV” magic prefix (controller validates this before parsing FLV) |
| 0x0A-0x0C | 46 4C 56 |
Literal “FLV” |
| 0x0D | Version | 0x01 |
| 0x0E | Flags | 0x07 = has audio + video + data |
| 0x0F-0x12 | Header Size | 0x00000009 |
| 0x13-0x16 | PrevTagSize0 | 0x00000000 |
Immediately after the FLV header the camera emits a series of 0x12 script tags that describe the stream and keep the controller synchronized.
Encodes basic stream descriptors in AMF:
| Field | Example from capture | Notes |
|---|---|---|
audioBandwidth |
0x40EF400000000000 (~119000.0) |
Nominal AAC bitrate in bps |
audioChannels |
0x3FF0000000000000 (2) |
Mono/Stereo indicator |
audioFrequency |
0x40E7700000000000 (48 kHz) |
Sample rate |
channelId |
0x4000000000000000 (2) |
Protect’s logical channel index |
extendedFormat |
true |
Marks the stream as uP extendedFlv |
hasAudio/hasVideo |
true |
Flags consumed by the controller UI |
streamId |
0x4000000000000000 (2) |
Mirrors channelId |
streamName |
E0nB5LcAqWiOYjDL |
Randomly assigned per session |
videoBandwidth |
0x413E848000000000 (~499200.0) |
Target H.264 bitrate |
videoFps |
0x4038000000000000 (24 fps) |
Frame rate |
videoHeight |
0x4086800000000000 (1040) |
Height in pixels (device-dependent) |
videoWidth |
0x4094000000000000 (1280) |
Width in pixels |
Protect’s per-module stats channel. Each key (cs, m, r, sp, t) contains cur, max, min doubles describing current/max/min bitrates for capture, motion, recording, streaming, and talkback.
Provides timing info so Protect can align frames:
| Field | Description |
|---|---|
streamClock |
Running presentation timestamp |
streamClockBase |
Offset used to create absolute times |
wallClock |
Unix time (double) generated by camera |
The capture shows the first 0x09 (video) tag immediately after onClockSync. Its payload begins with the AVC video packet header:
09 ; Tag type = video
00 00 0061 ; Data size (0x61 bytes following)
00 01 5f90 ; Timestamp + extension
00 00 00 ; Stream ID
17 ; FrameType=1 (keyframe), CodecID=7 (AVC)
00 ; AVCPacketType=0 (sequence header)
00 00 00 ; Composition time
ff e1 00 1d ... ; avcC blob (SPS/PPS) and first IDR NALU
Subsequent video tags will set AVCPacketType=1 and contain raw H.264 NALUs. Audio tags follow the standard FLV AAC framing (0x08 tag, AACPacketType + payload). They are not present in the first 1024 bytes above but show up later in the same session when the camera enables talkback/microphone.
Motion and smart-detect metadata are also delivered via 0x12 script tags. They were not present in the first kilobyte of the sample capture, but production cameras interleave messages such as:
["event", {"motion": true, "zone": "default"}]between video/audio frames whenever activity is detected.
| Tag Type | Purpose | Format | Notes |
|---|---|---|---|
0x12 |
Metadata / Events | AMF0 | onMetaData, onMpma, onClockSync, motion events |
0x08 |
Audio | AAC | Appears once microphone/talkback is active |
0x09 |
Video | H.264/AVC | Keyframes (AVCPacketType=0) and delta frames (=1) |
- Real sessions start with the UniFi header + FLV header,
onMetaData,onMpma, andonClockSyncbefore any video frames. - Video tags use AVC NALUs exactly as FLV expects; Protect does not wrap them in any extra framing beyond the initial uP prefix.
- Audio tags are optional and appear only when the driver advertises
hasAudio. - Controllers can toggle audio dynamically (
suppressAudiosetting) and expect cameras to stop emitting0x08tags immediately.
The capture below shows the first 0x200 bytes of an actual Protect session. The UniFi prefix is present (DE 19 16 15 … 75 50), followed by the FLV header and onMetaData, onMpma, onClockSync, and AVC sequence headers.
de 19 16 15 47 17 de 19 16 75 50 46 4c 56 01 07 00 00 00 09
00 00 00 00 12 00 01 22 00 00 00 00 00 00 00 02 00 0a 6f 6e
4d 65 74 61 44 61 74 61 03 00 0e 61 75 64 69 6f 42 61 6e 64
77 69 64 74 68 00 40 ef 40 00 00 00 00 00 00 0d 61 75 64 69
6f 43 68 61 6e 6e 65 6c 73 00 3f f0 00 00 00 00 00 00 0e 61
75 64 69 6f 46 72 65 71 75 65 6e 63 79 00 40 e7 70 00 00 00
00 00 00 09 63 68 61 6e 6e 65 6c 49 64 00 40 00 00 00 00 00
00 00 00 0e 65 78 74 65 6e 64 65 64 46 6f 72 6d 61 74 01 01
00 08 68 61 73 41 75 64 69 6f 01 01 00 08 68 61 73 56 69 64
65 6f 01 01 00 08 73 74 72 65 61 6d 49 64 00 40 00 00 00 00
00 00 00 00 0a 73 74 72 65 61 6d 4e 61 6d 65 02 00 10 45 30
6e 42 35 4c 63 41 71 57 69 4f 59 6a 44 4c 00 0e 76 69 64 65
6f 42 61 6e 64 77 69 64 74 68 00 41 3e 84 80 00 00 00 00 00
... (metadata continues)
00 00 00 00 00 17 00 00 00 00 01 4d 40 1f ff e1 00 1d 67 4d
40 1f 8d 8d 40 28 02 df f8 0b 70 10 10 14 00 00 0f a0 00 02
ee 02 76 82 21 1a 80 01 00 04 68 ee 38 80 ... (AVC SPS/PPS + keyframe)
Key sections:
- 0x0000–0x000A: UniFi uPFLV prefix (
DE 19 16 15 47 17 DE 19 16 75 50). - 0x000B–0x0016: FLV header (
FLV, version0x01, flags0x07, header size0x00000009). - 0x0017–0x00F8:
onMetaDataAMF blob (audio/video bandwidth, FPS, resolution, stream nameE0nB5LcAqWiOYjDL). - 0x0150–0x01B8:
onMpmametadata (current/max/min bitrates). - 0x0220–0x0280:
onClockSyncmetadata (streamClock,wallClock). - 0x0290–0x02FF: AVC sequence header (
avcC, SPS67 4D 40 1F …, PPS68 EE 38 80). - 0x0300+: Start of keyframe data (H.264 IDR NALUs).
If you would like to make a donation to support development, please use Github Sponsors.