UDP Streaming

Send live radio samples as UDP packets and watch them arrive back in a spectrum analyzer.

Blocks 5
Category Networking
Revision 1
License MIT

Blocks used

UDP Receiver
Spectrum Analyzer
SDR
UDP Transmitter
Note

Overview

Getting radio samples out of CyberEther and into another program, or onto another computer, is the first step toward a remote receiver, a recording server, or a decoder written in another language. The User Datagram Protocol is the simplest way to do it. Each packet travels on its own, with no connection to open and no acknowledgment to wait for, so the sender never stalls because a reader fell behind.[1] At 2 MS/s, the complex float32 samples the graph works with add up to 16 MB/s, or about 10,900 packets every second.

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

Warning

This flowgraph requires native CyberEther, because the UDP blocks are not part of CyberEther Web.

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 UDP Transmitter sends each buffer as raw bytes, real part then imaginary part, with no header. 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 UDP header is only 8 bytes, and the protocol guarantees neither delivery nor order.[1] A datagram larger than a network frame is split in transit and lost whole if any piece goes missing, so senders are advised to stay below the path MTU.[2] The default Packet Size of 1,472 bytes fills the 1,500 bytes an Ethernet frame carries[3] once the 20-byte IPv4 header[4] and the UDP header are added. Each packet holds 184 whole samples, so a lost packet leaves a gap but never splits a sample.

Quantity Value
Samples per packet 184
Packets per buffer 357
Packets per second about 10,900
Bytes per packet with IPv4 and UDP headers 1,500

The UDP Receiver reads on a background thread into a ring buffer that holds four of its output buffers, or 131 ms of signal, so a whole burst of 357 packets always fits. Packet boundaries are ignored, and each cycle copies out one full buffer once enough bytes have arrived. 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, and Buffer Loss on the receiver should stay at 0.00%. Buffer Loss only counts samples the graph was too slow to read. A datagram the operating system drops never reaches the receiver, so it leaves no count, only a short gap in the stream that can show up as a brief streak in the waterfall. Dropped Packets on the transmitter counts only packets the operating system refused to send.

The one-way design shows when the link breaks. Changing Port on the UDP Transmitter freezes the display, but the transmitter keeps its full Throughput and Dropped Packets stays at 0, because nothing on a UDP path reports that nobody is listening. Restoring the port brings the display back at once, with nothing to reconnect.

For a deeper treatment of UDP and datagram sizing, see the sources below.

Going further

A few lines of Python outside CyberEther can read the same stream, which is the starting point for a program of your own. Set Port on the UDP Transmitter to 5001, which sends the stream to the script instead of the UDP Receiver, and run the code below with Python and NumPy. Once per second it prints the packet rate, the sample rate, and the average power. At the saved settings it should read about 10,900 packets per second and 2.00 MS/s, and a lower rate means datagrams were dropped before the script read them.

PYTHON
import socket
import time

import numpy as np

PORT = 5001

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024)
sock.bind(("127.0.0.1", PORT))

packets = 0
samples = 0
energy = 0.0
start = None

while True:
    x = np.frombuffer(sock.recv(65536), dtype=np.complex64)
    if start is None:
        start = time.monotonic()
    packets += 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"{packets / elapsed:,.0f} packets/s, {samples / elapsed / 1e6:.2f} MS/s, {level:.1f} dBFS")
        packets = 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. Packet Size accepts up to 65,507 bytes, but anything larger than a network frame is split in transit, so the default is the safer choice off this machine.

To reach another computer, set Address on the UDP Transmitter to that computer and Address on its UDP Receiver to 0.0.0.0. A multicast group address such as 239.1.1.1 on both ends sends one stream to every receiver that joins the group. A receiver on 0.0.0.0 accepts packets from anyone, and Filter Source with a Source Address limits it to one sender.

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

References

References

  1. J. Postel, "User datagram protocol," IETF, RFC 768, Aug. 1980. rfc-editor.org/rfc/rfc768 ↩ ↩2

  2. L. Eggert, G. Fairhurst, and G. Shepherd, "UDP usage guidelines," IETF, RFC 8085 (BCP 145), Mar. 2017. rfc-editor.org/rfc/rfc8085 ↩

  3. C. Hornig, "A standard for the transmission of IP datagrams over Ethernet networks," IETF, RFC 894, Apr. 1984. rfc-editor.org/rfc/rfc894 ↩

  4. J. Postel, "Internet protocol," IETF, RFC 791 (STD 5), Sep. 1981. rfc-editor.org/rfc/rfc791 ↩

CyberEther

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

Star on GitHub

© 2026 CyberEther. All rights reserved.

The final frontier!