Sitemap

Open Live: Building a Software Broadcast Mixer on Commodity GPU Hardware

9 min readJun 23, 2026

--

The broadcast switcher has been one of the last pieces of production infrastructure that resisted software-defined approaches. Dedicated hardware mixers from established vendors work well, but they come with costs that are hard to justify for many productions: proprietary hardware, locked-down upgrade paths, and workflows that assume you have a physical control surface in a physical room. Open Live takes a different approach. It is a fully software-based audio and video mixer built on open source components, running on standard GPU hardware, and controllable over a REST API or a Stream Deck.

This post explains how it works at a technical level: what runs under the hood, why WHEP matters for browser-based monitoring, and how the system handles production control without proprietary hardware.

Strom: the GStreamer engine underneath

Everything in Open Live is built on top of Strom, an open source Rust application that provides a web-based visual interface for building and managing GStreamer media pipelines (github.com/Eyevinn/strom).

GStreamer is a mature, well-tested media processing framework, but working with it directly means writing pipeline definitions by hand and managing element state through code. Strom abstracts this into a block-based model: each block corresponds to a GStreamer sub-pipeline (an input source, a video compositor, an audio mixer bus, an output sink), and blocks are wired together into flows. The backend is an Axum HTTP server in Rust; the frontend compiles to WASM via egui.

Press enter or click to view image in full size

When you tell Strom to activate a production, it constructs the underlying GStreamer pipeline, starts it, and manages state transitions. When you cut to a different source, it sends the appropriate commands to the vision mixer block inside the running pipeline. The REST API (/api/flows, /api/blocks, /api/whep-streams) is what both the Open Live API layer and external controllers talk to.

The Open Live system adds a Node.js / Fastify orchestration layer on top of Strom (github.com/Eyevinn/open-live). This layer manages productions (a named configuration of sources and templates), source registries, and persistent state in CouchDB. The React studio UI (github.com/Eyevinn/open-live-studio) connects to the Open Live API over REST and WebSocket and provides the production controller interface in a browser.

Press enter or click to view image in full size

Open Live Studio: the production controller in a browser

The studio UI (github.com/Eyevinn/open-live-studio) is a React 19 application built with Vite and TailwindCSS v4. It is the piece that makes Open Live feel like a broadcast control room — inside a browser tab.

Press enter or click to view image in full size

The main controller page is split into three functional areas. At the top sits the program preview: a 16:9 video tile showing the live WHEP output from the current production, with a status badge indicating connection state (LIVE, CONNECTING, ERROR). Below that is the transition panel — the T-bar equivalent — with a PGM row (currently on-air source, highlighted red) and a PVW row (preview selection, highlighted green). Cut and Auto buttons trigger the corresponding Strom commands. Transition type (Mix, Dip, Push) and duration (0.5s, 1s, 2s, or a custom value) are set here. Below the transition panel is the source bus: a horizontal strip of clickable source tiles, each showing the source name, status, and stream type. Keyboard shortcuts map Spacebar to CUT and Enter to AUTO, which is useful when a physical Stream Deck is not at hand.

There is also a dedicated tally page at /tally. It shows a grid of large colored blocks — red for PGM, green for PVW, gray for off — designed to be opened on an external monitor and visible across the room. No auth, no shell wrapper, just a full-page tally display.

The real-time state for all of this — tally signals, on-air status, overlay alpha — arrives over a WebSocket at /ws/productions/:id/controller. Sources and productions are polled from the REST API every five seconds. This split is intentional: REST handles the slower-changing configuration state; the WebSocket carries the low-latency production signals. The outbound message types cover the full control surface: CUT, TRANSITION, TAKE, SET_PVW, FTB, DSK_TOGGLE, GRAPHIC_ON, MACRO_EXEC.

State in the frontend is managed with Zustand and Immer. The production store holds the current production, source list, and activation status. The viewer store holds the live WHEP stream object that feeds the program preview video element.

WHEP for multiview and program preview

In traditional broadcast setups, multiview and program preview require dedicated SDI or HDMI outputs feeding physical monitors, or a dedicated SDI-over-IP infrastructure. Open Live uses WHEP (WebRTC-HTTP Egress Protocol) to deliver these streams to any browser on the network.

