Fixing OpenWebRX+'s DAB+ crackle: a sample rate bug hiding behind a dozen red herrings

31 August 2026 · 3 min read

If you've tried listening to DAB on OpenWebRX+ and found it plays sped-up with dropouts on some stations, this is for you. Short version: it's a known bug, it's fixable tonight, and the fix is below. Long version, if you want the diagnostic path: keep reading.

The symptom

Tuned into a UK DAB multiplex (Swansea SW Wales, 223.936 MHz, RTL-SDR Blog V4). FIC decoded fine — station list, ensemble label, everything looked healthy. Audio played back sped up, distorted, dropping out — consistently, on every browser tested, at every gain setting tried.

What it wasn't

Before finding the actual cause, I ruled out, in order:

  • Gain/AGC — tested manual gain across the full range (0 to 55, plus hardware AGC). No setting produced clean audio.
  • RTL-SDR frequency drift — instrumented the AFC correction loop directly and confirmed it was stable, well under 1ppm. Not a calibration issue.
  • CPU frequency scaling — the box was running its CPU governor at powersave, capped well below rated clock. Switching to performance made no measurable difference to the artifact rate.
  • USB/RTL-SDR buffer depthrtl_connector's buffer count is hardcoded low in its source. Swapped it for an rtl_tcp-backed source with much deeper buffering. Decode went to zero FIC/CRC errors — genuinely clean RF chain — but the audio artifact was unchanged. This was the key result: it proved the bug was downstream of decode, not an RF/timing problem.
  • ADPCM websocket compression — disabled it to rule out compression-related glitching. Made things audibly worse, which ruled out compression as the cause rather than confirming it.

What it was

The station was broadcasting HE-AAC v2, 32 kHz Stereo — a completely normal, common configuration for UK DAB+. But OpenWebRX+'s DAB decoder chain declares a hardcoded output rate of 48000 Hz to the rest of its audio pipeline, with no actual resampling step to make that true. Every downstream buffer, every resampling ratio calculation, assumes 48 kHz input. When the real data arrives at 32 kHz, everything downstream is working from a false premise — hence the speed/pitch distortion and dropouts.

This turned out to already be a filed, open issue upstream: jketterl/openwebrx#384, described almost word-for-word — "32 kHz stations play sped up and then drop out" — and explicitly noted as common in the UK. A fix had also already been proposed in PR #419, refined by a follow-up contributor into a working patch.

The fix

The core idea: don't trust a declared rate — read the real one. dablin (the DAB+ decoder OpenWebRX+ shells out to) can output WAV instead of raw PCM (-w instead of -p), and a WAV header carries the actual sample rate and channel count. Pipe that through ffmpeg to force a real resample to 48 kHz, and the mismatch disappears for any station regardless of its native rate.

def _buildArgs(self):
    dab_cmd = (
        "dablin -w -s {:#06x} | mbuffer -q -m 4M | "
        "ffmpeg -v error -probesize 4096 -analyzeduration 0 -f wav -i pipe:0 "
        "-af aresample=48000 -f f32le -ar 48000 -ac 2 pipe:1"
    ).format(self.serviceId)
    return ["bash", "-c", dab_cmd]

This replaces the _buildArgs() method of the DablinModule class that wraps dablin (in csdr/module/toolbox.py on the luarvique/openwebrx fork — path may differ slightly by version). Needs ffmpeg and mbuffer installed (apt install ffmpeg mbuffer on Debian/Ubuntu).

Confirmed clean audio afterward on the same station, and on others in the multiplex at different bitrates. Remaining glitches on the odd station were ordinary weak-signal artifacts on lower-error-protection sub-channels — not the systematic issue this fix addresses.

The actual lesson

Nearly everything I tested was a reasonable hypothesis given the symptom, and nearly all of it was wrong. The result that actually mattered was the one that isolated where the problem lived (before vs. after decode) rather than what it looked like (crackle, speed, dropouts all sound similar from several different root causes). Once the RF/decode chain was proven clean, the remaining search space was small enough that a five-minute search for existing bug reports found it immediately — something worth doing before several hours of gain sliders and frequency correction, in hindsight, but the negative results weren't wasted: they're exactly what made the eventual GitHub search specific enough to land on the right issue.