├── .gitignore
├── CMakeLists.txt
├── NOTES.txt
├── README.md
├── TODO.txt
├── include
├── AirspySource.h
├── AudioOutput.h
├── BladeRFSource.h
├── DataBuffer.h
├── Filter.h
├── FmDecode.h
├── HackRFSource.h
├── MovingAverage.h
├── RtlSdrSource.h
├── SoftFM.h
├── Source.h
├── fastatan2.h
├── parsekv.h
└── util.h
├── main.cpp
├── pyfm.py
└── sfmbase
├── AirspySource.cpp
├── AudioOutput.cpp
├── BladeRFSource.cpp
├── Filter.cpp
├── FmDecode.cpp
├── HackRFSource.cpp
└── RtlSdrSource.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | .cproject
2 | .project
3 | .settings/
4 | build/
5 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # CMake definitions for SoftFM
2 |
3 | cmake_minimum_required(VERSION 3.0.2)
4 | project(SoftFM)
5 |
6 | find_package(Threads)
7 | find_package(PkgConfig)
8 | find_package(ALSA REQUIRED)
9 | find_package(Boost 1.47)
10 |
11 | # Find RTL-SDR library.
12 | pkg_check_modules(PKG_RTLSDR librtlsdr)
13 | find_path(RTLSDR_INCLUDE_DIR rtl-sdr.h
14 | HINT ${PKG_RTLSDR_INCLUDE_DIRS})
15 | find_library(RTLSDR_LIBRARY librtlsdr.a
16 | HINT ${PKG_RTLSDR_LIBRARY_DIRS})
17 |
18 | # Find HackRF library.
19 | pkg_check_modules(PKG_HACKRF libhackrf)
20 | find_path(HACKRF_INCLUDE_DIR hackrf.h
21 | HINT ${PKG_HACKRF_INCLUDE_DIRS})
22 | find_library(HACKRF_LIBRARY libhackrf.a
23 | HINT ${PKG_HACKRF_LIBRARY_DIRS})
24 |
25 | # Find Airspy library.
26 | pkg_check_modules(PKG_AIRSPY libairspy)
27 | find_path(AIRSPY_INCLUDE_DIR airspy.h
28 | HINT ${PKG_AIRSPY_INCLUDE_DIRS})
29 | find_library(AIRSPY_LIBRARY libairspy.a
30 | HINT ${PKG_AIRSPY_LIBRARY_DIRS})
31 |
32 | # Find BladeRF library.
33 | pkg_check_modules(PKG_BLADERF libbladerf)
34 | find_path(BLADERF_INCLUDE_DIR libbladeRF.h
35 | HINT ${PKG_BLADERF_INCLUDE_DIRS})
36 | find_library(BLADERF_LIBRARY libbladeRF.so
37 | HINT ${PKG_BLADERF_LIBRARY_DIRS})
38 |
39 | # Find libusb
40 | pkg_check_modules(PKG_LIBUSB libusb-1.0)
41 | find_path(LIBUSB_INCLUDE_DIR libusb.h
42 | HINT ${PKG_LIBUSB_INCLUDE_DIRS}
43 | PATH_SUFFIXES libusb-1.0)
44 | find_library(LIBUSB_LIBRARY usb-1.0
45 | HINT ${PKG_LIBUSB_LIBRARY_DIRS})
46 |
47 | if(RTLSDR_INCLUDE_DIR AND RTLSDR_LIBRARY)
48 | message(STATUS "Found librtlsdr: ${RTLSDR_INCLUDE_DIR}, ${RTLSDR_LIBRARY}")
49 | else()
50 | message(WARNING "Can not find Osmocom RTL-SDR library")
51 | message("Try again with environment variable PKG_CONFIG_PATH")
52 | message("or with -DRTLSDR_INCLUDE_DIR=/path/rtlsdr/include")
53 | message(" -DRTLSDR_LIBRARY=/path/rtlsdr/lib/librtlsdr.a")
54 | endif()
55 |
56 | set(RTLSDR_INCLUDE_DIRS ${RTLSDR_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
57 | set(RTLSDR_LIBRARIES ${RTLSDR_LIBRARY} ${LIBUSB_LIBRARY})
58 |
59 | set(HACKRF_INCLUDE_DIRS ${HACKRF_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
60 | set(HACKRF_LIBRARIES ${HACKRF_LIBRARY} ${LIBUSB_LIBRARY})
61 |
62 | set(AIRSPY_INCLUDE_DIRS ${AIRSPY_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
63 | set(AIRSPY_LIBRARIES ${AIRSPY_LIBRARY} ${LIBUSB_LIBRARY})
64 |
65 | set(BLADERF_INCLUDE_DIRS ${BLADERF_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
66 | set(BLADERF_LIBRARIES ${BLADERF_LIBRARY} ${LIBUSB_LIBRARY})
67 |
68 | # Compiler flags.
69 | set(CMAKE_CXX_FLAGS "-Wall -std=c++11 -O2 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}")
70 |
71 | set(sfmbase_SOURCES
72 | sfmbase/Filter.cpp
73 | sfmbase/FmDecode.cpp
74 | sfmbase/AudioOutput.cpp
75 | )
76 |
77 | set(sfmbase_HEADERS
78 | include/AudioOutput.h
79 | include/Filter.h
80 | include/FmDecode.h
81 | include/MovingAverage.h
82 | include/Source.h
83 | include/SoftFM.h
84 | include/DataBuffer.h
85 | include/fastatan2.h
86 | include/parsekv.h
87 | include/util.h
88 | )
89 |
90 | # Base sources
91 |
92 | set(sfmbase_SOURCES
93 | ${sfmbase_SOURCES}
94 | ${sfmbase_HEADERS}
95 | )
96 |
97 | # RTL-SDR sources
98 |
99 | set(sfmrtlsdr_SOURCES
100 | sfmbase/RtlSdrSource.cpp
101 | )
102 |
103 | set(sfmrtlsdr_HEADERS
104 | include/RtlSdrSource.h
105 | )
106 |
107 | set(sfmrtlsdr_SOURCES
108 | ${sfmrtlsdr_SOURCES}
109 | ${sfmrtlsdr_HEADERS}
110 | )
111 |
112 | # HackRF sources
113 |
114 | set(sfmhackrf_SOURCES
115 | sfmbase/HackRFSource.cpp
116 | )
117 |
118 | set(sfmhackrf_HEADERS
119 | include/HackRFSource.h
120 | )
121 |
122 | set(sfmhackrf_SOURCES
123 | ${sfmhackrf_SOURCES}
124 | ${sfmhackrf_HEADERS}
125 | )
126 |
127 | # Airspy sources
128 |
129 | set(sfmairspy_SOURCES
130 | sfmbase/AirspySource.cpp
131 | )
132 |
133 | set(sfmairspy_HEADERS
134 | include/AirspySource.h
135 | )
136 |
137 | set(sfmairspy_SOURCES
138 | ${sfmairspy_SOURCES}
139 | ${sfmairspy_HEADERS}
140 | )
141 |
142 | # BLadeRF sources
143 |
144 | set(sfmbladerf_SOURCES
145 | sfmbase/BladeRFSource.cpp
146 | )
147 |
148 | set(sfmbladerf_HEADERS
149 | include/BladeRFSource.h
150 | )
151 |
152 | set(sfmbladerf_SOURCES
153 | ${sfmbladerf_SOURCES}
154 | ${sfmbladerf_HEADERS}
155 | )
156 |
157 | # Libraries
158 |
159 | add_library(sfmbase STATIC
160 | ${sfmbase_SOURCES}
161 | )
162 |
163 | add_library(sfmrtlsdr STATIC
164 | ${sfmrtlsdr_SOURCES}
165 | )
166 |
167 | add_library(sfmhackrf STATIC
168 | ${sfmhackrf_SOURCES}
169 | )
170 |
171 | add_library(sfmairspy STATIC
172 | ${sfmairspy_SOURCES}
173 | )
174 |
175 | add_library(sfmbladerf STATIC
176 | ${sfmbladerf_SOURCES}
177 | )
178 |
179 | add_executable(softfm
180 | main.cpp
181 | )
182 |
183 | include_directories(
184 | ${CMAKE_SOURCE_DIR}/include
185 | ${ALSA_INCLUDE_DIRS}
186 | ${EXTRA_INCLUDES}
187 | )
188 |
189 | target_link_libraries(softfm
190 | sfmbase
191 | sfmrtlsdr
192 | sfmhackrf
193 | sfmairspy
194 | sfmbladerf
195 | ${CMAKE_THREAD_LIBS_INIT}
196 | ${ALSA_LIBRARIES}
197 | ${EXTRA_LIBS}
198 | )
199 |
200 | target_include_directories(sfmrtlsdr PUBLIC
201 | ${RTLSDR_INCLUDE_DIRS}
202 | )
203 |
204 | target_link_libraries(sfmrtlsdr
205 | ${RTLSDR_LIBRARIES}
206 | )
207 |
208 | target_link_libraries(sfmhackrf
209 | ${HACKRF_LIBRARIES}
210 | )
211 |
212 | target_link_libraries(sfmairspy
213 | ${AIRSPY_LIBRARIES}
214 | )
215 |
216 | target_link_libraries(sfmbladerf
217 | ${BLADERF_LIBRARIES}
218 | )
219 |
220 | install(TARGETS softfm DESTINATION bin)
221 | install(TARGETS sfmbase sfmrtlsdr sfmhackrf sfmairspy sfmbladerf DESTINATION lib)
222 |
--------------------------------------------------------------------------------
/NOTES.txt:
--------------------------------------------------------------------------------
1 |
2 | This file contains random notitions
3 | -----------------------------------
4 |
5 |
6 | Valid sample rates
7 | ------------------
8 |
9 | Sample rates between 300001 Hz and 900000 Hz (inclusive) are not supported.
10 | They cause an invalid configuration of the RTL chip.
11 |
12 | rsamp_ratio = 28.8 MHz * 2**22 / sample_rate
13 | If bit 27 and bit 28 of rsamp_ratio are different, the RTL chip malfunctions.
14 |
15 |
16 | Behaviour of RTL and Elonics tuner
17 | ----------------------------------
18 |
19 | The RTL chip has a configurable 32-tap FIR filter running at 28.8 MS/s.
20 | RTL-SDR currently configures it for cutoff at 1.2 MHz (2.4 MS/s).
21 |
22 | Casual test of ADC mismatch:
23 | * DC offset in order of 1 code step
24 | * I/Q gain mismatch in order of 4%
25 | * I/Q phase mismatch in order of 1% of sample interval
26 |
27 | With tuner in auto-gain mode, device autonomously switches between gain
28 | settings during a run. The LNA gain seems to switch between ~ 24 dB
29 | and ~ 34 dB without intermediate steps.
30 |
31 | With RTL in AGC mode, the level of the digital sample stream is normalized
32 | to -6 dB FS. Unknown whether this is an analog or digital gain stage.
33 |
34 | At first I suspected that AGC mode may be a cooperation between the RTL and
35 | the Elonics tuner. I thought that the RTL would monitor the level and send
36 | digital control signals to the Elonics to dynamically change the tuner IF gain.
37 | However that is in fact NOT what happens. (Manually changing IF gain in AGC
38 | mode causes a brief level spike, while manually rewriting the same IF gain in
39 | AGC mode does not have any effect).
40 | It seems more likely that AGC is a digital gain in the downsampling filter.
41 |
42 |
43 | Default settings in librtlsdr
44 | -----------------------------
45 |
46 | Elonics LNA gain: when auto tuner gain: autonomous control with slow update
47 | otherwise gain as configured via rtlsdr_set_tuner_gain
48 | Elonics mixer gain: autonomous control disabled,
49 | gain depending on rtlsdr_set_tuner_gain
50 | Elonics IF linearity: optimize sensitivity (default), auto switch disabled
51 | Elonics IF gain: +6, +0, +0, +0, +9, +9 (non-standard mode)
52 | Elonics IF filters: matched to sample rate (note this may not be optimal)
53 | RTL AGC mode off
54 |
55 |
56 | Effect of IF signal filtering
57 | -----------------------------
58 |
59 | Carson bandwidth rule:
60 | IF_half_bandwidth = peak_freq_devation + modulating_freq
61 |
62 | In case of broadcast FM, this is
63 | 75 kHz + 53 kHz = 128 kHz (worst case)
64 | 19 kHz + 53 kHz = 72 kHz (typical case)
65 |
66 | Simulations of IF filtering show:
67 | * narrow IF filter reduces noise in the baseband
68 | * narrow IF filter causes gain roll-off for high modulating frequencies
69 | * narrow IF filter causes harmonic distortion at high modulating deviation
70 |
71 | IF filter with 100 kHz half-bandwidth:
72 | * baseband gain >= -1 dB up to 75 kHz
73 | * less than 0.1% distortion of modulating signal at 19 kHz peak deviation
74 | * ~ 2% distortion of modulating signal at 75 kHz peak devation
75 |
76 | IF filter with 75 kHz half-bandwidth:
77 | * baseband gain ~ -3 dB at 60 kHz, ~ -8 dB at 75 kHz
78 | * ~ 1% distortion of modulating signal at 19 kHz peak deviation
79 |
80 | Optimal IF bandwidth is probably somewhere between 75 and 100 kHz, with
81 | roll-off not too steep.
82 | Weak stations benefit from a narrow IF filter to reduce noise.
83 | Strong stations benefit from a wider IF filter to reduce harmonics.
84 |
85 |
86 | Effect of settings on baseband SNR
87 | ----------------------------------
88 |
89 | Note: all measurements 10 second duration
90 | Note: all measurements have LO frequency set to station + 250 kHz
91 |
92 |
93 | STATION SRATE LNA IF GAIN AGC SOFT BW IF LEVEL GUARD/PILOT
94 |
95 | radio3 1 MS/s 24 dB default off 150 kHz 0.19 -62.6 dB/Hz
96 | radio3 1.5 MS 24 dB default off 150 kHz 0.19 -62.7 dB/Hz
97 | radio3 2 MS/s 24 dB default off 150 kHz 0.18 -62.7 dB/Hz
98 |
99 | radio3 2 MS/s 34 dB default off 150 kHz 0.46 -64.0 dB/Hz
100 | radio3 2 MS/s 34 dB default off 80 kHz -64.0 dB/Hz
101 | radio3 2 MS/s 34 dB default off 150 kHz adccal -64.0 dB/Hz
102 |
103 | radio4 2 MS/s 24 dB default off 150 kHz 0.04 -41.1 dB/Hz
104 |
105 | radio4 1 MS/s 34 dB default off 150 kHz 0.06 -43.3 dB/Hz
106 | radio4 1 MS/s 34 dB default off 80 kHz -51.2 dB/Hz
107 |
108 | radio4 2 MS/s 34 dB default off 150 kHz 0.10 -42.4 dB/Hz
109 | radio4 2 MS/s 34 dB default off 80 kHz -48.2 dB/Hz
110 | radio4 2 MS/s 34 dB default off 150 kHz adccal -42.4 dB/Hz
111 |
112 | Conclusion: Sample rate (1 MS/s to 2 MS/s) has little effect on quality.
113 | Conclusion: LNA gain has little effect on quality.
114 | Conclusion: Narrow IF filter improves quality of weak station.
115 | Conclusion: ADC gain/offset calibration has no effect on quality.
116 |
117 |
118 | STATION SRATE LNA IF GAIN AGC SOFT BW IF LEVEL GUARD/PILOT
119 |
120 | radio3 2 MS/s 34 dB 9,0,0,0,9,9 off 80 kHz 0.38 -63.2 dB/Hz
121 | radio3 2 MS/s 34 dB 0,0,0,0,3,3 off 80 kHz 0.03 -61.5 dB/Hz
122 | radio3 2 MS/s 34 dB 0,0,0,0,3,9 off 80 kHz 0.07 -61.5 dB/Hz
123 | radio3 2 MS/s 34 dB 0,0,0,0,3,15 off 80 kHz 0.11 -60.5 dB/Hz
124 | radio3 2 MS/s 34 dB 0,0,0,0,9,15 off 80 kHz 0.22 -61.0 dB/Hz
125 | radio3 2 MS/s 34 dB 0,0,0,0,15,15 off 80 kHz 0.36 -61.4 dB/Hz
126 | radio3 2 MS/s 34 dB 0,0,6,0,15,15 off 80 kHz 0.66 CLIP -63.5 dB/Hz
127 | radio3 2 MS/s 34 dB 0,6,0,0,3,3 off 80 kHz 0.07 -62.9 dB/Hz
128 | radio3 2 MS/s 34 dB 9,3,0,0,3,3 off 80 kHz 0.14 -63.9 dB/Hz
129 | radio3 2 MS/s 34 dB 9,9,0,0,3,3 off 80 kHz 0.26 -64.2 dB/Hz
130 | radio3 2 MS/s 34 dB 9,9,3,2,3,3 off 80 kHz 0.45 -64.1 dB/Hz
131 | radio3 2 MS/s 34 dB 9,9,6,0,3,3 off 80 kHz 0.49 -63.7 dB/Hz
132 | radio3 2 MS/s 34 dB 9,9,9,0,6,3 off 80 kHz 0.77 CLIP -61.2 dB/Hz
133 |
134 | radio4 2 MS/s 34 dB 9,0,0,0,9,9 off 80 kHz 0.09 -41.5 dB/Hz
135 | radio4 2 MS/s 34 dB 0,0,0,0,3,3 off 80 kHz 0.01 -36.7 dB/Hz
136 | radio4 2 MS/s 34 dB 0,0,0,0,3,9 off 80 kHz 0.02 -39.9 dB/Hz
137 | radio4 2 MS/s 34 dB 0,0,0,0,3,15 off 80 kHz 0.03 -40.4 dB/Hz
138 | radio4 2 MS/s 34 dB 0,0,0,0,9,15 off 80 kHz 0.06 -39.9 dB/Hz
139 | radio4 2 MS/s 34 dB 0,0,0,0,15,15 off 80 kHz 0.09 -40.9 dB/Hz
140 | radio4 2 MS/s 34 dB 0,0,6,0,15,15 off 80 kHz 0.17 -38.4 dB/Hz
141 | radio4 2 MS/s 34 dB 0,6,0,0,3,3 off 80 kHz 0.02 -36.9 dB/Hz
142 | radio4 2 MS/s 34 dB 9,3,0,0,3,3 off 80 kHz 0.03 -37.5 dB/Hz
143 | radio4 2 MS/s 34 dB 9,9,0,0,3,3 off 80 kHz 0.07 -39.4 dB/Hz
144 | radio4 2 MS/s 34 dB 9,9,3,2,3,3 off 80 kHz 0.11 -38.5 dB/Hz
145 | radio4 2 MS/s 34 dB 9,9,6,0,3,3 off 80 kHz 0.12 -38.0 dB/Hz
146 | radio4 2 MS/s 34 dB 9,9,9,0,6,3 off 80 kHz 0.22 -37.5 dB/Hz
147 |
148 | Conclusion: IF gain has little effect on quality, although very low gain
149 | has an adverse effect. Librtlsdr defaults look good.
150 |
151 |
152 | STATION SRATE LNA IF GAIN AGC SOFT BW IF LEVEL GUARD/PILOT
153 |
154 | radio3 2 MS/s 34 dB default off 80 kHz 0.36 -62.1 dB/Hz
155 | radio3 2 MS/s 34 dB default ON 80 kHz 0.36 -61.6 dB/Hz
156 |
157 | radio4 2 MS/s 34 dB default off 80 kHz 0.11 -38.5 dB/Hz
158 | radio4 2 MS/s 34 dB default ON 80 kHz 0.36 CLIP -38.0 dB/Hz
159 |
160 | Conclusion: AGC mode has little effect on quality.
161 |
162 |
163 | Stereo pilot frequency
164 | ----------------------
165 |
166 | Measuring 19 kHz pilot frequency vs Internet NTP (ADSL):
167 |
168 | radio2 19 kHz pilot = 18999.79 Hz +- 0.04 Hz (7 hours measurement)
169 | radio3 19 kHz pilot = 18999.8 Hz +- 0.05 Hz (5 hours measurement)
170 | radio538 19 kHz pilot = 18999.78 Hz +- 0.04 Hz (6 hours measurement)
171 |
172 | Conclusion: stereo pilot is not a reliable time source.
173 |
174 |
175 | Ferrites
176 | --------
177 |
178 | Claming a ferrite block on the USB cable, as close as possible to the DVB
179 | received, reduces disturbance peaks in the spectrum by ~ 6 dB.
180 | A second ferrite block at the PC side gives another small improvement.
181 |
182 | The ferrite causes a clearly audible improvement of weak stations.
183 |
184 |
185 | DIY antenna
186 | -----------
187 |
188 | Constructed antenna from a vertical telescopic rod antenna (85 cm)
189 | and two steel wire ground radials ~ 85 cm.
190 | Antenna placed indoors close to window.
191 | Antenna connected to DVB receiver via 1.5 meter 75 Ohm coax cable.
192 |
193 | The ground radials are floppy and look ridiculous but they are essential.
194 | No ground radials, no reception.
195 |
196 | The DIY antenna has ~ 20 dB more signal than the basic DVB antenna.
197 |
198 | The DIY antenna causes notable improvement of weak stations.
199 | Radio4 reception improves from very bad to almost good.
200 |
201 | However, strong stations (radio3) sound slightly worse with the DIY antenna
202 | than with the basic DVB antenna.
203 | Theory: Distortion caused by clipping I/Q samples due to strong antenna signal.
204 | No, that's not it. Reducing LNA gain or IF gain does not help much;
205 | small DVB antenna still sounds better than DIY antenna.
206 | Difference only clear in stereo mode.
207 | Don't know what's going on here, maybe the DIY antenna is just not good.
208 |
209 |
210 | IF processing
211 | -------------
212 |
213 | Idea: Filter I/Q samples with 3rd order Butterworth filter
214 | instead of 10th order FIR filter.
215 | Implemented in branch "iirfilter".
216 | Speed is unchanged.
217 | Sound quality is not much changed for strong stations.
218 | Sound quality is a bit worse for weak stations (at 1 MS/s).
219 | Conclusion: not worthwhile.
220 |
221 | Idea: Downsample I/Q samples to ~ 250 kS/s BEFORE quadrature detection
222 | instead of immediately after detection. Would reduce amount of work in
223 | I/Q filtering for same or higher FIR filter order.
224 | Implemented in branch "filterif".
225 | CPU time reduced by ~ 25%.
226 | Sound quality very slightly worse.
227 | Conclusion: not worthwhile.
228 |
229 |
230 | Local radio stations
231 | --------------------
232 |
233 | radio2 92600000 (good)
234 | radio3 96800000 (good)
235 | radio4 94300000 (bad)
236 | qmusic 100700000 (medium)
237 | radio538 102100000 (medium)
238 | radio10 103800000 (bad)
239 | radio west 89300000 (medium)
240 |
241 | --
242 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NGSoftFM
2 | ========
3 |
4 | **NGSoftFM** is a command line software decoder for FM broadcast radio with stereo support
5 |
6 |
Introduction
7 |
8 | **NGSoftFM** is a software-defined radio receiver for FM broadcast radio. Stereo
9 | decoding is supported. It is written in C++. It is a derivative work of SoftFM (https://github.com/jorisvr/SoftFM) with a new application design and new features. It also corrects wrong de-emphasis scheme for stereo signals.
10 |
11 | Hardware supported:
12 |
13 | - **RTL-SDR** based (RTL2832-based) hardware is suppoeted and uses the _librtlsdr_ library to interface with the RTL-SDR hardware.
14 | - **HackRF** One and variants are supported with _libhackrf_ library.
15 | - **Airspy** is supported with _libairspy_ library.
16 | - **BladeRF** is supported with _libbladerf_ library.
17 |
18 | The purposes of NGSoftFM are:
19 |
20 | - experimenting with digital signal processing and software radio;
21 | - investigating the stability of the 19 kHz pilot;
22 | - doing the above while listening to my favorite radio station.
23 |
24 | NGSoftFM actually produces pretty good stereo sound
25 | when receiving a strong radio station. Weak stations are noisy,
26 | but NGSoftFM gets much better results than rtl_fm (bundled with RTL-SDR)
27 | and the few GNURadio-based FM receivers I have seen.
28 |
29 | NGSoftFM provides:
30 |
31 | - mono or stereo decoding of FM broadcasting stations
32 | - real-time playback to soundcard or dumping to file
33 | - command-line interface (no GUI, no visualization, nothing fancy)
34 |
35 | NGSoftFM requires:
36 |
37 | - Linux
38 | - C++11
39 | - RTL-SDR library (http://sdr.osmocom.org/trac/wiki/rtl-sdr)
40 | - HackRF library (https://github.com/mossmann/hackrf/tree/master/host/libhackrf)
41 | - Airspy library (https://github.com/airspy/host/tree/master/libairspy)
42 | - supported RTL-SDR DVB-T receiver or HackRF Rx/Tx
43 | - medium-fast computer (NGSoftFM takes 25% CPU time on a 1.6 GHz Core i3, ~12% of one core of a Core i7 5700HQ @ 2.7 GHz)
44 | - medium-strong FM radio signal. However the R820T2 based dongles give better results than the former R820T based dongles. HackRF, Airspy or BladeRF are usually even better but you have to spend the buck for the bang.
45 |
46 | For the latest version, see https://github.com/f4exb/ngsoftfm
47 |
48 | Branches:
49 |
50 | - _master_ is the "production" branch with the most stable release
51 | - _dev_ is the development branch that contains current developments that will be eventually released in the master branch
52 |
53 |
54 |
61 |
62 | The Osmocom RTL-SDR library must be installed before you can build NGSoftFM.
63 | See http://sdr.osmocom.org/trac/wiki/rtl-sdr for more information.
64 | NGSoftFM has been tested successfully with RTL-SDR 0.5.3. Normally your distribution should provide the appropriate librtlsdr package.
65 | If you go with your own installation of librtlsdr you have to specify the include path and library path. For example if you installed it in `/opt/install/librtlsdr` you have to add `-DRTLSDR_INCLUDE_DIR=/opt/install/librtlsdr/include -DRTLSDR_LIBRARY=/opt/install/librtlsdr/lib/librtlsdr.a` to the cmake options
66 |
67 | To install the library from a Debian/Ubuntu installation just do:
68 |
69 | - `sudo apt-get install librtlsdr-dev`
70 |
71 |
HackRF support
72 |
73 | For now HackRF support must be installed even if no HackRF device is connected.
74 |
75 | If you install from source (https://github.com/mossmann/hackrf/tree/master/host/libhackrf) in your own installation path you have to specify the include path and library path. For example if you installed it in `/opt/install/libhackrf` you have to add `-DHACKRF_INCLUDE_DIR=/opt/install/libhackrf/include -DHACKRF_LIBRARY=/opt/install/libhackrf/lib/libhackrf.a` to the cmake options.
76 |
77 | To install the library from a Debian/Ubuntu installation just do:
78 |
79 | - `sudo apt-get install libhackrf-dev`
80 |
81 |
Airspy support
82 |
83 | For now Airspy support must be installed even if no Airspy device is connected.
84 |
85 | If you install from source (https://github.com/airspy/host/tree/master/libairspy) in your own installation path you have to specify the include path and library path. For example if you installed it in `/opt/install/libairspy` you have to add `-DAIRSPY_INCLUDE_DIR=/opt/install/libairspy/include -DHACKRF_LIBRARY=/opt/install/libairspy/lib/libairspy.a` to the cmake options.
86 |
87 | To install the library from a Debian/Ubuntu installation just do:
88 |
89 | - `sudo apt-get install libairspy-dev`
90 |
91 |
BladeRF support
92 |
93 | For now BladeRF support must be installed even if no Airspy device is connected.
94 |
95 | If you install from source (https://github.com/Nuand/bladeRF) in your own installation path you have to specify the include path and library path. For example if you installed it in `/opt/install/libbladerf` you have to add `-DBLADERF_INCLUDE_DIR=/opt/install/libbladerf/include -DBLADERF_LIBRARY=/opt/install/libbladerf/lib/libbladeRF.so` to the cmake options.
96 |
97 | To install the library from a Debian/Ubuntu installation just do:
98 |
99 | - `sudo apt-get install libbladerf-dev`
100 |
101 | Note: for the BladeRF to work effectively on FM broadcast frequencies you have to fit it with the XB200 extension board.
102 |
103 |
Installing
104 |
105 | To install NGSoftFM, download and unpack the source code and go to the
106 | top level directory. Then do like this:
107 |
108 | - `mkdir build`
109 | - `cd build`
110 | - `cmake ..`
111 |
112 | CMake tries to find librtlsdr. If this fails, you need to specify
113 | the location of the library in one the following ways:
114 |
115 | - `cmake .. -DRTLSDR_INCLUDE_DIR=/path/rtlsdr/include -DRTLSDR_LIBRARY_PATH=/path/rtlsdr/lib/librtlsdr.a`
116 | - `PKG_CONFIG_PATH=/path/to/rtlsdr/lib/pkgconfig cmake ..`
117 |
118 | Compile and install
119 |
120 | - `make -j8` (for machines with 8 CPUs)
121 | - `make install`
122 |
123 |
124 |
Running
125 |
126 |
Examples
127 |
128 | Basic usage:
129 |
130 | - `./softfm -t rtlsdr -c freq=94600000` Tunes to 94.6 MHz
131 |
132 | Specify gain:
133 |
134 | - `./softfm -t rtlsdr -c freq=94600000,gain=22.9` Tunes to 94.6 MHz and sets gain to 22.9 dB
135 |
136 |
All options
137 |
138 | - `-t devtype` is mandatory and must be `rtlsdr` for RTL-SDR devices or `hackrf` for HackRF.
139 | - `-c config` Comma separated list of configuration options as key=value pairs or just key for switches. Depends on device type (see next paragraph).
140 | - `-d devidx` Device index, 'list' to show device list (default 0)
141 | - `-r pcmrate` Audio sample rate in Hz (default 48000 Hz)
142 | - `-M ` Disable stereo decoding
143 | - `-R filename` Write audio data as raw S16_LE samples. Uuse filename `-` to write to stdout
144 | - `-W filename` Write audio data to .WAV file
145 | - `-P [device]` Play audio via ALSA device (default `default`). Use `aplay -L` to get the list of devices for your system
146 | - `-T filename` Write pulse-per-second timestamps. Use filename '-' to write to stdout
147 | - `-b seconds` Set audio buffer size in seconds
148 |
149 |
Device type specific configuration options
150 |
151 |
RTL-SDR
152 |
153 | - `freq=` Desired tune frequency in Hz. Accepted range from 10M to 2.2G. (default 100M: `100000000`)
154 | - `gain=` (default `auto`)
155 | - `auto` Selects gain automatically
156 | - `list` Lists available gains and exit
157 | - `` gain in dB. Possible gains in dB are: `0.0, 0.9, 1.4, 2.7, 3.7, 7.7, 8.7, 12.5, 14.4, 15.7, 16.6, 19.7, 20.7, 22.9, 25.4, 28.0, 29.7, 32.8, 33.8, 36.4, 37.2, 38.6, 40.2, 42.1, 43.4, 43.9, 44.5, 48.0, 49.6`
158 | - `srate=` Device sample rate. valid values in the [225001, 300000], [900001, 3200000] ranges. (default `1000000`)
159 | - `blklen=` Device block length in bytes (default RTL-SDR default i.e. 64k)
160 | - `agc` Activates device AGC (default off)
161 |
162 |
HackRF
163 |
164 | - `freq=` Desired tune frequency in Hz. Valid range from 1M to 6G. (default 100M: `100000000`)
165 | - `srate=` Device sample rate (default `5000000`). Valid values from 1M to 20M. In fact rates lower than 10M are not specified in the datasheets of the ADC chip however a rate of `1000000` (1M) still works well with NGSoftFM.
166 | - `lgain=` LNA gain in dB. Valid values are: `0, 8, 16, 24, 32, 40, list`. `list` lists valid values and exits. (default `16`)
167 | - `vgain=` VGA gain in dB. Valid values are: `0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, list`. `list` lists valid values and exits. (default `22`)
168 | - `bwfilter=` RF (IF) filter bandwith in MHz. Actual value is taken as the closest to the following values: `1.75, 2.5, 3.5, 5, 5.5, 6, 7, 8, 9, 10, 12, 14, 15, 20, 24, 28, list`. `list` lists valid values and exits. (default `2.5`)
169 | - `extamp` Turn on the extra amplifier (default off)
170 | - `antbias` Turn on the antenna bias for remote LNA (default off)
171 |
172 |
Airspy
173 |
174 | - `freq=` Desired tune frequency in Hz. Valid range from 1M to 1.8G. (default 100M: `100000000`)
175 | - `srate=` Device sample rate. `list` lists valid values and exits. (default `10000000`). Valid values depend on the Airspy firmware. Airspy firmware and library must support dynamic sample rate query.
176 | - `lgain=` LNA gain in dB. Valid values are: `0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, list`. `list` lists valid values and exits. (default `8`)
177 | - `mgain=` Mixer gain in dB. Valid values are: `0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, 15, list`. `list` lists valid values and exits. (default `8`)
178 | - `vgain=` VGA gain in dB. Valid values are: `0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, 15, list`. `list` lists valid values and exits. (default `0`)
179 | - `antbias` Turn on the antenna bias for remote LNA (default off)
180 | - `lagc` Turn on the LNA AGC (default off)
181 | - `magc` Turn on the mixer AGC (default off)
182 |
183 |
BladeRF
184 |
185 | - `freq=` Desired tune frequency in Hz. Valid range low boundary depends whether the XB200 extension board is fitted (default `300000000`).
186 | - XB200 fitted: 100kHz to 3,8 GHz
187 | - XB200 not fitted: 300 MHZ to 3.8 GHz.
188 | - `srate=` Device sample rate in Hz. Valid range is 48kHZ to 40MHz. (default `1000000`).
189 | - `bw=` IF filter bandwidth in Hz. `list` lists valid values and exits. (default `1500000`).
190 | - `lgain=` LNA gain in dB. Valid values are: `0, 3, 6, list`. `list` lists valid values and exits. (default `3`)
191 | - `v1gain=` VGA1 gain in dB. Valid values are: `5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, list`. `list` lists valid values and exits. (default `20`)
192 | - `v2gain=` VGA2 gain in dB. Valid values are: `0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, list`. `list` lists valid values and exits. (default `9`)
193 |
194 |
195 |
License
196 |
197 | **NGSoftFM**, copyright (C) 2015, Edouard Griffiths, F4EXB
198 |
199 | This program is free software; you can redistribute it and/or modify
200 | it under the terms of the GNU General Public License as published by
201 | the Free Software Foundation; either version 2 of the License, or
202 | (at your option) any later version.
203 |
204 | This program is distributed in the hope that it will be useful,
205 | but WITHOUT ANY WARRANTY; without even the implied warranty of
206 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
207 | GNU General Public License for more details.
208 |
209 | You should have received a copy of the GNU General Public License along
210 | with this program; if not, see http://www.gnu.org/licenses/gpl-2.0.html
211 |
--------------------------------------------------------------------------------
/TODO.txt:
--------------------------------------------------------------------------------
1 | * (speedup) maybe replace high-order FIR downsampling filter with 2nd order butterworth followed by lower order FIR filter
2 | * (feature) implement RDS decoding
3 |
--------------------------------------------------------------------------------
/include/AirspySource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_AIRSPYSOURCE_H_
20 | #define INCLUDE_AIRSPYSOURCE_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include "libairspy/airspy.h"
26 |
27 | #include "Source.h"
28 |
29 | #define AIRSPY_MAX_DEVICE (32)
30 |
31 | class AirspySource : public Source
32 | {
33 | public:
34 |
35 | /** Open Airspy device. */
36 | AirspySource(int dev_index);
37 |
38 | /** Close Airspy device. */
39 | virtual ~AirspySource();
40 |
41 | virtual bool configure(std::string configuration);
42 |
43 | /** Return current sample frequency in Hz. */
44 | virtual std::uint32_t get_sample_rate();
45 |
46 | /** Return device current center frequency in Hz. */
47 | virtual std::uint32_t get_frequency();
48 |
49 | /** Print current parameters specific to device type */
50 | virtual void print_specific_parms();
51 |
52 | virtual bool start(DataBuffer *buf, std::atomic_bool *stop_flag);
53 | virtual bool stop();
54 |
55 | /** Return true if the device is OK, return false if there is an error. */
56 | virtual operator bool() const
57 | {
58 | return m_dev && m_error.empty();
59 | }
60 |
61 | /** Return a list of supported devices. */
62 | static void get_device_names(std::vector& devices);
63 |
64 | private:
65 | /**
66 | * Configure Airspy tuner and prepare for streaming.
67 | *
68 | * sampleRateIndex :: desired sample rate index in the sample rates enumeration list.
69 | * frequency :: desired center frequency in Hz.
70 | * bias_ant :: antenna bias
71 | * lna_gain :: desired LNA gain: 0 to 14 dB.
72 | * mix_gain :: desired mixer gain: 0 to 15 dB.
73 | * vga_gain :: desired VGA gain: 0 to 15 dB
74 | * lna_agc :: LNA AGC
75 | * mix_agc :: Mixer AGC
76 | *
77 | * Return true for success, false if an error occurred.
78 | */
79 | bool configure(int sampleRateIndex,
80 | uint32_t frequency,
81 | bool bias_ant,
82 | int lna_gain,
83 | int mix_gain,
84 | int vga_gain,
85 | bool lna_agc,
86 | bool mix_agc
87 | );
88 |
89 | void callback(const short* buf, int len);
90 | static int rx_callback(airspy_transfer_t* transfer);
91 | static void run(airspy_device* dev, std::atomic_bool *stop_flag);
92 |
93 | struct airspy_device* m_dev;
94 | uint32_t m_sampleRate;
95 | uint32_t m_frequency;
96 | int m_lnaGain;
97 | int m_mixGain;
98 | int m_vgaGain;
99 | bool m_biasAnt;
100 | bool m_lnaAGC;
101 | bool m_mixAGC;
102 | bool m_running;
103 | std::thread *m_thread;
104 | static AirspySource *m_this;
105 | static const std::vector m_lgains;
106 | static const std::vector m_mgains;
107 | static const std::vector m_vgains;
108 | std::vector m_srates;
109 | std::string m_lgainsStr;
110 | std::string m_mgainsStr;
111 | std::string m_vgainsStr;
112 | std::string m_sratesStr;
113 | };
114 |
115 | #endif /* INCLUDE_AIRSPYSOURCE_H_ */
116 |
--------------------------------------------------------------------------------
/include/AudioOutput.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_AUDIOOUTPUT_H
20 | #define SOFTFM_AUDIOOUTPUT_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "SoftFM.h"
28 |
29 |
30 | /** Base class for writing audio data to file or playback. */
31 | class AudioOutput
32 | {
33 | public:
34 |
35 | /** Destructor. */
36 | virtual ~AudioOutput() { }
37 |
38 | /**
39 | * Write audio data.
40 | *
41 | * Return true on success.
42 | * Return false if an error occurs.
43 | */
44 | virtual bool write(const SampleVector& samples) = 0;
45 |
46 | /** Return the last error, or return an empty string if there is no error. */
47 | std::string error()
48 | {
49 | std::string ret(m_error);
50 | m_error.clear();
51 | return ret;
52 | }
53 |
54 | /** Return true if the stream is OK, return false if there is an error. */
55 | operator bool() const
56 | {
57 | return (!m_zombie) && m_error.empty();
58 | }
59 |
60 | protected:
61 | /** Constructor. */
62 | AudioOutput() : m_zombie(false) { }
63 |
64 | /** Encode a list of samples as signed 16-bit little-endian integers. */
65 | static void samplesToInt16(const SampleVector& samples,
66 | std::vector& bytes);
67 |
68 | std::string m_error;
69 | bool m_zombie;
70 |
71 | private:
72 | AudioOutput(const AudioOutput&); // no copy constructor
73 | AudioOutput& operator=(const AudioOutput&); // no assignment operator
74 | };
75 |
76 |
77 | /** Write audio data as raw signed 16-bit little-endian data. */
78 | class RawAudioOutput : public AudioOutput
79 | {
80 | public:
81 |
82 | /**
83 | * Construct raw audio writer.
84 | *
85 | * filename :: file name (including path) or "-" to write to stdout
86 | */
87 | RawAudioOutput(const std::string& filename);
88 |
89 | ~RawAudioOutput();
90 | bool write(const SampleVector& samples);
91 |
92 | private:
93 | int m_fd;
94 | std::vector m_bytebuf;
95 | };
96 |
97 |
98 | /** Write audio data as .WAV file. */
99 | class WavAudioOutput : public AudioOutput
100 | {
101 | public:
102 |
103 | /**
104 | * Construct .WAV writer.
105 | *
106 | * filename :: file name (including path) or "-" to write to stdout
107 | * samplerate :: audio sample rate in Hz
108 | * stereo :: true if the output stream contains stereo data
109 | */
110 | WavAudioOutput(const std::string& filename,
111 | unsigned int samplerate,
112 | bool stereo);
113 |
114 | ~WavAudioOutput();
115 | bool write(const SampleVector& samples);
116 |
117 | private:
118 |
119 | /** (Re-)Write .WAV header. */
120 | bool write_header(unsigned int nsamples);
121 |
122 | static void encode_chunk_id(std::uint8_t * ptr, const char * chunkname);
123 |
124 | template
125 | static void set_value(std::uint8_t * ptr, T value);
126 |
127 | const unsigned numberOfChannels;
128 | const unsigned sampleRate;
129 | std::FILE *m_stream;
130 | std::vector m_bytebuf;
131 | };
132 |
133 |
134 | /** Write audio data to ALSA device. */
135 | class AlsaAudioOutput : public AudioOutput
136 | {
137 | public:
138 |
139 | /**
140 | * Construct ALSA output stream.
141 | *
142 | * dename :: ALSA PCM device
143 | * samplerate :: audio sample rate in Hz
144 | * stereo :: true if the output stream contains stereo data
145 | */
146 | AlsaAudioOutput(const std::string& devname,
147 | unsigned int samplerate,
148 | bool stereo);
149 |
150 | ~AlsaAudioOutput();
151 | bool write(const SampleVector& samples);
152 |
153 | private:
154 | unsigned int m_nchannels;
155 | struct _snd_pcm * m_pcm;
156 | std::vector m_bytebuf;
157 | };
158 |
159 | #endif
160 |
--------------------------------------------------------------------------------
/include/BladeRFSource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_BLADERFSOURCE_H
20 | #define SOFTFM_BLADERFSOURCE_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include "libbladeRF.h"
27 |
28 | #include "Source.h"
29 |
30 | class BladeRFSource : public Source
31 | {
32 | public:
33 |
34 | /** Open BladeRF device. */
35 | BladeRFSource(const char *serial);
36 |
37 | /** Close BladeRF device. */
38 | virtual ~BladeRFSource();
39 |
40 | virtual bool configure(std::string configuration);
41 |
42 | /** Return current sample frequency in Hz. */
43 | virtual std::uint32_t get_sample_rate();
44 |
45 | /** Return device current center frequency in Hz. */
46 | virtual std::uint32_t get_frequency();
47 |
48 | /** Print current parameters specific to device type */
49 | virtual void print_specific_parms();
50 |
51 | virtual bool start(DataBuffer* samples, std::atomic_bool *stop_flag);
52 | virtual bool stop();
53 |
54 | /** Return true if the device is OK, return false if there is an error. */
55 | virtual operator bool() const
56 | {
57 | return m_dev && m_error.empty();
58 | }
59 |
60 | /** Return a list of supported devices. */
61 | static void get_device_names(std::vector& devices);
62 |
63 | private:
64 | /**
65 | * Configure RTL-SDR tuner and prepare for streaming.
66 | *
67 | * sample_rate :: desired sample rate in Hz.
68 | * frequency :: desired center frequency in Hz.
69 | * bandwidth :: desired filter bandwidth in Hz.
70 | * lna_gain :: desired LNA gain index (1: 0dB, 2: 3dB, 3: 6dB).
71 | * vga1_gain :: desired VGA1 gain.
72 | * vga2_gain :: desired VGA2 gain.
73 | *
74 | * Return true for success, false if an error occurred.
75 | */
76 | bool configure(uint32_t sample_rate,
77 | uint32_t frequency,
78 | uint32_t bandwidth,
79 | int lna_gainIndex,
80 | int vga1_gain,
81 | int vga2_gain);
82 |
83 | /**
84 | * Fetch a bunch of samples from the device.
85 | *
86 | * This function must be called regularly to maintain streaming.
87 | * Return true for success, false if an error occurred.
88 | */
89 | static bool get_samples(IQSampleVector *samples);
90 |
91 | static void run();
92 |
93 | struct bladerf *m_dev;
94 | uint32_t m_sampleRate;
95 | uint32_t m_actualSampleRate;
96 | uint32_t m_frequency;
97 | uint32_t m_minFrequency;
98 | uint32_t m_bandwidth;
99 | uint32_t m_actualBandwidth;
100 | int m_lnaGain;
101 | int m_vga1Gain;
102 | int m_vga2Gain;
103 | std::thread *m_thread;
104 | static const int m_blockSize = 1<<14;
105 | static BladeRFSource *m_this;
106 | static const std::vector m_lnaGains;
107 | static const std::vector m_vga1Gains;
108 | static const std::vector m_vga2Gains;
109 | static const std::vector m_halfbw;
110 | std::string m_lnaGainsStr;
111 | std::string m_vga1GainsStr;
112 | std::string m_vga2GainsStr;
113 | std::string m_bwfiltStr;
114 | };
115 |
116 | #endif // SOFTFM_BLADERFSOURCE_H
117 |
--------------------------------------------------------------------------------
/include/DataBuffer.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef _INCLUDE_DATABUFFER_H_
20 | #define _INCLUDE_DATABUFFER_H_
21 |
22 | #include
23 | #include
24 | #include
25 |
26 |
27 | /** Buffer to move sample data between threads. */
28 | template
29 | class DataBuffer
30 | {
31 | public:
32 | /** Constructor. */
33 | DataBuffer()
34 | : m_qlen(0)
35 | , m_end_marked(false)
36 | { }
37 |
38 | /** Add samples to the queue. */
39 | void push(std::vector&& samples)
40 | {
41 | if (!samples.empty()) {
42 | std::unique_lock lock(m_mutex);
43 | m_qlen += samples.size();
44 | m_queue.push(move(samples));
45 | lock.unlock();
46 | m_cond.notify_all();
47 | }
48 | }
49 |
50 | /** Mark the end of the data stream. */
51 | void push_end()
52 | {
53 | std::unique_lock lock(m_mutex);
54 | m_end_marked = true;
55 | lock.unlock();
56 | m_cond.notify_all();
57 | }
58 |
59 | /** Return number of samples in queue. */
60 | std::size_t queued_samples()
61 | {
62 | std::unique_lock lock(m_mutex);
63 | return m_qlen;
64 | }
65 |
66 | /**
67 | * If the queue is non-empty, remove a block from the queue and
68 | * return the samples. If the end marker has been reached, return
69 | * an empty vector. If the queue is empty, wait until more data is pushed
70 | * or until the end marker is pushed.
71 | */
72 | std::vector pull()
73 | {
74 | std::vector ret;
75 | std::unique_lock lock(m_mutex);
76 | while (m_queue.empty() && !m_end_marked)
77 | m_cond.wait(lock);
78 | if (!m_queue.empty()) {
79 | m_qlen -= m_queue.front().size();
80 | swap(ret, m_queue.front());
81 | m_queue.pop();
82 | }
83 | return ret;
84 | }
85 |
86 | /** Return true if the end has been reached at the Pull side. */
87 | bool pull_end_reached()
88 | {
89 | std::unique_lock lock(m_mutex);
90 | return m_qlen == 0 && m_end_marked;
91 | }
92 |
93 | /** Wait until the buffer contains minfill samples or an end marker. */
94 | void wait_buffer_fill(std::size_t minfill)
95 | {
96 | std::unique_lock lock(m_mutex);
97 | while (m_qlen < minfill && !m_end_marked)
98 | m_cond.wait(lock);
99 | }
100 |
101 | private:
102 | std::size_t m_qlen;
103 | bool m_end_marked;
104 | std::queue> m_queue;
105 | std::mutex m_mutex;
106 | std::condition_variable m_cond;
107 | };
108 |
109 | #endif
110 |
111 |
--------------------------------------------------------------------------------
/include/Filter.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_FILTER_H
20 | #define SOFTFM_FILTER_H
21 |
22 | #include
23 | #include "SoftFM.h"
24 |
25 |
26 | /** Fine tuner which shifts the frequency of an IQ signal by a fixed offset. */
27 | class FineTuner
28 | {
29 | public:
30 |
31 | /**
32 | * Construct fine tuner.
33 | *
34 | * table_size :: Size of internal sin/cos tables, determines the resolution
35 | * of the frequency shift.
36 | *
37 | * freq_shift :: Frequency shift. Signal frequency will be shifted by
38 | * (sample_rate * freq_shift / table_size).
39 | */
40 | FineTuner(unsigned int table_size, int freq_shift);
41 |
42 | /** Process samples. */
43 | void process(const IQSampleVector& samples_in, IQSampleVector& samples_out);
44 |
45 | private:
46 | unsigned int m_index;
47 | IQSampleVector m_table;
48 | };
49 |
50 |
51 | /** Low-pass filter for IQ samples, based on Lanczos FIR filter. */
52 | class LowPassFilterFirIQ
53 | {
54 | public:
55 |
56 | /**
57 | * Construct low-pass filter.
58 | *
59 | * filter_order :: FIR filter order.
60 | * cutoff :: Cutoff frequency relative to the full sample rate
61 | * (valid range 0.0 ... 0.5).
62 | */
63 | LowPassFilterFirIQ(unsigned int filter_order, double cutoff);
64 |
65 | /** Process samples. */
66 | void process(const IQSampleVector& samples_in, IQSampleVector& samples_out);
67 |
68 | private:
69 | std::vector m_coeff;
70 | IQSampleVector m_state;
71 | };
72 |
73 |
74 | /**
75 | * Downsampler with low-pass FIR filter for real-valued signals.
76 | *
77 | * Step 1: Low-pass filter based on Lanczos FIR filter
78 | * Step 2: (optional) Decimation by an arbitrary factor (integer or float)
79 | */
80 | class DownsampleFilter
81 | {
82 | public:
83 |
84 | /**
85 | * Construct low-pass filter with optional downsampling.
86 | *
87 | * filter_order :: FIR filter order
88 | * cutoff :: Cutoff frequency relative to the full input sample rate
89 | * (valid range 0.0 .. 0.5)
90 | * downsample :: Decimation factor (>= 1) or 1 to disable
91 | * integer_factor :: Enables a faster and more precise algorithm that
92 | * only works for integer downsample factors.
93 | *
94 | * The output sample rate is (input_sample_rate / downsample)
95 | */
96 | DownsampleFilter(unsigned int filter_order, double cutoff,
97 | double downsample=1, bool integer_factor=true);
98 |
99 | /** Process samples. */
100 | void process(const SampleVector& samples_in, SampleVector& samples_out);
101 |
102 | private:
103 | double m_downsample;
104 | unsigned int m_downsample_int;
105 | unsigned int m_pos_int;
106 | Sample m_pos_frac;
107 | SampleVector m_coeff;
108 | SampleVector m_state;
109 | };
110 |
111 |
112 | /** First order low-pass IIR filter for real-valued signals. */
113 | class LowPassFilterRC
114 | {
115 | public:
116 |
117 | /**
118 | * Construct 1st order low-pass IIR filter.
119 | *
120 | * timeconst :: RC time constant in seconds (1 / (2 * PI * cutoff_freq)
121 | */
122 | LowPassFilterRC(double timeconst);
123 |
124 | /** Process samples. */
125 | void process(const SampleVector& samples_in, SampleVector& samples_out);
126 |
127 | /** Process samples in-place. */
128 | void process_inplace(SampleVector& samples);
129 |
130 | /** Process interleaved samples. */
131 | void process_interleaved(const SampleVector& samples_in, SampleVector& samples_out);
132 |
133 | /** Process interleaved samples in-place. */
134 | void process_interleaved_inplace(SampleVector& samples);
135 |
136 | private:
137 | double m_timeconst;
138 | Sample m_a1;
139 | Sample m_b0;
140 | Sample m_y0_1;
141 | Sample m_y1_1;
142 | };
143 |
144 |
145 | /** Low-pass filter for real-valued signals based on Butterworth IIR filter. */
146 | class LowPassFilterIir
147 | {
148 | public:
149 |
150 | /**
151 | * Construct 4th order low-pass IIR filter.
152 | *
153 | * cutoff :: Low-pass cutoff relative to the sample frequency
154 | * (valid range 0.0 .. 0.5, 0.5 = Nyquist)
155 | */
156 | LowPassFilterIir(double cutoff);
157 |
158 | /** Process samples. */
159 | void process(const SampleVector& samples_in, SampleVector& samples_out);
160 |
161 | private:
162 | Sample b0, a1, a2, a3, a4;
163 | Sample y1, y2, y3, y4;
164 | };
165 |
166 |
167 | /** High-pass filter for real-valued signals based on Butterworth IIR filter. */
168 | class HighPassFilterIir
169 | {
170 | public:
171 |
172 | /**
173 | * Construct 2nd order high-pass IIR filter.
174 | *
175 | * cutoff :: High-pass cutoff relative to the sample frequency
176 | * (valid range 0.0 .. 0.5, 0.5 = Nyquist)
177 | */
178 | HighPassFilterIir(double cutoff);
179 |
180 | /** Process samples. */
181 | void process(const SampleVector& samples_in, SampleVector& samples_out);
182 |
183 | /** Process samples in-place. */
184 | void process_inplace(SampleVector& samples);
185 |
186 | private:
187 | Sample b0, b1, b2, a1, a2;
188 | Sample x1, x2, y1, y2;
189 | };
190 |
191 | #endif
192 |
--------------------------------------------------------------------------------
/include/FmDecode.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_FMDECODE_H
20 | #define SOFTFM_FMDECODE_H
21 |
22 | #include
23 | #include
24 |
25 | #include "SoftFM.h"
26 | #include "Filter.h"
27 |
28 |
29 | /* Detect frequency by phase discrimination between successive samples. */
30 | class PhaseDiscriminator
31 | {
32 | public:
33 |
34 | /**
35 | * Construct phase discriminator.
36 | *
37 | * max_freq_dev :: Full scale frequency deviation relative to the
38 | * full sample frequency.
39 | */
40 | PhaseDiscriminator(double max_freq_dev);
41 |
42 | /**
43 | * Process samples.
44 | * Output is a sequence of frequency estimates, scaled such that
45 | * output value +/- 1.0 represents the maximum frequency deviation.
46 | */
47 | void process(const IQSampleVector& samples_in, SampleVector& samples_out);
48 |
49 | private:
50 | const Sample m_freq_scale_factor;
51 | IQSample m_last1_sample;
52 | IQSample m_last2_sample;
53 | };
54 |
55 |
56 | /** Phase-locked loop for stereo pilot. */
57 | class PilotPhaseLock
58 | {
59 | public:
60 |
61 | /** Expected pilot frequency (used for PPS events). */
62 | static constexpr int pilot_frequency = 19000;
63 |
64 | /** Timestamp event produced once every 19000 pilot periods. */
65 | struct PpsEvent
66 | {
67 | std::uint64_t pps_index;
68 | std::uint64_t sample_index;
69 | double block_position;
70 | };
71 |
72 | /**
73 | * Construct phase-locked loop.
74 | *
75 | * freq :: 19 kHz center frequency relative to sample freq
76 | * (0.5 is Nyquist)
77 | * bandwidth :: bandwidth relative to sample frequency
78 | * minsignal :: minimum pilot amplitude
79 | */
80 | PilotPhaseLock(double freq, double bandwidth, double minsignal);
81 |
82 | /**
83 | * Process samples and extract 19 kHz pilot tone.
84 | * Generate phase-locked 38 kHz tone with unit amplitude.
85 | */
86 | void process(const SampleVector& samples_in, SampleVector& samples_out);
87 |
88 | /** Return true if the phase-locked loop is locked. */
89 | bool locked() const
90 | {
91 | return m_lock_cnt >= m_lock_delay;
92 | }
93 |
94 | /** Return detected amplitude of pilot signal. */
95 | double get_pilot_level() const
96 | {
97 | return 2 * m_pilot_level;
98 | }
99 |
100 | /** Return PPS events from the most recently processed block. */
101 | std::vector get_pps_events() const
102 | {
103 | return m_pps_events;
104 | }
105 |
106 | private:
107 | Sample m_minfreq, m_maxfreq;
108 | Sample m_phasor_b0, m_phasor_a1, m_phasor_a2;
109 | Sample m_phasor_i1, m_phasor_i2, m_phasor_q1, m_phasor_q2;
110 | Sample m_loopfilter_b0, m_loopfilter_b1;
111 | Sample m_loopfilter_x1;
112 | Sample m_freq, m_phase;
113 | Sample m_minsignal;
114 | Sample m_pilot_level;
115 | int m_lock_delay;
116 | int m_lock_cnt;
117 | int m_pilot_periods;
118 | std::uint64_t m_pps_cnt;
119 | std::uint64_t m_sample_cnt;
120 | std::vector m_pps_events;
121 | };
122 |
123 |
124 | /** Complete decoder for FM broadcast signal. */
125 | class FmDecoder
126 | {
127 | public:
128 | static constexpr double default_deemphasis = 50;
129 | static constexpr double default_bandwidth_if = 100000;
130 | static constexpr double default_freq_dev = 75000;
131 | static constexpr double default_bandwidth_pcm = 15000;
132 | static constexpr double pilot_freq = 19000;
133 |
134 | /**
135 | * Construct FM decoder.
136 | *
137 | * sample_rate_if :: IQ sample rate in Hz.
138 | * tuning_offset :: Frequency offset in Hz of radio station with respect
139 | * to receiver LO frequency (positive value means
140 | * station is at higher frequency than LO).
141 | * sample_rate_pcm :: Audio sample rate.
142 | * stereo :: True to enable stereo decoding.
143 | * deemphasis :: Time constant of de-emphasis filter in microseconds
144 | * (50 us for broadcast FM, 0 to disable de-emphasis).
145 | * bandwidth_if :: Half bandwidth of IF signal in Hz
146 | * (~ 100 kHz for broadcast FM)
147 | * freq_dev :: Full scale carrier frequency deviation
148 | * (75 kHz for broadcast FM)
149 | * bandwidth_pcm :: Half bandwidth of audio signal in Hz
150 | * (15 kHz for broadcast FM)
151 | * downsample :: Downsampling factor to apply after FM demodulation.
152 | * Set to 1 to disable.
153 | */
154 | FmDecoder(double sample_rate_if,
155 | double tuning_offset,
156 | double sample_rate_pcm,
157 | bool stereo=true,
158 | double deemphasis=50,
159 | double bandwidth_if=default_bandwidth_if,
160 | double freq_dev=default_freq_dev,
161 | double bandwidth_pcm=default_bandwidth_pcm,
162 | unsigned int downsample=1);
163 |
164 | /**
165 | * Process IQ samples and return audio samples.
166 | *
167 | * If the decoder is set in stereo mode, samples for left and right
168 | * channels are interleaved in the output vector (even if no stereo
169 | * signal is detected). If the decoder is set in mono mode, the output
170 | * vector only contains samples for one channel.
171 | */
172 | void process(const IQSampleVector& samples_in,
173 | SampleVector& audio);
174 |
175 | /** Return true if a stereo signal is detected. */
176 | bool stereo_detected() const
177 | {
178 | return m_stereo_detected;
179 | }
180 |
181 | /** Return actual frequency offset in Hz with respect to receiver LO. */
182 | double get_tuning_offset() const
183 | {
184 | double tuned = - m_tuning_shift * m_sample_rate_if /
185 | double(m_tuning_table_size);
186 | return tuned + m_baseband_mean * m_freq_dev;
187 | }
188 |
189 | /** Return RMS IF level (where full scale IQ signal is 1.0). */
190 | double get_if_level() const
191 | {
192 | return m_if_level;
193 | }
194 |
195 | /** Return RMS baseband signal level (where nominal level is 0.707). */
196 | double get_baseband_level() const
197 | {
198 | return m_baseband_level;
199 | }
200 |
201 | /** Return amplitude of stereo pilot (nominal level is 0.1). */
202 | double get_pilot_level() const
203 | {
204 | return m_pilotpll.get_pilot_level();
205 | }
206 |
207 | /** Return PPS events from the most recently processed block. */
208 | std::vector get_pps_events() const
209 | {
210 | return m_pilotpll.get_pps_events();
211 | }
212 |
213 | private:
214 | /** Demodulate stereo L-R signal. */
215 | void demod_stereo(const SampleVector& samples_baseband,
216 | SampleVector& samples_stereo);
217 |
218 | /** Duplicate mono signal in left/right channels. */
219 | void mono_to_left_right(const SampleVector& samples_mono,
220 | SampleVector& audio);
221 |
222 | /** Extract left/right channels from mono/stereo signals. */
223 | void stereo_to_left_right(const SampleVector& samples_mono,
224 | const SampleVector& samples_stereo,
225 | SampleVector& audio);
226 |
227 | // Data members.
228 | const double m_sample_rate_if;
229 | const double m_sample_rate_baseband;
230 | const int m_tuning_table_size;
231 | const int m_tuning_shift;
232 | const double m_freq_dev;
233 | const unsigned int m_downsample;
234 | const bool m_stereo_enabled;
235 | bool m_stereo_detected;
236 | double m_if_level;
237 | double m_baseband_mean;
238 | double m_baseband_level;
239 |
240 | IQSampleVector m_buf_iftuned;
241 | IQSampleVector m_buf_iffiltered;
242 | SampleVector m_buf_baseband;
243 | SampleVector m_buf_mono;
244 | SampleVector m_buf_rawstereo;
245 | SampleVector m_buf_stereo;
246 |
247 | FineTuner m_finetuner;
248 | LowPassFilterFirIQ m_iffilter;
249 | PhaseDiscriminator m_phasedisc;
250 | DownsampleFilter m_resample_baseband;
251 | PilotPhaseLock m_pilotpll;
252 | DownsampleFilter m_resample_mono;
253 | DownsampleFilter m_resample_stereo;
254 | HighPassFilterIir m_dcblock_mono;
255 | HighPassFilterIir m_dcblock_stereo;
256 | LowPassFilterRC m_deemph_mono;
257 | LowPassFilterRC m_deemph_stereo;
258 | };
259 |
260 | #endif
261 |
--------------------------------------------------------------------------------
/include/HackRFSource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_HACKRFSOURCE_H_
20 | #define INCLUDE_HACKRFSOURCE_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include "libhackrf/hackrf.h"
26 |
27 | #include "Source.h"
28 |
29 | class HackRFSource : public Source
30 | {
31 | public:
32 |
33 | //static const int default_block_length = 65536;
34 |
35 | /** Open HackRF device. */
36 | HackRFSource(int dev_index);
37 |
38 | /** Close HackRF device. */
39 | virtual ~HackRFSource();
40 |
41 | virtual bool configure(std::string configuration);
42 |
43 | /** Return current sample frequency in Hz. */
44 | virtual std::uint32_t get_sample_rate();
45 |
46 | /** Return device current center frequency in Hz. */
47 | virtual std::uint32_t get_frequency();
48 |
49 | /** Print current parameters specific to device type */
50 | virtual void print_specific_parms();
51 |
52 | virtual bool start(DataBuffer *buf, std::atomic_bool *stop_flag);
53 | virtual bool stop();
54 |
55 | /** Return true if the device is OK, return false if there is an error. */
56 | virtual operator bool() const
57 | {
58 | return m_dev && m_error.empty();
59 | }
60 |
61 | /** Return a list of supported devices. */
62 | static void get_device_names(std::vector& devices);
63 |
64 | private:
65 | /**
66 | * Configure HackRF tuner and prepare for streaming.
67 | *
68 | * sample_rate :: desired sample rate in Hz.
69 | * frequency :: desired center frequency in Hz.
70 | * ext_amp :: extra amplifier engaged
71 | * lna_gain :: desired LNA gain: 0, 3 or 6 dB.
72 | * vga1_gain :: desired VGA1 gain:
73 | * vga2_gain :: desired VGA1 gain:
74 | *
75 | * Return true for success, false if an error occurred.
76 | */
77 | bool configure(uint32_t sample_rate,
78 | uint32_t frequency,
79 | bool ext_amp,
80 | bool bias_ant,
81 | int lna_gain,
82 | int vga_gain,
83 | uint32_t bandwidth
84 | );
85 |
86 | void callback(const char* buf, int len);
87 | static int rx_callback(hackrf_transfer* transfer);
88 | static void run(hackrf_device* dev, std::atomic_bool *stop_flag);
89 |
90 | struct hackrf_device* m_dev;
91 | uint32_t m_sampleRate;
92 | uint64_t m_frequency;
93 | int m_lnaGain;
94 | int m_vgaGain;
95 | uint32_t m_bandwidth;
96 | bool m_extAmp;
97 | bool m_biasAnt;
98 | bool m_running;
99 | std::thread *m_thread;
100 | static HackRFSource *m_this;
101 | static const std::vector m_lgains;
102 | static const std::vector m_vgains;
103 | static const std::vector m_bwfilt;
104 | std::string m_lgainsStr;
105 | std::string m_vgainsStr;
106 | std::string m_bwfiltStr;
107 | };
108 |
109 | #endif /* INCLUDE_HACKRFSOURCE_H_ */
110 |
--------------------------------------------------------------------------------
/include/MovingAverage.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_MOVINGAVERAGE_H
20 | #define INCLUDE_MOVINGAVERAGE_H
21 |
22 | #include
23 | #include
24 |
25 | template class MovingAverage {
26 | public:
27 | MovingAverage() :
28 | m_history(),
29 | m_sum(0),
30 | m_ptr(0)
31 | {
32 | }
33 |
34 | MovingAverage(int historySize, Type initial) :
35 | m_history(historySize, initial),
36 | m_sum((float) historySize * initial),
37 | m_ptr(0)
38 | {
39 | }
40 |
41 | void resize(int historySize, Type initial)
42 | {
43 | m_history.resize(historySize);
44 | for(size_t i = 0; i < m_history.size(); i++)
45 | m_history[i] = initial;
46 | m_sum = (float) m_history.size() * initial;
47 | m_ptr = 0;
48 | }
49 |
50 | void feed(Type value)
51 | {
52 | m_sum -= m_history[m_ptr];
53 | m_history[m_ptr] = value;
54 | m_sum += value;
55 | m_ptr++;
56 | if(m_ptr >= m_history.size())
57 | m_ptr = 0;
58 | }
59 |
60 | void fill(Type value)
61 | {
62 | for(size_t i = 0; i < m_history.size(); i++)
63 | m_history[i] = value;
64 | m_sum = (float) m_history.size() * value;
65 | }
66 |
67 | Type average() const
68 | {
69 | return m_sum / (float) m_history.size();
70 | }
71 |
72 | Type sum() const
73 | {
74 | return m_sum;
75 | }
76 |
77 | protected:
78 | std::vector m_history;
79 | Type m_sum;
80 | uint m_ptr;
81 | };
82 |
83 | #endif // INCLUDE_MOVINGAVERAGE_H
84 |
--------------------------------------------------------------------------------
/include/RtlSdrSource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_RTLSDRSOURCE_H
20 | #define SOFTFM_RTLSDRSOURCE_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "Source.h"
28 |
29 | class RtlSdrSource : public Source
30 | {
31 | public:
32 |
33 | static const int default_block_length = 65536;
34 |
35 | /** Open RTL-SDR device. */
36 | RtlSdrSource(int dev_index);
37 |
38 | /** Close RTL-SDR device. */
39 | virtual ~RtlSdrSource();
40 |
41 | virtual bool configure(std::string configuration);
42 |
43 | /** Return current sample frequency in Hz. */
44 | virtual std::uint32_t get_sample_rate();
45 |
46 | /** Return device current center frequency in Hz. */
47 | virtual std::uint32_t get_frequency();
48 |
49 | /** Print current parameters specific to device type */
50 | virtual void print_specific_parms();
51 |
52 | virtual bool start(DataBuffer* samples, std::atomic_bool *stop_flag);
53 | virtual bool stop();
54 |
55 | /** Return true if the device is OK, return false if there is an error. */
56 | virtual operator bool() const
57 | {
58 | return m_dev && m_error.empty();
59 | }
60 |
61 | /** Return a list of supported devices. */
62 | static void get_device_names(std::vector& devices);
63 |
64 | private:
65 | /**
66 | * Configure RTL-SDR tuner and prepare for streaming.
67 | *
68 | * sample_rate :: desired sample rate in Hz.
69 | * frequency :: desired center frequency in Hz.
70 | * tuner_gain :: desired tuner gain in 0.1 dB, or INT_MIN for auto-gain.
71 | * block_length :: preferred number of samples per block.
72 | *
73 | * Return true for success, false if an error occurred.
74 | */
75 | bool configure(std::uint32_t sample_rate,
76 | std::uint32_t frequency,
77 | int tuner_gain,
78 | int block_length=default_block_length,
79 | bool agcmode=false);
80 |
81 | /** Return a list of supported tuner gain settings in units of 0.1 dB. */
82 | std::vector get_tuner_gains();
83 |
84 | /** Return current tuner gain in units of 0.1 dB. */
85 | int get_tuner_gain();
86 |
87 | /**
88 | * Fetch a bunch of samples from the device.
89 | *
90 | * This function must be called regularly to maintain streaming.
91 | * Return true for success, false if an error occurred.
92 | */
93 | static bool get_samples(IQSampleVector *samples);
94 |
95 | static void run();
96 |
97 | struct rtlsdr_dev * m_dev;
98 | int m_block_length;
99 | std::vector m_gains;
100 | std::string m_gainsStr;
101 | bool m_confAgc;
102 | std::thread *m_thread;
103 | static RtlSdrSource *m_this;
104 | };
105 |
106 | #endif
107 |
--------------------------------------------------------------------------------
/include/SoftFM.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_H
20 | #define SOFTFM_H
21 |
22 | #include
23 | #include
24 |
25 | typedef std::complex IQSample;
26 | typedef std::vector IQSampleVector;
27 |
28 | typedef double Sample;
29 | typedef std::vector SampleVector;
30 |
31 |
32 | /** Compute mean and RMS over a sample vector. */
33 | inline void samples_mean_rms(const SampleVector& samples,
34 | double& mean, double& rms)
35 | {
36 | Sample vsum = 0;
37 | Sample vsumsq = 0;
38 |
39 | unsigned int n = samples.size();
40 | for (unsigned int i = 0; i < n; i++) {
41 | Sample v = samples[i];
42 | vsum += v;
43 | vsumsq += v * v;
44 | }
45 |
46 | mean = vsum / n;
47 | rms = sqrt(vsumsq / n);
48 | }
49 |
50 | #endif
51 |
--------------------------------------------------------------------------------
/include/Source.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_SOURCE_H_
20 | #define INCLUDE_SOURCE_H_
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | #include "SoftFM.h"
27 | #include "DataBuffer.h"
28 |
29 | class Source
30 | {
31 | public:
32 | Source() : m_confFreq(0), m_buf(0) {}
33 | virtual ~Source() {}
34 |
35 | /**
36 | * Configure device and prepare for streaming.
37 | */
38 | virtual bool configure(std::string configuration) = 0;
39 |
40 | /** Return current sample frequency in Hz. */
41 | virtual std::uint32_t get_sample_rate() = 0;
42 |
43 | /** Return device current center frequency in Hz. */
44 | virtual std::uint32_t get_frequency() = 0;
45 |
46 | /** Return current configured center frequency in Hz. */
47 | std::uint32_t get_configured_frequency() const
48 | {
49 | return m_confFreq;
50 | }
51 |
52 | /** Print current parameters specific to device type */
53 | virtual void print_specific_parms() = 0;
54 |
55 | /** start device before sampling loop.
56 | * Give it a reference to the buffer of samples */
57 | virtual bool start(DataBuffer *buf, std::atomic_bool *stop_flag) = 0;
58 |
59 | /** stop device after sampling loop */
60 | virtual bool stop() = 0;
61 |
62 | /** Return true if the device is OK, return false if there is an error. */
63 | virtual operator bool() const = 0;
64 |
65 | /** Return name of opened RTL-SDR device. */
66 | std::string get_device_name() const
67 | {
68 | return m_devname;
69 | }
70 |
71 | /** Return the last error, or return an empty string if there is no error. */
72 | std::string error()
73 | {
74 | std::string ret(m_error);
75 | m_error.clear();
76 | return ret;
77 | }
78 |
79 | protected:
80 | std::string m_devname;
81 | std::string m_error;
82 | uint32_t m_confFreq;
83 | DataBuffer *m_buf;
84 | std::atomic_bool *m_stop_flag;
85 | };
86 |
87 | #endif /* INCLUDE_SOURCE_H_ */
88 |
--------------------------------------------------------------------------------
/include/fastatan2.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_FASTATAN2_H_
20 | #define INCLUDE_FASTATAN2_H_
21 |
22 | #include
23 |
24 | // Fast arctan2
25 |
26 | inline float fastatan2(float y, float x)
27 | {
28 | if ( x == 0.0f )
29 | {
30 | if ( y > 0.0f )
31 | {
32 | return M_PI/2.0f;
33 | }
34 |
35 | if ( y == 0.0f ) {
36 | return 0.0f;
37 | }
38 |
39 | return -M_PI/2.0f;
40 | }
41 |
42 | float atan;
43 | float z = y/x;
44 |
45 | if ( fabs( z ) < 1.0f )
46 | {
47 | atan = z/(1.0f + 0.277778f*z*z);
48 |
49 | if ( x < 0.0f )
50 | {
51 | if ( y < 0.0f )
52 | {
53 | return atan - M_PI;
54 | }
55 |
56 | return atan + M_PI;
57 | }
58 | }
59 | else
60 | {
61 | atan = (M_PI/2.0f) - z/(z*z + 0.277778f);
62 |
63 | if ( y < 0.0f )
64 | {
65 | return atan - M_PI;
66 | }
67 | }
68 |
69 | return atan;
70 | }
71 |
72 | #endif /* INCLUDE_FASTATAN2_H_ */
73 |
--------------------------------------------------------------------------------
/include/parsekv.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | SoftFM - Software decoder for FM broadcast radio with stereo support
3 |
4 | Key-Value parser (http://www.boost.org/doc/libs/1_47_0/libs/spirit/example/qi/key_value_sequence.cpp)
5 |
6 | Usage:
7 |
8 | int main()
9 | {
10 | namespace qi = boost::spirit::qi;
11 |
12 | std::string input("key1=value1,key2,key3=value3");
13 | std::string::iterator begin = input.begin();
14 | std::string::iterator end = input.end();
15 |
16 | parsekv::key_value_sequence p;
17 | parsekv::pairs_type m;
18 |
19 | if (!qi::parse(begin, end, p, m))
20 | {
21 | std::cout << "Parsing failed\n";
22 | }
23 | else
24 | {
25 | std::cout << "Parsing succeeded, found entries:\n";
26 | parsekv::pairs_type::iterator end = m.end();
27 | for (parsekv::pairs_type::iterator it = m.begin(); it != end; ++it)
28 | {
29 | std::cout << (*it).first;
30 | if (!(*it).second.empty())
31 | std::cout << "=" << (*it).second;
32 | std::cout << std::endl;
33 | }
34 | }
35 | return 0;
36 | }
37 |
38 | ------------------------------------------------------------------------------
39 | Copyright (C) 2015 Edouard Griffiths, F4EXB
40 |
41 | This program is free software; you can redistribute it and/or modify
42 | it under the terms of the GNU General Public License as published by
43 | the Free Software Foundation as version 3 of the License, or
44 |
45 | This program is distributed in the hope that it will be useful,
46 | but WITHOUT ANY WARRANTY; without even the implied warranty of
47 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 | GNU General Public License V3 for more details.
49 |
50 | You should have received a copy of the GNU General Public License
51 | along with this program. If not, see .
52 |
53 | ******************************************************************************/
54 |
55 | #ifndef INCLUDE_PARSEKV_H_
56 | #define INCLUDE_PARSEKV_H_
57 |
58 | #include
59 | #include