A small virtual webcam for Linux that segments the foreground with MediaPipe / RVM, blurs or replaces the background, and exposes the result on a v4l2loopback device that Zoom, Meet, Teams, OBS, Firefox, and Chrome treat as a regular webcam.
flowchart LR
cam(["📷 Physical cam<br/><sub>/dev/video0</sub>"])
seg["🧠 <b>Segment</b><br/><sub>MediaPipe · RVM<br/>ONNX Runtime</sub>"]
comp["🎨 <b>Composite</b><br/><sub>blur · replace<br/>auto-frame</sub>"]
vcam(["📺 Virtual cam<br/><sub>/dev/video10</sub>"])
cam == RGBA ==> seg
seg == mask ==> comp
comp == YUY2 ==> vcam
classDef device fill:#0f172a,stroke:#60a5fa,stroke-width:2px,color:#f8fafc;
classDef rust fill:#7c2d12,stroke:#fb923c,stroke-width:2px,color:#fff7ed;
class cam,vcam device;
class seg,comp rust;
Why it exists. Existing options on Linux are either heavy (Python + CUDA + OpenCV stacks) or shallow (basic chroma key with hard cuts). This is a single Rust binary that runs MediaPipe / RVM on CPU via ONNX Runtime, with a native egui UI, no Python, no CUDA, and edge quality on par with the leading proprietary alternatives.
- Features
- Install & run
- GPU acceleration (optional, NVIDIA)
- Using it
- Performance
- Troubleshooting
- Repo layout
- Contributing
- License
- Two switchable models (chosen live in the GUI):
- RVM (Robust Video Matting) — recurrent video matting, best edges, no flicker (~15 MB). Default.
- Selfie multiclass — per-frame, fixed 256×256 (~16 MB). Low-CPU fallback.
- Three background modes — passthrough, blur (intensity slider, 4–32 px radius), replace with a saved image.
- Saved background library — imports are copied to
~/.local/share/linux-broadcast/backgrounds/so they survive across launches. - Auto-frame — a smoothed horizontal recenter plus a light foreground zoom that keeps the silhouette centered as you move. Off by default; toggle in the sidebar's Settings. Skipped when the background mode is None (no plane to paint over).
- Full-resolution capture — automatically negotiates MJPEG so USB webcams deliver native HD at 30 fps; raw YUYV is bandwidth-capped to ~10 fps at 720p on USB 2.0. Falls back to raw automatically if the camera can't do MJPEG (
LB_FORCE_RAW=1forces it). The capture is always aspect-preserved — cropped to your output ratio, never stretched. - Lazy producer mode — the physical webcam LED only lights when an app is actually reading the virtual cam, like NVIDIA Broadcast. See Lazy mode.
- System tray — the close button hides to the tray;
Quitfrom the tray menu actually exits. - Live preview pane in the GUI; the footer shows the real output framerate and the camera's native capture format (e.g.
MJPEG 1280×720). Settings persist to~/.config/linux-broadcast/config.toml. - No CUDA, no PyTorch, no Python — single Rust binary, ~25 MB plus the bundled ONNX/font assets.
Out of scope: audio / microphone effects.
Tested on Ubuntu 24.04+ / Mint 22+ / Debian trixie+. The package depends on v4l2loopback-dkms (≥ 0.12.8) and the GStreamer plugin set; everything else is statically baked into the binary.
sudo apt install ./linux-broadcast_<version>_amd64.debThat's it. Behind the scenes the package:
- Installs the kernel module via DKMS and loads it now (
postinstrunsmodprobe v4l2loopback), so/dev/video10is available immediately. - Drops
/etc/modprobe.d/linux-broadcast.confwith the right options (devices=1 video_nr=10 card_label="LinuxBroadcast" exclusive_caps=1 max_buffers=2) and/etc/modules-load.d/linux-broadcast.confso the module reloads on every boot — no manualmodprobeever required. - Registers the app menu entry and icon under
/usr/share/applications/and/usr/share/icons/hicolor/64x64/apps/.
apt remove linux-broadcast unloads the module and removes the launcher; apt purge additionally drops the /etc/modprobe.d and /etc/modules-load.d drop-ins (preserved as conffiles otherwise).
Want it always running so Zoom / Meet / Signal / Firefox just see "LinuxBroadcast" in their camera list at every login? Open the GUI and flip Start on login in the sidebar — see Start on login below.
# With an AUR helper:
yay -S linux-broadcast-bin
# or:
paru -S linux-broadcast-binlinux-broadcast-bin repackages the official .deb from GitHub Releases, so you get the same binary, the same /etc/modprobe.d + /etc/modules-load.d drop-ins, and the same desktop integration. The AUR install hook reloads v4l2loopback immediately after install (DKMS rebuild permitting) and refreshes the desktop / icon caches.
The AUR dep on v4l2loopback-dkms will be resolved by your helper. If you'd rather use v4l2loopback-dkms-git or a kernel-specific v4l2loopback-* flavour, install it first and AUR will accept it as a provides match.
LinuxBroadcast runs segmentation on the CPU by default. On an NVIDIA RTX-class
GPU you can offload it to the GPU (~5× faster inference, frees a CPU core) by
installing the optional linux-broadcast-cuda add-on and the NVIDIA CUDA 13
runtime + cuDNN 9. Without them, LinuxBroadcast transparently stays on the CPU.
-
Install the add-on package (ships only the ONNX Runtime CUDA provider libs):
sudo apt install ./linux-broadcast-cuda_<version>-1_amd64.deb
-
Install the NVIDIA CUDA 13 runtime libraries (NVIDIA's apt repo):
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2604/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb && sudo apt update sudo apt install cuda-libraries-13-3 # runtime libs only, no driver/toolkit
(On Ubuntu 24.04 use the
ubuntu2404repo path.) -
Install cuDNN 9 for CUDA 13. Ubuntu 26.04 has no apt package yet; use NVIDIA's redistributable tarball:
wget https://developer.download.nvidia.com/compute/cudnn/redist/cudnn/linux-x86_64/cudnn-linux-x86_64-9.23.1.3_cuda13-archive.tar.xz tar -xf cudnn-linux-x86_64-9.23.1.3_cuda13-archive.tar.xz sudo mkdir -p /usr/local/cudnn/lib sudo cp -a cudnn-linux-x86_64-9.23.1.3_cuda13-archive/lib/libcudnn*.so* /usr/local/cudnn/lib/ echo /usr/local/cudnn/lib | sudo tee /etc/ld.so.conf.d/cudnn.conf && sudo ldconfig
Verify it engaged: RUST_LOG=info linux-broadcast logs
ONNX Runtime: CUDA execution provider registered. A working NVIDIA driver
(nvidia-smi) supporting CUDA 13 is required.
Use this when hacking on the code; the .deb is the right choice for everyday use.
sudo apt install -y \
build-essential pkg-config \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
libxkbcommon-dev libwayland-dev \
libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
libgtk-3-dev libayatana-appindicator3-dev \
v4l2loopback-dkms \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libavThe libgtk-3-dev / libayatana-appindicator3-dev pair is pulled in by the tray-icon crate at build time; at runtime only libayatana-appindicator3-1 is required (already declared in the .deb's Depends).
The .deb does this automatically; for source builds, set the module up by hand once:
# Reload the module so our params actually take effect — modprobe is a
# no-op if the module is already loaded with different options.
sudo modprobe -r v4l2loopback 2>/dev/null
sudo modprobe v4l2loopback devices=1 video_nr=10 card_label="LinuxBroadcast" \
exclusive_caps=1 max_buffers=2To make this survive reboots, drop the same options into /etc/modprobe.d/linux-broadcast.conf and linux-broadcast into /etc/modules-load.d/ (or just install the .deb, which ships these files for you).
git clone https://github.com/Pedrojok01/linux-broadcast.git
cd linux-broadcast
cargo run --release -p linux-broadcastONNX Runtime's libonnxruntime.so is fetched automatically the first time you build (ort crate, download-binaries feature). The MediaPipe / RVM models and the Inter / JetBrains Mono fonts ship in-tree.
A headless mode is available for sanity checks and CI:
cargo run --release -p linux-broadcast -- --headless
# or, equivalently:
LB_HEADLESS=1 cargo run --release -p linux-broadcastcargo install cargo-deb
cargo deb -p linux-broadcast
# artefact: target/debian/linux-broadcast_<version>_amd64.debLinuxBroadcast can run silently in the background at every login so the virtual cam is already up by the time you open Zoom / Meet / Signal / Firefox. It's off by default — flip the toggle in the sidebar's Settings section to enable it. The GUI writes (or removes) ~/.config/autostart/LinuxBroadcast-autostart.desktop, which runs linux-broadcast --headless on login. No system files are touched and no root is needed; uninstalling the .deb won't remove your autostart entry, and disabling the toggle will.
By default LinuxBroadcast only opens your physical camera when something is actually reading the virtual cam — Meet / Zoom / Signal / Firefox / ffplay. The rest of the time the LED is off and CPU is at idle, even with the GUI open. The transition is fast: opening Meet lights the camera within ~2 s; closing it releases the camera ~3 s later. Both windows debounce browser capability-probes and in-call camera-switcher flicker.
The footer shows what's happening: ● Idle (no app reading), ● Standby (no consumer) (LB is up but nothing wants the cam yet), or ● LIVE → firefox (12345) while a real consumer is attached.
When the LinuxBroadcast GUI is open and visible, the preview pane counts as a consumer so you can see your composited self while configuring backgrounds. Minimising the window drops that signal and the camera goes back to sleep (assuming nothing else is reading).
The pipeline is always running while LinuxBroadcast is open — there's no Start/Stop button. Conferencing apps see LinuxBroadcast in their camera list the moment LB starts, and the physical webcam only powers on when something actually reads the virtual cam (see Lazy mode).
- Pick a physical camera in Camera.
- Pick a model in Model — RVM is the default (best edges); multiclass is the low-CPU fallback. Switching restarts the pipeline automatically.
- Set the scene with the segmented control —
None(passthrough),Blur(slider for intensity), orReplace(uses the active library tile). - Click + Import to add background images; click any thumbnail to switch to it live; right-click → Remove deletes it.
- Toggle Auto-frame, Show preview, or Start on login in the sidebar's Settings section as needed. All three persist to
config.toml. - Open Zoom / Meet / Signal / Firefox / OBS and pick
LinuxBroadcastas the camera — that's it. Closing LB's window keeps it running in the tray;Quitfrom the tray menu actually shuts it down.
Reference numbers on 1280×720, Logitech C920. CPU = one x86 core; GPU = NVIDIA RTX 4060 Ti via the optional cuda feature (measured with RVM, the default model).
Segmentation (inference) per frame:
| Model | CPU | GPU |
|---|---|---|
| Selfie multiclass (256×256) | ~10 ms | — |
RVM (downsample_ratio=0.5) |
~40–60 ms | ~12 ms |
End-to-end throughput (RVM):
| Background mode | CPU | GPU |
|---|---|---|
| Virtual background | ~15 fps | 30 fps (camera-bound) |
| Blur | ~13 fps | 30 fps (camera-bound) |
Composite is CPU-side either way: ~2 ms for a virtual background, ~10 ms for blur (a downscaled-plate blur, down from ~40 ms). On GPU, segmentation drops ~5× (~64 → ~12 ms) and frees a CPU core, so both modes clear the camera's 30 fps ceiling. Multiclass leaves room for 1080p; RVM at 1080p needs downsample_ratio=0.25 (in crates/pipeline/src/segmenter.rs).
/dev/video10doesn't appear. With the.debor AUR package, this is handled automatically: the install hook doesmodprobe -rfirst to drop any stale module, then reloads it with the right params. For source builds, run those two commands by hand (see step 2 above)./dev/video10is "busy" or "not a video capture device". That'sexclusive_caps=1doing its job: the device only exposes CAPTURE while LinuxBroadcast is producing frames. Real apps see it; rawffplaymay not until the producer is running.apt install v4l2loopback-dkmsfails on kernel 6.8+. You have the broken 0.12.7 — install ≥ 0.12.8 from upstream or your distro backports.- The window icon shows in the title bar but the taskbar entry stays generic on Wayland. First launch installs
~/.local/share/icons/.../LinuxBroadcast.pngand a matching.desktopfile; KDE may needkbuildsycoca6 --noincrementalonce or a re-login to refresh its sycoca cache. - The image looks soft / low frame rate. LinuxBroadcast auto-selects MJPEG for full-resolution 30 fps. A few cheap webcams ship a buggy hardware JPEG encoder; if the picture looks blocky, run with
LB_FORCE_RAW=1to force uncompressed capture (lower max framerate, but no JPEG artefacts). - The image looks stretched / proportions are wrong. Fixed in 0.3.0: the pipeline now crops to your output aspect ratio instead of stretching the camera into it. A non-16:9 resolution saved by an older version self-corrects (with a slightly narrower field of view); set width/height to a 16:9 pair like
1280×720for the full view.
crates/
pipeline/ # GStreamer graph + ort segmenter + compositor (no GUI deps)
app/ # eframe/egui GUI, theme, config, background library
models/ # bundled ONNX (multiclass / RVM)
assets/fonts/ # Inter + JetBrains Mono
packaging/
scripts/ # Debian postinst / prerm / postrm
aur/ # PKGBUILD, .SRCINFO, install hook for linux-broadcast-bin
*.desktop # menu entry / icon / modprobe.d & modules-load.d conffiles
DESIGN.md # design tokens (colour, spacing, type) + rationale
CLAUDE.md # in-depth dev notes (pipeline, gotchas, model details)
CLAUDE.md is the longer engineering map — pipeline plumbing, GStreamer settings that took a session to nail, model conventions, where to put new modes / models. Read it before non-trivial changes.
# Format
cargo fmt --all
# Lint (CI runs this with -D warnings)
cargo clippy --workspace --all-targets -- -D warnings
# Build
cargo build --workspace
# Headless smoke (uses the saved config; needs /dev/video10 + a real camera)
LB_HEADLESS=1 cargo run --release -p linux-broadcastThe toolchain is pinned via rust-toolchain.toml (current stable + rustfmt + clippy); rustfmt config lives in rustfmt.toml. CI in .github/workflows/ci.yml runs fmt --check, clippy -D warnings, and a release build on every push and PR.
For substantive changes:
- Stay clippy-clean. CI fails on warnings.
- Keep all pixel data in RGBA8; the mask in
f32. The pipeline assumes both end-to-end. - New segmentation models: add a
ModelKindvariant +segment_*function incrates/pipeline/src/segmenter.rs, bundle the ONNX, surface it in the GUI'sModeldropdown. CLAUDE.md has a step-by-step. - New background modes: add a
Backgroundvariant + branch in the compositor, plus aModeinapp/src/config.rsand a tab inui::sidebar_scene. Live changes flow over the existingCommand::SetBackgroundchannel — no graph rebuild.
GPL-3.0-or-later. See LICENSE for the full text.
This project bundles Robust Video Matting (models/rvm.onnx), released under GPL-3.0, which is what makes the entire binary GPL-3.0. The MediaPipe ONNX file in models/ is Apache-2.0; the bundled fonts in assets/fonts/ are SIL Open Font License 1.1. See models/README.md and assets/fonts/ for per-asset attribution.

