WebSocket Streaming

Stream live radio samples through a WebSocket server and receive them back into the graph.

Blocks 5
Category Networking
Revision 1
License MIT

Blocks used

SDR
WebSocket Receiver
WebSocket Transmitter
Spectrum Analyzer
Note

Overview

Getting radio samples out of CyberEther and into another program, a web page, or another computer is the first step toward a remote receiver, a shared live view, or a decoder written in another language. A WebSocket suits that job. It gives browser applications two-way communication with a server over one connection, and it is designed to work over the usual web ports and through HTTP proxies.[1] At 2 MS/s, the complex float32 samples the graph works with add up to 16 MB/s, or about 30 messages of 512 KiB every second.

This flowgraph sends a live radio stream through a WebSocket Transmitter, which hosts a server on the loopback address 127.0.0.1, to a WebSocket Receiver that connects to it on the same machine, and shows the received samples on a spectrum analyzer. The same two blocks work across a network, or with any program that speaks WebSocket. Running it needs a USB radio supported by the SDR block, such as an RTL-SDR, and a free port 8765 on this machine.

Warning

This flowgraph requires native CyberEther, because the WebSocket Transmitter cannot host a server in the browser.

How it works

The SDR block delivers buffers of 8 batches of 8,192 complex samples, 512 KiB of data every 32.8 ms. The WebSocket Transmitter sends each buffer as one binary message of raw bytes, real part then imaginary part, with no header of its own. The type, shape, sample rate, and frequency stay with the sender, so the receiver is set to the same CF32 type and buffer shape, and the analyzer labels its axis in bins.

A WebSocket opens with an HTTP handshake and then frames each message with a short header. Frames from a server are not masked, and a payload over 65,535 bytes takes a 64-bit length, so each 512 KiB message here costs 10 bytes of framing.[1] Every connected client gets every buffer, and a client that joins late starts at the next one.

The connection runs over TCP, which resends lost data, delivers it in order, and lets a slow reader hold the sender back.[2] Nothing is lost on the way, so a slow client slows the whole graph instead, and the server drops a client that stops reading.

Quantity Value
Message size 524,288 bytes
Framing per message 10 bytes
Messages per second about 30.5
Largest message the transmitter sends 16 MiB

The WebSocket Receiver connects on a background thread and appends each binary message to a ring buffer that holds four of its output buffers, or 131 ms of signal. Every new connection starts from an empty ring, so each received buffer lines up with one sent message. Each cycle copies out one full buffer, and when the graph falls behind, new bytes overwrite the oldest and Buffer Loss counts them.

What to look for

The analyzer shows the received samples as a trace above a waterfall, just as it would when fed directly by the radio. Each station is a hump in the trace and a stripe in the waterfall, and the tuned frequency sits in the center bin. At 2 MS/s each of the 8,192 bins is about 244 Hz wide, so a 200 kHz FM channel spans about 820 bins. The trace averages about eight buffers, a quarter of a second, and the 512 waterfall rows cover about 2.1 seconds.

Throughput should read about 16 MB/s on both network blocks, Clients on the transmitter should read 1, and Buffer Loss on the receiver should stay at 0.00%. Throughput on the transmitter is summed over every client, so a second client doubles it.

The connection shows when the link breaks. Changing Port on the WebSocket Transmitter closes the server, Clients drops to 0, the display freezes, and the receiver logs a warning and retries twice a second. Restoring the port brings the stream back by itself, starting clean at the next buffer.

For a deeper treatment of WebSocket framing and TCP flow control, see the sources below.

Going further

A few lines of Python outside CyberEther can join the same server as a second client, which is the starting point for a program of your own. Run the code below with Python, NumPy, and the websockets package while the flowgraph runs. Clients on the transmitter goes to 2, and once per second the script prints the message rate, the sample rate, and the average power. At the saved settings it should read about 30.5 messages per second and 2.00 MS/s. The websockets package refuses messages over 1 MiB by default, so the code lifts that limit for larger buffers.

PYTHON
import time

import numpy as np
from websockets.sync.client import connect

URL = "ws://127.0.0.1:8765"

with connect(URL, max_size=None) as ws:
    messages = 0
    samples = 0
    energy = 0.0
    start = time.monotonic()
    for message in ws:
        x = np.frombuffer(message, dtype=np.complex64)
        messages += 1
        samples += x.size
        energy += float(np.sum(np.abs(x) ** 2))
        elapsed = time.monotonic() - start
        if elapsed >= 1.0:
            level = 10 * np.log10(energy / samples) if energy > 0 else float("-inf")
            print(f"{messages / elapsed:.1f} messages/s, {samples / elapsed / 1e6:.2f} MS/s, {level:.1f} dBFS")
            messages = 0
            samples = 0
            energy = 0.0
            start = time.monotonic()

Batches and Samples on the receiver are independent of the radio, since the stream carries no shape. Setting Samples to 2,048 gives the analyzer 2,048 bins, and raising Buffer Multiplier to 16 keeps the ring at four sender buffers. The transmitter refuses a buffer larger than 16 MiB, so very large Batches or Samples on the radio need a smaller setting.

To reach another computer, set Address on the WebSocket Transmitter to 0.0.0.0 and point a WebSocket Receiver there at ws:// followed by the address of this machine and :8765. The server is plain ws with no encryption or login, so anyone who can reach the port can read the stream. In Client mode the transmitter connects out to a relay at URL instead, which is how a browser running CyberEther Web sends samples, since a page cannot host a server.

The UDP Streaming entry sends the same samples as packets instead, the Spectrum Analyzer entry explains the display, and the block catalog covers every setting of both WebSocket blocks.

References

References

  1. I. Fette and A. Melnikov, "The WebSocket protocol," IETF, RFC 6455, Dec. 2011. rfc-editor.org/rfc/rfc6455 ↩ ↩2

  2. W. Eddy, Ed., "Transmission control protocol (TCP)," IETF, RFC 9293 (STD 7), Aug. 2022. rfc-editor.org/rfc/rfc9293 ↩

CyberEther

High-performance GPU-accelerated signal processing and visualization framework that runs anywhere.

Star on GitHub

© 2026 CyberEther. All rights reserved.

The final frontier!