DAB/DAB+ multicast streamer — modelled after dvblast, for digital radio.
Receives a DAB/DAB+ ensemble from an SDR device and streams each radio service as an individual MPEG-TS multicast stream (RTP or raw UDP, chosen per service). Each stream carries a single audio programme with full DVB PSI tables (PAT, PMT, SDT, EIT) and DLS (now-playing text) updates.
RTL-SDR (or compatible SDR)
│
▼
OFDM demodulator
(frequency sync, FFT, Viterbi)
│
▼
DAB multiplex decoder
(FIC → ensemble/service info)
(MSC → subchannel audio data)
│
▼
DAB+ superframe filter
(Reed-Solomon FEC, LATM audio)
│
▼
MPEG-TS packetiser
(PAT + PMT + SDT + EIT + audio PES)
│
▼
multicast RTP/UDP 239.x.x.x:port
One stream is sent per configured service. VLC, mplayer, ffmpeg, and IPTV clients can tune each address directly.
sudo apt install \
build-essential cmake \
librtlsdr-dev \
libfftw3-dev \
libfaad-dev \
libmpg123-dev \
libtwolame-dev \
libmp3lame-devOptional:
libairspy-dev— Airspy HF+ supportlibSoapySDR-dev— generic SDR support via SoapySDR
cmake -B build -S .
cmake --build build -j$(nproc)Optional build flags:
| Flag | Default | Description |
|---|---|---|
-DRTLSDR=ON |
ON | RTL-SDR USB dongle support |
-DAIRSPY=OFF |
OFF | Airspy HF+ support |
-DSOAPYSDR=OFF |
OFF | SoapySDR generic driver |
-DKISS_FFT=OFF |
OFF | Use KISS FFT instead of FFTW3 |
Scan a single frequency:
./dablast -f 202928000 --scanOr scan multiple frequencies in one pass using a frequency list file:
./dablast -l /etc/dab-Brisbane.freqs --scanExample /etc/dab-Brisbane.freqs:
197648000 # 8B
202928000 # 9A
204640000 # 9B
206352000 # 9C
208064000 # 9D
Or skip the frequency file entirely and sweep every channel in dablast's
own built-in table (Band III + Band L, 5A..13F/LA..LP — the same list
--list-channels prints):
./dablast --scan --all-channelsdablast will scan each frequency in turn, printing results to stdout and
progress/status to stderr. Frequencies with no DAB signal are skipped after
a 5 s sync timeout (vs. 30 s for a single -f FREQ scan, where a signal is
already expected) - a channel with no signal at all produces no other
output, so dablast prints a "Still waiting for signal..." heartbeat every
3 s so a multi-frequency sweep never looks indistinguishable from a hung
process. This makes scanning every DAB channel in Band III (all 38 of
them, e.g. via dablast --list-channels) take a few minutes in the
worst case rather than the better part of 20.
Every progress line also shows the global DAB channel designator (e.g.
"9A") alongside the frequency when it's a recognized one, e.g. dablast: tuning to 202928000 Hz (9A) ... - useful for matching a scan's progress
against a known channel list rather than memorising raw frequencies.
Output example (stderr):
Ensemble found. Collecting service list...
Ensemble: DAB+ Brisbane 1 (9A) EId 0x1006 202928000 Hz
Output example (stdout), one line per service as name:frequency:sid:codec,
where codec is dab (native MPEG-1/2 Layer II) or dab+ (HE-AACv2):
MMM 80s:202928000:0x12A5:dab+
HeartHits:202928000:0x12A7:dab+
Inspire Digital:202928000:0x12CD:dab
Coles CBD:202928000:0x1259:dab+
GOLD 80s:202928000:0x126D:dab+
Each line maps a service to a destination, with four mandatory, comma-separated fields:
# /etc/dablast-202928000.conf
# Format: ADDR:PORT,SID,CODEC,TRANSPORT
239.2.2.1:20000,0x12A5,passthrough,rtp # MMM 80s
239.2.2.2:20000,0x12A7,passthrough,rtp # HeartHits
239.2.2.3:20000,0x12CD,mp2,rtp # Inspire Digital, transcoded to MP2
239.2.2.4:20000,0x1259,passthrough,udp # Coles CBD, raw UDP instead of RTP
All four fields are mandatory — there are no defaults. ADDR:PORT is the
multicast destination (see "Full option reference" below for the exact
rules). Any malformed line is a hard error: dablast prints the offending
line and exits immediately rather than silently skipping it. Duplicate
multicast ADDR:PORT destinations are rejected the same way.
dablast -f 202928000 -c /etc/dablast-202928000.confTuning:
-f FREQ Centre frequency in Hz (e.g. 202928000)
-l FREQFILE File of frequencies to scan (one per line)
Hardware:
-a INDEX Adapter/device index (default: 0)
-F DRIVER Input driver: rtl_sdr (default), rtl_tcp, soapysdr
-g GAIN RF gain in tenths of dB, or -1 for AGC (default: -1)
Modes:
--scan Scan ensemble and print service list, then exit
--list-channels Print the built-in DAB channel table (Band III +
Band L, 5A..13F/LA..LP) as NAME:FREQ_HZ, then exit.
No device or frequency needed - useful for driving
a full-spectrum scan across every known channel
without needing a region-specific frequency file
the way DVB does.
--all-channels With --scan: scan every channel in that same
built-in table directly, instead of -f or -l - a
one-flag full-spectrum scan with no frequency
file needed at all.
-c FILE Stream config, one service per line, four mandatory
comma-separated fields: ADDR:PORT,SID,CODEC,TRANSPORT
ADDR:PORT is the multicast destination.
CODEC:
passthrough HE-AACv2 for DAB+, MP2 for DAB, exactly as
broadcast. No decode/encode work at all.
mp2 Transcode to MPEG-1/2 Audio Layer II; DAB
sources are already MP2 and pass through
untouched. Costs a decode+encode pass per
DAB+ service and a lossy re-encoding
generation loss, in exchange for maximum
client compatibility (see "Audio quality"
below).
mp3 Transcode to MPEG-1/2 Audio Layer III (via
libmp3lame), VBR joint stereo, ~80kbps
average. Always a decode+encode pass -
DAB itself has no Layer III mode, so
there's no untouched-passthrough case the
way mp2 has for native DAB sources.
TRANSPORT:
rtp RFC 2250 RTP header + MPEG-TS to the
multicast group. Play with rtp://ADDR:PORT
udp Raw MPEG-TS, no RTP header, to the
multicast group. Play with udp://ADDR:PORT
Duplicate multicast ADDR:PORT destinations are rejected at
parse time. Any malformed line is a hard error: dablast
prints it and exits.
Other:
-h Show this help
Run one instance per DAB ensemble (frequency). Each instance uses one SDR device:
dablast -f 197648000 -a 0 -c /etc/dablast-197648000.conf &
dablast -f 202928000 -a 1 -c /etc/dablast-202928000.conf &# VLC — a service configured with the rtp transport
vlc rtp://@239.2.2.1:20000
# VLC — a service configured with the udp transport
vlc udp://@239.2.2.4:20000Or add the multicast addresses to any IPTV playlist (M3U), using the URL scheme that matches the transport column configured for that service.
With the passthrough codec, dablast passes audio through from the DAB
multiplex without re-encoding — the audio quality is exactly what
the broadcaster transmits.
| Service type | Codec | What you should hear |
|---|---|---|
| DAB | MPEG-1 Audio Layer II (MP2) | Stereo, 48 kHz — universally supported |
| DAB+ | HE-AACv2 (SBR + Parametric Stereo) | Stereo, 48 kHz — requires capable decoder |
DAB+ requires a specific decoder to play at full quality. Here is what HE-AACv2 actually does and why it can go wrong:
- The broadcast transmits a mono AAC core at 24 kHz — this is the base layer
- SBR (Spectral Band Replication) reconstructs the upper frequencies, bringing the output up to 48 kHz
- Parametric Stereo (PS) reconstructs the stereo image from the mono core, giving full left/right stereo
A decoder that handles all three layers correctly will output full 48 kHz stereo. A decoder that only reads the base layer will output 24 kHz mono — noticeably thin and low quality.
Desktop VLC works correctly because it uses FAAD2, a decoder specifically built for DAB+ that handles 960-sample AAC frames and the full HE-AACv2 chain.
Many other players (Android apps, hardware IPTV boxes, some media servers) use hardware AAC decoders or general-purpose software decoders that do not support the 960-sample frame size used by DAB+. These will fall back to the 24 kHz mono base layer regardless of how the stream is signalled. This is a decoder limitation, not a problem with the stream itself — the stream content is correct.
To sidestep the DAB+ decoder-compatibility problem above, dablast can decode DAB+ (HE-AACv2) audio and re-encode it to plain MP2 — a format every player, hardware IPTV box, and Android app already decodes correctly at full quality. Choose this per service via the codec column in the config file:
239.2.2.3:20000,0x12CD,mp2,rtp # Inspire Digital, transcoded to MP2
| Codec column | Output codec | DAB (native MP2) services | DAB+ (HE-AACv2) services |
|---|---|---|---|
passthrough |
native | MP2 passthrough | HE-AACv2 passthrough |
mp2 |
MP2 | passthrough (already MP2, untouched) | decoded + re-encoded to MP2 |
mp3 |
MP3 | decoded + re-encoded to MP3 | decoded + re-encoded to MP3 |
Trade-offs of mp2 versus passthrough:
- CPU cost: DAB+ services now require a real decode+encode per service instead of a byte copy. Fine for a handful of services on a modern PC; budget accordingly on something like a Raspberry Pi — only enable transcoding on the services that actually need it, since each transcoded service adds real-time CPU load and too many at once can starve the OFDM demodulator and cause dropouts.
- Quality: re-encoding DAB+ audio is a second lossy generation (AAC → PCM → MP2), on top of whatever the broadcaster already lost encoding to AAC. It fixes the 24 kHz-mono-fallback problem, but isn't bit-identical to the broadcast.
- Compatibility gain: MP2 output plays at full quality (48 kHz, stereo) on literally anything, with no dependence on the client having a DAB+-aware AAC decoder.
There's no way to transcode a DAB (MP2) source up to HE-AACv2 — dablast
only has an MP2 encoder, not an AAC one — so a DAB service configured with
mp2 is always a passthrough. This is a deliberate scope limit rather than
a gap: nobody has needed it, and a compliant HE-AACv2 encoder is a real
licensing/dependency burden to carry for a corner case.
mp3 behaves like mp2 (same decode step for DAB+, same untouched-vs-
transcode split doesn't apply — MP3 isn't a DAB native format at all, so
every service, DAB or DAB+, goes through a decode+encode pass) but
targets MPEG-1/2 Audio Layer III via libmp3lame instead, VBR joint stereo
at roughly 80kbps average:
239.2.2.5:20000,0x126D,mp3,rtp # GOLD 80s, transcoded to MP3
Same CPU-cost caveat as mp2 applies — budget accordingly, and only
transcode the services that actually need it. Choose mp3 over mp2 for
smaller output at a given perceptual quality (Layer III's usual advantage
over Layer II); choose mp2 where the client specifically expects it,
since it's the more universally-supported IPTV/broadcast format of the two.
dablast is multicast-only — the transport column just picks how each service's packets are framed on the wire:
| Transport column | Wire format | Play with |
|---|---|---|
rtp |
12-byte RTP header (RFC 2250, PT=33) + MPEG-TS | rtp://ADDR:PORT |
udp |
Raw MPEG-TS, no header | udp://ADDR:PORT |
239.2.2.4:20000,0x1259,passthrough,udp # Coles CBD, raw UDP instead of RTP
Unicast delivery, if you need it, is a job for an external UDP-to-TCP proxy sitting in front of the multicast stream — not something dablast does itself.
- RTL-SDR — any RTL2832U-based USB dongle (default, tested)
The following drivers are included from the welle.io backend and should work in principle, but have not been tested with dablast:
- Airspy HF+ — compile with
-DAIRSPY=ON - SoapySDR — any SoapySDR-supported device, compile with
-DSOAPYSDR=ON - rtl_tcp — network-attached RTL-SDR via rtl_tcp server, use
-F rtl_tcp - Raw IQ file — for testing, use
-F rawfile
Copyright (C) 2026 Paul Stanley paul.stanley@tutanota.com
GNU General Public License v3.0 or later — see LICENSE.md.
This project incorporates code from welle.io, SDR-J, DABlin, and third-party libraries. See LICENSE.md for details.