Skip to content

Repository files navigation

wbcam

Publish IP cameras into a WebRTC conference room — one participant per camera — so a whole fleet can be watched from an ordinary browser tab, with no NVR, no port forwarding and no VPN.

camera 1 --rtsp--> ┐
camera 2 --rtsp--> ├── wbcam ──wss──> WB Stream / LiveKit ──> browser
camera N --rtsp--> ┘   N participants                        N tiles

H.264 and H.265 are forwarded without transcoding, so a Raspberry-Pi-class box can carry a dozen cameras. The conference service does the NAT traversal, the fan-out and the UI.

Built for OpenIPC cameras, but nothing in it is OpenIPC-specific — any camera with an RTSP URL works.

Status

Working, and measured rather than assumed. Four OpenIPC cameras across three SoCs (hi3516ev300, hi3516av300, t31) ran live as four conference tiles, decoded from the subscriber side with zero errors. See docs/findings.md for what was tested and what broke.

Treat it as a solid proof of concept: the media path is proven, the operational edges (long soaks, large fleets) are not yet.

Quick start

go install github.com/OpenIPC/wbcam/cmd/wbcam@latest

# 1. Ask the camera what it actually emits. Do this first, always.
wbcam probe -url rtsp://192.168.1.10:554/stream=1 -user root -pass secret

# 2. Configure.
curl -O https://raw.githubusercontent.com/OpenIPC/wbcam/master/config.example.yaml
mv config.example.yaml cams.yaml && $EDITOR cams.yaml

# 3. Run.
wbcam run -config cams.yaml

# 4. Watch: open the room in a browser, and the local status page.
open http://127.0.0.1:8099/

Pick a backend

livekit — your own LiveKit server. No third party, no rate limits, no account. Recommended if you already self-host.

wbstream — the WB Stream service. Cameras join anonymously as guests; you need an account only to create the room.

A note on being a good neighbour: the wbstream backend registers one guest account per camera through the same flow the web client uses. That service rate-limits guest registration and will return 429 if you start a fleet at once — start_stagger and the token cache exist to keep wbcam inside sensible bounds. Don't point hundreds of cameras at someone else's free service. If you need scale, self-host LiveKit.

Your video leaves your network either way. With wbstream it passes through third-party infrastructure. Decide accordingly.

Prepare the camera

wbcam probe will tell you if something is wrong, but the short version for OpenIPC/majestic:

curl -u root:pass -X POST -H 'Content-Type: application/json' \
  -d '{"video1":{"enabled":true,"codec":"h264","profile":"base","size":"704x576","fps":15,"bitrate":1024,"gopSize":"1"}}' \
  http://CAMERA/api/v1/config
setting value why
stream sub-stream a tile in an N-up grid is 200-400 px wide; 1080p into it is 10x waste
profile base Main is not a profile LiveKit can negotiate honestly
gopSize 1 (second) sub-second time-to-first-picture and loss recovery
B-frames off RTSP delivers decode order, RTP carries presentation order
rcMode cbr we cannot adapt bitrate, so VBR spikes just cause loss

The enum is base, not baseline — the API rejects the latter.

Commands

command what it does
wbcam run the gateway
wbcam probe what a camera emits, judged against what the SFU can carry
wbcam watch subscribe and capture what the SFU actually delivers, to a file
wbcam listen dump room data packets, metadata and participant events
wbcam room-create create a WB Stream room (needs an account token)
wbcam console-preview render the control tile to a PNG

probe and watch are the two that earn their keep when something looks wrong: one shows what you sent, the other what arrived.

In-call control

Set control.enabled: true and wbcam joins as an extra participant that publishes a console tile and takes commands as room reactions:

reaction action
👏 refresh status
❤️ start all cameras
😢 stop all cameras
😂 move the cursor
👎 toggle the selected camera

Reactions rather than chat because chat never reaches the SFU — see docs/findings.md. Five buttons plus a screen is a TV remote, which is enough to pick a camera and toggle it.

Authorisation is by participant identity, resolved automatically from the room's owner. It fails closed: with nothing authorised, nothing is obeyed. Guests cannot publish data at all, so a stranger holding your room link cannot reach the control channel even before the allowlist is consulted.

Things that will bite you

H.264 profile. LiveKit negotiates only 42e01f and 640032. A Main-profile camera still negotiates — pion offers 42e01f unconditionally — and is then forwarded under an SDP that misdescribes it. Some browsers decode it, some paint black. wbcam probe grades this; fix it on the camera.

No bitrate adaptation. Passthrough means no encoder, so nothing can respond to congestion. Overshoot is packet loss, and every tile breaks at once. Size the uplink for the worst case and pin CBR.

Rooms die with their owner. On WB Stream a non-permanent room is reaped when the host leaves; camera guests cannot hold it open.

Guest registration is rate limited. Cold-starting four cameras at 400 ms apart earned a 429. The token cache in state_dir means a restart re-uses identities and avoids it entirely — don't delete that directory.

Build and test

go build ./cmd/wbcam
go test -race ./...

The RTSP suite stands up a real in-process RTSP server serving fragmented H.264, so ingest, reassembly, PTS handling and parameter-set seeding are all covered without hardware.

License

Apache-2.0. See LICENSE and NOTICE.

The WB Stream authentication flow was derived from olcrtc, along with several hard-won details of publishing video through pion and the LiveKit SDK.

About

Publish IP cameras into a WebRTC conference room, one participant per camera. H.264/H.265 passthrough, no transcoding.

Topics

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages