WRecorder provides multi-camera streaming between machines using GStreamer over UDP: a broadcast discovery channel for zero-config discovery, and unicast streaming (via multiudpsink) for efficient per-receiver delivery.
- Multi-stream scripts: camera_streamer.py, camera_receiver.py
- Startup scripts: launch1.sh, launch2.sh
- Required runtime defaults: argument_defaults.json
- Legacy documentation: OLD.md
Both scripts require argument_defaults.json at runtime with sections both, streamer-only, receiver-only. Missing/invalid files, unknown keys, or missing required keys cause fail-fast startup errors. CLI flags override JSON values.
- Python 3.13 (specifically 3.13)
- GStreamer, GTK, and system dependencies
- pip packages (installed via virtual environment)
- Install system dependencies, create virtual environment, & install Python packages:
Choose one based on your setup:
# For systems with display (GUI receiver with PyQt6)
make setup-headed
# For headless systems (no GUI packages)
make setup-headless
# Default (equivalent to setup-headed)
make setupIf this isn't working, read through the Makefile to understand the steps and try running them manually. Common issues include missing GStreamer plugins or Python dependencies.
- The
Makefilewill detect your virtual environment (.venvorenv) and use it automatically. - PyQt6 is used for the receiver GUI in headed mode; omit it in headless mode.
headed_requirements.txtincludes PyQt6 and PyGObject for GUI;headless_requirements.txtcontains minimal dependencies only.--mosaicis available on the streamer for a single combined view and is off by default.- Make sure you have an existing OpenCV build with GStreamer support. If you don't, consider building it separately or obtaining a prebuilt wheel.
Make sure to run make setup first to install dependencies and set up the virtual environment. Then activate the virtual environment:
source .venv/bin/activateStart streamer:
python3 camera_streamer.pyStart receiver:
python3 camera_receiver.pyBy default, receiver uses auto-config=on to auto-discover available streamers and their port ranges. Override discovery with --auto-config off and manual flags, or filter by streamer name with --streamer-name-filter. Multicast IP is always 224.1.1.1.
| Argument | Description | Default | Expected Input |
|---|---|---|---|
--base-port |
Starting port for first camera stream; additional streams use base-port + index |
5555 |
1-65535 |
--camera-ids |
Space-separated camera IDs | [0] |
Camera device indices (e.g., 0 2 4) |
--auto-find-cameras |
Auto-detect cameras and override --camera-ids |
on |
on|off |
--bitrate |
Target H.264 stream bitrate | 500000 |
bps (>= 1) |
--target-fps |
Target stream FPS | 30 |
>= 1 |
--simulate-cameras |
Simulate N cameras instead of real devices | null (disabled) |
>= 1 or null |
--simulate-loss |
Simulate network packet loss percentage | 0.0 |
0.0 - 100.0 |
--streamer-name |
Discovery identity name for receiver filtering | wrecorder-streamer |
String |
--announce-discovery |
Broadcast discovery metadata (streamer name + port range) | on |
on|off |
--discovery-port |
UDP discovery port | 5550 |
1-65535 |
--discovery-interval |
Seconds between discovery packets | 1.0 |
Seconds (float) |
Discovery packets now include stream_count as the number of camera streams represented by the advertisement and mosaic as an explicit layout hint for single-window versus mosaic rendering.
| --control-port | UDP control port for subscription requests (receivers send SUBSCRIBE_REQUEST here) | 5551 | 1-65535 |
| Argument | Description | Default | Expected Input |
|---|---|---|---|
--broadcast-ip |
Legacy Publisher IP (ignored for multicast) | 0.0.0.0 |
IP address |
--base-port |
Starting port for first subscribed stream | 5555 |
1-65535 |
--count |
Number of sequential ports to subscribe to | 1 |
Integer >= 1 |
--timeout |
Total setup timeout | 10.0 |
Seconds (float) |
--auto-config |
Auto-configure from discovery announcements | on |
on|off |
--streamer-name-filter |
Accept only matching streamer name from discovery | null (no filter) |
String or null |
--discovery-port |
UDP discovery port | 5550 |
1-65535 |
--discovery-timeout |
Discovery phase timeout override | null (auto budget) |
Seconds (float) or null |
Notes on discovery & subscription:
- The receiver listens for discovery broadcasts (default port
5550). When a desired streamer is discovered, the receiver opens local unicastudpsrclisteners on the requested port(s) and sends a JSONSUBSCRIBE_REQUESTto the streamer's--control-port(default5551) advertising its reachable IP and the ports it will listen on. - The streamer runs a lightweight UDP control server that accepts
SUBSCRIBE_REQUESTmessages and dynamically adds the receiver as a unicast target using GStreamer'smultiudpsink.emit("add", ip, port)so streams are sent directly to subscribing receivers.
This hybrid approach keeps discovery simple (broadcast) while avoiding multicast penalties on WiFi by delivering actual video over unicast to each subscriber.
nmcli dev wifi
nmtuiManual streamer setup:
python3 camera_streamer.py --base-port 5555 --camera-ids 0 2 4 --bitrate 500000 --target-fps 30Discovery receiver with filter:
python3 camera_receiver.py --auto-config on --streamer-name-filter cam-pi-1Simulated cameras:
python3 camera_streamer.py --simulate-cameras 4- No camera detected: verify device nodes and permissions, then run
v4l2-ctl --list-devices. - Receiver cannot connect: verify stream host/ports and firewall rules.
- Discovery not finding streamer: verify same subnet and matching discovery port.
- If a camera is physically disconnected, a streamer restart may still be required depending on device/driver behavior.