WHEP is an HTTP signaling layer on top of standard WebRTC. A client sends a POST request to a /whep/{endpoint_id} URL with an SDP offer; the server responds with an SDP answer; the browser opens a direct WebRTC media connection. No plugin, no SDK, no special application. Just a browser tab.

In Strom, the builtin.whep_output block wraps a whepserversink GStreamer element. Each output gets a stable proxy URL so the endpoint address does not change when the underlying ephemeral port is reallocated. The multiview in Open Live is a single WHEP stream — Strom stitches all the source tiles together server-side using its video compositor and delivers the result as one composited output. You get the full multiview in any browser tab at sub-500ms latency without the browser managing multiple independent connections.

Strom ships with built-in player pages: /player/whep for a single stream, /player/whep-streams for a multi-stream gallery view that auto-refreshes every five seconds as streams come and go. In practice this means a director's assistant can open a browser tab with all active sources visible, with sub-500ms glass-to-glass latency over a local network.

Codec-wise, H.264 is the default and the safest choice for broad browser support. VP8 and VP9 are also widely supported in WebRTC. H.265 is available in Strom but browser support for H.265 over WebRTC is not universal, so it is not a reliable choice for the “any browser, no plugin” use case. Audio goes over Opus, which is the WebRTC standard.

WHIP (WebRTC-HTTP Ingress Protocol) is the mirror of WHEP on the ingest side. Camera operators or remote contributors can push a WebRTC stream to a /whip/{endpoint_id} URL using any WHIP-capable encoder. Open Live also accepts MPEG-TS/SRT, NDI, AES67/Ravenna, Blackmagic DeckLink SDI, and EFP/SRT as input formats, so it works alongside existing infrastructure rather than requiring a full replacement.

The webrtc-player library

Handling WHEP in a browser involves more than a single WebRTC API call. You need to manage ICE candidate gathering, deal with SDP profile negotiation quirks, detect stream freezes, and reconnect gracefully when a peer connection drops. Eyevinn has packaged this logic as @eyevinn/webrtc-player (github.com/Eyevinn/webrtc-player), a media-server-independent WebRTC player library for browsers.

The library’s design is built around pluggable adapters. Each adapter handles the SDP offer/answer exchange for a specific protocol, because that signaling step is the part that varies between WebRTC media servers. The WHEP adapter covers both client-offer mode (browser sends offer, server answers) and server-offer mode (browser requests an offer, then answers it), switching automatically based on the server’s response codes. It negotiates audio and video independently, so it can fall back to video-only if the audio track is unsupported.

import { WebRTCPlayer } from '@eyevinn/webrtc-player';

const player = new WebRTCPlayer({
video: document.querySelector('video'),
type: 'whep',
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});

await player.load(new URL('https://your-strom-host/whep/program'));

player.on('no-media', () => console.log('stream frozen'));
player.on('media-recovered', () => console.log('stream recovered'));

The library emits no-media when no data has arrived for a configurable threshold (default 30 seconds), media-recovered when the stream resumes, and peer-connection-failed when the underlying RTCPeerConnection gives up. It attempts reconnection automatically, with a configurable attempt limit, resetting the counter on each successful connection.

Open Live Studio ships its own WhepClient class (src/lib/webrtc.ts) rather than importing the npm package. The reason is tighter integration with the studio's specific requirements: the ICE servers are proxied through the Open Live API backend (to avoid CORS and auth issues with remote Strom instances), the SDP negotiation strips Opus stereo parameters before sending to Strom for compatibility, and there is a health monitor that runs every second, checks video frame statistics, and re-attaches the track if the stream has been frozen for more than three seconds after packet loss. The inline implementation also includes test signal generation — SMPTE color bars and labelled colored canvas tiles with timecode — used for pre-production checks without live camera sources.

The webrtc-player library is the right starting point for anyone embedding a WHEP player in their own application. For a tightly integrated production controller like Open Live Studio, the inline approach gives the control surface ownership over exactly how reconnection and recovery behave.

