# 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. |