├── .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 |

Prerequisites

55 | 56 |

Base requirements

57 | 58 | - `sudo apt-get install cmake pkg-config libusb-1.0-0-dev libasound2-dev libboost-all-dev` 59 | 60 |

RTL-SDR support

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 60 | #include 61 | #include 62 | 63 | namespace parsekv 64 | { 65 | namespace qi = boost::spirit::qi; 66 | 67 | typedef std::map pairs_type; 68 | 69 | template 70 | struct key_value_sequence 71 | : qi::grammar 72 | { 73 | key_value_sequence() 74 | : key_value_sequence::base_type(query) 75 | { 76 | query = pair >> *((qi::lit(',') | '&') >> pair); 77 | pair = key >> -('=' >> value); 78 | key = qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9"); 79 | value = +qi::char_("a-zA-Z_0-9."); 80 | } 81 | 82 | qi::rule query; 83 | qi::rule()> pair; 84 | qi::rule key, value; 85 | }; 86 | } 87 | 88 | 89 | 90 | #endif /* INCLUDE_PARSEKV_H_ */ 91 | -------------------------------------------------------------------------------- /include/util.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_UTIL_H_ 20 | #define INCLUDE_UTIL_H_ 21 | 22 | inline bool parse_dbl(const char *s, double& v) 23 | { 24 | char *endp; 25 | 26 | v = strtod(s, &endp); 27 | 28 | if (endp == s) 29 | { 30 | return false; 31 | } 32 | 33 | if (*endp == 'k') 34 | { 35 | v *= 1.0e3; 36 | endp++; 37 | } 38 | else if (*endp == 'M') 39 | { 40 | v *= 1.0e6; 41 | endp++; 42 | } 43 | else if (*endp == 'G') 44 | { 45 | v *= 1.0e9; 46 | endp++; 47 | } 48 | 49 | return (*endp == '\0'); 50 | } 51 | 52 | #endif /* INCLUDE_UTIL_H_ */ 53 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 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 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "util.h" 34 | #include "SoftFM.h" 35 | #include "DataBuffer.h" 36 | #include "FmDecode.h" 37 | #include "AudioOutput.h" 38 | #include "MovingAverage.h" 39 | 40 | #include "RtlSdrSource.h" 41 | #include "HackRFSource.h" 42 | #include "AirspySource.h" 43 | #include "BladeRFSource.h" 44 | 45 | /** Flag is set on SIGINT / SIGTERM. */ 46 | static std::atomic_bool stop_flag(false); 47 | 48 | 49 | /** Simple linear gain adjustment. */ 50 | void adjust_gain(SampleVector& samples, double gain) 51 | { 52 | for (unsigned int i = 0, n = samples.size(); i < n; i++) { 53 | samples[i] *= gain; 54 | } 55 | } 56 | 57 | /** 58 | * Get data from output buffer and write to output stream. 59 | * 60 | * This code runs in a separate thread. 61 | */ 62 | void write_output_data(AudioOutput *output, DataBuffer *buf, 63 | unsigned int buf_minfill) 64 | { 65 | while (!stop_flag.load()) { 66 | 67 | if (buf->queued_samples() == 0) { 68 | // The buffer is empty. Perhaps the output stream is consuming 69 | // samples faster than we can produce them. Wait until the buffer 70 | // is back at its nominal level to make sure this does not happen 71 | // too often. 72 | buf->wait_buffer_fill(buf_minfill); 73 | } 74 | 75 | if (buf->pull_end_reached()) { 76 | // Reached end of stream. 77 | break; 78 | } 79 | 80 | // Get samples from buffer and write to output. 81 | SampleVector samples = buf->pull(); 82 | output->write(samples); 83 | if (!(*output)) { 84 | fprintf(stderr, "ERROR: AudioOutput: %s\n", output->error().c_str()); 85 | } 86 | } 87 | } 88 | 89 | 90 | /** Handle Ctrl-C and SIGTERM. */ 91 | static void handle_sigterm(int sig) 92 | { 93 | stop_flag.store(true); 94 | 95 | std::string msg = "\nGot signal "; 96 | msg += strsignal(sig); 97 | msg += ", stopping ...\n"; 98 | 99 | const char *s = msg.c_str(); 100 | write(STDERR_FILENO, s, strlen(s)); 101 | } 102 | 103 | 104 | void usage() 105 | { 106 | fprintf(stderr, 107 | "Usage: softfm [options]\n" 108 | " -t devtype Device type:\n" 109 | " - rtlsdr: RTL-SDR devices\n" 110 | " - hackrf: HackRF One or Jawbreaker\n" 111 | " - airspy: Airspy\n" 112 | " - bladerf: BladeRF\n" 113 | " -c config Comma separated key=value configuration pairs or just key for switches\n" 114 | " See below for valid values per device type\n" 115 | " -d devidx Device index, 'list' to show device list (default 0)\n" 116 | " -r pcmrate Audio sample rate in Hz (default 48000 Hz)\n" 117 | " -M Disable stereo decoding\n" 118 | " -R filename Write audio data as raw S16_LE samples\n" 119 | " use filename '-' to write to stdout\n" 120 | " -W filename Write audio data to .WAV file\n" 121 | " -P [device] Play audio via ALSA device (default 'default')\n" 122 | " -T filename Write pulse-per-second timestamps\n" 123 | " use filename '-' to write to stdout\n" 124 | " -b seconds Set audio buffer size in seconds\n" 125 | "\n" 126 | "Configuration options for RTL-SDR devices\n" 127 | " freq= Frequency of radio station in Hz (default 100000000)\n" 128 | " valid values: 10M to 2.2G (working range depends on device)\n" 129 | " srate= IF sample rate in Hz (default 1000000)\n" 130 | " (valid ranges: [225001, 300000], [900001, 3200000]))\n" 131 | " gain= Set LNA gain in dB, or 'auto',\n" 132 | " or 'list' to just get a list of valid values (default auto)\n" 133 | " blklen= Set audio buffer size in seconds (default RTL-SDR default)\n" 134 | " agc Enable RTL AGC mode (default disabled)\n" 135 | "\n" 136 | "Configuration options for HackRF devices\n" 137 | " freq= Frequency of radio station in Hz (default 100000000)\n" 138 | " valid values: 1M to 6G\n" 139 | " srate= IF sample rate in Hz (default 5000000)\n" 140 | " (valid ranges: [2500000,20000000]))\n" 141 | " lgain= LNA gain in dB. 'list' to just get a list of valid values: (default 16)\n" 142 | " vgain= VGA gain in dB. 'list' to just get a list of valid values: (default 22)\n" 143 | " bwfilter= Filter bandwidth in MHz. 'list' to just get a list of valid values: (default 2.5)\n" 144 | " extamp Enable extra RF amplifier (default disabled)\n" 145 | " antbias Enable antemma bias (default disabled)\n" 146 | "\n" 147 | "Configuration options for Airspy devices\n" 148 | " freq= Frequency of radio station in Hz (default 100000000)\n" 149 | " valid values: 24M to 1.8G\n" 150 | " srate= IF sample rate in Hz. Depends on Airspy firmware and libairspy support\n" 151 | " Airspy firmware and library must support dynamic sample rate query. (default 10000000)\n" 152 | " lgain= LNA gain in dB. 'list' to just get a list of valid values: (default 8)\n" 153 | " mgain= Mixer gain in dB. 'list' to just get a list of valid values: (default 8)\n" 154 | " vgain= VGA gain in dB. 'list' to just get a list of valid values: (default 8)\n" 155 | " antbias Enable antemma bias (default disabled)\n" 156 | " lagc Enable LNA AGC (default disabled)\n" 157 | " magc Enable mixer AGC (default disabled)\n" 158 | "\n" 159 | "Configuration options for BladeRF devices\n" 160 | " freq= Frequency of radio station in Hz (default 300000000)\n" 161 | " valid values (with XB200): 100k to 3.8G\n" 162 | " valid values (without XB200): 300M to 3.8G\n" 163 | " srate= IF sample rate in Hz. Valid values: 48k to 40M (default 1000000)\n" 164 | " bw= Bandwidth in Hz. 'list' to just get a list of valid values: (default 1500000)\n" 165 | " lgain= LNA gain in dB. 'list' to just get a list of valid values: (default 3)\n" 166 | " v1gain= VGA1 gain in dB. 'list' to just get a list of valid values: (default 20)\n" 167 | " v2gain= VGA2 gain in dB. 'list' to just get a list of valid values: (default 9)\n" 168 | "\n"); 169 | } 170 | 171 | 172 | void badarg(const char *label) 173 | { 174 | usage(); 175 | fprintf(stderr, "ERROR: Invalid argument for %s\n", label); 176 | exit(1); 177 | } 178 | 179 | 180 | bool parse_int(const char *s, int& v, bool allow_unit=false) 181 | { 182 | char *endp; 183 | long t = strtol(s, &endp, 10); 184 | if (endp == s) 185 | return false; 186 | if ( allow_unit && *endp == 'k' && 187 | t > INT_MIN / 1000 && t < INT_MAX / 1000 ) { 188 | t *= 1000; 189 | endp++; 190 | } 191 | if (*endp != '\0' || t < INT_MIN || t > INT_MAX) 192 | return false; 193 | v = t; 194 | return true; 195 | } 196 | 197 | 198 | /** Return Unix time stamp in seconds. */ 199 | double get_time() 200 | { 201 | struct timeval tv; 202 | gettimeofday(&tv, NULL); 203 | return tv.tv_sec + 1.0e-6 * tv.tv_usec; 204 | } 205 | 206 | static bool get_device(std::vector &devnames, std::string& devtype, Source **srcsdr, int devidx) 207 | { 208 | if (strcasecmp(devtype.c_str(), "rtlsdr") == 0) 209 | { 210 | RtlSdrSource::get_device_names(devnames); 211 | } 212 | else if (strcasecmp(devtype.c_str(), "hackrf") == 0) 213 | { 214 | HackRFSource::get_device_names(devnames); 215 | } 216 | else if (strcasecmp(devtype.c_str(), "airspy") == 0) 217 | { 218 | AirspySource::get_device_names(devnames); 219 | } 220 | else if (strcasecmp(devtype.c_str(), "bladerf") == 0) 221 | { 222 | BladeRFSource::get_device_names(devnames); 223 | } 224 | else 225 | { 226 | fprintf(stderr, "ERROR: wrong device type (-t option) must be one of the following:\n"); 227 | fprintf(stderr, " rtlsdr, hackrf, airspy, bladerf\n"); 228 | return false; 229 | } 230 | 231 | if (devidx < 0 || (unsigned int)devidx >= devnames.size()) 232 | { 233 | if (devidx != -1) 234 | { 235 | fprintf(stderr, "ERROR: invalid device index %d\n", devidx); 236 | } 237 | 238 | fprintf(stderr, "Found %u devices:\n", (unsigned int)devnames.size()); 239 | 240 | for (unsigned int i = 0; i < devnames.size(); i++) 241 | { 242 | fprintf(stderr, "%2u: %s\n", i, devnames[i].c_str()); 243 | } 244 | 245 | return false; 246 | } 247 | 248 | fprintf(stderr, "using device %d: %s\n", devidx, devnames[devidx].c_str()); 249 | 250 | if (strcasecmp(devtype.c_str(), "rtlsdr") == 0) 251 | { 252 | // Open RTL-SDR device. 253 | *srcsdr = new RtlSdrSource(devidx); 254 | } 255 | else if (strcasecmp(devtype.c_str(), "hackrf") == 0) 256 | { 257 | // Open HackRF device. 258 | *srcsdr = new HackRFSource(devidx); 259 | } 260 | else if (strcasecmp(devtype.c_str(), "airspy") == 0) 261 | { 262 | // Open Airspy device. 263 | *srcsdr = new AirspySource(devidx); 264 | } 265 | else if (strcasecmp(devtype.c_str(), "bladerf") == 0) 266 | { 267 | // Open BladeRF device. 268 | *srcsdr = new BladeRFSource(devnames[devidx].c_str()); 269 | } 270 | 271 | return true; 272 | } 273 | 274 | int main(int argc, char **argv) 275 | { 276 | int devidx = 0; 277 | int pcmrate = 48000; 278 | bool stereo = true; 279 | enum OutputMode { MODE_RAW, MODE_WAV, MODE_ALSA }; 280 | OutputMode outmode = MODE_ALSA; 281 | std::string filename; 282 | std::string alsadev("default"); 283 | std::string ppsfilename; 284 | FILE * ppsfile = NULL; 285 | double bufsecs = -1; 286 | std::string config_str; 287 | std::string devtype_str; 288 | std::vector devnames; 289 | Source *srcsdr = 0; 290 | 291 | fprintf(stderr, 292 | "SoftFM - Software decoder for FM broadcast radio\n"); 293 | 294 | const struct option longopts[] = { 295 | { "devtype", 2, NULL, 't' }, 296 | { "config", 2, NULL, 'c' }, 297 | { "dev", 1, NULL, 'd' }, 298 | { "pcmrate", 1, NULL, 'r' }, 299 | { "mono", 0, NULL, 'M' }, 300 | { "raw", 1, NULL, 'R' }, 301 | { "wav", 1, NULL, 'W' }, 302 | { "play", 2, NULL, 'P' }, 303 | { "pps", 1, NULL, 'T' }, 304 | { "buffer", 1, NULL, 'b' }, 305 | { NULL, 0, NULL, 0 } }; 306 | 307 | int c, longindex; 308 | while ((c = getopt_long(argc, argv, 309 | "t:c:d:r:MR:W:P::T:b:", 310 | longopts, &longindex)) >= 0) { 311 | switch (c) { 312 | case 't': 313 | devtype_str.assign(optarg); 314 | break; 315 | case 'c': 316 | config_str.assign(optarg); 317 | break; 318 | case 'd': 319 | if (!parse_int(optarg, devidx)) 320 | devidx = -1; 321 | break; 322 | case 'r': 323 | if (!parse_int(optarg, pcmrate, true) || pcmrate < 1) { 324 | badarg("-r"); 325 | } 326 | break; 327 | case 'M': 328 | stereo = false; 329 | break; 330 | case 'R': 331 | outmode = MODE_RAW; 332 | filename = optarg; 333 | break; 334 | case 'W': 335 | outmode = MODE_WAV; 336 | filename = optarg; 337 | break; 338 | case 'P': 339 | outmode = MODE_ALSA; 340 | if (optarg != NULL) 341 | alsadev = optarg; 342 | break; 343 | case 'T': 344 | ppsfilename = optarg; 345 | break; 346 | case 'b': 347 | if (!parse_dbl(optarg, bufsecs) || bufsecs < 0) { 348 | badarg("-b"); 349 | } 350 | break; 351 | default: 352 | usage(); 353 | fprintf(stderr, "ERROR: Invalid command line options\n"); 354 | exit(1); 355 | } 356 | } 357 | 358 | if (optind < argc) 359 | { 360 | usage(); 361 | fprintf(stderr, "ERROR: Unexpected command line options\n"); 362 | exit(1); 363 | } 364 | 365 | // Catch Ctrl-C and SIGTERM 366 | struct sigaction sigact; 367 | sigact.sa_handler = handle_sigterm; 368 | sigemptyset(&sigact.sa_mask); 369 | sigact.sa_flags = SA_RESETHAND; 370 | 371 | if (sigaction(SIGINT, &sigact, NULL) < 0) 372 | { 373 | fprintf(stderr, "WARNING: can not install SIGINT handler (%s)\n", strerror(errno)); 374 | } 375 | 376 | if (sigaction(SIGTERM, &sigact, NULL) < 0) 377 | { 378 | fprintf(stderr, "WARNING: can not install SIGTERM handler (%s)\n", strerror(errno)); 379 | } 380 | 381 | // Open PPS file. 382 | if (!ppsfilename.empty()) 383 | { 384 | if (ppsfilename == "-") 385 | { 386 | fprintf(stderr, "writing pulse-per-second markers to stdout\n"); 387 | ppsfile = stdout; 388 | } 389 | else 390 | { 391 | fprintf(stderr, "writing pulse-per-second markers to '%s'\n", ppsfilename.c_str()); 392 | ppsfile = fopen(ppsfilename.c_str(), "w"); 393 | 394 | if (ppsfile == NULL) 395 | { 396 | fprintf(stderr, "ERROR: can not open '%s' (%s)\n", ppsfilename.c_str(), strerror(errno)); 397 | exit(1); 398 | } 399 | } 400 | 401 | fprintf(ppsfile, "#pps_index sample_index unix_time\n"); 402 | fflush(ppsfile); 403 | } 404 | 405 | // Calculate number of samples in audio buffer. 406 | unsigned int outputbuf_samples = 0; 407 | 408 | if (bufsecs < 0 && (outmode == MODE_ALSA || (outmode == MODE_RAW && filename == "-"))) 409 | { 410 | // Set default buffer to 1 second for interactive output streams. 411 | outputbuf_samples = pcmrate; 412 | } 413 | else if (bufsecs > 0) 414 | { 415 | // Calculate nr of samples for configured buffer length. 416 | outputbuf_samples = (unsigned int)(bufsecs * pcmrate); 417 | } 418 | 419 | if (outputbuf_samples > 0) 420 | { 421 | fprintf(stderr, "output buffer: %.1f seconds\n", outputbuf_samples / double(pcmrate)); 422 | } 423 | 424 | // Prepare output writer. 425 | std::unique_ptr audio_output; 426 | 427 | switch (outmode) 428 | { 429 | case MODE_RAW: 430 | fprintf(stderr, "writing raw 16-bit audio samples to '%s'\n", filename.c_str()); 431 | audio_output.reset(new RawAudioOutput(filename)); 432 | break; 433 | case MODE_WAV: 434 | fprintf(stderr, "writing audio samples to '%s'\n", filename.c_str()); 435 | audio_output.reset(new WavAudioOutput(filename, pcmrate, stereo)); 436 | break; 437 | case MODE_ALSA: 438 | fprintf(stderr, "playing audio to ALSA device '%s'\n", alsadev.c_str()); 439 | audio_output.reset(new AlsaAudioOutput(alsadev, pcmrate, stereo)); 440 | break; 441 | } 442 | 443 | if (!(*audio_output)) 444 | { 445 | fprintf(stderr, "ERROR: AudioOutput: %s\n", audio_output->error().c_str()); 446 | exit(1); 447 | } 448 | 449 | if (!get_device(devnames, devtype_str, &srcsdr, devidx)) 450 | { 451 | exit(1); 452 | } 453 | 454 | if (!(*srcsdr)) 455 | { 456 | fprintf(stderr, "ERROR source: %s\n", srcsdr->error().c_str()); 457 | delete srcsdr; 458 | exit(1); 459 | } 460 | 461 | 462 | // Configure device and start streaming. 463 | if (!srcsdr->configure(config_str)) 464 | { 465 | fprintf(stderr, "ERROR: configuration: %s\n", srcsdr->error().c_str()); 466 | delete srcsdr; 467 | exit(1); 468 | } 469 | 470 | double freq = srcsdr->get_configured_frequency(); 471 | fprintf(stderr, "tuned for: %.6f MHz\n", freq * 1.0e-6); 472 | 473 | double tuner_freq = srcsdr->get_frequency(); 474 | fprintf(stderr, "device tuned for: %.6f MHz\n", tuner_freq * 1.0e-6); 475 | 476 | double ifrate = srcsdr->get_sample_rate(); 477 | fprintf(stderr, "IF sample rate: %.0f Hz\n", ifrate); 478 | 479 | double delta_if = tuner_freq - freq; 480 | MovingAverage ppm_average(40, 0.0f); 481 | 482 | srcsdr->print_specific_parms(); 483 | 484 | // Create source data queue. 485 | DataBuffer source_buffer; 486 | 487 | // ownership will be transferred to thread therefore the unique_ptr with move is convenient 488 | // if the pointer is to be shared with the main thread use shared_ptr (and no move) instead 489 | std::unique_ptr up_srcsdr(srcsdr); 490 | 491 | // Start reading from device in separate thread. 492 | //std::thread source_thread(read_source_data, std::move(up_srcsdr), &source_buffer); 493 | up_srcsdr->start(&source_buffer, &stop_flag); 494 | 495 | if (!up_srcsdr) { 496 | fprintf(stderr, "ERROR: source: %s\n", up_srcsdr->error().c_str()); 497 | exit(1); 498 | } 499 | 500 | // The baseband signal is empty above 100 kHz, so we can 501 | // downsample to ~ 200 kS/s without loss of information. 502 | // This will speed up later processing stages. 503 | unsigned int downsample = std::max(1, int(ifrate / 215.0e3)); 504 | fprintf(stderr, "baseband downsampling factor %u\n", downsample); 505 | 506 | // Prevent aliasing at very low output sample rates. 507 | double bandwidth_pcm = std::min(FmDecoder::default_bandwidth_pcm, 0.45 * pcmrate); 508 | fprintf(stderr, "audio sample rate: %u Hz\n", pcmrate); 509 | fprintf(stderr, "audio bandwidth: %.3f kHz\n", bandwidth_pcm * 1.0e-3); 510 | 511 | // Prepare decoder. 512 | FmDecoder fm(ifrate, // sample_rate_if 513 | freq - tuner_freq, // tuning_offset 514 | pcmrate, // sample_rate_pcm 515 | stereo, // stereo 516 | FmDecoder::default_deemphasis, // deemphasis, 517 | FmDecoder::default_bandwidth_if, // bandwidth_if 518 | FmDecoder::default_freq_dev, // freq_dev 519 | bandwidth_pcm, // bandwidth_pcm 520 | downsample); // downsample 521 | 522 | // If buffering enabled, start background output thread. 523 | DataBuffer output_buffer; 524 | std::thread output_thread; 525 | 526 | if (outputbuf_samples > 0) 527 | { 528 | unsigned int nchannel = stereo ? 2 : 1; 529 | output_thread = std::thread(write_output_data, 530 | audio_output.get(), 531 | &output_buffer, 532 | outputbuf_samples * nchannel); 533 | } 534 | 535 | SampleVector audiosamples; 536 | bool inbuf_length_warning = false; 537 | double audio_level = 0; 538 | bool got_stereo = false; 539 | 540 | double block_time = get_time(); 541 | 542 | // Main loop. 543 | for (unsigned int block = 0; !stop_flag.load(); block++) 544 | { 545 | 546 | // Check for overflow of source buffer. 547 | if (!inbuf_length_warning && source_buffer.queued_samples() > 10 * ifrate) 548 | { 549 | fprintf(stderr, "\nWARNING: Input buffer is growing (system too slow)\n"); 550 | inbuf_length_warning = true; 551 | } 552 | 553 | // Pull next block from source buffer. 554 | IQSampleVector iqsamples = source_buffer.pull(); 555 | 556 | if (iqsamples.empty()) 557 | { 558 | break; 559 | } 560 | 561 | double prev_block_time = block_time; 562 | block_time = get_time(); 563 | 564 | // Decode FM signal. 565 | fm.process(iqsamples, audiosamples); 566 | 567 | // Measure audio level. 568 | double audio_mean, audio_rms; 569 | samples_mean_rms(audiosamples, audio_mean, audio_rms); 570 | audio_level = 0.95 * audio_level + 0.05 * audio_rms; 571 | 572 | // Set nominal audio volume. 573 | adjust_gain(audiosamples, 0.5); 574 | 575 | ppm_average.feed(((fm.get_tuning_offset() + delta_if) / tuner_freq) * -1.0e6); // the minus factor is to show the ppm correction to make and not the one made 576 | 577 | // Show statistics. 578 | fprintf(stderr, 579 | "\rblk=%6d freq=%10.6fMHz ppm=%+6.2f IF=%+5.1fdB BB=%+5.1fdB audio=%+5.1fdB ", 580 | block, 581 | (tuner_freq + fm.get_tuning_offset()) * 1.0e-6, 582 | ppm_average.average(), 583 | //((fm.get_tuning_offset() + delta_if) / tuner_freq) * 1.0e6, 584 | 20*log10(fm.get_if_level()), 585 | 20*log10(fm.get_baseband_level()) + 3.01, 586 | 20*log10(audio_level) + 3.01); 587 | 588 | if (outputbuf_samples > 0) 589 | { 590 | unsigned int nchannel = stereo ? 2 : 1; 591 | std::size_t buflen = output_buffer.queued_samples(); 592 | fprintf(stderr, " buf=%.1fs ", buflen / nchannel / double(pcmrate)); 593 | } 594 | 595 | fflush(stderr); 596 | 597 | // Show stereo status. 598 | if (fm.stereo_detected() != got_stereo) 599 | { 600 | got_stereo = fm.stereo_detected(); 601 | 602 | if (got_stereo) 603 | { 604 | fprintf(stderr, "\ngot stereo signal (pilot level = %f)\n", fm.get_pilot_level()); 605 | } 606 | else 607 | { 608 | fprintf(stderr, "\nlost stereo signal\n"); 609 | } 610 | } 611 | 612 | // Write PPS markers. 613 | if (ppsfile != NULL) 614 | { 615 | for (const PilotPhaseLock::PpsEvent& ev : fm.get_pps_events()) 616 | { 617 | double ts = prev_block_time; 618 | ts += ev.block_position * (block_time - prev_block_time); 619 | fprintf(ppsfile, "%8s %14s %18.6f\n", 620 | std::to_string(ev.pps_index).c_str(), 621 | std::to_string(ev.sample_index).c_str(), 622 | ts); 623 | fflush(ppsfile); 624 | } 625 | } 626 | 627 | // Throw away first block. It is noisy because IF filters 628 | // are still starting up. 629 | if (block > 0) 630 | { 631 | // Write samples to output. 632 | if (outputbuf_samples > 0) 633 | { 634 | // Buffered write. 635 | output_buffer.push(move(audiosamples)); 636 | } 637 | else 638 | { 639 | // Direct write. 640 | audio_output->write(audiosamples); 641 | } 642 | } 643 | } 644 | 645 | fprintf(stderr, "\n"); 646 | 647 | // Join background threads. 648 | //source_thread.join(); 649 | up_srcsdr->stop(); 650 | 651 | if (outputbuf_samples > 0) 652 | { 653 | output_buffer.push_end(); 654 | output_thread.join(); 655 | } 656 | 657 | // No cleanup needed; everything handled by destructors 658 | 659 | return 0; 660 | } 661 | 662 | /* end */ 663 | -------------------------------------------------------------------------------- /sfmbase/AirspySource.cpp: -------------------------------------------------------------------------------- 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 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "util.h" 27 | #include "parsekv.h" 28 | #include "AirspySource.h" 29 | 30 | AirspySource *AirspySource::m_this = 0; 31 | const std::vector AirspySource::m_lgains({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}); 32 | const std::vector AirspySource::m_mgains({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); 33 | const std::vector AirspySource::m_vgains({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); 34 | 35 | // Open Airspy device. 36 | AirspySource::AirspySource(int dev_index) : 37 | m_dev(0), 38 | m_sampleRate(10000000), 39 | m_frequency(100000000), 40 | m_lnaGain(8), 41 | m_mixGain(8), 42 | m_vgaGain(0), 43 | m_biasAnt(false), 44 | m_lnaAGC(false), 45 | m_mixAGC(false), 46 | m_running(false), 47 | m_thread(0) 48 | { 49 | airspy_error rc = (airspy_error) airspy_init(); 50 | 51 | if (rc != AIRSPY_SUCCESS) 52 | { 53 | std::ostringstream err_ostr; 54 | err_ostr << "Failed to open Airspy library (" << rc << ": " << airspy_error_name(rc) << ")"; 55 | m_error = err_ostr.str(); 56 | m_dev = 0; 57 | } 58 | else 59 | { 60 | for (int i = 0; i < AIRSPY_MAX_DEVICE; i++) 61 | { 62 | rc = (airspy_error) airspy_open(&m_dev); 63 | 64 | if (rc == AIRSPY_SUCCESS) 65 | { 66 | if (i == dev_index) 67 | { 68 | break; 69 | } 70 | } 71 | else 72 | { 73 | std::ostringstream err_ostr; 74 | err_ostr << "Failed to open Airspy device at sequence " << i; 75 | m_error = err_ostr.str(); 76 | m_dev = 0; 77 | } 78 | } 79 | } 80 | 81 | if (m_dev) 82 | { 83 | uint32_t nbSampleRates; 84 | uint32_t *sampleRates; 85 | 86 | airspy_get_samplerates(m_dev, &nbSampleRates, 0); 87 | 88 | sampleRates = new uint32_t[nbSampleRates]; 89 | 90 | airspy_get_samplerates(m_dev, sampleRates, nbSampleRates); 91 | 92 | if (nbSampleRates == 0) 93 | { 94 | m_error = "Failed to get Airspy device sample rate list"; 95 | airspy_close(m_dev); 96 | m_dev = 0; 97 | } 98 | else 99 | { 100 | for (uint32_t i=0; i& devices) 167 | { 168 | airspy_device *airspy_ptr; 169 | airspy_read_partid_serialno_t read_partid_serialno; 170 | uint32_t serial_msb = 0; 171 | uint32_t serial_lsb = 0; 172 | airspy_error rc; 173 | int i; 174 | 175 | rc = (airspy_error) airspy_init(); 176 | 177 | if (rc != AIRSPY_SUCCESS) 178 | { 179 | std::cerr << "AirspySource::get_device_names: Failed to open Airspy library: " << rc << ": " << airspy_error_name(rc) << std::endl; 180 | return; 181 | } 182 | 183 | for (i=0; i < AIRSPY_MAX_DEVICE; i++) 184 | { 185 | rc = (airspy_error) airspy_open(&airspy_ptr); 186 | std::cerr << "AirspySource::get_device_names: try to get device " << i << " serial number" << std::endl; 187 | 188 | if (rc == AIRSPY_SUCCESS) 189 | { 190 | std::cerr << "AirspySource::get_device_names: device " << i << " open OK" << std::endl; 191 | 192 | rc = (airspy_error) airspy_board_partid_serialno_read(airspy_ptr, &read_partid_serialno); 193 | 194 | if (rc == AIRSPY_SUCCESS) 195 | { 196 | serial_msb = read_partid_serialno.serial_no[2]; 197 | serial_lsb = read_partid_serialno.serial_no[3]; 198 | std::ostringstream devname_ostr; 199 | devname_ostr << "Serial " << std::hex << std::setw(8) << std::setfill('0') << serial_msb << serial_lsb; 200 | devices.push_back(devname_ostr.str()); 201 | } 202 | else 203 | { 204 | std::cerr << "AirspySource::get_device_names: failed to get device " << i << " serial number: " << rc << ": " << airspy_error_name(rc) << std::endl; 205 | } 206 | 207 | airspy_close(airspy_ptr); 208 | } 209 | else 210 | { 211 | std::cerr << "AirspySource::get_device_names: enumerated " << i << " Airspy devices: " << airspy_error_name(rc) << std::endl; 212 | break; // finished 213 | } 214 | } 215 | 216 | rc = (airspy_error) airspy_exit(); 217 | std::cerr << "AirspySource::get_device_names: Airspy library exit: " << rc << ": " << airspy_error_name(rc) << std::endl; 218 | } 219 | 220 | std::uint32_t AirspySource::get_sample_rate() 221 | { 222 | return m_sampleRate; 223 | } 224 | 225 | std::uint32_t AirspySource::get_frequency() 226 | { 227 | return m_frequency; 228 | } 229 | 230 | void AirspySource::print_specific_parms() 231 | { 232 | fprintf(stderr, "LNA gain: %d\n", m_lnaGain); 233 | fprintf(stderr, "Mixer gain: %d\n", m_mixGain); 234 | fprintf(stderr, "VGA gain: %d\n", m_vgaGain); 235 | fprintf(stderr, "Antenna bias %s\n", m_biasAnt ? "enabled" : "disabled"); 236 | fprintf(stderr, "LNA AGC %s\n", m_lnaAGC ? "enabled" : "disabled"); 237 | fprintf(stderr, "Mixer AGC %s\n", m_mixAGC ? "enabled" : "disabled"); 238 | } 239 | 240 | bool AirspySource::configure(int sampleRateIndex, 241 | uint32_t frequency, 242 | bool bias_ant, 243 | int lna_gain, 244 | int mix_gain, 245 | int vga_gain, 246 | bool lna_agc, 247 | bool mix_agc 248 | ) 249 | { 250 | m_frequency = frequency; 251 | m_biasAnt = bias_ant; 252 | m_lnaGain = lna_gain; 253 | m_mixGain = mix_gain; 254 | m_vgaGain = vga_gain; 255 | m_lnaAGC = lna_agc; 256 | m_mixAGC = mix_agc; 257 | 258 | airspy_error rc; 259 | 260 | if (!m_dev) { 261 | return false; 262 | } 263 | 264 | rc = (airspy_error) airspy_set_freq(m_dev, static_cast(m_frequency)); 265 | 266 | if (rc != AIRSPY_SUCCESS) 267 | { 268 | std::ostringstream err_ostr; 269 | err_ostr << "Could not set center frequency to " << m_frequency << " Hz"; 270 | m_error = err_ostr.str(); 271 | return false; 272 | } 273 | 274 | rc = (airspy_error) airspy_set_samplerate(m_dev, static_cast(sampleRateIndex)); 275 | 276 | if (rc != AIRSPY_SUCCESS) 277 | { 278 | std::ostringstream err_ostr; 279 | err_ostr << "Could not set center sample rate to " << m_srates[sampleRateIndex] << " Hz"; 280 | m_error = err_ostr.str(); 281 | return false; 282 | } 283 | else 284 | { 285 | m_sampleRate = m_srates[sampleRateIndex]; 286 | } 287 | 288 | rc = (airspy_error) airspy_set_lna_gain(m_dev, m_lnaGain); 289 | 290 | if (rc != AIRSPY_SUCCESS) 291 | { 292 | std::ostringstream err_ostr; 293 | err_ostr << "Could not set LNA gain to " << m_lnaGain << " dB"; 294 | m_error = err_ostr.str(); 295 | return false; 296 | } 297 | 298 | rc = (airspy_error) airspy_set_mixer_gain(m_dev, m_mixGain); 299 | 300 | if (rc != AIRSPY_SUCCESS) 301 | { 302 | std::ostringstream err_ostr; 303 | err_ostr << "Could not set mixer gain to " << m_mixGain << " dB"; 304 | m_error = err_ostr.str(); 305 | return false; 306 | } 307 | 308 | rc = (airspy_error) airspy_set_vga_gain(m_dev, m_vgaGain); 309 | 310 | if (rc != AIRSPY_SUCCESS) 311 | { 312 | std::ostringstream err_ostr; 313 | err_ostr << "Could not set VGA gain to " << m_vgaGain << " dB"; 314 | m_error = err_ostr.str(); 315 | return false; 316 | } 317 | 318 | rc = (airspy_error) airspy_set_rf_bias(m_dev, (m_biasAnt ? 1 : 0)); 319 | 320 | if (rc != AIRSPY_SUCCESS) 321 | { 322 | std::ostringstream err_ostr; 323 | err_ostr << "Could not set bias antenna to " << m_biasAnt; 324 | m_error = err_ostr.str(); 325 | return false; 326 | } 327 | 328 | rc = (airspy_error) airspy_set_lna_agc(m_dev, (m_lnaAGC ? 1 : 0)); 329 | 330 | if (rc != AIRSPY_SUCCESS) 331 | { 332 | std::ostringstream err_ostr; 333 | err_ostr << "Could not set LNA AGC to " << m_lnaAGC; 334 | m_error = err_ostr.str(); 335 | return false; 336 | } 337 | 338 | rc = (airspy_error) airspy_set_mixer_agc(m_dev, (m_mixAGC ? 1 : 0)); 339 | 340 | if (rc != AIRSPY_SUCCESS) 341 | { 342 | std::ostringstream err_ostr; 343 | err_ostr << "Could not set mixer AGC to " << m_mixAGC; 344 | m_error = err_ostr.str(); 345 | return false; 346 | } 347 | 348 | return true; 349 | } 350 | 351 | bool AirspySource::configure(std::string configurationStr) 352 | { 353 | namespace qi = boost::spirit::qi; 354 | std::string::iterator begin = configurationStr.begin(); 355 | std::string::iterator end = configurationStr.end(); 356 | 357 | int sampleRateIndex = 0; 358 | uint32_t frequency = 100000000; 359 | int lnaGain = 8; 360 | int mixGain = 8; 361 | int vgaGain = 0; 362 | bool antBias = false; 363 | bool lnaAGC = false; 364 | bool mixAGC = false; 365 | 366 | parsekv::key_value_sequence p; 367 | parsekv::pairs_type m; 368 | 369 | if (!qi::parse(begin, end, p, m)) 370 | { 371 | m_error = "Configuration parsing failed\n"; 372 | return false; 373 | } 374 | else 375 | { 376 | if (m.find("srate") != m.end()) 377 | { 378 | std::cerr << "AirspySource::configure: srate: " << m["srate"] << std::endl; 379 | 380 | if (strcasecmp(m["srate"].c_str(), "list") == 0) 381 | { 382 | m_error = "Available sample rates (Hz): " + m_sratesStr; 383 | return false; 384 | } 385 | 386 | m_sampleRate = atoi(m["srate"].c_str()); 387 | uint32_t i; 388 | 389 | for (i = 0; i < m_srates.size(); i++) 390 | { 391 | if (m_srates[i] == static_cast(m_sampleRate)) 392 | { 393 | sampleRateIndex = i; 394 | break; 395 | } 396 | } 397 | 398 | if (i == m_srates.size()) 399 | { 400 | m_error = "Invalid sample rate"; 401 | m_sampleRate = 0; 402 | return false; 403 | } 404 | } 405 | 406 | if (m.find("freq") != m.end()) 407 | { 408 | std::cerr << "AirspySource::configure: freq: " << m["freq"] << std::endl; 409 | frequency = atoi(m["freq"].c_str()); 410 | 411 | if ((frequency < 24000000) || (frequency > 1800000000)) 412 | { 413 | m_error = "Invalid frequency"; 414 | return false; 415 | } 416 | } 417 | 418 | if (m.find("lgain") != m.end()) 419 | { 420 | std::cerr << "AirspySource::configure: lgain: " << m["lgain"] << std::endl; 421 | 422 | if (strcasecmp(m["lgain"].c_str(), "list") == 0) 423 | { 424 | m_error = "Available LNA gains (dB): " + m_lgainsStr; 425 | return false; 426 | } 427 | 428 | lnaGain = atoi(m["lgain"].c_str()); 429 | 430 | if (find(m_lgains.begin(), m_lgains.end(), lnaGain) == m_lgains.end()) 431 | { 432 | m_error = "LNA gain not supported. Available gains (dB): " + m_lgainsStr; 433 | return false; 434 | } 435 | } 436 | 437 | if (m.find("mgain") != m.end()) 438 | { 439 | std::cerr << "AirspySource::configure: mgain: " << m["mgain"] << std::endl; 440 | 441 | if (strcasecmp(m["mgain"].c_str(), "list") == 0) 442 | { 443 | m_error = "Available mixer gains (dB): " + m_mgainsStr; 444 | return false; 445 | } 446 | 447 | mixGain = atoi(m["mgain"].c_str()); 448 | 449 | if (find(m_mgains.begin(), m_mgains.end(), mixGain) == m_mgains.end()) 450 | { 451 | m_error = "Mixer gain not supported. Available gains (dB): " + m_mgainsStr; 452 | return false; 453 | } 454 | } 455 | 456 | if (m.find("vgain") != m.end()) 457 | { 458 | std::cerr << "AirspySource::configure: vgain: " << m["vgain"] << std::endl; 459 | vgaGain = atoi(m["vgain"].c_str()); 460 | 461 | if (strcasecmp(m["vgain"].c_str(), "list") == 0) 462 | { 463 | m_error = "Available VGA gains (dB): " + m_vgainsStr; 464 | return false; 465 | } 466 | 467 | if (find(m_vgains.begin(), m_vgains.end(), vgaGain) == m_vgains.end()) 468 | { 469 | m_error = "VGA gain not supported. Available gains (dB): " + m_vgainsStr; 470 | return false; 471 | } 472 | } 473 | 474 | if (m.find("antbias") != m.end()) 475 | { 476 | std::cerr << "AirspySource::configure: antbias" << std::endl; 477 | antBias = true; 478 | } 479 | 480 | if (m.find("lagc") != m.end()) 481 | { 482 | std::cerr << "AirspySource::configure: lagc" << std::endl; 483 | lnaAGC = true; 484 | } 485 | 486 | if (m.find("magc") != m.end()) 487 | { 488 | std::cerr << "AirspySource::configure: magc" << std::endl; 489 | mixAGC = true; 490 | } 491 | } 492 | 493 | m_confFreq = frequency; 494 | double tuner_freq = frequency + 0.25 * m_srates[sampleRateIndex]; 495 | return configure(sampleRateIndex, tuner_freq, antBias, lnaGain, mixGain, vgaGain, lnaAGC, mixAGC); 496 | } 497 | 498 | bool AirspySource::start(DataBuffer *buf, std::atomic_bool *stop_flag) 499 | { 500 | m_buf = buf; 501 | m_stop_flag = stop_flag; 502 | 503 | if (m_thread == 0) 504 | { 505 | std::cerr << "AirspySource::start: starting" << std::endl; 506 | m_running = true; 507 | m_thread = new std::thread(run, m_dev, stop_flag); 508 | sleep(1); 509 | return *this; 510 | } 511 | else 512 | { 513 | std::cerr << "AirspySource::start: error" << std::endl; 514 | m_error = "Source thread already started"; 515 | return false; 516 | } 517 | } 518 | 519 | void AirspySource::run(airspy_device* dev, std::atomic_bool *stop_flag) 520 | { 521 | std::cerr << "AirspySource::run" << std::endl; 522 | 523 | airspy_error rc = (airspy_error) airspy_start_rx(dev, rx_callback, 0); 524 | 525 | if (rc == AIRSPY_SUCCESS) 526 | { 527 | while (!stop_flag->load() && (airspy_is_streaming(dev) == AIRSPY_TRUE)) 528 | { 529 | sleep(1); 530 | } 531 | 532 | rc = (airspy_error) airspy_stop_rx(dev); 533 | 534 | if (rc != AIRSPY_SUCCESS) 535 | { 536 | std::cerr << "AirspySource::run: Cannot stop Airspy Rx: " << rc << ": " << airspy_error_name(rc) << std::endl; 537 | } 538 | } 539 | else 540 | { 541 | std::cerr << "AirspySource::run: Cannot start Airspy Rx: " << rc << ": " << airspy_error_name(rc) << std::endl; 542 | } 543 | } 544 | 545 | bool AirspySource::stop() 546 | { 547 | std::cerr << "AirspySource::stop" << std::endl; 548 | 549 | m_thread->join(); 550 | delete m_thread; 551 | return true; 552 | } 553 | 554 | int AirspySource::rx_callback(airspy_transfer_t* transfer) 555 | { 556 | int len = transfer->sample_count * 2; // interleaved I/Q samples 557 | 558 | if (m_this) 559 | { 560 | m_this->callback((short *) transfer->samples, len); 561 | } 562 | 563 | return 0; 564 | } 565 | 566 | void AirspySource::callback(const short* buf, int len) 567 | { 568 | IQSampleVector iqsamples; 569 | 570 | iqsamples.resize(len/2); 571 | 572 | for (int i = 0, j = 0; i < len; i+=2, j++) 573 | { 574 | int32_t re = buf[i]; 575 | int32_t im = buf[i+1]; 576 | iqsamples[j] = IQSample( re / IQSample::value_type(1<<11), // 12 bits samples 577 | im / IQSample::value_type(1<<11) ); 578 | } 579 | 580 | m_buf->push(move(iqsamples)); 581 | } 582 | -------------------------------------------------------------------------------- /sfmbase/AudioOutput.cpp: -------------------------------------------------------------------------------- 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 | #define _FILE_OFFSET_BITS 64 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include "SoftFM.h" 31 | #include "AudioOutput.h" 32 | 33 | 34 | 35 | /* **************** class AudioOutput **************** */ 36 | 37 | // Encode a list of samples as signed 16-bit little-endian integers. 38 | void AudioOutput::samplesToInt16(const SampleVector& samples, 39 | std::vector& bytes) 40 | { 41 | bytes.resize(2 * samples.size()); 42 | 43 | SampleVector::const_iterator i = samples.begin(); 44 | SampleVector::const_iterator n = samples.end(); 45 | std::vector::iterator k = bytes.begin(); 46 | 47 | while (i != n) { 48 | Sample s = *(i++); 49 | s = std::max(Sample(-1.0), std::min(Sample(1.0), s)); 50 | long v = lrint(s * 32767); 51 | unsigned long u = v; 52 | *(k++) = u & 0xff; 53 | *(k++) = (u >> 8) & 0xff; 54 | } 55 | } 56 | 57 | 58 | /* **************** class RawAudioOutput **************** */ 59 | 60 | // Construct raw audio writer. 61 | RawAudioOutput::RawAudioOutput(const std::string& filename) 62 | { 63 | if (filename == "-") { 64 | 65 | m_fd = STDOUT_FILENO; 66 | 67 | } else { 68 | 69 | m_fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); 70 | if (m_fd < 0) { 71 | m_error = "can not open '" + filename + "' (" + 72 | strerror(errno) + ")"; 73 | m_zombie = true; 74 | return; 75 | } 76 | 77 | } 78 | } 79 | 80 | 81 | // Destructor. 82 | RawAudioOutput::~RawAudioOutput() 83 | { 84 | // Close file descriptor. 85 | if (m_fd >= 0 && m_fd != STDOUT_FILENO) { 86 | close(m_fd); 87 | } 88 | } 89 | 90 | 91 | // Write audio data. 92 | bool RawAudioOutput::write(const SampleVector& samples) 93 | { 94 | if (m_fd < 0) 95 | return false; 96 | 97 | // Convert samples to bytes. 98 | samplesToInt16(samples, m_bytebuf); 99 | 100 | // Write data. 101 | std::size_t p = 0; 102 | std::size_t n = m_bytebuf.size(); 103 | while (p < n) { 104 | 105 | ssize_t k = ::write(m_fd, m_bytebuf.data() + p, n - p); 106 | if (k <= 0) { 107 | if (k == 0 || errno != EINTR) { 108 | m_error = "write failed ("; 109 | m_error += strerror(errno); 110 | m_error += ")"; 111 | return false; 112 | } 113 | } else { 114 | p += k; 115 | } 116 | } 117 | 118 | return true; 119 | } 120 | 121 | 122 | /* **************** class WavAudioOutput **************** */ 123 | 124 | // Construct .WAV writer. 125 | WavAudioOutput::WavAudioOutput(const std::string& filename, 126 | unsigned int samplerate, 127 | bool stereo) 128 | : numberOfChannels(stereo ? 2 : 1) 129 | , sampleRate(samplerate) 130 | { 131 | m_stream = fopen(filename.c_str(), "wb"); 132 | if (m_stream == NULL) { 133 | m_error = "can not open '" + filename + "' (" + 134 | strerror(errno) + ")"; 135 | m_zombie = true; 136 | return; 137 | } 138 | 139 | // Write initial header with a dummy sample count. 140 | // This will be replaced with the actual header once the WavFile is closed. 141 | if (!write_header(0x7fff0000)) { 142 | m_error = "can not write to '" + filename + "' (" + 143 | strerror(errno) + ")"; 144 | m_zombie = true; 145 | } 146 | } 147 | 148 | 149 | // Destructor. 150 | WavAudioOutput::~WavAudioOutput() 151 | { 152 | // We need to go back and fill in the header ... 153 | 154 | if (!m_zombie) { 155 | 156 | const unsigned bytesPerSample = 2; 157 | 158 | const long currentPosition = ftell(m_stream); 159 | 160 | assert((currentPosition - 44) % bytesPerSample == 0); 161 | 162 | const unsigned totalNumberOfSamples = (currentPosition - 44) / bytesPerSample; 163 | 164 | assert(totalNumberOfSamples % numberOfChannels == 0); 165 | 166 | // Put header in front 167 | 168 | if (fseek(m_stream, 0, SEEK_SET) == 0) { 169 | write_header(totalNumberOfSamples); 170 | } 171 | } 172 | 173 | // Done writing the file 174 | 175 | if (m_stream) { 176 | fclose(m_stream); 177 | } 178 | } 179 | 180 | 181 | // Write audio data. 182 | bool WavAudioOutput::write(const SampleVector& samples) 183 | { 184 | if (m_zombie) 185 | return false; 186 | 187 | // Convert samples to bytes. 188 | samplesToInt16(samples, m_bytebuf); 189 | 190 | // Write samples to file. 191 | std::size_t k = fwrite(m_bytebuf.data(), 1, m_bytebuf.size(), m_stream); 192 | if (k != m_bytebuf.size()) { 193 | m_error = "write failed ("; 194 | m_error += strerror(errno); 195 | m_error += ")"; 196 | return false; 197 | } 198 | 199 | return true; 200 | } 201 | 202 | 203 | // (Re)write .WAV header. 204 | bool WavAudioOutput::write_header(unsigned int nsamples) 205 | { 206 | const unsigned bytesPerSample = 2; 207 | const unsigned bitsPerSample = 16; 208 | 209 | enum wFormatTagId 210 | { 211 | WAVE_FORMAT_PCM = 0x0001, 212 | WAVE_FORMAT_IEEE_FLOAT = 0x0003 213 | }; 214 | 215 | assert(nsamples % numberOfChannels == 0); 216 | 217 | // synthesize header 218 | 219 | uint8_t wavHeader[44]; 220 | 221 | encode_chunk_id (wavHeader + 0, "RIFF"); 222 | set_value(wavHeader + 4, 36 + nsamples * bytesPerSample); 223 | encode_chunk_id (wavHeader + 8, "WAVE"); 224 | encode_chunk_id (wavHeader + 12, "fmt "); 225 | set_value(wavHeader + 16, 16); 226 | set_value(wavHeader + 20, WAVE_FORMAT_PCM); 227 | set_value(wavHeader + 22, numberOfChannels); 228 | set_value(wavHeader + 24, sampleRate ); // sample rate 229 | set_value(wavHeader + 28, sampleRate * numberOfChannels * bytesPerSample); // byte rate 230 | set_value(wavHeader + 32, numberOfChannels * bytesPerSample); // block size 231 | set_value(wavHeader + 34, bitsPerSample); 232 | encode_chunk_id (wavHeader + 36, "data"); 233 | set_value(wavHeader + 40, nsamples * bytesPerSample); 234 | 235 | return fwrite(wavHeader, 1, 44, m_stream) == 44; 236 | } 237 | 238 | 239 | void WavAudioOutput::encode_chunk_id(uint8_t * ptr, const char * chunkname) 240 | { 241 | for (unsigned i = 0; i < 4; ++i) 242 | { 243 | assert(chunkname[i] != '\0'); 244 | ptr[i] = chunkname[i]; 245 | } 246 | assert(chunkname[4] == '\0'); 247 | } 248 | 249 | 250 | template 251 | void WavAudioOutput::set_value(uint8_t * ptr, T value) 252 | { 253 | for (std::size_t i = 0; i < sizeof(T); ++i) 254 | { 255 | ptr[i] = value & 0xff; 256 | value >>= 8; 257 | } 258 | } 259 | 260 | 261 | /* **************** class AlsaAudioOutput **************** */ 262 | 263 | // Construct ALSA output stream. 264 | AlsaAudioOutput::AlsaAudioOutput(const std::string& devname, 265 | unsigned int samplerate, 266 | bool stereo) 267 | { 268 | m_pcm = NULL; 269 | m_nchannels = stereo ? 2 : 1; 270 | 271 | int r = snd_pcm_open(&m_pcm, devname.c_str(), 272 | SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); 273 | 274 | if (r < 0) { 275 | m_error = "can not open PCM device '" + devname + "' (" + 276 | strerror(-r) + ")"; 277 | m_zombie = true; 278 | return; 279 | } 280 | 281 | snd_pcm_nonblock(m_pcm, 0); 282 | 283 | r = snd_pcm_set_params(m_pcm, 284 | SND_PCM_FORMAT_S16_LE, 285 | SND_PCM_ACCESS_RW_INTERLEAVED, 286 | m_nchannels, 287 | samplerate, 288 | 1, // allow soft resampling 289 | 500000); // latency in us 290 | 291 | if (r < 0) { 292 | m_error = "can not set PCM parameters ("; 293 | m_error += strerror(-r); 294 | m_error += ")"; 295 | m_zombie = true; 296 | } 297 | } 298 | 299 | 300 | // Destructor. 301 | AlsaAudioOutput::~AlsaAudioOutput() 302 | { 303 | // Close device. 304 | if (m_pcm != NULL) { 305 | snd_pcm_close(m_pcm); 306 | } 307 | } 308 | 309 | 310 | // Write audio data. 311 | bool AlsaAudioOutput::write(const SampleVector& samples) 312 | { 313 | if (m_zombie) 314 | return false; 315 | 316 | // Convert samples to bytes. 317 | samplesToInt16(samples, m_bytebuf); 318 | 319 | // Write data. 320 | unsigned int p = 0; 321 | unsigned int n = samples.size() / m_nchannels; 322 | unsigned int framesize = 2 * m_nchannels; 323 | while (p < n) { 324 | 325 | int k = snd_pcm_writei(m_pcm, 326 | m_bytebuf.data() + p * framesize, n - p); 327 | if (k < 0) { 328 | m_error = "write failed ("; 329 | m_error += strerror(errno); 330 | m_error += ")"; 331 | // After an underrun, ALSA keeps returning error codes until we 332 | // explicitly fix the stream. 333 | snd_pcm_recover(m_pcm, k, 0); 334 | return false; 335 | } else { 336 | p += k; 337 | } 338 | } 339 | 340 | return true; 341 | } 342 | 343 | /* end */ 344 | -------------------------------------------------------------------------------- /sfmbase/BladeRFSource.cpp: -------------------------------------------------------------------------------- 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 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "util.h" 28 | #include "parsekv.h" 29 | #include "BladeRFSource.h" 30 | 31 | BladeRFSource *BladeRFSource::m_this = 0; 32 | const std::vector BladeRFSource::m_lnaGains({0, 3, 6}); 33 | const std::vector BladeRFSource::m_vga1Gains({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}); 34 | const std::vector BladeRFSource::m_vga2Gains({0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30}); 35 | const std::vector BladeRFSource::m_halfbw({750000, 875000, 1250000, 1375000, 1500000, 1920000, 2500000, 2750000, 3000000, 3500000, 4375000, 5000000, 6000000, 7000000, 10000000, 14000000}); 36 | 37 | // Open BladeRF device. 38 | BladeRFSource::BladeRFSource(const char *serial) : 39 | m_dev(0), 40 | m_sampleRate(1000000), 41 | m_actualSampleRate(1000000), 42 | m_frequency(300000000), 43 | m_minFrequency(300000000), 44 | m_bandwidth(1500000), 45 | m_actualBandwidth(1500000), 46 | m_lnaGain(3), 47 | m_vga1Gain(6), 48 | m_vga2Gain(5), 49 | m_thread(0) 50 | { 51 | int status; 52 | struct bladerf_devinfo info; 53 | 54 | bladerf_init_devinfo(&info); 55 | 56 | if (serial != 0) 57 | { 58 | strncpy(info.serial, serial, BLADERF_SERIAL_LENGTH - 1); 59 | info.serial[BLADERF_SERIAL_LENGTH - 1] = '\0'; 60 | } 61 | 62 | status = bladerf_open_with_devinfo(&m_dev, &info); 63 | 64 | if (status == BLADERF_ERR_NODEV) 65 | { 66 | std::ostringstream err_ostr; 67 | err_ostr << "No devices available with serial=" << serial; 68 | m_error = err_ostr.str(); 69 | m_dev = 0; 70 | } 71 | else if (status != 0) 72 | { 73 | std::ostringstream err_ostr; 74 | err_ostr << "Failed to open device with serial=" << serial; 75 | m_error = err_ostr.str(); 76 | m_dev = 0; 77 | } 78 | else 79 | { 80 | int fpga_loaded = bladerf_is_fpga_configured(m_dev); 81 | 82 | if (fpga_loaded < 0) 83 | { 84 | std::ostringstream err_ostr; 85 | err_ostr << "Failed to check FPGA state: " << bladerf_strerror(fpga_loaded); 86 | m_error = err_ostr.str(); 87 | m_dev = 0; 88 | } 89 | else if (fpga_loaded == 0) 90 | { 91 | m_error = "The device's FPGA is not loaded."; 92 | m_dev = 0; 93 | } 94 | else 95 | { 96 | if ((status = bladerf_sync_config(m_dev, BLADERF_MODULE_RX, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0) 97 | { 98 | std::ostringstream err_ostr; 99 | err_ostr << "bladerf_sync_config failed with return code " << status; 100 | m_error = err_ostr.str(); 101 | m_dev = 0; 102 | } 103 | else 104 | { 105 | if ((status = bladerf_enable_module(m_dev, BLADERF_MODULE_RX, true)) < 0) 106 | { 107 | std::ostringstream err_ostr; 108 | err_ostr << "bladerf_enable_module failed with return code " << status; 109 | m_error = err_ostr.str(); 110 | m_dev = 0; 111 | } 112 | else 113 | { 114 | if (bladerf_expansion_attach(m_dev, BLADERF_XB_200) == 0) 115 | { 116 | std::cerr << "BladeRFSource::BladeRFSource: Attached XB200 extension" << std::endl; 117 | 118 | if ((status = bladerf_xb200_set_path(m_dev, BLADERF_MODULE_RX, BLADERF_XB200_MIX)) != 0) 119 | { 120 | std::cerr << "BladeRFSource::BladeRFSource: bladerf_xb200_set_path failed with return code " << status << std::endl; 121 | } 122 | else 123 | { 124 | if ((status = bladerf_xb200_set_filterbank(m_dev, BLADERF_MODULE_RX, BLADERF_XB200_AUTO_1DB)) != 0) 125 | { 126 | std::cerr << "BladeRFSource::BladeRFSource: bladerf_xb200_set_filterbank failed with return code " << status << std::endl; 127 | } 128 | else 129 | { 130 | std::cerr << "BladeRFSource::BladeRFSource: XB200 configured. Min freq set to 100kHz" << std::endl; 131 | m_minFrequency = 100000; 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | std::ostringstream lgains_ostr; 141 | 142 | for (int g: m_lnaGains) { 143 | lgains_ostr << g << " "; 144 | } 145 | 146 | m_lnaGainsStr = lgains_ostr.str(); 147 | 148 | std::ostringstream v1gains_ostr; 149 | 150 | for (int g: m_vga1Gains) { 151 | v1gains_ostr << g << " "; 152 | } 153 | 154 | m_vga1GainsStr = v1gains_ostr.str(); 155 | 156 | std::ostringstream v2gains_ostr; 157 | 158 | for (int g: m_vga2Gains) { 159 | v2gains_ostr << g << " "; 160 | } 161 | 162 | m_vga2GainsStr = v2gains_ostr.str(); 163 | 164 | std::ostringstream bw_ostr; 165 | 166 | for (int b: m_halfbw) { 167 | bw_ostr << 2*b << " "; 168 | } 169 | 170 | m_bwfiltStr = bw_ostr.str(); 171 | 172 | m_this = this; 173 | } 174 | 175 | 176 | // Close BladeRF device. 177 | BladeRFSource::~BladeRFSource() 178 | { 179 | if (m_dev) { 180 | bladerf_close(m_dev); 181 | } 182 | 183 | m_this = 0; 184 | } 185 | 186 | bool BladeRFSource::configure(std::string configurationStr) 187 | { 188 | namespace qi = boost::spirit::qi; 189 | std::string::iterator begin = configurationStr.begin(); 190 | std::string::iterator end = configurationStr.end(); 191 | 192 | uint32_t sample_rate = 1000000; 193 | uint32_t frequency = 300000000; 194 | uint32_t bandwidth = 1500000; 195 | int lnaGainIndex = 2; // 3 dB 196 | int vga1Gain = 20; 197 | int vga2Gain = 9; 198 | 199 | parsekv::key_value_sequence p; 200 | parsekv::pairs_type m; 201 | 202 | if (!qi::parse(begin, end, p, m)) 203 | { 204 | m_error = "Configuration parsing failed\n"; 205 | return false; 206 | } 207 | else 208 | { 209 | if (m.find("srate") != m.end()) 210 | { 211 | std::cerr << "BladeRFSource::configure: srate: " << m["srate"] << std::endl; 212 | sample_rate = atoi(m["srate"].c_str()); 213 | 214 | if ((sample_rate < 48000) || (sample_rate > 40000000)) 215 | { 216 | m_error = "Invalid sample rate"; 217 | return false; 218 | } 219 | } 220 | 221 | if (m.find("freq") != m.end()) 222 | { 223 | std::cerr << "BladeRFSource::configure: freq: " << m["freq"] << std::endl; 224 | frequency = atoi(m["freq"].c_str()); 225 | 226 | if ((frequency < m_minFrequency) || (frequency > 3800000000)) 227 | { 228 | m_error = "Invalid frequency"; 229 | return false; 230 | } 231 | } 232 | 233 | if (m.find("bw") != m.end()) 234 | { 235 | std::cerr << "BladeRFSource::configure: bw: " << m["bw"] << std::endl; 236 | 237 | if (strcasecmp(m["bw"].c_str(), "list") == 0) 238 | { 239 | m_error = "Available bandwidths (Hz): " + m_bwfiltStr; 240 | return false; 241 | } 242 | 243 | bandwidth = atoi(m["bw"].c_str()); 244 | 245 | if (bandwidth < 0) 246 | { 247 | m_error = "Invalid bandwidth"; 248 | return false; 249 | } 250 | } 251 | 252 | if (m.find("v1gain") != m.end()) 253 | { 254 | std::cerr << "BladeRFSource::configure: v1gain: " << m["v1gain"] << std::endl; 255 | 256 | if (strcasecmp(m["v1gain"].c_str(), "list") == 0) 257 | { 258 | m_error = "Available VGA1 gains (dB): " + m_vga1GainsStr; 259 | return false; 260 | } 261 | 262 | vga1Gain = atoi(m["v1gain"].c_str()); 263 | 264 | if (find(m_vga1Gains.begin(), m_vga1Gains.end(), vga1Gain) == m_vga1Gains.end()) 265 | { 266 | m_error = "VGA1 gain not supported. Available gains (dB): " + m_vga1GainsStr; 267 | return false; 268 | } 269 | } 270 | 271 | if (m.find("v2gain") != m.end()) 272 | { 273 | std::cerr << "BladeRFSource::configure: v2gain: " << m["v2gain"] << std::endl; 274 | 275 | if (strcasecmp(m["v2gain"].c_str(), "list") == 0) 276 | { 277 | m_error = "Available VGA2 gains (dB): " + m_vga2GainsStr; 278 | return false; 279 | } 280 | 281 | vga1Gain = atoi(m["v2gain"].c_str()); 282 | 283 | if (find(m_vga2Gains.begin(), m_vga2Gains.end(), vga2Gain) == m_vga2Gains.end()) 284 | { 285 | m_error = "VGA2 gain not supported. Available gains (dB): " + m_vga2GainsStr; 286 | return false; 287 | } 288 | } 289 | 290 | if (m.find("lgain") != m.end()) 291 | { 292 | std::cerr << "BladeRFSource::configure: lgain: " << m["lgain"] << std::endl; 293 | 294 | if (strcasecmp(m["lgain"].c_str(), "list") == 0) 295 | { 296 | m_error = "Available LNA gains (dB): " + m_lnaGainsStr; 297 | return false; 298 | } 299 | 300 | int lnaGain = atoi(m["lgain"].c_str()); 301 | uint32_t i; 302 | 303 | for (i = 0; i < m_lnaGains.size(); i++) 304 | { 305 | if (m_lnaGains[i] == lnaGain) 306 | { 307 | lnaGainIndex = i+1; 308 | break; 309 | } 310 | } 311 | 312 | if (i == m_lnaGains.size()) 313 | { 314 | m_error = "Invalid LNA gain"; 315 | return false; 316 | } 317 | } 318 | 319 | // Intentionally tune at a higher frequency to avoid DC offset. 320 | m_confFreq = frequency; 321 | double tuner_freq = frequency + 0.25 * sample_rate; 322 | 323 | return configure(sample_rate, tuner_freq, bandwidth, lnaGainIndex, vga1Gain, vga2Gain); 324 | } 325 | } 326 | 327 | // Configure RTL-SDR tuner and prepare for streaming. 328 | bool BladeRFSource::configure(uint32_t sample_rate, 329 | uint32_t frequency, 330 | uint32_t bandwidth, 331 | int lna_gainIndex, 332 | int vga1_gain, 333 | int vga2_gain) 334 | { 335 | m_frequency = frequency; 336 | m_vga1Gain = vga1_gain; 337 | m_vga2Gain = vga2_gain; 338 | m_lnaGain = m_lnaGains[lna_gainIndex-1]; 339 | 340 | if (bladerf_set_sample_rate(m_dev, BLADERF_MODULE_RX, sample_rate, &m_actualSampleRate) < 0) 341 | { 342 | m_error = "Cannot set sample rate"; 343 | return false; 344 | } 345 | 346 | if (bladerf_set_frequency( m_dev, BLADERF_MODULE_RX, frequency ) != 0) 347 | { 348 | m_error = "Cannot set Rx frequency"; 349 | return false; 350 | } 351 | 352 | if (bladerf_set_bandwidth(m_dev, BLADERF_MODULE_RX, bandwidth, &m_actualBandwidth) < 0) 353 | { 354 | m_error = "Cannot set Rx bandwidth"; 355 | return false; 356 | } 357 | 358 | if (bladerf_set_lna_gain(m_dev, static_cast(lna_gainIndex)) != 0) 359 | { 360 | m_error = "Cannot set LNA gain"; 361 | return false; 362 | } 363 | 364 | if (bladerf_set_rxvga1(m_dev, vga1_gain) != 0) 365 | { 366 | m_error = "Cannot set VGA1 gain"; 367 | return false; 368 | } 369 | 370 | if (bladerf_set_rxvga2(m_dev, vga2_gain) != 0) 371 | { 372 | m_error = "Cannot set VGA2 gain"; 373 | return false; 374 | } 375 | 376 | return true; 377 | } 378 | 379 | 380 | // Return current sample frequency in Hz. 381 | uint32_t BladeRFSource::get_sample_rate() 382 | { 383 | return m_actualSampleRate; 384 | } 385 | 386 | // Return device current center frequency in Hz. 387 | uint32_t BladeRFSource::get_frequency() 388 | { 389 | return static_cast(m_frequency); 390 | } 391 | 392 | void BladeRFSource::print_specific_parms() 393 | { 394 | fprintf(stderr, "Bandwidth: %d\n", m_actualBandwidth); 395 | fprintf(stderr, "LNA gain: %d\n", m_lnaGain); 396 | fprintf(stderr, "VGA1 gain: %d\n", m_vga1Gain); 397 | fprintf(stderr, "VGA2 gain: %d\n", m_vga2Gain); 398 | } 399 | 400 | bool BladeRFSource::start(DataBuffer* buf, std::atomic_bool *stop_flag) 401 | { 402 | m_buf = buf; 403 | m_stop_flag = stop_flag; 404 | 405 | if (m_thread == 0) 406 | { 407 | m_thread = new std::thread(run); 408 | return true; 409 | } 410 | else 411 | { 412 | m_error = "Source thread already started"; 413 | return false; 414 | } 415 | } 416 | 417 | bool BladeRFSource::stop() 418 | { 419 | if (m_thread) 420 | { 421 | m_thread->join(); 422 | delete m_thread; 423 | m_thread = 0; 424 | } 425 | 426 | return true; 427 | } 428 | 429 | void BladeRFSource::run() 430 | { 431 | IQSampleVector iqsamples; 432 | 433 | while (!m_this->m_stop_flag->load() && get_samples(&iqsamples)) 434 | { 435 | m_this->m_buf->push(move(iqsamples)); 436 | } 437 | } 438 | 439 | // Fetch a bunch of samples from the device. 440 | bool BladeRFSource::get_samples(IQSampleVector *samples) 441 | { 442 | int res; 443 | std::vector buf(2*m_blockSize); 444 | 445 | if ((res = bladerf_sync_rx(m_this->m_dev, buf.data(), m_blockSize, 0, 10000)) < 0) 446 | { 447 | m_this->m_error = "bladerf_sync_rx failed"; 448 | return false; 449 | } 450 | 451 | samples->resize(m_blockSize); 452 | 453 | for (int i = 0; i < m_blockSize; i++) 454 | { 455 | int32_t re = buf[2*i]; 456 | int32_t im = buf[2*i+1]; 457 | (*samples)[i] = IQSample( re / IQSample::value_type(1<<11), 458 | im / IQSample::value_type(1<<11) ); 459 | } 460 | 461 | return true; 462 | } 463 | 464 | 465 | // Return a list of supported devices. 466 | void BladeRFSource::get_device_names(std::vector& devices) 467 | { 468 | struct bladerf_devinfo *devinfo = 0; 469 | 470 | int count = bladerf_get_device_list(&devinfo); 471 | 472 | for (int i = 0; i < count; i++) 473 | { 474 | devices.push_back(std::string(devinfo[i].serial)); 475 | } 476 | 477 | if (devinfo) 478 | { 479 | bladerf_free_device_list(devinfo); 480 | } 481 | } 482 | 483 | /* end */ 484 | -------------------------------------------------------------------------------- /sfmbase/Filter.cpp: -------------------------------------------------------------------------------- 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 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "Filter.h" 26 | 27 | 28 | 29 | /** Prepare Lanczos FIR filter coefficients. */ 30 | template 31 | static void make_lanczos_coeff(unsigned int filter_order, double cutoff, 32 | std::vector& coeff) 33 | { 34 | coeff.resize(filter_order + 1); 35 | 36 | // Prepare Lanczos FIR filter. 37 | // t[i] = (i - order/2) 38 | // coeff[i] = Sinc(2 * cutoff * t[i]) * Sinc(t[i] / (order/2 + 1)) 39 | // coeff /= sum(coeff) 40 | 41 | double ysum = 0.0; 42 | 43 | // Calculate filter kernel. 44 | for (int i = 0; i <= (int)filter_order; i++) { 45 | int t2 = 2 * i - filter_order; 46 | 47 | double y; 48 | if (t2 == 0) { 49 | y = 1.0; 50 | } else { 51 | double x1 = cutoff * t2; 52 | double x2 = t2 / double(filter_order + 2); 53 | y = ( sin(M_PI * x1) / M_PI / x1 ) * 54 | ( sin(M_PI * x2) / M_PI / x2 ); 55 | } 56 | 57 | coeff[i] = y; 58 | ysum += y; 59 | } 60 | 61 | // Apply correction factor to ensure unit gain at DC. 62 | for (unsigned i = 0; i <= filter_order; i++) { 63 | coeff[i] /= ysum; 64 | } 65 | } 66 | 67 | 68 | /* **************** class FineTuner **************** */ 69 | 70 | // Construct finetuner. 71 | FineTuner::FineTuner(unsigned int table_size, int freq_shift) 72 | : m_index(0) 73 | , m_table(table_size) 74 | { 75 | double phase_step = 2.0 * M_PI / double(table_size); 76 | for (unsigned int i = 0; i < table_size; i++) { 77 | double phi = (((int64_t)freq_shift * i) % table_size) * phase_step; 78 | double pcos = cos(phi); 79 | double psin = sin(phi); 80 | m_table[i] = IQSample(pcos, psin); 81 | } 82 | } 83 | 84 | 85 | // Process samples. 86 | void FineTuner::process(const IQSampleVector& samples_in, 87 | IQSampleVector& samples_out) 88 | { 89 | unsigned int tblidx = m_index; 90 | unsigned int tblsiz = m_table.size(); 91 | unsigned int n = samples_in.size(); 92 | 93 | samples_out.resize(n); 94 | 95 | for (unsigned int i = 0; i < n; i++) { 96 | samples_out[i] = samples_in[i] * m_table[tblidx]; 97 | tblidx++; 98 | if (tblidx == tblsiz) 99 | tblidx = 0; 100 | } 101 | 102 | m_index = tblidx; 103 | } 104 | 105 | 106 | /* **************** class LowPassFilterFirIQ **************** */ 107 | 108 | // Construct low-pass filter. 109 | LowPassFilterFirIQ::LowPassFilterFirIQ(unsigned int filter_order, double cutoff) 110 | : m_state(filter_order) 111 | { 112 | make_lanczos_coeff(filter_order, cutoff, m_coeff); 113 | } 114 | 115 | 116 | // Process samples. 117 | void LowPassFilterFirIQ::process(const IQSampleVector& samples_in, 118 | IQSampleVector& samples_out) 119 | { 120 | unsigned int order = m_state.size(); 121 | unsigned int n = samples_in.size(); 122 | 123 | samples_out.resize(n); 124 | 125 | if (n == 0) 126 | return; 127 | 128 | // NOTE: We use m_coeff the wrong way around because it is slightly 129 | // faster to scan forward through the array. The result is still correct 130 | // because the coefficients are symmetric. 131 | 132 | // The first few samples need data from m_state. 133 | unsigned int i = 0; 134 | for (; i < n && i < order; i++) { 135 | IQSample y = 0; 136 | for (unsigned int j = 0; j < order - i; j++) 137 | y += m_state[i+j] * m_coeff[j]; 138 | for (unsigned int j = order - i; j <= order; j++) 139 | y += samples_in[i-order+j] * m_coeff[j]; 140 | samples_out[i] = y; 141 | } 142 | 143 | // Remaining samples only need data from samples_in. 144 | for (; i < n; i++) { 145 | IQSample y = 0; 146 | IQSampleVector::const_iterator inp = samples_in.begin() + i - order; 147 | for (unsigned int j = 0; j <= order; j++) 148 | y += inp[j] * m_coeff[j]; 149 | samples_out[i] = y; 150 | } 151 | 152 | // Update m_state. 153 | if (n < order) { 154 | copy(m_state.begin() + n, m_state.end(), m_state.begin()); 155 | copy(samples_in.begin(), samples_in.end(), m_state.end() - n); 156 | } else { 157 | copy(samples_in.end() - order, samples_in.end(), m_state.begin()); 158 | } 159 | } 160 | 161 | 162 | /* **************** class DownsampleFilter **************** */ 163 | 164 | // Construct low-pass filter with optional downsampling. 165 | DownsampleFilter::DownsampleFilter(unsigned int filter_order, double cutoff, 166 | double downsample, bool integer_factor) 167 | : m_downsample(downsample) 168 | , m_downsample_int(integer_factor ? lrint(downsample) : 0) 169 | , m_pos_int(0) 170 | , m_pos_frac(0) 171 | , m_state(filter_order) 172 | { 173 | assert(downsample >= 1); 174 | assert(filter_order > 1); 175 | 176 | // Force the first coefficient to zero and append an extra zero at the 177 | // end of the array. This ensures we can always obtain (filter_order+1) 178 | // coefficients by linear interpolation between adjacent array elements. 179 | make_lanczos_coeff(filter_order - 1, cutoff, m_coeff); 180 | m_coeff.insert(m_coeff.begin(), 0); 181 | m_coeff.push_back(0); 182 | } 183 | 184 | 185 | // Process samples. 186 | void DownsampleFilter::process(const SampleVector& samples_in, 187 | SampleVector& samples_out) 188 | { 189 | unsigned int order = m_state.size(); 190 | unsigned int n = samples_in.size(); 191 | 192 | if (m_downsample_int != 0) { 193 | 194 | // Integer downsample factor, no linear interpolation. 195 | // This is relatively simple. 196 | 197 | unsigned int p = m_pos_int; 198 | unsigned int pstep = m_downsample_int; 199 | 200 | samples_out.resize((n - p + pstep - 1) / pstep); 201 | 202 | // The first few samples need data from m_state. 203 | unsigned int i = 0; 204 | for (; p < n && p < order; p += pstep, i++) { 205 | Sample y = 0; 206 | for (unsigned int j = 1; j <= p; j++) 207 | y += samples_in[p-j] * m_coeff[j]; 208 | for (unsigned int j = p + 1; j <= order; j++) 209 | y += m_state[order+p-j] * m_coeff[j]; 210 | samples_out[i] = y; 211 | } 212 | 213 | // Remaining samples only need data from samples_in. 214 | for (; p < n; p += pstep, i++) { 215 | Sample y = 0; 216 | for (unsigned int j = 1; j <= order; j++) 217 | y += samples_in[p-j] * m_coeff[j]; 218 | samples_out[i] = y; 219 | } 220 | 221 | assert(i == samples_out.size()); 222 | 223 | // Update index of start position in text sample block. 224 | m_pos_int = p - n; 225 | 226 | } else { 227 | 228 | // Fractional downsample factor via linear interpolation of 229 | // the FIR coefficient table. This is a bitch. 230 | 231 | // Estimate number of output samples we can produce in this run. 232 | Sample p = m_pos_frac; 233 | Sample pstep = m_downsample; 234 | unsigned int n_out = int(2 + n / pstep); 235 | 236 | samples_out.resize(n_out); 237 | 238 | // Produce output samples. 239 | unsigned int i = 0; 240 | Sample pf = p; 241 | unsigned int pi = int(pf); 242 | while (pi < n) { 243 | Sample k1 = pf - pi; 244 | Sample k0 = 1 - k1; 245 | 246 | Sample y = 0; 247 | for (unsigned int j = 0; j <= order; j++) { 248 | Sample k = m_coeff[j] * k0 + m_coeff[j+1] * k1; 249 | Sample s = (j <= pi) ? samples_in[pi-j] : m_state[order+pi-j]; 250 | y += k * s; 251 | } 252 | samples_out[i] = y; 253 | 254 | i++; 255 | pf = p + i * pstep; 256 | pi = int(pf); 257 | } 258 | 259 | // We may overestimate the number of samples by 1 or 2. 260 | assert(i <= n_out && i + 2 >= n_out); 261 | samples_out.resize(i); 262 | 263 | // Update fractional index of start position in text sample block. 264 | // Limit to 0 to avoid catastrophic results of rounding errors. 265 | m_pos_frac = pf - n; 266 | if (m_pos_frac < 0) 267 | m_pos_frac = 0; 268 | } 269 | 270 | // Update m_state. 271 | if (n < order) { 272 | copy(m_state.begin() + n, m_state.end(), m_state.begin()); 273 | copy(samples_in.begin(), samples_in.end(), m_state.end() - n); 274 | } else { 275 | copy(samples_in.end() - order, samples_in.end(), m_state.begin()); 276 | } 277 | 278 | } 279 | 280 | 281 | /* **************** class LowPassFilterRC **************** */ 282 | 283 | // Construct 1st order low-pass IIR filter. 284 | LowPassFilterRC::LowPassFilterRC(double timeconst) : 285 | m_timeconst(timeconst), 286 | m_y0_1(0), 287 | m_y1_1(0) 288 | { 289 | m_a1 = - exp(-1/m_timeconst);; 290 | m_b0 = 1 + m_a1; 291 | } 292 | 293 | 294 | // Process samples. 295 | void LowPassFilterRC::process(const SampleVector& samples_in, SampleVector& samples_out) 296 | { 297 | /* 298 | * Continuous domain: 299 | * H(s) = 1 / (1 - s * timeconst) 300 | * 301 | * Discrete domain: 302 | * H(z) = (1 - exp(-1/timeconst)) / (1 - exp(-1/timeconst) / z) 303 | */ 304 | unsigned int n = samples_in.size(); 305 | samples_out.resize(n); 306 | 307 | Sample y = m_y0_1; 308 | 309 | for (unsigned int i = 0; i < n; i++) 310 | { 311 | Sample x = samples_in[i]; 312 | y = m_b0 * x - m_a1 * y; 313 | samples_out[i] = y; 314 | } 315 | 316 | m_y0_1 = y; 317 | } 318 | 319 | // Process interleaved samples. 320 | void LowPassFilterRC::process_interleaved(const SampleVector& samples_in, SampleVector& samples_out) 321 | { 322 | /* 323 | * Continuous domain: 324 | * H(s) = 1 / (1 - s * timeconst) 325 | * 326 | * Discrete domain: 327 | * H(z) = (1 - exp(-1/timeconst)) / (1 - exp(-1/timeconst) / z) 328 | */ 329 | unsigned int n = samples_in.size(); 330 | samples_out.resize(n); 331 | 332 | Sample y0 = m_y0_1; 333 | Sample y1 = m_y1_1; 334 | 335 | for (unsigned int i = 0; i < n-1; i+=2) 336 | { 337 | Sample x0 = samples_in[i]; 338 | y0 = m_b0 * x0 - m_a1 * y0; 339 | samples_out[i] = y0; 340 | 341 | Sample x1 = samples_in[i+1]; 342 | y1 = m_b0 * x1 - m_a1 * y1; 343 | samples_out[i+1] = y1; 344 | } 345 | 346 | m_y0_1 = y0; 347 | m_y1_1 = y1; 348 | } 349 | 350 | 351 | // Process samples in-place. 352 | void LowPassFilterRC::process_inplace(SampleVector& samples) 353 | { 354 | unsigned int n = samples.size(); 355 | 356 | Sample y = m_y0_1; 357 | 358 | for (unsigned int i = 0; i < n; i++) 359 | { 360 | Sample x = samples[i]; 361 | y = m_b0 * x - m_a1 * y; 362 | samples[i] = y; 363 | } 364 | 365 | m_y0_1 = y; 366 | } 367 | 368 | // Process interleaved samples in-place. 369 | void LowPassFilterRC::process_interleaved_inplace(SampleVector& samples) 370 | { 371 | unsigned int n = samples.size(); 372 | 373 | Sample y0 = m_y0_1; 374 | Sample y1 = m_y1_1; 375 | 376 | for (unsigned int i = 0; i < n-1; i+=2) 377 | { 378 | Sample x0 = samples[i]; 379 | y0 = m_b0 * x0 - m_a1 * y0; 380 | samples[i] = y0; 381 | 382 | Sample x1 = samples[i+1]; 383 | y1 = m_b0 * x1 - m_a1 * y1; 384 | samples[i+1] = y1; 385 | } 386 | 387 | m_y0_1 = y0; 388 | m_y1_1 = y1; 389 | } 390 | 391 | /* **************** class LowPassFilterIir **************** */ 392 | 393 | // Construct 4th order low-pass IIR filter. 394 | LowPassFilterIir::LowPassFilterIir(double cutoff) 395 | : y1(0), y2(0), y3(0), y4(0) 396 | { 397 | typedef std::complex CDbl; 398 | 399 | // Angular cutoff frequency. 400 | double w = 2 * M_PI * cutoff; 401 | 402 | // Poles 1 and 4 are a conjugate pair, and poles 2 and 3 are another pair. 403 | // Continuous domain: 404 | // p_k = w * exp( (2*k + n - 1) / (2*n) * pi * j) 405 | CDbl p1s = w * exp((2*1 + 4 - 1) / double(2 * 4) * CDbl(0, M_PI)); 406 | CDbl p2s = w * exp((2*2 + 4 - 1) / double(2 * 4) * CDbl(0, M_PI)); 407 | 408 | // Map poles to discrete-domain via matched Z transform. 409 | CDbl p1z = exp(p1s); 410 | CDbl p2z = exp(p2s); 411 | 412 | // Discrete-domain transfer function: 413 | // H(z) = b0 / ( (1 - p1/z) * (1 - p4/z) * (1 - p2/z) * (1 - p3/z) ) 414 | // = b0 / ( (1 - (p1+p4)/z + p1*p4/z**2) * 415 | // (1 - (p2+p3)/z + p2*p3/z**2) ) 416 | // = b0 / (1 - (p1 + p4 + p2 + p3)/z 417 | // + (p1*p4 + p2*p3 + (p1+p4)*(p2+p3))/z**2 418 | // - ((p1+p4)*p2*p3 + (p2+p3)*p1*p4)/z**3 419 | // + p1*p4*p2*p3/z**4 420 | // 421 | // Note that p3 = conj(p2), p4 = conj(p1) 422 | // Therefore p1+p4 == 2*real(p1), p1*p4 == abs(p1*p1) 423 | // 424 | a1 = - (2*real(p1z) + 2*real(p2z)); 425 | a2 = (abs(p1z*p1z) + abs(p2z*p2z) + 2*real(p1z) * 2*real(p2z)); 426 | a3 = - (2*real(p1z) * abs(p2z*p2z) + 2*real(p2z) * abs(p1z*p1z)); 427 | a4 = abs(p1z*p1z) * abs(p2z*p2z); 428 | 429 | // Choose b0 to get unit DC gain. 430 | b0 = 1 + a1 + a2 + a3 + a4; 431 | } 432 | 433 | 434 | // Process samples. 435 | void LowPassFilterIir::process(const SampleVector& samples_in, 436 | SampleVector& samples_out) 437 | { 438 | unsigned int n = samples_in.size(); 439 | 440 | samples_out.resize(n); 441 | 442 | for (unsigned int i = 0; i < n; i++) { 443 | Sample x = samples_in[i]; 444 | Sample y = b0 * x - a1 * y1 - a2 * y2 - a3 * y3 - a4 * y4; 445 | y4 = y3; y3 = y2; y2 = y1; y1 = y; 446 | samples_out[i] = y; 447 | } 448 | } 449 | 450 | 451 | /* **************** class HighPassFilterIir **************** */ 452 | 453 | // Construct 2nd order high-pass IIR filter. 454 | HighPassFilterIir::HighPassFilterIir(double cutoff) 455 | : x1(0), x2(0), y1(0), y2(0) 456 | { 457 | typedef std::complex CDbl; 458 | 459 | // Angular cutoff frequency. 460 | double w = 2 * M_PI * cutoff; 461 | 462 | // Poles 1 and 2 are a conjugate pair. 463 | // Continuous-domain: 464 | // p_k = w / exp( (2*k + n - 1) / (2*n) * pi * j) 465 | CDbl p1s = w / exp((2*1 + 2 - 1) / double(2 * 2) * CDbl(0, M_PI)); 466 | 467 | // Map poles to discrete-domain via matched Z transform. 468 | CDbl p1z = exp(p1s); 469 | 470 | // Both zeros are located in s = 0, z = 1. 471 | 472 | // Discrete-domain transfer function: 473 | // H(z) = g * (1 - 1/z) * (1 - 1/z) / ( (1 - p1/z) * (1 - p2/z) ) 474 | // = g * (1 - 2/z + 1/z**2) / (1 - (p1+p2)/z + (p1*p2)/z**2) 475 | // 476 | // Note that z2 = conj(z1). 477 | // Therefore p1+p2 == 2*real(p1), p1*2 == abs(p1*p1), z4 = conj(z1) 478 | // 479 | b0 = 1; 480 | b1 = -2; 481 | b2 = 1; 482 | a1 = -2 * real(p1z); 483 | a2 = abs(p1z*p1z); 484 | 485 | // Adjust b coefficients to get unit gain at Nyquist frequency (z=-1). 486 | double g = (b0 - b1 + b2) / (1 - a1 + a2); 487 | b0 /= g; 488 | b1 /= g; 489 | b2 /= g; 490 | } 491 | 492 | 493 | // Process samples. 494 | void HighPassFilterIir::process(const SampleVector& samples_in, 495 | SampleVector& samples_out) 496 | { 497 | unsigned int n = samples_in.size(); 498 | 499 | samples_out.resize(n); 500 | 501 | for (unsigned int i = 0; i < n; i++) { 502 | Sample x = samples_in[i]; 503 | Sample y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; 504 | x2 = x1; x1 = x; 505 | y2 = y1; y1 = y; 506 | samples_out[i] = y; 507 | } 508 | } 509 | 510 | 511 | // Process samples in-place. 512 | void HighPassFilterIir::process_inplace(SampleVector& samples) 513 | { 514 | unsigned int n = samples.size(); 515 | 516 | for (unsigned int i = 0; i < n; i++) { 517 | Sample x = samples[i]; 518 | Sample y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; 519 | x2 = x1; x1 = x; 520 | y2 = y1; y1 = y; 521 | samples[i] = y; 522 | } 523 | } 524 | 525 | /* end */ 526 | -------------------------------------------------------------------------------- /sfmbase/FmDecode.cpp: -------------------------------------------------------------------------------- 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 | #include 20 | #include 21 | 22 | #include "fastatan2.h" 23 | #include "FmDecode.h" 24 | 25 | 26 | 27 | /** Fast approximation of atan function. */ 28 | static inline Sample fast_atan(Sample x) 29 | { 30 | // http://stackoverflow.com/questions/7378187/approximating-inverse-trigonometric-funcions 31 | 32 | Sample y = 1; 33 | Sample p = 0; 34 | 35 | if (x < 0) { 36 | x = -x; 37 | y = -1; 38 | } 39 | 40 | if (x > 1) { 41 | p = y; 42 | y = -y; 43 | x = 1 / x; 44 | } 45 | 46 | const Sample b = 0.596227; 47 | y *= (b*x + x*x) / (1 + 2*b*x + x*x); 48 | 49 | return (y + p) * Sample(M_PI_2); 50 | } 51 | 52 | 53 | /** Compute RMS level over a small prefix of the specified sample vector. */ 54 | static IQSample::value_type rms_level_approx(const IQSampleVector& samples) 55 | { 56 | unsigned int n = samples.size(); 57 | n = (n + 63) / 64; 58 | 59 | IQSample::value_type level = 0; 60 | for (unsigned int i = 0; i < n; i++) { 61 | const IQSample& s = samples[i]; 62 | IQSample::value_type re = s.real(), im = s.imag(); 63 | level += re * re + im * im; 64 | } 65 | 66 | return sqrt(level / n); 67 | } 68 | 69 | 70 | /* **************** class PhaseDiscriminator **************** */ 71 | 72 | // Construct phase discriminator. 73 | PhaseDiscriminator::PhaseDiscriminator(double max_freq_dev) 74 | : m_freq_scale_factor(1.0 / (max_freq_dev * 2.0 * M_PI)) 75 | { } 76 | 77 | 78 | // Process samples. 79 | void PhaseDiscriminator::process(const IQSampleVector& samples_in, 80 | SampleVector& samples_out) 81 | { 82 | unsigned int n = samples_in.size(); 83 | IQSample s0 = m_last1_sample; 84 | 85 | samples_out.resize(n); 86 | 87 | for (unsigned int i = 0; i < n; i++) { 88 | IQSample s1(samples_in[i]); 89 | IQSample d(conj(s0) * s1); 90 | //Sample w = atan2(d.imag(), d.real()); 91 | Sample w = fastatan2(d.imag(), d.real()); // fast approximation of atan2 92 | samples_out[i] = w * m_freq_scale_factor; 93 | s0 = s1; 94 | } 95 | 96 | m_last2_sample = m_last1_sample; 97 | m_last1_sample = s0; 98 | } 99 | 100 | 101 | /* **************** class PilotPhaseLock **************** */ 102 | 103 | // Construct phase-locked loop. 104 | PilotPhaseLock::PilotPhaseLock(double freq, double bandwidth, double minsignal) 105 | { 106 | /* 107 | * This is a type-2, 4th order phase-locked loop. 108 | * 109 | * Open-loop transfer function: 110 | * G(z) = K * (z - q1) / ((z - p1) * (z - p2) * (z - 1) * (z - 1)) 111 | * K = 3.788 * (bandwidth * 2 * Pi)**3 112 | * q1 = exp(-0.1153 * bandwidth * 2*Pi) 113 | * p1 = exp(-1.146 * bandwidth * 2*Pi) 114 | * p2 = exp(-5.331 * bandwidth * 2*Pi) 115 | * 116 | * I don't understand what I'm doing; hopefully it will work. 117 | */ 118 | 119 | // Set min/max locking frequencies. 120 | m_minfreq = (freq - bandwidth) * 2.0 * M_PI; 121 | m_maxfreq = (freq + bandwidth) * 2.0 * M_PI; 122 | 123 | // Set valid signal threshold. 124 | m_minsignal = minsignal; 125 | m_lock_delay = int(20.0 / bandwidth); 126 | m_lock_cnt = 0; 127 | m_pilot_level = 0; 128 | 129 | // Create 2nd order filter for I/Q representation of phase error. 130 | // Filter has two poles, unit DC gain. 131 | double p1 = exp(-1.146 * bandwidth * 2.0 * M_PI); 132 | double p2 = exp(-5.331 * bandwidth * 2.0 * M_PI); 133 | m_phasor_a1 = - p1 - p2; 134 | m_phasor_a2 = p1 * p2; 135 | m_phasor_b0 = 1 + m_phasor_a1 + m_phasor_a2; 136 | 137 | // Create loop filter to stabilize the loop. 138 | double q1 = exp(-0.1153 * bandwidth * 2.0 * M_PI); 139 | m_loopfilter_b0 = 0.62 * bandwidth * 2.0 * M_PI; 140 | m_loopfilter_b1 = - m_loopfilter_b0 * q1; 141 | 142 | // After the loop filter, the phase error is integrated to produce 143 | // the frequency. Then the frequency is integrated to produce the phase. 144 | // These integrators form the two remaining poles, both at z = 1. 145 | 146 | // Initialize frequency and phase. 147 | m_freq = freq * 2.0 * M_PI; 148 | m_phase = 0; 149 | 150 | m_phasor_i1 = 0; 151 | m_phasor_i2 = 0; 152 | m_phasor_q1 = 0; 153 | m_phasor_q2 = 0; 154 | m_loopfilter_x1 = 0; 155 | 156 | // Initialize PPS generator. 157 | m_pilot_periods = 0; 158 | m_pps_cnt = 0; 159 | m_sample_cnt = 0; 160 | } 161 | 162 | 163 | // Process samples. 164 | void PilotPhaseLock::process(const SampleVector& samples_in, 165 | SampleVector& samples_out) 166 | { 167 | unsigned int n = samples_in.size(); 168 | 169 | samples_out.resize(n); 170 | 171 | bool was_locked = (m_lock_cnt >= m_lock_delay); 172 | m_pps_events.clear(); 173 | 174 | if (n > 0) 175 | m_pilot_level = 1000.0; 176 | 177 | for (unsigned int i = 0; i < n; i++) { 178 | 179 | // Generate locked pilot tone. 180 | Sample psin = sin(m_phase); 181 | Sample pcos = cos(m_phase); 182 | 183 | // Generate double-frequency output. 184 | // sin(2*x) = 2 * sin(x) * cos(x) 185 | samples_out[i] = 2 * psin * pcos; 186 | 187 | // Multiply locked tone with input. 188 | Sample x = samples_in[i]; 189 | Sample phasor_i = psin * x; 190 | Sample phasor_q = pcos * x; 191 | 192 | // Run IQ phase error through low-pass filter. 193 | phasor_i = m_phasor_b0 * phasor_i 194 | - m_phasor_a1 * m_phasor_i1 195 | - m_phasor_a2 * m_phasor_i2; 196 | phasor_q = m_phasor_b0 * phasor_q 197 | - m_phasor_a1 * m_phasor_q1 198 | - m_phasor_a2 * m_phasor_q2; 199 | m_phasor_i2 = m_phasor_i1; 200 | m_phasor_i1 = phasor_i; 201 | m_phasor_q2 = m_phasor_q1; 202 | m_phasor_q1 = phasor_q; 203 | 204 | // Convert I/Q ratio to estimate of phase error. 205 | Sample phase_err; 206 | if (phasor_i > abs(phasor_q)) { 207 | // We are within +/- 45 degrees from lock. 208 | // Use simple linear approximation of arctan. 209 | phase_err = phasor_q / phasor_i; 210 | } else if (phasor_q > 0) { 211 | // We are lagging more than 45 degrees behind the input. 212 | phase_err = 1; 213 | } else { 214 | // We are more than 45 degrees ahead of the input. 215 | phase_err = -1; 216 | } 217 | 218 | // Detect pilot level (conservative). 219 | m_pilot_level = std::min(m_pilot_level, phasor_i); 220 | 221 | // Run phase error through loop filter and update frequency estimate. 222 | m_freq += m_loopfilter_b0 * phase_err 223 | + m_loopfilter_b1 * m_loopfilter_x1; 224 | m_loopfilter_x1 = phase_err; 225 | 226 | // Limit frequency to allowable range. 227 | m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq)); 228 | 229 | // Update locked phase. 230 | m_phase += m_freq; 231 | if (m_phase > 2.0 * M_PI) { 232 | m_phase -= 2.0 * M_PI; 233 | m_pilot_periods++; 234 | 235 | // Generate pulse-per-second. 236 | if (m_pilot_periods == pilot_frequency) { 237 | m_pilot_periods = 0; 238 | if (was_locked) { 239 | struct PpsEvent ev; 240 | ev.pps_index = m_pps_cnt; 241 | ev.sample_index = m_sample_cnt + i; 242 | ev.block_position = double(i) / double(n); 243 | m_pps_events.push_back(ev); 244 | m_pps_cnt++; 245 | } 246 | } 247 | } 248 | } 249 | 250 | // Update lock status. 251 | if (2 * m_pilot_level > m_minsignal) { 252 | if (m_lock_cnt < m_lock_delay) 253 | m_lock_cnt += n; 254 | } else { 255 | m_lock_cnt = 0; 256 | } 257 | 258 | // Drop PPS events when pilot not locked. 259 | if (m_lock_cnt < m_lock_delay) { 260 | m_pilot_periods = 0; 261 | m_pps_cnt = 0; 262 | m_pps_events.clear(); 263 | } 264 | 265 | // Update sample counter. 266 | m_sample_cnt += n; 267 | } 268 | 269 | 270 | /* **************** class FmDecoder **************** */ 271 | 272 | FmDecoder::FmDecoder(double sample_rate_if, 273 | double tuning_offset, 274 | double sample_rate_pcm, 275 | bool stereo, 276 | double deemphasis, 277 | double bandwidth_if, 278 | double freq_dev, 279 | double bandwidth_pcm, 280 | unsigned int downsample) 281 | 282 | // Initialize member fields 283 | : m_sample_rate_if(sample_rate_if) 284 | , m_sample_rate_baseband(sample_rate_if / downsample) 285 | , m_tuning_table_size(64) 286 | , m_tuning_shift(lrint(-64.0 * tuning_offset / sample_rate_if)) 287 | , m_freq_dev(freq_dev) 288 | , m_downsample(downsample) 289 | , m_stereo_enabled(stereo) 290 | , m_stereo_detected(false) 291 | , m_if_level(0) 292 | , m_baseband_mean(0) 293 | , m_baseband_level(0) 294 | 295 | // Construct FineTuner 296 | , m_finetuner(m_tuning_table_size, m_tuning_shift) 297 | 298 | // Construct LowPassFilterFirIQ 299 | , m_iffilter(10, bandwidth_if / sample_rate_if) 300 | 301 | // Construct PhaseDiscriminator 302 | , m_phasedisc(freq_dev / sample_rate_if) 303 | 304 | // Construct DownsampleFilter for baseband 305 | , m_resample_baseband(8 * downsample, 0.4 / downsample, downsample, true) 306 | 307 | // Construct PilotPhaseLock 308 | , m_pilotpll(pilot_freq / m_sample_rate_baseband, // freq 309 | 50 / m_sample_rate_baseband, // bandwidth 310 | 0.01) // minsignal (was 0.04) 311 | 312 | // Construct DownsampleFilter for mono channel 313 | , m_resample_mono( 314 | int(m_sample_rate_baseband / 1000.0), // filter_order 315 | bandwidth_pcm / m_sample_rate_baseband, // cutoff 316 | m_sample_rate_baseband / sample_rate_pcm, // downsample 317 | false) // integer_factor 318 | 319 | // Construct DownsampleFilter for stereo channel 320 | , m_resample_stereo( 321 | int(m_sample_rate_baseband / 1000.0), // filter_order 322 | bandwidth_pcm / m_sample_rate_baseband, // cutoff 323 | m_sample_rate_baseband / sample_rate_pcm, // downsample 324 | false) // integer_factor 325 | 326 | // Construct HighPassFilterIir 327 | , m_dcblock_mono(30.0 / sample_rate_pcm) 328 | , m_dcblock_stereo(30.0 / sample_rate_pcm) 329 | 330 | // Construct LowPassFilterRC 331 | , m_deemph_mono( 332 | (deemphasis == 0) ? 1.0 : (deemphasis * sample_rate_pcm * 1.0e-6)) 333 | , m_deemph_stereo( 334 | (deemphasis == 0) ? 1.0 : (deemphasis * sample_rate_pcm * 1.0e-6)) 335 | 336 | { 337 | // nothing more to do 338 | } 339 | 340 | 341 | void FmDecoder::process(const IQSampleVector& samples_in, 342 | SampleVector& audio) 343 | { 344 | // Fine tuning. 345 | m_finetuner.process(samples_in, m_buf_iftuned); 346 | 347 | // Low pass filter to isolate station. 348 | m_iffilter.process(m_buf_iftuned, m_buf_iffiltered); 349 | 350 | // Measure IF level. 351 | double if_rms = rms_level_approx(m_buf_iffiltered); 352 | m_if_level = 0.95 * m_if_level + 0.05 * if_rms; 353 | 354 | // Extract carrier frequency. 355 | m_phasedisc.process(m_buf_iffiltered, m_buf_baseband); 356 | 357 | // Downsample baseband signal to reduce processing. 358 | if (m_downsample > 1) { 359 | SampleVector tmp(move(m_buf_baseband)); 360 | m_resample_baseband.process(tmp, m_buf_baseband); 361 | } 362 | 363 | // Measure baseband level. 364 | double baseband_mean, baseband_rms; 365 | samples_mean_rms(m_buf_baseband, baseband_mean, baseband_rms); 366 | m_baseband_mean = 0.95 * m_baseband_mean + 0.05 * baseband_mean; 367 | m_baseband_level = 0.95 * m_baseband_level + 0.05 * baseband_rms; 368 | 369 | // Extract mono audio signal. 370 | m_resample_mono.process(m_buf_baseband, m_buf_mono); 371 | 372 | // DC blocking 373 | m_dcblock_mono.process_inplace(m_buf_mono); 374 | 375 | if (m_stereo_enabled) 376 | { 377 | // Lock on stereo pilot. 378 | m_pilotpll.process(m_buf_baseband, m_buf_rawstereo); 379 | m_stereo_detected = m_pilotpll.locked(); 380 | 381 | // Demodulate stereo signal. 382 | demod_stereo(m_buf_baseband, m_buf_rawstereo); 383 | 384 | // Extract audio and downsample. 385 | // NOTE: This MUST be done even if no stereo signal is detected yet, 386 | // because the downsamplers for mono and stereo signal must be 387 | // kept in sync. 388 | m_resample_stereo.process(m_buf_rawstereo, m_buf_stereo); 389 | 390 | // DC blocking 391 | m_dcblock_stereo.process_inplace(m_buf_stereo); 392 | 393 | if (m_stereo_detected) 394 | { 395 | // Extract left/right channels from (L+R) / (L-R) signals. 396 | stereo_to_left_right(m_buf_mono, m_buf_stereo, audio); 397 | m_deemph_stereo.process_interleaved_inplace(audio); // L and R de-emphasis. 398 | } 399 | else 400 | { 401 | m_deemph_mono.process_inplace(m_buf_mono); // De-emphasis. 402 | // Duplicate mono signal in left/right channels. 403 | mono_to_left_right(m_buf_mono, audio); 404 | } 405 | } 406 | else 407 | { 408 | m_deemph_mono.process_inplace(m_buf_mono); // De-emphasis. 409 | // Just return mono channel. 410 | audio = move(m_buf_mono); 411 | } 412 | } 413 | 414 | 415 | // Demodulate stereo L-R signal. 416 | void FmDecoder::demod_stereo(const SampleVector& samples_baseband, 417 | SampleVector& samples_rawstereo) 418 | { 419 | // Just multiply the baseband signal with the double-frequency pilot. 420 | // And multiply by 1.17 to get the full amplitude. 421 | // That's all. 422 | 423 | unsigned int n = samples_baseband.size(); 424 | assert(n == samples_rawstereo.size()); 425 | 426 | for (unsigned int i = 0; i < n; i++) { 427 | samples_rawstereo[i] *= 1.17 * samples_baseband[i]; 428 | } 429 | } 430 | 431 | 432 | // Duplicate mono signal in left/right channels. 433 | void FmDecoder::mono_to_left_right(const SampleVector& samples_mono, 434 | SampleVector& audio) 435 | { 436 | unsigned int n = samples_mono.size(); 437 | 438 | audio.resize(2*n); 439 | for (unsigned int i = 0; i < n; i++) { 440 | Sample m = samples_mono[i]; 441 | audio[2*i] = m; 442 | audio[2*i+1] = m; 443 | } 444 | } 445 | 446 | 447 | // Extract left/right channels from (L+R) / (L-R) signals. 448 | void FmDecoder::stereo_to_left_right(const SampleVector& samples_mono, 449 | const SampleVector& samples_stereo, 450 | SampleVector& audio) 451 | { 452 | unsigned int n = samples_mono.size(); 453 | assert(n == samples_stereo.size()); 454 | 455 | audio.resize(2*n); 456 | for (unsigned int i = 0; i < n; i++) { 457 | Sample m = samples_mono[i]; 458 | Sample s = samples_stereo[i]; 459 | audio[2*i] = m + s; 460 | audio[2*i+1] = m - s; 461 | } 462 | } 463 | 464 | /* end */ 465 | -------------------------------------------------------------------------------- /sfmbase/HackRFSource.cpp: -------------------------------------------------------------------------------- 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 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "util.h" 27 | #include "parsekv.h" 28 | #include "HackRFSource.h" 29 | 30 | HackRFSource *HackRFSource::m_this = 0; 31 | const std::vector HackRFSource::m_lgains({0, 8, 16, 24, 32, 40}); 32 | const std::vector HackRFSource::m_vgains({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}); 33 | const std::vector HackRFSource::m_bwfilt({1750000, 2500000, 3500000, 5000000, 5500000, 6000000, 7000000, 8000000, 9000000, 10000000, 12000000, 14000000, 15000000, 20000000, 24000000, 28000000}); 34 | 35 | // Open HackRF device. 36 | HackRFSource::HackRFSource(int dev_index) : 37 | m_dev(0), 38 | m_sampleRate(5000000), 39 | m_frequency(100000000), 40 | m_lnaGain(16), 41 | m_vgaGain(22), 42 | m_bandwidth(2500000), 43 | m_extAmp(false), 44 | m_biasAnt(false), 45 | m_running(false), 46 | m_thread(0) 47 | { 48 | hackrf_error rc = (hackrf_error) hackrf_init(); 49 | 50 | if (rc != HACKRF_SUCCESS) 51 | { 52 | std::ostringstream err_ostr; 53 | err_ostr << "Failed to open HackRF library (" << rc << ": " << hackrf_error_name(rc) << ")"; 54 | m_error = err_ostr.str(); 55 | m_dev = 0; 56 | } 57 | else 58 | { 59 | hackrf_device_list_t *hackrf_devices = hackrf_device_list(); 60 | 61 | rc = (hackrf_error) hackrf_device_list_open(hackrf_devices, dev_index, &m_dev); 62 | 63 | if (rc != HACKRF_SUCCESS) 64 | { 65 | std::ostringstream err_ostr; 66 | err_ostr << "Failed to open HackRF device " << dev_index << " (" << rc << ": " << hackrf_error_name(rc) << ")"; 67 | m_error = err_ostr.str(); 68 | m_dev = 0; 69 | } 70 | } 71 | 72 | std::ostringstream lgains_ostr; 73 | 74 | for (int g: m_lgains) { 75 | lgains_ostr << g << " "; 76 | } 77 | 78 | m_lgainsStr = lgains_ostr.str(); 79 | 80 | std::ostringstream vgains_ostr; 81 | 82 | for (int g: m_vgains) { 83 | vgains_ostr << g << " "; 84 | } 85 | 86 | m_vgainsStr = vgains_ostr.str(); 87 | 88 | std::ostringstream bwfilt_ostr; 89 | bwfilt_ostr << std::fixed << std::setprecision(2); 90 | 91 | for (int b: m_bwfilt) { 92 | bwfilt_ostr << b * 1e-6 << " "; 93 | } 94 | 95 | m_bwfiltStr = bwfilt_ostr.str(); 96 | 97 | m_this = this; 98 | } 99 | 100 | HackRFSource::~HackRFSource() 101 | { 102 | if (m_dev) { 103 | hackrf_close(m_dev); 104 | } 105 | 106 | hackrf_error rc = (hackrf_error) hackrf_exit(); 107 | std::cerr << "HackRFSource::~HackRFSource: HackRF library exit: " << rc << ": " << hackrf_error_name(rc) << std::endl; 108 | 109 | m_this = 0; 110 | } 111 | 112 | void HackRFSource::get_device_names(std::vector& devices) 113 | { 114 | hackrf_device *hackrf_ptr; 115 | read_partid_serialno_t read_partid_serialno; 116 | hackrf_error rc; 117 | int i; 118 | 119 | rc = (hackrf_error) hackrf_init(); 120 | 121 | if (rc != HACKRF_SUCCESS) 122 | { 123 | std::cerr << "HackRFSource::get_device_names: Failed to open HackRF library: " << rc << ": " << hackrf_error_name(rc) << std::endl; 124 | return; 125 | } 126 | 127 | hackrf_device_list_t *hackrf_devices = hackrf_device_list(); 128 | 129 | devices.clear(); 130 | 131 | for (i=0; i < hackrf_devices->devicecount; i++) 132 | { 133 | rc = (hackrf_error) hackrf_device_list_open(hackrf_devices, i, &hackrf_ptr); 134 | 135 | if (rc == HACKRF_SUCCESS) 136 | { 137 | std::cerr << "HackRFSource::get_device_names: try to get device " << i << " serial number" << std::endl; 138 | rc = (hackrf_error) hackrf_board_partid_serialno_read(hackrf_ptr, &read_partid_serialno); 139 | 140 | if (rc != HACKRF_SUCCESS) 141 | { 142 | std::cerr << "HackRFSource::get_device_names: failed to get device " << i << " serial number: " << rc << ": " << hackrf_error_name(rc) << std::endl; 143 | hackrf_close(hackrf_ptr); 144 | continue; 145 | } 146 | else 147 | { 148 | std::cerr << "HackRFSource::get_device_names: device " << i << " OK" << std::endl; 149 | hackrf_close(hackrf_ptr); 150 | } 151 | 152 | uint32_t serial_msb = read_partid_serialno.serial_no[2]; 153 | uint32_t serial_lsb = read_partid_serialno.serial_no[3]; 154 | std::ostringstream devname_ostr; 155 | 156 | devname_ostr << "Serial " << std::hex << std::setw(8) << std::setfill('0') << serial_msb << serial_lsb; 157 | devices.push_back(devname_ostr.str()); 158 | } 159 | else 160 | { 161 | std::cerr << "HackRFSource::get_device_names: failed to open device " << i << std::endl; 162 | } 163 | } 164 | 165 | hackrf_device_list_free(hackrf_devices); 166 | rc = (hackrf_error) hackrf_exit(); 167 | std::cerr << "HackRFSource::get_device_names: HackRF library exit: " << rc << ": " << hackrf_error_name(rc) << std::endl; 168 | } 169 | 170 | std::uint32_t HackRFSource::get_sample_rate() 171 | { 172 | return m_sampleRate; 173 | } 174 | 175 | std::uint32_t HackRFSource::get_frequency() 176 | { 177 | return m_frequency; 178 | } 179 | 180 | void HackRFSource::print_specific_parms() 181 | { 182 | fprintf(stderr, "LNA gain: %d\n", m_lnaGain); 183 | fprintf(stderr, "VGA gain: %d\n", m_vgaGain); 184 | fprintf(stderr, "Bandwidth %d\n", m_bandwidth); 185 | fprintf(stderr, "External Amp %s\n", m_extAmp ? "enabled" : "disabled"); 186 | fprintf(stderr, "Bias ant %s\n", m_biasAnt ? "enabled" : "disabled"); 187 | } 188 | 189 | bool HackRFSource::configure(uint32_t sample_rate, 190 | uint32_t frequency, 191 | bool ext_amp, 192 | bool bias_ant, 193 | int lna_gain, 194 | int vga_gain, 195 | uint32_t bandwidth 196 | ) 197 | { 198 | m_sampleRate = sample_rate; 199 | m_frequency = frequency; 200 | m_extAmp = ext_amp; 201 | m_biasAnt = bias_ant; 202 | m_lnaGain = lna_gain; 203 | m_vgaGain = vga_gain; 204 | m_bandwidth = bandwidth; 205 | hackrf_error rc; 206 | 207 | if (!m_dev) { 208 | return false; 209 | } 210 | 211 | rc = (hackrf_error) hackrf_set_freq(m_dev, static_cast(m_frequency)); 212 | 213 | if (rc != HACKRF_SUCCESS) 214 | { 215 | std::ostringstream err_ostr; 216 | err_ostr << "Could not set center frequency to " << m_frequency << " Hz"; 217 | m_error = err_ostr.str(); 218 | return false; 219 | } 220 | 221 | rc = (hackrf_error) hackrf_set_sample_rate_manual(m_dev, m_sampleRate, 1); 222 | 223 | if (rc != HACKRF_SUCCESS) 224 | { 225 | std::ostringstream err_ostr; 226 | err_ostr << "Could not set center sample rate to " << m_sampleRate << " Hz"; 227 | m_error = err_ostr.str(); 228 | return false; 229 | } 230 | 231 | rc = (hackrf_error) hackrf_set_lna_gain(m_dev, m_lnaGain); 232 | 233 | if (rc != HACKRF_SUCCESS) 234 | { 235 | std::ostringstream err_ostr; 236 | err_ostr << "Could not set LNA gain to " << m_lnaGain << " dB"; 237 | m_error = err_ostr.str(); 238 | return false; 239 | } 240 | 241 | rc = (hackrf_error) hackrf_set_vga_gain(m_dev, m_vgaGain); 242 | 243 | if (rc != HACKRF_SUCCESS) 244 | { 245 | std::ostringstream err_ostr; 246 | err_ostr << "Could not set VGA gain to " << m_vgaGain << " dB"; 247 | m_error = err_ostr.str(); 248 | return false; 249 | } 250 | 251 | rc = (hackrf_error) hackrf_set_antenna_enable(m_dev, (m_biasAnt ? 1 : 0)); 252 | 253 | if (rc != HACKRF_SUCCESS) 254 | { 255 | std::ostringstream err_ostr; 256 | err_ostr << "Could not set bias antenna to " << m_biasAnt; 257 | m_error = err_ostr.str(); 258 | return false; 259 | } 260 | 261 | rc = (hackrf_error) hackrf_set_amp_enable(m_dev, (m_extAmp ? 1 : 0)); 262 | 263 | if (rc != HACKRF_SUCCESS) 264 | { 265 | std::ostringstream err_ostr; 266 | err_ostr << "Could not set extra amplifier to " << m_extAmp; 267 | m_error = err_ostr.str(); 268 | return false; 269 | } 270 | 271 | uint32_t hackRFBandwidth = hackrf_compute_baseband_filter_bw_round_down_lt(m_bandwidth); 272 | rc = (hackrf_error) hackrf_set_baseband_filter_bandwidth(m_dev, hackRFBandwidth); 273 | 274 | if (rc != HACKRF_SUCCESS) 275 | { 276 | std::ostringstream err_ostr; 277 | err_ostr << "Could not set bandwidth to " << hackRFBandwidth << " Hz (" << m_bandwidth << " Hz requested)"; 278 | m_error = err_ostr.str(); 279 | return false; 280 | } 281 | 282 | return true; 283 | } 284 | 285 | bool HackRFSource::configure(std::string configurationStr) 286 | { 287 | namespace qi = boost::spirit::qi; 288 | std::string::iterator begin = configurationStr.begin(); 289 | std::string::iterator end = configurationStr.end(); 290 | 291 | uint32_t sampleRate = 5000000; 292 | uint32_t frequency = 100000000; 293 | int lnaGain = 16; 294 | int vgaGain = 22; 295 | uint32_t bandwidth = 2500000; 296 | bool extAmp = false; 297 | bool antBias = false; 298 | 299 | parsekv::key_value_sequence p; 300 | parsekv::pairs_type m; 301 | 302 | if (!qi::parse(begin, end, p, m)) 303 | { 304 | m_error = "Configuration parsing failed\n"; 305 | return false; 306 | } 307 | else 308 | { 309 | if (m.find("srate") != m.end()) 310 | { 311 | std::cerr << "HackRFSource::configure: srate: " << m["srate"] << std::endl; 312 | sampleRate = atoi(m["srate"].c_str()); 313 | 314 | if ((sampleRate < 1000000) || (sampleRate > 20000000)) 315 | { 316 | m_error = "Invalid sample rate"; 317 | return false; 318 | } 319 | } 320 | 321 | if (m.find("freq") != m.end()) 322 | { 323 | std::cerr << "HackRFSource::configure: freq: " << m["freq"] << std::endl; 324 | frequency = strtoll(m["freq"].c_str(), 0, 10); 325 | 326 | if ((frequency < 1000000) || (frequency > 6000000000)) 327 | { 328 | m_error = "Invalid frequency"; 329 | return false; 330 | } 331 | } 332 | 333 | if (m.find("lgain") != m.end()) 334 | { 335 | std::cerr << "HackRFSource::configure: lgain: " << m["lgain"] << std::endl; 336 | 337 | if (strcasecmp(m["lgain"].c_str(), "list") == 0) 338 | { 339 | m_error = "Available LNA gains (dB): " + m_lgainsStr; 340 | return false; 341 | } 342 | 343 | lnaGain = atoi(m["lgain"].c_str()); 344 | 345 | if (find(m_lgains.begin(), m_lgains.end(), lnaGain) == m_lgains.end()) 346 | { 347 | m_error = "LNA gain not supported. Available gains (dB): " + m_lgainsStr; 348 | return false; 349 | } 350 | } 351 | 352 | if (m.find("vgain") != m.end()) 353 | { 354 | std::cerr << "HackRFSource::configure: vgain: " << m["vgain"] << std::endl; 355 | vgaGain = atoi(m["vgain"].c_str()); 356 | 357 | if (strcasecmp(m["vgain"].c_str(), "list") == 0) 358 | { 359 | m_error = "Available VGA gains (dB): " + m_vgainsStr; 360 | return false; 361 | } 362 | 363 | if (find(m_vgains.begin(), m_vgains.end(), vgaGain) == m_vgains.end()) 364 | { 365 | m_error = "VGA gain not supported. Available gains (dB): " + m_vgainsStr; 366 | return false; 367 | } 368 | } 369 | 370 | if (m.find("bwfilter") != m.end()) 371 | { 372 | std::cerr << "HackRFSource::configure: bwfilter: " << m["bwfilter"] << std::endl; 373 | bandwidth = atoi(m["bwfilter"].c_str()); 374 | 375 | if (strcasecmp(m["bwfilter"].c_str(), "list") == 0) 376 | { 377 | m_error = "Available filter bandwidths (MHz): " + m_bwfiltStr; 378 | return false; 379 | } 380 | 381 | double tmpbwd; 382 | 383 | if (!parse_dbl(m["bwfilter"].c_str(), tmpbwd)) 384 | { 385 | m_error = "Invalid filter bandwidth"; 386 | return false; 387 | } 388 | else 389 | { 390 | long int tmpbwi = lrint(tmpbwd * 1000000); 391 | 392 | if (tmpbwi <= INT_MIN || tmpbwi >= INT_MAX) { 393 | m_error = "Invalid filter bandwidth"; 394 | return false; 395 | } 396 | else 397 | { 398 | bandwidth = tmpbwi; 399 | 400 | if (find(m_bwfilt.begin(), m_bwfilt.end(), bandwidth) == m_bwfilt.end()) 401 | { 402 | m_error = "Filter bandwidth not supported. Available bandwidths (MHz): " + m_bwfiltStr; 403 | return false; 404 | } 405 | } 406 | } 407 | } 408 | 409 | if (m.find("extamp") != m.end()) 410 | { 411 | std::cerr << "HackRFSource::configure: extamp" << std::endl; 412 | extAmp = true; 413 | } 414 | 415 | if (m.find("antbias") != m.end()) 416 | { 417 | std::cerr << "HackRFSource::configure: antbias" << std::endl; 418 | antBias = true; 419 | } 420 | } 421 | 422 | m_confFreq = frequency; 423 | double tuner_freq = frequency + 0.25 * sampleRate; 424 | return configure(sampleRate, tuner_freq, extAmp, antBias, lnaGain, vgaGain, bandwidth); 425 | } 426 | 427 | bool HackRFSource::start(DataBuffer *buf, std::atomic_bool *stop_flag) 428 | { 429 | m_buf = buf; 430 | m_stop_flag = stop_flag; 431 | 432 | if (m_thread == 0) 433 | { 434 | std::cerr << "HackRFSource::start: starting" << std::endl; 435 | m_running = true; 436 | m_thread = new std::thread(run, m_dev, stop_flag); 437 | sleep(1); 438 | return *this; 439 | } 440 | else 441 | { 442 | std::cerr << "HackRFSource::start: error" << std::endl; 443 | m_error = "Source thread already started"; 444 | return false; 445 | } 446 | } 447 | 448 | void HackRFSource::run(hackrf_device* dev, std::atomic_bool *stop_flag) 449 | { 450 | std::cerr << "HackRFSource::run" << std::endl; 451 | 452 | hackrf_error rc = (hackrf_error) hackrf_start_rx(dev, rx_callback, 0); 453 | 454 | if (rc == HACKRF_SUCCESS) 455 | { 456 | while (!stop_flag->load() && (hackrf_is_streaming(dev) == HACKRF_TRUE)) 457 | { 458 | sleep(1); 459 | } 460 | 461 | rc = (hackrf_error) hackrf_stop_rx(dev); 462 | 463 | if (rc != HACKRF_SUCCESS) 464 | { 465 | std::cerr << "HackRFSource::run: Cannot stop HackRF Rx: " << rc << ": " << hackrf_error_name(rc) << std::endl; 466 | } 467 | } 468 | else 469 | { 470 | std::cerr << "HackRFSource::run: Cannot start HackRF Rx: " << rc << ": " << hackrf_error_name(rc) << std::endl; 471 | } 472 | } 473 | 474 | bool HackRFSource::stop() 475 | { 476 | std::cerr << "HackRFSource::stop" << std::endl; 477 | 478 | m_thread->join(); 479 | delete m_thread; 480 | return true; 481 | } 482 | 483 | int HackRFSource::rx_callback(hackrf_transfer* transfer) 484 | { 485 | int bytes_to_write = transfer->valid_length; 486 | 487 | if (m_this) 488 | { 489 | m_this->callback((char *) transfer->buffer, bytes_to_write); 490 | } 491 | 492 | return 0; 493 | } 494 | 495 | void HackRFSource::callback(const char* buf, int len) 496 | { 497 | IQSampleVector iqsamples; 498 | 499 | iqsamples.resize(len/2); 500 | 501 | for (int i = 0; i < len/2; i++) { 502 | int32_t re = buf[2*i]; 503 | int32_t im = buf[2*i+1]; 504 | iqsamples[i] = IQSample( (re - 128) / IQSample::value_type(128), 505 | (im - 128) / IQSample::value_type(128) ); 506 | } 507 | 508 | m_buf->push(move(iqsamples)); 509 | } 510 | -------------------------------------------------------------------------------- /sfmbase/RtlSdrSource.cpp: -------------------------------------------------------------------------------- 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 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "util.h" 28 | #include "parsekv.h" 29 | #include "RtlSdrSource.h" 30 | 31 | RtlSdrSource *RtlSdrSource::m_this = 0; 32 | 33 | // Open RTL-SDR device. 34 | RtlSdrSource::RtlSdrSource(int dev_index) : 35 | m_dev(0), 36 | m_block_length(default_block_length), 37 | m_thread(0) 38 | { 39 | int r; 40 | 41 | const char *devname = rtlsdr_get_device_name(dev_index); 42 | if (devname != NULL) 43 | m_devname = devname; 44 | 45 | r = rtlsdr_open(&m_dev, dev_index); 46 | 47 | if (r < 0) 48 | { 49 | m_error = "Failed to open RTL-SDR device ("; 50 | m_error += strerror(-r); 51 | m_error += ")"; 52 | } 53 | else 54 | { 55 | m_gains = get_tuner_gains(); 56 | std::ostringstream gains_ostr; 57 | 58 | gains_ostr << std::fixed << std::setprecision(1); 59 | 60 | for (int g: m_gains) 61 | { 62 | gains_ostr << 0.1 * g << " "; 63 | } 64 | 65 | m_gainsStr = gains_ostr.str(); 66 | } 67 | 68 | m_this = this; 69 | } 70 | 71 | 72 | // Close RTL-SDR device. 73 | RtlSdrSource::~RtlSdrSource() 74 | { 75 | if (m_dev) 76 | rtlsdr_close(m_dev); 77 | 78 | m_this = 0; 79 | } 80 | 81 | bool RtlSdrSource::configure(std::string configurationStr) 82 | { 83 | namespace qi = boost::spirit::qi; 84 | std::string::iterator begin = configurationStr.begin(); 85 | std::string::iterator end = configurationStr.end(); 86 | 87 | uint32_t sample_rate = 1000000; 88 | uint32_t frequency = 100000000; 89 | int tuner_gain = INT_MIN; 90 | int block_length = default_block_length; 91 | bool agcmode = false; 92 | 93 | parsekv::key_value_sequence p; 94 | parsekv::pairs_type m; 95 | 96 | if (!qi::parse(begin, end, p, m)) 97 | { 98 | m_error = "Configuration parsing failed\n"; 99 | return false; 100 | } 101 | else 102 | { 103 | if (m.find("srate") != m.end()) 104 | { 105 | std::cerr << "RtlSdrSource::configure: srate: " << m["srate"] << std::endl; 106 | sample_rate = atoi(m["srate"].c_str()); 107 | 108 | if ((sample_rate < 225001) 109 | || ((sample_rate > 300000) && (sample_rate < 900001)) 110 | || (sample_rate > 3200000)) 111 | { 112 | m_error = "Invalid sample rate"; 113 | return false; 114 | } 115 | } 116 | 117 | if (m.find("freq") != m.end()) 118 | { 119 | std::cerr << "RtlSdrSource::configure: freq: " << m["freq"] << std::endl; 120 | frequency = atoi(m["freq"].c_str()); 121 | 122 | if ((frequency < 10000000) || (frequency > 2200000000)) 123 | { 124 | m_error = "Invalid frequency"; 125 | return false; 126 | } 127 | } 128 | 129 | if (m.find("gain") != m.end()) 130 | { 131 | std::string gain_str = m["gain"]; 132 | std::cerr << "RtlSdrSource::configure: gain: " << gain_str << std::endl; 133 | 134 | if (strcasecmp(gain_str.c_str(), "auto") == 0) 135 | { 136 | tuner_gain = INT_MIN; 137 | } 138 | else if (strcasecmp(gain_str.c_str(), "list") == 0) 139 | { 140 | m_error = "Available gains (dB): " + m_gainsStr; 141 | return false; 142 | } 143 | else 144 | { 145 | double tmpgain; 146 | 147 | if (!parse_dbl(gain_str.c_str(), tmpgain)) 148 | { 149 | m_error = "Invalid gain"; 150 | return false; 151 | } 152 | else 153 | { 154 | long int tmpgain2 = lrint(tmpgain * 10); 155 | 156 | if (tmpgain2 <= INT_MIN || tmpgain2 >= INT_MAX) { 157 | m_error = "Invalid gain"; 158 | return false; 159 | } 160 | else 161 | { 162 | tuner_gain = tmpgain2; 163 | 164 | if (find(m_gains.begin(), m_gains.end(), tuner_gain) == m_gains.end()) 165 | { 166 | m_error = "Gain not supported. Available gains (dB): " + m_gainsStr; 167 | return false; 168 | } 169 | } 170 | } 171 | } 172 | } // gain 173 | 174 | if (m.find("blklen") != m.end()) 175 | { 176 | std::cerr << "RtlSdrSource::configure: blklen: " << m["blklen"] << std::endl; 177 | block_length = atoi(m["blklen"].c_str()); 178 | } 179 | 180 | if (m.find("agc") != m.end()) 181 | { 182 | std::cerr << "RtlSdrSource::configure: agc" << std::endl; 183 | agcmode = true; 184 | } 185 | 186 | // Intentionally tune at a higher frequency to avoid DC offset. 187 | m_confFreq = frequency; 188 | m_confAgc = agcmode; 189 | double tuner_freq = frequency + 0.25 * sample_rate; 190 | 191 | return configure(sample_rate, tuner_freq, tuner_gain, block_length, agcmode); 192 | } 193 | } 194 | 195 | // Configure RTL-SDR tuner and prepare for streaming. 196 | bool RtlSdrSource::configure(uint32_t sample_rate, 197 | uint32_t frequency, 198 | int tuner_gain, 199 | int block_length, 200 | bool agcmode) 201 | { 202 | int r; 203 | 204 | if (!m_dev) 205 | return false; 206 | 207 | r = rtlsdr_set_sample_rate(m_dev, sample_rate); 208 | if (r < 0) { 209 | m_error = "rtlsdr_set_sample_rate failed"; 210 | return false; 211 | } 212 | 213 | r = rtlsdr_set_center_freq(m_dev, frequency); 214 | if (r < 0) { 215 | m_error = "rtlsdr_set_center_freq failed"; 216 | return false; 217 | } 218 | 219 | if (tuner_gain == INT_MIN) { 220 | r = rtlsdr_set_tuner_gain_mode(m_dev, 0); 221 | if (r < 0) { 222 | m_error = "rtlsdr_set_tuner_gain_mode could not set automatic gain"; 223 | return false; 224 | } 225 | } else { 226 | r = rtlsdr_set_tuner_gain_mode(m_dev, 1); 227 | if (r < 0) { 228 | m_error = "rtlsdr_set_tuner_gain_mode could not set manual gain"; 229 | return false; 230 | } 231 | 232 | r = rtlsdr_set_tuner_gain(m_dev, tuner_gain); 233 | if (r < 0) { 234 | m_error = "rtlsdr_set_tuner_gain failed"; 235 | return false; 236 | } 237 | } 238 | 239 | // set RTL AGC mode 240 | r = rtlsdr_set_agc_mode(m_dev, int(agcmode)); 241 | if (r < 0) { 242 | m_error = "rtlsdr_set_agc_mode failed"; 243 | return false; 244 | } 245 | 246 | // set block length 247 | m_block_length = (block_length < 4096) ? 4096 : 248 | (block_length > 1024 * 1024) ? 1024 * 1024 : 249 | block_length; 250 | m_block_length -= m_block_length % 4096; 251 | 252 | // reset buffer to start streaming 253 | if (rtlsdr_reset_buffer(m_dev) < 0) { 254 | m_error = "rtlsdr_reset_buffer failed"; 255 | return false; 256 | } 257 | 258 | return true; 259 | } 260 | 261 | 262 | // Return current sample frequency in Hz. 263 | uint32_t RtlSdrSource::get_sample_rate() 264 | { 265 | return rtlsdr_get_sample_rate(m_dev); 266 | } 267 | 268 | // Return device current center frequency in Hz. 269 | uint32_t RtlSdrSource::get_frequency() 270 | { 271 | return rtlsdr_get_center_freq(m_dev); 272 | } 273 | 274 | void RtlSdrSource::print_specific_parms() 275 | { 276 | int lnagain = get_tuner_gain(); 277 | 278 | if (lnagain == INT_MIN) 279 | fprintf(stderr, "LNA gain: auto\n"); 280 | else 281 | fprintf(stderr, "LNA gain: %.1f dB\n", 282 | 0.1 * lnagain); 283 | 284 | fprintf(stderr, "RTL AGC mode: %s\n", 285 | m_confAgc ? "enabled" : "disabled"); 286 | } 287 | 288 | // Return current tuner gain in units of 0.1 dB. 289 | int RtlSdrSource::get_tuner_gain() 290 | { 291 | return rtlsdr_get_tuner_gain(m_dev); 292 | } 293 | 294 | 295 | // Return a list of supported tuner gain settings in units of 0.1 dB. 296 | std::vector RtlSdrSource::get_tuner_gains() 297 | { 298 | int num_gains = rtlsdr_get_tuner_gains(m_dev, NULL); 299 | if (num_gains <= 0) 300 | return std::vector(); 301 | 302 | std::vector gains(num_gains); 303 | if (rtlsdr_get_tuner_gains(m_dev, gains.data()) != num_gains) 304 | return std::vector(); 305 | 306 | return gains; 307 | } 308 | 309 | bool RtlSdrSource::start(DataBuffer* buf, std::atomic_bool *stop_flag) 310 | { 311 | m_buf = buf; 312 | m_stop_flag = stop_flag; 313 | 314 | if (m_thread == 0) 315 | { 316 | m_thread = new std::thread(run); 317 | return true; 318 | } 319 | else 320 | { 321 | m_error = "Source thread already started"; 322 | return false; 323 | } 324 | } 325 | 326 | bool RtlSdrSource::stop() 327 | { 328 | if (m_thread) 329 | { 330 | m_thread->join(); 331 | delete m_thread; 332 | m_thread = 0; 333 | } 334 | 335 | return true; 336 | } 337 | 338 | void RtlSdrSource::run() 339 | { 340 | IQSampleVector iqsamples; 341 | 342 | while (!m_this->m_stop_flag->load() && get_samples(&iqsamples)) 343 | { 344 | m_this->m_buf->push(move(iqsamples)); 345 | } 346 | } 347 | 348 | // Fetch a bunch of samples from the device. 349 | bool RtlSdrSource::get_samples(IQSampleVector *samples) 350 | { 351 | int r, n_read; 352 | 353 | if (!m_this->m_dev) { 354 | return false; 355 | } 356 | 357 | if (!samples) { 358 | return false; 359 | } 360 | 361 | std::vector buf(2 * m_this->m_block_length); 362 | 363 | r = rtlsdr_read_sync(m_this->m_dev, buf.data(), 2 * m_this->m_block_length, &n_read); 364 | 365 | if (r < 0) 366 | { 367 | m_this->m_error = "rtlsdr_read_sync failed"; 368 | return false; 369 | } 370 | 371 | if (n_read != 2 *m_this-> m_block_length) 372 | { 373 | m_this->m_error = "short read, samples lost"; 374 | return false; 375 | } 376 | 377 | samples->resize(m_this->m_block_length); 378 | 379 | for (int i = 0; i < m_this->m_block_length; i++) 380 | { 381 | int32_t re = buf[2*i]; 382 | int32_t im = buf[2*i+1]; 383 | (*samples)[i] = IQSample( (re - 128) / IQSample::value_type(128), 384 | (im - 128) / IQSample::value_type(128) ); 385 | } 386 | 387 | return true; 388 | } 389 | 390 | 391 | // Return a list of supported devices. 392 | void RtlSdrSource::get_device_names(std::vector& devices) 393 | { 394 | char manufacturer[256], product[256], serial[256]; 395 | int device_count = rtlsdr_get_device_count(); 396 | 397 | if (device_count > 0) 398 | { 399 | devices.resize(device_count); 400 | } 401 | 402 | devices.clear(); 403 | 404 | for (int i = 0; i < device_count; i++) 405 | { 406 | if (!rtlsdr_get_device_usb_strings(i, manufacturer, product, serial)) 407 | { 408 | std::ostringstream name_ostr; 409 | name_ostr << manufacturer << " " << product << " " << serial; 410 | devices.push_back(name_ostr.str()); 411 | } 412 | } 413 | } 414 | 415 | /* end */ 416 | --------------------------------------------------------------------------------