A traditional analog-style radio scanner, built on an RTL-SDR (or compatible)
USB dongle instead of dedicated scanner hardware. Pure Qt/C++ — no Python,
no external DSP framework, just Qt6 and librtlsdr.
Works with any RTL2832U-based dongle supported by librtlsdr — plain
RTL-SDR (V3/V4) and the Nooelec NESDR family (including the V5), which use
the same chipset and driver. Only one device is used at a time, by design.
- Grouped scanning. Frequencies within 2.4MHz of each other are captured together in a single wideband tune, and checked for squelch break simultaneously via a per-channel digital mixer/filter/decimator chain — much faster than physically retuning the dongle for every entry in the list, since one capture can cover many channels at once.
- FM / NFM / AM, with per-frequency squelch — either live auto-squelch (which self-adjusts: tightens if it detects chatter/static breaking through, loosens during long quiet stretches to try to catch weaker signals) or a fixed value from a one-shot Auto Tune noise-floor measurement.
- Mains-hum notch filter — cascaded 50/60Hz (+ harmonics) notch applied to demodulated audio, for hum coupled in electrically via USB power/ground rather than part of the received signal.
- Explore mode — sweep between two frequencies at a custom step, reusing the same grouped capture engine.
- Scanner-style LCD readout and a live dBFS signal strip chart, both decoupled from the scan/demod thread (their own repaint timers) so a busy display never slows down actual scanning. Toggle either on/off from the View menu.
- Activity log — one row per completed transmission (time, frequency, label, duration), across every channel in the currently-tuned capture group, not just whichever one is on screen.
- The frequency table highlights the active row during a live call, and supports Select All / Deselect All (respecting the current group filter).
- Detect / Connect / Disconnect device flow: auto-detects on launch; Detect reappears any time you're disconnected, so swapping to a different SDR is just Disconnect → Detect → pick from the list → Connect.
- Frequency list persists automatically (JSON, autoloads on startup), plus CSV/JSON export and JSON import, with free-form group tags for organizing and exporting subsets.
Pre-built packages are published on the
Releases page
for every tagged version (v*.*.*):
| OS | Package |
|---|---|
| Debian 13 (trixie) | sdr-scanner_<version>-1deb13_amd64.deb |
| Ubuntu 26.04 | sdr-scanner_<version>-1ub2604_amd64.deb |
| Fedora 44 | sdr-scanner-<version>-1.fed44.x86_64.rpm |
| Windows (64-bit) | sdr-scanner-<version>-win64.zip |
Each Linux package declares its own runtime dependencies (Qt6, librtlsdr,
etc.) automatically via dpkg-shlibdeps/RPM's auto-requires, so a normal
apt install ./sdr-scanner_*.deb or dnf install ./sdr-scanner-*.rpm
resolves everything from the distro's own repos — nothing to hand-install
first. The Windows zip is self-contained (Qt and librtlsdr DLLs included)
— unzip it anywhere and run sdr-scanner.exe. Each RTL-SDR dongle still
needs a one-time WinUSB driver bind via Zadig;
see lib/README.md for why.
macOS isn't packaged/built by CI; the source itself avoids Linux-specific APIs, so building from source may still work there, just untested.
# Debian 13 / Ubuntu 26.04
sudo apt install ./sdr-scanner_*.deb
# Fedora 44
sudo dnf install ./sdr-scanner-*.rpmSame as any other RTL-SDR tool: you may need udev rules / plugdev group
membership for non-root USB access, and the kernel's competing DVB driver
(dvb_usb_rtl28xxu) blacklisted if it grabs the device first.
Requires CMake 3.19+, a C++17 compiler, Qt6 (Widgets + Multimedia), and
librtlsdr.
Debian 13 / Ubuntu 26.04:
sudo apt install build-essential cmake qt6-base-dev qt6-multimedia-dev \
librtlsdr-dev libusb-1.0-0-devFedora 44:
sudo dnf install gcc-c++ cmake qt6-qtbase-devel qt6-qtmultimedia-devel \
rtl-sdr-devel libusb1-develThen:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -jRun: ./build/sdr-scanner.
Windows (MinGW):
librtlsdr and its headers are vendored under lib/ (see
lib/README.md for what's there and why), so nothing
needs to be installed for that part. Qt6 itself does, and it has to be the
MinGW build (Qt's MSVC builds use an incompatible ABI for a MinGW-built
app) — MinGW-w64 GCC 13.1.0 is the version this was built and tested
against, since it's the toolchain Qt's own official MinGW kit bundles, and
CMake's MinGW find_library logic needs that same kind of toolchain to
resolve lib/win64/librtlsdr.dll.a.
The easiest way to get a matching Qt6 + MinGW pair is
aqtinstall (pip install aqtinstall), which installs the same official binaries as the Qt online
installer without needing a Qt account:
python -m aqt install-qt windows desktop 6.9.3 win64_mingw -O C:\Qt -m qtmultimedia
python -m aqt install-tool windows desktop tools_mingw1310 -O C:\QtThen, with C:\Qt\Tools\mingw1310_64\bin and CMake/Ninja on PATH:
cmake -S . -B build-win -G Ninja -DCMAKE_BUILD_TYPE=Release `
-DCMAKE_PREFIX_PATH=C:\Qt\6.9.3\mingw_64
cmake --build build-win -jThe build automatically copies librtlsdr.dll next to the exe and runs
windeployqt to pull in the Qt DLLs, so build-win\sdr-scanner.exe is
runnable as-is. To produce a standalone folder you can zip up and hand to
someone else, cmake --install build-win --prefix dist instead (this is
what CI does to build the release zip). Each RTL-SDR dongle needs a
one-time driver rebind to WinUSB via Zadig first;
see lib/README.md for why.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCPACK_GENERATOR=DEB # or RPM
cmake --build build -j
cmake --build build --target package # or: (cd build && cpack)- Grouping: frequencies are greedily clustered into capture groups spanning at most ~2MHz (leaving margin inside the RTL-SDR's 2.4Msps capture window for filter roll-off), each tuned once and checked for activity on every member frequency from the same capture.
- Channelizer: per-channel complex NCO mixer + two-stage FIR decimation (2.4Msps → 240kHz → 48kHz), independent of RF offset within the group.
- Threading: the scan engine owns a dedicated
QThreadrunning librtlsdr's blocking async read loop; retuning always happens between streaming sessions (never from inside the read callback) to avoid I2C timeouts from interleaving tuner control commands with active USB bulk streaming. The LCD and signal chart poll a mutex-guarded snapshot on their own fixed-rate timers, fully decoupled from the scan thread.
- sdrtrunk (GPLv3) — no code was copied, but its FM/AM demodulator implementations were read early on to confirm the standard polar-discriminator and magnitude-detector algorithms used here. Given that reference, this project is licensed GPLv3 as well.
- librtlsdr (GPL-2.0+) — the RTL2832U driver this application links against at runtime.
GPLv3 — see LICENSE. This project links against librtlsdr
(GPL-2.0+), which requires a GPL-compatible license for the combined
distributed work.
