Evaluated 7 approaches for multi-channel CB radio monitoring on Raspberry Pi 400. Top candidates: RTLSDR-Airband (existing software, quick setup) and custom C with librtlsdr + liquid-dsp (~950 LOC, meets all requirements exactly). Ruled out SDRTrunk, SDRAngel, GNU Radio, rtl_fm, and Go-based approaches. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
364 lines
17 KiB
Markdown
364 lines
17 KiB
Markdown
# Research: SDR Scanner & Recorder — Software Options
|
||
|
||
> Compiled 2026-03-17
|
||
> Status: Complete — ready for architecture decision
|
||
|
||
## Executive Summary
|
||
|
||
Five approaches were evaluated for monitoring 3-4 CB radio frequencies (27 MHz band, NFM, EU region) simultaneously with an RTL-SDR dongle on a headless Raspberry Pi 400. The top two candidates are:
|
||
|
||
1. **RTLSDR-Airband** — existing production software that does almost exactly what we need, with minimal setup. Best for validating the RF setup quickly.
|
||
2. **Custom C with librtlsdr + liquid-dsp** — lightweight, fully tailored to requirements, ~800-1200 lines of C. Best for precise control over hang time, WAV output, and YAML config.
|
||
|
||
The recommended path is: **start with RTLSDR-Airband to validate the RF chain**, then build custom if its limitations (MP3 output, limited hang time control) are blockers.
|
||
|
||
---
|
||
|
||
## Options Evaluated
|
||
|
||
| Option | Simultaneous Channels | Squelch | WAV Output | Headless Pi 400 | Config File | Complexity |
|
||
| :--- | :---: | :---: | :---: | :---: | :---: | :--- |
|
||
| **RTLSDR-Airband** | Yes | Yes (auto) | No (MP3) | Yes | Yes (libconfig) | Install + configure |
|
||
| **Custom C (librtlsdr + liquid-dsp)** | Yes | Yes | Yes | Yes | Yes (YAML) | ~800-1200 LOC |
|
||
| **GNU Radio + Python** | Yes | Yes | Partial | Yes | Custom wrapper | Medium-High |
|
||
| **SDRAngel (server)** | Yes | Partial | Yes | Yes | REST API only | High |
|
||
| **SDRTrunk** | Yes | Yes | Yes | Marginal | XML + GUI | Over-engineered |
|
||
| **rtl_fm** | No (sequential) | Yes | Via pipe | Yes | CLI flags | Low |
|
||
|
||
---
|
||
|
||
## 1. RTLSDR-Airband — Best Existing Software
|
||
|
||
**Repository:** [github.com/rtl-airband/RTLSDR-Airband](https://github.com/rtl-airband/RTLSDR-Airband) — 902 stars, GPL-2.0, actively maintained (v5.1.1, March 2025)
|
||
|
||
**What it does:** FFT-based multi-channel AM/NFM demodulator designed for narrowband radio monitoring. Splits a wideband IQ capture into sub-channels, demodulates each independently, and records to files with squelch gating.
|
||
|
||
### Fit for Requirements
|
||
|
||
| Requirement | Status | Notes |
|
||
| :--- | :---: | :--- |
|
||
| FR-001: Simultaneous monitoring | Yes | Up to 8 channels per dongle in multichannel mode |
|
||
| FR-002: NFM demodulation | Yes | Build with `-DNFM=ON` |
|
||
| FR-003: Squelch-based detection | Yes | Auto-squelch (noise floor + ~10 dB) or manual threshold |
|
||
| FR-004: WAV recording | **No** | Outputs MP3. WAV would require source modification or post-processing with ffmpeg |
|
||
| FR-005: Hang time / conversation grouping | Partial | `split_on_transmission = true` splits per-transmission, but hang time granularity is limited |
|
||
| FR-006: YAML configuration | **No** | Uses libconfig format (similar structure, different syntax) |
|
||
| FR-007: Console logging | Yes | Logs to stdout/syslog |
|
||
|
||
### Strengths
|
||
|
||
- **Production-grade and battle-tested** — used widely for airband monitoring
|
||
- **Extremely low CPU usage** — ~15% on a Pi 3 for one dongle. 3-4 channels on Pi 400 would be trivial
|
||
- **FFT-based channelizer** — computational cost is nearly constant regardless of channel count
|
||
- **ARM NEON optimized** — SIMD on ARMv7+, GPU-accelerated FFT on Pi 2/3
|
||
- **Simple config file** — declarative channel definitions with per-channel squelch and output settings
|
||
- **Immediate results** — install, configure, run
|
||
|
||
### Limitations
|
||
|
||
- **MP3 output only** — no native WAV support. MP3 encoding adds some CPU overhead but is negligible for 3-4 channels
|
||
- **Designed for VHF airband** — 27 MHz is outside its typical use case. Should work since it just processes whatever the RTL-SDR captures, but needs testing
|
||
- **libconfig syntax** — not YAML, but functionally equivalent
|
||
- **Limited hang time control** — `split_on_transmission` mode creates separate files per transmission; configurable silence duration exists but may not match the exact "keep recording for N seconds" behavior in FR-005
|
||
|
||
### Example Configuration for CB
|
||
|
||
```
|
||
devices: (
|
||
{
|
||
type = "rtlsdr";
|
||
index = 0;
|
||
gain = 28;
|
||
centerfreq = 27185000;
|
||
mode = "multichannel";
|
||
channels: (
|
||
{
|
||
freq = 27065000;
|
||
modulation = "nfm";
|
||
outputs: ({
|
||
type = "file";
|
||
directory = "/home/pi/recordings";
|
||
filename_template = "CB_27065";
|
||
split_on_transmission = true;
|
||
include_freq = true;
|
||
});
|
||
},
|
||
{
|
||
freq = 27185000;
|
||
modulation = "nfm";
|
||
outputs: ({
|
||
type = "file";
|
||
directory = "/home/pi/recordings";
|
||
filename_template = "CB_27185";
|
||
split_on_transmission = true;
|
||
include_freq = true;
|
||
});
|
||
}
|
||
// ... more channels ...
|
||
);
|
||
}
|
||
);
|
||
```
|
||
|
||
### Verdict
|
||
|
||
**Best starting point.** Gets you from zero to working recordings with minimal effort. Use it to validate the RF setup (antenna, gain, squelch levels, 27 MHz reception quality) before committing to a custom build. If MP3 output and limited hang time control are acceptable, this may be the final solution.
|
||
|
||
---
|
||
|
||
## 2. Custom C with librtlsdr + liquid-dsp — Best Custom Build
|
||
|
||
A lightweight purpose-built application that does exactly what the requirements specify and nothing more.
|
||
|
||
### Architecture
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ RTL-SDR Dongle (centered on 27.185 MHz, 960 kHz) │
|
||
└──────────────────────┬───────────────────────────────┘
|
||
│ IQ stream (uint8 pairs)
|
||
▼
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ Thread 1: rtlsdr_read_async() → ring buffer │
|
||
└──────────────────────┬───────────────────────────────┘
|
||
│
|
||
┌────────────┼────────────┐
|
||
▼ ▼ ▼
|
||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||
│ Channel 1│ │ Channel 2│ │ Channel N│ Thread 2: DSP processing
|
||
│ │ │ │ │ │
|
||
│ NCO mix │ │ NCO mix │ │ NCO mix │ nco_crcf_mix_down()
|
||
│ FIR+dec │ │ FIR+dec │ │ FIR+dec │ firfilt_crcf + decimate
|
||
│ FM demod │ │ FM demod │ │ FM demod │ freqdem_demodulate_block()
|
||
│ Squelch │ │ Squelch │ │ Squelch │ agc_crcf (6-state machine)
|
||
│ Hang time│ │ Hang time│ │ Hang time│ Custom timer logic
|
||
│ WAV write│ │ WAV write│ │ WAV write│ PCM file I/O
|
||
└──────────┘ └──────────┘ └──────────┘
|
||
```
|
||
|
||
### Fit for Requirements
|
||
|
||
| Requirement | Status | Notes |
|
||
| :--- | :---: | :--- |
|
||
| FR-001: Simultaneous monitoring | Yes | Parallel DSP pipelines from single IQ stream |
|
||
| FR-002: NFM demodulation | Yes | liquid-dsp `freqdem` |
|
||
| FR-003: Squelch-based detection | Yes | liquid-dsp `agc_crcf` squelch with 6-state machine |
|
||
| FR-004: WAV recording | Yes | Direct PCM WAV write |
|
||
| FR-005: Hang time | Yes | Custom timer on top of squelch state machine |
|
||
| FR-006: YAML configuration | Yes | Via libyaml or similar lightweight parser |
|
||
| FR-007: Console logging | Yes | printf/fprintf to stdout |
|
||
|
||
### Key Libraries
|
||
|
||
**librtlsdr:**
|
||
- Async read API delivers IQ data via callbacks in 256 KB buffers
|
||
- IQ format: 8-bit unsigned interleaved pairs (I, Q, I, Q...)
|
||
- Sample rates: 225-300 kHz or 900 kHz–3.2 MHz
|
||
- 27 MHz is within normal R820T2 tuner range — no direct sampling needed
|
||
|
||
**liquid-dsp** ([github.com/jgaeddert/liquid-dsp](https://github.com/jgaeddert/liquid-dsp)):
|
||
- 2.2k stars, MIT license, actively maintained (v1.7.0, Feb 2025)
|
||
- Zero dependencies beyond standard C + math
|
||
- Provides every DSP primitive needed:
|
||
|
||
| Need | liquid-dsp Object | Function |
|
||
| :--- | :--- | :--- |
|
||
| Frequency translation | `nco_crcf` | Mix channel to baseband |
|
||
| Low-pass filter | `firfilt_crcf` | Channel isolation before decimation |
|
||
| Decimation | `msresamp_crcf` | Reduce sample rate to audio |
|
||
| FM demodulation | `freqdem` | Phase discriminator |
|
||
| Squelch | `agc_crcf` | 6-state squelch machine (RISE/SIGNALHI/FALL/SIGNALLO/TIMEOUT/ENABLED) |
|
||
|
||
### Performance Estimate (Pi 400)
|
||
|
||
The Cortex-A72 @ 1.8 GHz with NEON does ~7 GFLOPS. Total DSP load for 4 channels:
|
||
|
||
| Operation | Per channel | 4 channels |
|
||
| :--- | :--- | :--- |
|
||
| NCO mix-down (960 kHz) | ~2 MFLOPS | ~8 MFLOPS |
|
||
| FIR filter + decimate (64 taps, 960→12.5 kHz) | ~60 MFLOPS | ~240 MFLOPS |
|
||
| FM demod (12.5 kHz) | negligible | negligible |
|
||
| Squelch + WAV I/O | negligible | negligible |
|
||
| **Total** | | **~250 MFLOPS (~3.5% of Pi 400 capacity)** |
|
||
|
||
This leaves massive headroom. Even 10 channels would be fine.
|
||
|
||
### Estimated Scope
|
||
|
||
| Component | Lines of C |
|
||
| :--- | :--- |
|
||
| Main + config parsing (YAML) | ~150 |
|
||
| RTL-SDR setup + async callback + ring buffer | ~150 |
|
||
| Per-channel DSP pipeline | ~200 |
|
||
| Squelch state machine + hang time | ~100 |
|
||
| WAV file writer (headers, open/close) | ~150 |
|
||
| Threading + signal handling + cleanup | ~100 |
|
||
| Configuration structs + channel definitions | ~100 |
|
||
| **Total** | **~950 lines** |
|
||
|
||
**Dependencies:** librtlsdr, liquid-dsp, pthreads, libyaml (or a minimal YAML parser). No GNU Radio, no Python, no heavyweight frameworks.
|
||
|
||
### Reference Projects
|
||
|
||
- **sdrx** ([github.com/johanhedin/sdrx](https://github.com/johanhedin/sdrx)) — Small C++ multichannel AM receiver, tested on Pi 4 and Pi Zero 2 W. Uses per-channel NCO+filter+decimate approach. Good architectural reference.
|
||
- **rtl_fm** ([osmocom/rtl-sdr](https://github.com/osmocom/rtl-sdr/blob/master/src/rtl_fm.c)) — ~1500 lines of C, complete single-channel FM demod pipeline. Reference for FM discriminator implementation (all integer arithmetic).
|
||
- **liquid-dsp examples** — `agc_crcf_squelch_example.c` demonstrates the squelch state machine. Blog post by Andres Vahter shows complete FM demod pipeline with liquid-dsp.
|
||
|
||
### Verdict
|
||
|
||
**Best option if RTLSDR-Airband's limitations are blockers.** Fully meets all 7 functional requirements. Lightweight, fast, no unnecessary dependencies. The ~950 lines of C is a manageable scope, and liquid-dsp does the heavy DSP lifting. The main cost is development time (2-4 days for experienced C, longer if learning liquid-dsp).
|
||
|
||
---
|
||
|
||
## 3. GNU Radio + Python — Viable but Heavy
|
||
|
||
**Package:** `apt-get install gnuradio` on Raspberry Pi OS 64-bit (v3.10.5.1 in Bookworm)
|
||
|
||
### How It Would Work
|
||
|
||
A Python script builds a GNU Radio flowgraph:
|
||
1. **OsmoSDR Source** → captures IQ at ~1 MHz centered on 27.185 MHz
|
||
2. **N × Frequency Xlating FIR Filter** → isolates each CB channel, decimates to ~20 kHz
|
||
3. **N × NBFM Receive** → demodulates NFM
|
||
4. **N × Power Squelch** → detects activity, emits `squelch_sob`/`squelch_eob` stream tags
|
||
5. **Custom Embedded Python Block** → implements hang time logic, converts to burst tags
|
||
6. **N × Tagged File Sink** → writes WAV file per transmission
|
||
|
||
### Strengths
|
||
|
||
- Well-established multi-channel NFM pattern
|
||
- Power Squelch block with stream tags
|
||
- Runs headless, no GUI needed
|
||
- Existing reference project: **ham2mon** (280 stars, does multi-channel NBFM scanning)
|
||
|
||
### Limitations
|
||
|
||
- **Heavy dependency** — GNU Radio is a large framework (~500 MB installed)
|
||
- **Python required** for custom logic (hang time, file naming, YAML config) — user prefers Go or C/C++
|
||
- **Performance on Pi 400:** 3-4 channels at 1 MHz would use ~30-50% CPU (estimated). Feasible but much heavier than the custom C approach (~3.5% CPU)
|
||
- **ham2mon on Pi:** Users reported Pi 3B was too slow. Pi 400 should be OK for 3-4 channels but not with huge margin
|
||
- **No built-in hang time** — requires custom Python block
|
||
- **WAV file naming** — requires custom Python wrapper for `{frequency}_{timestamp}.wav` format
|
||
- **Squelch-to-burst conversion** — needs the third-party [squelch-to-burst](https://github.com/SDRWaveRunner/squelch-to-burst) embedded block
|
||
|
||
### Verdict
|
||
|
||
**Viable middle ground** between using existing software and building from scratch. But it combines the downsides of both: you still need significant custom Python code, AND you take on the weight of the full GNU Radio framework. Only recommended if you're already familiar with GNU Radio.
|
||
|
||
---
|
||
|
||
## 4. SDRAngel Server — Capable but Over-engineered
|
||
|
||
**Repository:** [github.com/f4exb/sdrangel](https://github.com/f4exb/sdrangel)
|
||
|
||
### Key Findings
|
||
|
||
- Has a headless server binary (`sdrangelsrv`) with full REST API — no GPU needed
|
||
- Supports multi-channel NFM from a single RTL-SDR
|
||
- Has a **Frequency Scanner** plugin for detecting activity
|
||
- Has a **Demod Analyzer** for recording demodulated audio as WAV with silence-based start/stop
|
||
- Runs on Pi ARM64 (Docker images available)
|
||
- **User's experience:** GUI version was very heavy on GPU
|
||
|
||
### Limitations
|
||
|
||
- **Demod Analyzer connects to one demodulator per instance** — simultaneous recording of multiple channels requires multiple instances, heavy on Pi
|
||
- **No simple config file** — all configuration via REST API or preset loading
|
||
- **Complex architecture** — massive C++/Qt codebase, overkill for 3-4 NFM channels
|
||
- **First-run FFTW wisdom generation** takes very long on Pi hardware
|
||
- **Initial setup requires GUI** (or manual XML editing) to create presets, then transfer to headless Pi
|
||
|
||
### Verdict
|
||
|
||
**Not recommended.** Extremely powerful but far too complex for this use case. The REST API approach adds unnecessary friction for what should be a "configure once, run forever" system.
|
||
|
||
---
|
||
|
||
## 5. SDRTrunk — Not a Fit
|
||
|
||
**Repository:** [github.com/DSheirer/sdrtrunk](https://github.com/DSheirer/sdrtrunk)
|
||
|
||
### Why Not
|
||
|
||
- **Designed for trunked radio systems** (P25, DMR) — conventional NFM monitoring means navigating trunked-radio concepts (aliases, talkgroups, playlists)
|
||
- **Java/JavaFX** — recommends 8-16 GB RAM; Pi 400 has 4 GB
|
||
- **No native headless mode** — workarounds exist but initial config requires GUI
|
||
- **27 MHz at edge of RTL-SDR tuner range** — SDRTrunk's direct sampling support is poorly documented
|
||
- **Over-engineered** for simple conventional NFM monitoring
|
||
|
||
### Verdict
|
||
|
||
**Ruled out.** Wrong tool for the job.
|
||
|
||
---
|
||
|
||
## 6. rtl_fm — Too Limited
|
||
|
||
**What it is:** Command-line FM demodulator included with librtlsdr.
|
||
|
||
### Why Not
|
||
|
||
- **Sequential scanning only** — hops between frequencies one at a time, cannot demodulate multiple channels simultaneously
|
||
- With only 3-4 channels this is more tolerable (less missed activity), but still not ideal
|
||
- Could work as a quick-and-dirty solution: `rtl_fm -M fm -f freq1 -f freq2 -f freq3 -s 12k -l 50 | sox ...`
|
||
|
||
### Verdict
|
||
|
||
**Fallback only.** Useful for initial RF testing (does my antenna receive 27 MHz? what's a good squelch level?) but does not meet the simultaneous monitoring requirement.
|
||
|
||
---
|
||
|
||
## 7. Other Tools Considered
|
||
|
||
| Tool | Why Not |
|
||
| :--- | :--- |
|
||
| **MultiFM (tsl-sdr)** | ARM NEON optimized channelizer, but no squelch or recording — needs significant wrapper scripting. Better to use liquid-dsp directly in a custom build. |
|
||
| **csdr** | Pipe-based DSP toolkit. Flexible but fragile for production use. Running parallel pipelines via `tee` is inefficient. |
|
||
| **rtl_power** | Spectrum power logger only — no demodulation or recording. Would need a second dongle. |
|
||
| **multimon-ng** | Digital mode decoder (POCSAG, DTMF), not an analog FM demodulator. Irrelevant. |
|
||
| **SDR#** | Windows only. Ruled out. |
|
||
| **Go-based custom build** | Go RTL-SDR bindings unmaintained (last commit 2016). Go DSP libraries all WIP/abandoned. Would end up wrapping C libraries via cgo — adds complexity without benefit. |
|
||
|
||
---
|
||
|
||
## Recommended Path
|
||
|
||
### Phase 1: Validate RF with RTLSDR-Airband
|
||
|
||
1. Install RTLSDR-Airband on the Pi 400
|
||
2. Configure 3-4 CB channels with NFM mode
|
||
3. Verify: Does 27 MHz reception work? What squelch levels are needed? What's the audio quality?
|
||
4. Assess: Are MP3 output and its recording behavior acceptable?
|
||
|
||
**If yes → done.** RTLSDR-Airband is the solution.
|
||
|
||
### Phase 2 (if needed): Custom C build with librtlsdr + liquid-dsp
|
||
|
||
Build a purpose-built ~950-line C application that meets all requirements exactly:
|
||
- WAV output with `{frequency}_{timestamp}.wav` naming
|
||
- Configurable hang time with conversation grouping
|
||
- YAML configuration
|
||
- Console activity logging
|
||
- Minimal CPU footprint (~3.5% on Pi 400)
|
||
|
||
Use RTLSDR-Airband's FFT channelizer architecture and sdrx's per-channel approach as references.
|
||
|
||
---
|
||
|
||
## Open Questions Resolved
|
||
|
||
| # | Original Question | Answer |
|
||
| :--- | :--- | :--- |
|
||
| 1 | Which existing software fits best? | RTLSDR-Airband is the closest fit |
|
||
| 2 | Simplest custom approach? | C with librtlsdr + liquid-dsp (~950 LOC) |
|
||
| 3 | Optimal sample rate? | 960 kHz covers the full CB band with margin |
|
||
| 4 | Audio sample rate for CB voice? | 8-12.5 kHz sufficient for CB voice (~3 kHz audio bandwidth) |
|
||
|
||
## New Open Questions
|
||
|
||
| # | Question | Context |
|
||
| :--- | :--- | :--- |
|
||
| 1 | Does RTLSDR-Airband work at 27 MHz? | It's designed for VHF airband. 27 MHz is within RTL-SDR tuner range but outside RTLSDR-Airband's typical use case. Needs testing. |
|
||
| 2 | Is MP3 output acceptable? | RTLSDR-Airband only outputs MP3. If WAV is a hard requirement, custom build is needed. |
|
||
| 3 | What are the specific CB frequencies to monitor? | User to provide the 3-4 EU CB NFM frequencies. |
|