GPU acceleration on standard hardware

A hardware switcher handles video compositing in dedicated silicon. Open Live does it on the GPU of a standard server. This changes the economics significantly: an NVIDIA GPU that costs a few hundred dollars can handle real-time 4K H.264/H.265 encoding via NVENC, and the same GPU handles video compositing through OpenGL with a CUDA-GL interop path that keeps frames in GPU memory throughout the pipeline.

The zero-copy aspect matters: without it, each frame in a composited multi-input layout would need to travel from GPU memory (where the decoded source lives) to CPU memory (where compositing happens) and back to GPU memory (for encoding). That round-trip is expensive at 1080p and worse at 4K. With CUDA-GL interop, the pipeline looks like this:

GPU: decode → GPU: composite (OpenGL) → GPU: encode (NVENC) → network

For headless server operation, Strom uses EGL device mode rather than requiring a display server:

docker run --gpus all \
-e GST_GL_WINDOW=egl-device \
-e GST_GL_PLATFORM=egl \
-e NVIDIA_DRIVER_CAPABILITIES=all \
-p 8080:8080 \
eyevinntechnology/strom:latest

Intel QSV and AMD VA-API are also supported, so the system is not locked to NVIDIA. Strom detects available hardware capabilities at startup and falls back to software processing if no GPU is present.

The audio signal chain

The audio mixer supports up to 32 input channels. Each channel runs through a configurable chain: gain control, high-pass filter, noise gate, compressor, four-band parametric EQ, pan, and fader. Processing uses a pure Rust port of the LSP plugins (github.com/srperens/lsp-plugins-rs), which are SIMD-accelerated in software — no GPU involved. On Linux, Strom can optionally use the original LV2 versions of the plugins instead; the Rust port is the default everywhere.

The master bus includes EBU R128 loudness metering, a mastering compressor, a four-band EQ, and a limiter. Auxiliary sends are configurable as pre- or post-fader for monitoring feeds. Subgroup buses are available for grouping related sources.

AES67/Ravenna audio is supported both as an input and output format, which means Open Live can operate in existing professional audio-over-IP environments without a separate audio matrix.

Control: API and Stream Deck

Because the entire system exposes a REST and WebSocket API, Bitfocus Companion maps hardware buttons to it without a dedicated driver. Companion is an open source application that works with Stream Deck, X-keys, and many other surfaces. The Open Live Companion module maps buttons to production control actions: cut, auto transition, mute/unmute channels, trigger DSK overlays, fade to black, AFL/PFL monitoring toggles.

The same API that Companion uses is available to any other system. A production automation system can pre-position sources and cue transitions by posting to the Open Live API. Monitoring infrastructure can subscribe to the WebSocket stream at /ws/productions/:id/controller to get real-time state updates.

Strom also includes a built-in MCP (Model Context Protocol) server at /api/mcp. This is primarily used for AI agent integration; it lets a Claude session query the current pipeline state, list active streams, and issue control commands. For standard production use the REST API is the primary interface.

A typical production flow looks like this: cameras feed MPEG-TS/SRT or EFP/SRT streams into Strom. The director’s browser shows a WHEP multiview with all sources plus the program output. The Stream Deck maps to cut and transition commands through Companion. The program output goes to a WHEP stream for any additional browser-based monitoring, and simultaneously to an SRT output for delivery to a CDN or downstream encoder.

Running it

Open Live is available as a hosted service at openlive.apps.osaas.io on Eyevinn’s Open Source Cloud, which provisions the Kubernetes infrastructure and GPU nodes.

The source code for all three repositories is MIT/Apache-2.0 licensed:

Documentation:

What is Open Source Cloud?

Managed open-source cloud for developers who want infrastructure, not DevOps

Open source as a service: run 183+ unmodified open-source services. Deploy your own code alongside them as a My App. Spin up Postgres, Valkey, and S3-compatible storage, push your Next.js app, done. No Kubernetes, no cloud config.

www.osaas.io

--

--

Eyevinn Technology
Eyevinn Technology

Written by Eyevinn Technology

We are consultants sharing the passion for the technology for a media consumer of the future.