diff --git a/RESEARCH.md b/RESEARCH.md new file mode 100644 index 0000000..19761cf --- /dev/null +++ b/RESEARCH.md @@ -0,0 +1,363 @@ +# 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. |