diff --git a/.travis.yml b/.travis.yml index d81eecc4..3bc5da67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: cpp -dist: trusty +dist: bionic sudo: false compiler: @@ -14,6 +14,7 @@ addons: - libmbe-dev - libsndfile1-dev - libitpp-dev + - librtlsdr-dev - portaudio19-dev before_script: diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c1663c5..fd2c0d04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ find_package(LibSndFile REQUIRED) find_package(MBE REQUIRED) find_package(ITPP REQUIRED) find_package(LibPortAudio) +find_package(RTLSDR) include_directories(SYSTEM ${LIBSNDFILE_INCLUDE_DIR} ${MBE_INCLUDE_DIR} ${ITPP_INCLUDE_DIR}) set(LIBS ${MBE_LIBRARY} ${LIBSNDFILE_LIBRARY} ${ITPP_LIBRARY}) @@ -20,9 +21,20 @@ if(PORTAUDIO_FOUND) add_definitions(-DUSE_PORTAUDIO) endif(PORTAUDIO_FOUND) +if(RTLSDR_FOUND) + find_package(Threads) + include_directories(SYSTEM ${RTLSDR_INCLUDE_DIRS}) + list(APPEND LIBS ${RTLSDR_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + add_definitions(-DUSE_RTLSDR) +endif(RTLSDR_FOUND) + FILE(GLOB SRCS src/*.c src/*.cpp) FILE(GLOB HEADERS include/*.h include/*.hpp) +if(NOT RTLSDR_FOUND) + list(REMOVE_ITEM SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/rtl_sdr_fm.cpp) +endif(NOT RTLSDR_FOUND) + configure_file("src/git_ver.c.in" "${CMAKE_CURRENT_BINARY_DIR}/git_ver.c" @ONLY) list(APPEND SRCS "${CMAKE_CURRENT_BINARY_DIR}/git_ver.c") diff --git a/cmake/FindLibSndFile.cmake b/cmake/FindLibSndFile.cmake index b12b358d..d9769219 100644 --- a/cmake/FindLibSndFile.cmake +++ b/cmake/FindLibSndFile.cmake @@ -12,5 +12,5 @@ SET(LIBSNDFILE_NAMES ${LIBSNDFILE_NAMES} sndfile libsndfile) FIND_LIBRARY(LIBSNDFILE_LIBRARY NAMES ${LIBSNDFILE_NAMES}) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LIBSNDFILE DEFAULT_MSG LIBSNDFILE_LIBRARY - LIBSNDFILE_INCLUDE_DIR) \ No newline at end of file +find_package_handle_standard_args(LibSndFile DEFAULT_MSG LIBSNDFILE_LIBRARY + LIBSNDFILE_INCLUDE_DIR) diff --git a/cmake/FindRTLSDR.cmake b/cmake/FindRTLSDR.cmake new file mode 100644 index 00000000..8c18b496 --- /dev/null +++ b/cmake/FindRTLSDR.cmake @@ -0,0 +1,29 @@ +# - Try to find rtlsdr - the hardware driver for the realtek chip in the dvb receivers +# Once done this will define +# RTLSDR_FOUND - System has rtlsdr +# RTLSDR_LIBRARIES - The rtlsdr libraries +# RTLSDR_INCLUDE_DIRS - The rtlsdr include directories +# RTLSDR_LIB_DIRS - The rtlsdr library directories + +if (NOT RTLSDR_FOUND) + + find_package(PkgConfig) + pkg_check_modules(RTLSDR_PKG librtlsdr) + set(RTLSDR_DEFINITIONS ${PC_RTLSDR_CFLAGS_OTHER}) + + find_path(RTLSDR_INCLUDE_DIR + NAMES rtl-sdr.h + HINTS ${RTLSDR_PKG_INCLUDE_DIRS}) + + find_library(RTLSDR_LIBRARY + NAMES rtlsdr + HINTS ${RTLSDR_PKG_LIBRARY_DIRS}) + + set(RTLSDR_LIBRARIES ${RTLSDR_LIBRARY}) + set(RTLSDR_INCLUDE_DIRS ${RTLSDR_INCLUDE_DIR}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(RTLSDR DEFAULT_MSG RTLSDR_LIBRARY RTLSDR_INCLUDE_DIR) + mark_as_advanced(RTLSDR_INCLUDE_DIR RTLSDR_LIBRARY) + +endif (NOT RTLSDR_FOUND) diff --git a/debian/control b/debian/control index 58239bc8..0479cf48 100644 --- a/debian/control +++ b/debian/control @@ -2,8 +2,8 @@ Source: dsd Section: hamradio Priority: optional Maintainer: Jared Szechy -Build-Depends: debhelper (>= 9), libmbe-dev, libitpp-dev, libsndfile1-dev, - portaudio19-dev, cmake, git, help2man +Build-Depends: debhelper (>= 9), libmbe-dev, libitpp-dev, librtlsdr-dev + libsndfile1-dev, portaudio19-dev, cmake, git, help2man Standards-Version: 4.1.4 Homepage: https://github.com/szechyjs/dsd Vcs-Git: https://github.com/szechyjs/dsd.git @@ -17,4 +17,4 @@ Pre-Depends: ${misc:Pre-Depends} Depends: ${shlibs:Depends}, ${misc:Depends} Description: Digital Speech Decoder DSD is able to decode several digital voice formats from discriminator tap - audio and synthesize the decoded speech. \ No newline at end of file + audio and synthesize the decoded speech. diff --git a/debian/copyright b/debian/copyright index 898cf452..46dadabe 100644 --- a/debian/copyright +++ b/debian/copyright @@ -20,6 +20,14 @@ Files: src/dstar_header.c include/dstar_header.h include/fcs.h Copyright: Copyright (C) 2010 by Kristoff Bonne, ON1ARF License: GPL-2 +Files: src/rtl_sdr_fm.cpp +Copyright: Copyright (C) 2012 by Steve Markgraf + Copyright (C) 2012 by Hoernchen + Copyright (C) 2012 by Kyle Keen + Copyright (C) 2013 by Elias Oenal + Copyright (C) 2014 by Kyle Keen +License: GPL-2 + Files: include/descramble.h Copyright: Copyright (C) 2011 by Jonathan Naylor, G4KLX License: GPL-2 @@ -103,4 +111,4 @@ License: BSL-1.0 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. \ No newline at end of file + DEALINGS IN THE SOFTWARE. diff --git a/include/dsd.h b/include/dsd.h index 22ef3e9b..1171d750 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -58,11 +58,14 @@ #define PA_LATENCY_OUT 0.100 #endif +#ifdef USE_RTLSDR +#include +#endif + /* * global variables */ -int exitflag; - +static volatile int exitflag; typedef struct { @@ -85,7 +88,9 @@ typedef struct #ifdef USE_PORTAUDIO PaStream* audio_in_pa_stream; #endif - int audio_in_type; // 0 for device, 1 for file, 2 for portaudio + uint32_t rtlsdr_center_freq; + int rtlsdr_ppm_error; + int audio_in_type; // 0 for device, 1 for file, 2 for portaudio, 3 for rtlsdr char audio_out_dev[1024]; int audio_out_fd; SNDFILE *audio_out_file; @@ -317,4 +322,15 @@ void processDSTAR_HD (dsd_opts * opts, dsd_state * state); short dmr_filter(short sample); short nxdn_filter(short sample); +#ifdef __cplusplus +extern "C" { +#endif +void open_rtlsdr_stream(dsd_opts *opts); +void cleanup_rtlsdr_stream(); +void get_rtlsdr_sample(int16_t *sample); +void rtlsdr_sighandler(); +#ifdef __cplusplus +} +#endif + #endif // DSD_H diff --git a/src/dsd_audio.c b/src/dsd_audio.c index 993add27..e706deb4 100644 --- a/src/dsd_audio.c +++ b/src/dsd_audio.c @@ -500,6 +500,10 @@ openAudioInDevice (dsd_opts * opts) exit(1); #endif } + else if(strncmp(opts->audio_in_dev, "rtl:", 3) == 0) + { + opts->audio_in_type = 3; + } else { struct stat stat_buf; diff --git a/src/dsd_main.c b/src/dsd_main.c index 4069ce02..075175e4 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -274,6 +274,8 @@ usage () printf (" -n Do not send synthesized speech to audio output device\n"); printf (" -w Output synthesized speech to a .wav file\n"); printf (" -a Display port audio devices\n"); + printf (" -c RTL-SDR center frequency\n"); + printf (" -P RTL-SDR ppm error (default = 0)\n"); printf ("\n"); printf ("Scanner control options:\n"); printf (" -B Serial port baud rate (default=115200)\n"); @@ -327,6 +329,12 @@ liveScanner (dsd_opts * opts, dsd_state * state) return; } } +#endif +#ifdef USE_RTLSDR + if(opts->audio_in_type == 3) + { + open_rtlsdr_stream(opts); + } #endif while (1) { @@ -411,6 +419,14 @@ cleanupAndExit (dsd_opts * opts, dsd_state * state) } #endif +#ifdef USE_RTLSDR + if(opts->audio_in_type == 3) + { + // TODO: cleanup demod threads + cleanup_rtlsdr_stream(); + } +#endif + printf("\n"); printf("Total audio errors: %i\n", state->debug_audio_errors); printf("Total header errors: %i\n", state->debug_header_errors); @@ -447,6 +463,35 @@ sigfun (int sig) { exitflag = 1; signal (SIGINT, SIG_DFL); +#ifdef USE_RTLSDR + rtlsdr_sighandler(); +#endif +} + +double atofs(char *s) +{ + char last; + int len; + double suff = 1.0; + len = strlen(s); + last = s[len-1]; + s[len-1] = '\0'; + switch (last) { + case 'g': + case 'G': + suff *= 1e3; + case 'm': + case 'M': + suff *= 1e3; + case 'k': + case 'K': + suff *= 1e3; + suff *= atof(s); + s[len-1] = last; + return suff; + } + s[len-1] = last; + return atof(s); } int @@ -469,7 +514,7 @@ main (int argc, char **argv) exitflag = 0; signal (SIGINT, sigfun); - while ((c = getopt (argc, argv, "haep:qstv:z:i:o:d:g:nw:B:C:R:f:m:u:x:A:S:M:rl")) != -1) + while ((c = getopt (argc, argv, "haep:P:qstv:z:i:o:d:c:g:nw:B:C:R:f:m:u:x:A:S:M:rl")) != -1) { opterr = 0; switch (c) @@ -506,6 +551,9 @@ main (int argc, char **argv) opts.unmute_encrypted_p25 = 1; } break; + case 'P': + sscanf (optarg, "%i", &opts.rtlsdr_ppm_error); + break; case 'q': opts.errorbars = 0; opts.verbose = 0; @@ -551,6 +599,10 @@ main (int argc, char **argv) opts.mbe_out_dir[1023] = '\0'; printf ("Writing mbe data files to directory %s\n", opts.mbe_out_dir); break; + case 'c': + opts.rtlsdr_center_freq = (uint32_t)atofs(optarg); + printf("Using center freq: %i\n", opts.rtlsdr_center_freq); + break; case 'g': sscanf (optarg, "%f", &opts.audio_gain); if (opts.audio_gain < (float) 0 ) diff --git a/src/dsd_symbol.c b/src/dsd_symbol.c index f286ae28..d34c7e60 100644 --- a/src/dsd_symbol.c +++ b/src/dsd_symbol.c @@ -88,18 +88,25 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync) cleanupAndExit (opts, state); } } - else - { + else if (opts->audio_in_type == 2) + { #ifdef USE_PORTAUDIO - PaError err = Pa_ReadStream( opts->audio_in_pa_stream, &sample, 1 ); - if( err != paNoError ) - { - fprintf( stderr, "An error occured while using the portaudio input stream\n" ); - fprintf( stderr, "Error number: %d\n", err ); - fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); - } + PaError err = Pa_ReadStream( opts->audio_in_pa_stream, &sample, 1 ); + if( err != paNoError ) + { + fprintf( stderr, "An error occured while using the portaudio input stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + } #endif - } + } + else if (opts->audio_in_type == 3) + { +#ifdef USE_RTLSDR + // TODO: need to read demodulated stream here + get_rtlsdr_sample(&sample); +#endif + } #ifdef TRACE_DSD state->debug_sample_index++; diff --git a/src/rtl_sdr_fm.cpp b/src/rtl_sdr_fm.cpp new file mode 100644 index 00000000..b8b6cb51 --- /dev/null +++ b/src/rtl_sdr_fm.cpp @@ -0,0 +1,986 @@ +/* + * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver + * Copyright (C) 2012 by Steve Markgraf + * Copyright (C) 2012 by Hoernchen + * Copyright (C) 2012 by Kyle Keen + * Copyright (C) 2013 by Elias Oenal + * Copyright (C) 2014 by Kyle Keen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dsd.h" + +#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_BUF_LENGTH (1 * 16384) +#define MAXIMUM_OVERSAMPLE 16 +#define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH) +#define AUTO_GAIN -100 +#define BUFFER_DUMP 4096 + +#define FREQUENCIES_LIMIT 1000 + +static int lcm_post[17] = {1,1,1,3,1,5,3,7,1,9,5,11,3,13,7,15,1}; +static int ACTUAL_BUF_LENGTH; + +static int *atan_lut = NULL; +static int atan_lut_size = 131072; /* 512 KB */ +static int atan_lut_coef = 8; + +struct dongle_state +{ + int exit_flag; + pthread_t thread; + rtlsdr_dev_t *dev; + int dev_index; + uint32_t freq; + uint32_t rate; + int gain; + uint16_t buf16[MAXIMUM_BUF_LENGTH]; + uint32_t buf_len; + int ppm_error; + int offset_tuning; + int direct_sampling; + int mute; + struct demod_state *demod_target; +}; + +struct demod_state +{ + int exit_flag; + pthread_t thread; + int16_t lowpassed[MAXIMUM_BUF_LENGTH]; + int lp_len; + int16_t lp_i_hist[10][6]; + int16_t lp_q_hist[10][6]; + int16_t result[MAXIMUM_BUF_LENGTH]; + int16_t droop_i_hist[9]; + int16_t droop_q_hist[9]; + int result_len; + int rate_in; + int rate_out; + int rate_out2; + int now_r, now_j; + int pre_r, pre_j; + int prev_index; + int downsample; /* min 1, max 256 */ + int post_downsample; + int output_scale; + int squelch_level, conseq_squelch, squelch_hits, terminate_on_squelch; + int downsample_passes; + int comp_fir_size; + int custom_atan; + int deemph, deemph_a; + int now_lpr; + int prev_lpr_index; + int dc_block, dc_avg; + void (*mode_demod)(struct demod_state*); + pthread_rwlock_t rw; + pthread_cond_t ready; + pthread_mutex_t ready_m; + struct output_state *output_target; +}; + +struct output_state +{ + int rate; + std::queue queue; + pthread_rwlock_t rw; + pthread_cond_t ready; + pthread_mutex_t ready_m; +}; + +struct controller_state +{ + int exit_flag; + pthread_t thread; + uint32_t freqs[FREQUENCIES_LIMIT]; + int freq_len; + int freq_now; + int edge; + int wb_mode; + pthread_cond_t hop; + pthread_mutex_t hop_m; +}; + +struct dongle_state dongle; +struct demod_state demod; +struct output_state output; +struct controller_state controller; + +#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) +#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) + +/* {length, coef, coef, coef} and scaled by 2^15 + for now, only length 9, optimal way to get +85% bandwidth */ +#define CIC_TABLE_MAX 10 +int cic_9_tables[][10] = { + {0,}, + {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156}, + {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128}, + {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129}, + {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122}, + {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120}, + {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120}, + {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119}, + {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119}, + {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119}, + {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, +}; + +void rotate_90(unsigned char *buf, uint32_t len) +/* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j + or [0, 1, -3, 2, -4, -5, 7, -6] */ +{ + uint32_t i; + unsigned char tmp; + for (i=0; ilp_len) { + d->now_r += d->lowpassed[i]; + d->now_j += d->lowpassed[i+1]; + i += 2; + d->prev_index++; + if (d->prev_index < d->downsample) { + continue; + } + d->lowpassed[i2] = d->now_r; // * d->output_scale; + d->lowpassed[i2+1] = d->now_j; // * d->output_scale; + d->prev_index = 0; + d->now_r = 0; + d->now_j = 0; + i2 += 2; + } + d->lp_len = i2; +} + +int low_pass_simple(int16_t *signal2, int len, int step) +// no wrap around, length must be multiple of step +{ + int i, i2, sum; + for(i=0; i < len; i+=step) { + sum = 0; + for(i2=0; i2rate_out; + int slow = s->rate_out2; + while (i < s->result_len) { + s->now_lpr += s->result[i]; + i++; + s->prev_lpr_index += slow; + if (s->prev_lpr_index < fast) { + continue; + } + s->result[i2] = (int16_t)(s->now_lpr / (fast/slow)); + s->prev_lpr_index -= fast; + s->now_lpr = 0; + i2 += 1; + } + s->result_len = i2; +} + +void fifth_order(int16_t *data, int length, int16_t *hist) +/* for half of interleaved data */ +{ + int i; + int16_t a, b, c, d, e, f; + a = hist[1]; + b = hist[2]; + c = hist[3]; + d = hist[4]; + e = hist[5]; + f = data[0]; + /* a downsample should improve resolution, so don't fully shift */ + data[0] = (a + (b+e)*5 + (c+d)*10 + f) >> 4; + for (i=4; i> 4; + } + /* archive */ + hist[0] = a; + hist[1] = b; + hist[2] = c; + hist[3] = d; + hist[4] = e; + hist[5] = f; +} + +void generic_fir(int16_t *data, int length, int *fir, int16_t *hist) +/* Okay, not at all generic. Assumes length 9, fix that eventually. */ +{ + int d, temp, sum; + for (d=0; d> 15 ; + hist[0] = hist[1]; + hist[1] = hist[2]; + hist[2] = hist[3]; + hist[3] = hist[4]; + hist[4] = hist[5]; + hist[5] = hist[6]; + hist[6] = hist[7]; + hist[7] = hist[8]; + hist[8] = temp; + } +} + +// define our own complex math ops because ARMv5 has no hardware float +void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) +{ + *cr = ar*br - aj*bj; + *cj = aj*br + ar*bj; +} + +int polar_discriminant(int ar, int aj, int br, int bj) +{ + int cr, cj; + double angle; + multiply(ar, aj, br, -bj, &cr, &cj); + angle = atan2((double)cj, (double)cr); + return (int)(angle / 3.14159 * (1<<14)); +} + +int fast_atan2(int y, int x) +/* pre scaled for int16 */ +{ + int yabs, angle; + int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14 + if (x==0 && y==0) { + return 0; + } + yabs = y; + if (yabs < 0) { + yabs = -yabs; + } + if (x >= 0) { + angle = pi4 - pi4 * (x-yabs) / (x+yabs); + } else { + angle = pi34 - pi4 * (x+yabs) / (yabs-x); + } + if (y < 0) { + return -angle; + } + return angle; +} + +int polar_disc_fast(int ar, int aj, int br, int bj) +{ + int cr, cj; + multiply(ar, aj, br, -bj, &cr, &cj); + return fast_atan2(cj, cr); +} + +int atan_lut_init(void) +{ + int i = 0; + + atan_lut = static_cast(malloc(atan_lut_size * sizeof(int))); + + for (i = 0; i < atan_lut_size; i++) { + atan_lut[i] = (int) (atan((double) i / (1< 0) + {return 1 << 13;} + if (cr == 0 && cj < 0) + {return -(1 << 13);} + if (cj == 0 && cr > 0) + {return 0;} + if (cj == 0 && cr < 0) + {return 1 << 14;} + } + + /* real range -32768 - 32768 use 64x range -> absolute maximum: 2097152 */ + x = (cj << atan_lut_coef) / cr; + x_abs = abs(x); + + if (x_abs >= atan_lut_size) { + /* we can use linear range, but it is not necessary */ + return (cj > 0) ? 1<<13 : -(1<<13); + } + + if (x > 0) { + return (cj > 0) ? atan_lut[x] : atan_lut[x] - (1<<14); + } else { + return (cj > 0) ? (1<<14) - atan_lut[-x] : -atan_lut[-x]; + } + + return 0; +} + +void fm_demod(struct demod_state *fm) +{ + int i, pcm; + int16_t *lp = fm->lowpassed; + pcm = polar_discriminant(lp[0], lp[1], + fm->pre_r, fm->pre_j); + fm->result[0] = (int16_t)pcm; + for (i = 2; i < (fm->lp_len-1); i += 2) { + switch (fm->custom_atan) { + case 0: + pcm = polar_discriminant(lp[i], lp[i+1], + lp[i-2], lp[i-1]); + break; + case 1: + pcm = polar_disc_fast(lp[i], lp[i+1], + lp[i-2], lp[i-1]); + break; + case 2: + pcm = polar_disc_lut(lp[i], lp[i+1], + lp[i-2], lp[i-1]); + break; + } + fm->result[i/2] = (int16_t)pcm; + } + fm->pre_r = lp[fm->lp_len - 2]; + fm->pre_j = lp[fm->lp_len - 1]; + fm->result_len = fm->lp_len/2; +} + +void raw_demod(struct demod_state *fm) +{ + int i; + for (i = 0; i < fm->lp_len; i++) { + fm->result[i] = (int16_t)fm->lowpassed[i]; + } + fm->result_len = fm->lp_len; +} + +void deemph_filter(struct demod_state *fm) +{ + static int avg; // cheating... + int i, d; + // de-emph IIR + // avg = avg * (1 - alpha) + sample * alpha; + for (i = 0; i < fm->result_len; i++) { + d = fm->result[i] - avg; + if (d > 0) { + avg += (d + fm->deemph_a/2) / fm->deemph_a; + } else { + avg += (d - fm->deemph_a/2) / fm->deemph_a; + } + fm->result[i] = (int16_t)avg; + } +} + +void dc_block_filter(struct demod_state *fm) +{ + int i, avg; + int64_t sum = 0; + for (i=0; i < fm->result_len; i++) { + sum += fm->result[i]; + } + avg = sum / fm->result_len; + avg = (avg + fm->dc_avg * 9) / 10; + for (i=0; i < fm->result_len; i++) { + fm->result[i] -= avg; + } + fm->dc_avg = avg; +} + +int rms(int16_t *samples, int len, int step) +/* largely lifted from rtl_power */ +{ + int i; + long p, t, s; + double dc, err; + + p = t = 0L; + for (i=0; idownsample_passes; + if (ds_p) { + for (i=0; i < ds_p; i++) { + fifth_order(d->lowpassed, (d->lp_len >> i), d->lp_i_hist[i]); + fifth_order(d->lowpassed+1, (d->lp_len >> i) - 1, d->lp_q_hist[i]); + } + d->lp_len = d->lp_len >> ds_p; + /* droop compensation */ + if (d->comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) { + generic_fir(d->lowpassed, d->lp_len, + cic_9_tables[ds_p], d->droop_i_hist); + generic_fir(d->lowpassed+1, d->lp_len-1, + cic_9_tables[ds_p], d->droop_q_hist); + } + } else { + low_pass(d); + } + /* power squelch */ + if (d->squelch_level) { + sr = rms(d->lowpassed, d->lp_len, 1); + if (sr < d->squelch_level) { + d->squelch_hits++; + for (i=0; ilp_len; i++) { + d->lowpassed[i] = 0; + } + } else { + d->squelch_hits = 0;} + } + d->mode_demod(d); /* lowpassed -> result */ + if (d->mode_demod == &raw_demod) { + return; + } + /* todo, fm noise squelch */ + // use nicer filter here too? + if (d->post_downsample > 1) { + d->result_len = low_pass_simple(d->result, d->result_len, d->post_downsample);} + if (d->deemph) { + deemph_filter(d);} + if (d->dc_block) { + dc_block_filter(d);} + if (d->rate_out2 > 0) { + low_pass_real(d); + //arbitrary_resample(d->result, d->result, d->result_len, d->result_len * d->rate_out2 / d->rate_out); + } +} + +static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) +{ + int i; + struct dongle_state *s = static_cast(ctx); + struct demod_state *d = s->demod_target; + + if (exitflag) { + return;} + if (!ctx) { + return;} + if (s->mute) { + for (i=0; imute; i++) { + buf[i] = 127;} + s->mute = 0; + } + if (!s->offset_tuning) { + rotate_90(buf, len);} + for (i=0; i<(int)len; i++) { + s->buf16[i] = (int16_t)buf[i] - 127;} + pthread_rwlock_wrlock(&d->rw); + memcpy(d->lowpassed, s->buf16, 2*len); + d->lp_len = len; + pthread_rwlock_unlock(&d->rw); + safe_cond_signal(&d->ready, &d->ready_m); +} + +static void *dongle_thread_fn(void *arg) +{ + struct dongle_state *s = static_cast(arg); + rtlsdr_read_async(s->dev, rtlsdr_callback, s, 0, s->buf_len); + return 0; +} + +static void *demod_thread_fn(void *arg) +{ + struct demod_state *d = static_cast(arg); + struct output_state *o = d->output_target; + while (!exitflag) { + safe_cond_wait(&d->ready, &d->ready_m); + pthread_rwlock_wrlock(&d->rw); + full_demod(d); + pthread_rwlock_unlock(&d->rw); + if (d->exit_flag) { + exitflag = 1; + } + if (d->squelch_level && d->squelch_hits > d->conseq_squelch) { + d->squelch_hits = d->conseq_squelch + 1; /* hair trigger */ + safe_cond_signal(&controller.hop, &controller.hop_m); + continue; + } + pthread_rwlock_wrlock(&o->rw); + for (int i = 0; i < d->result_len; i++) + { + o->queue.push(d->result[i]); + } + pthread_rwlock_unlock(&o->rw); + safe_cond_signal(&o->ready, &o->ready_m); + } + return 0; +} + +int nearest_gain(rtlsdr_dev_t *dev, int target_gain) +{ + int i, r, err1, err2, count, nearest; + int* gains; + r = rtlsdr_set_tuner_gain_mode(dev, 1); + if (r < 0) { + fprintf(stderr, "WARNING: Failed to enable manual gain.\n"); + return r; + } + count = rtlsdr_get_tuner_gains(dev, NULL); + if (count <= 0) { + return 0; + } + gains = static_cast(malloc(sizeof(int) * count)); + count = rtlsdr_get_tuner_gains(dev, gains); + nearest = gains[0]; + for (i=0; idownsample = (1000000 / dm->rate_in) + 1; + if (dm->downsample_passes) { + dm->downsample_passes = (int)log2(dm->downsample) + 1; + dm->downsample = 1 << dm->downsample_passes; + } + capture_freq = freq; + capture_rate = dm->downsample * dm->rate_in; + if (!d->offset_tuning) { + capture_freq = freq + capture_rate/4;} + capture_freq += cs->edge * dm->rate_in / 2; + dm->output_scale = (1<<15) / (128 * dm->downsample); + if (dm->output_scale < 1) { + dm->output_scale = 1;} + if (dm->mode_demod == &fm_demod) { + dm->output_scale = 1;} + d->freq = (uint32_t)capture_freq; + d->rate = (uint32_t)capture_rate; +} + +static void *controller_thread_fn(void *arg) +{ + // thoughts for multiple dongles + // might be no good using a controller thread if retune/rate blocks + int i; + struct controller_state *s = static_cast(arg); + + if (s->wb_mode) { + for (i=0; i < s->freq_len; i++) { + s->freqs[i] += 16000;} + } + + /* set up primary channel */ + optimal_settings(s->freqs[0], demod.rate_in); + if (dongle.direct_sampling) { + verbose_direct_sampling(dongle.dev, 1);} + if (dongle.offset_tuning) { + verbose_offset_tuning(dongle.dev);} + + /* Set the frequency */ + verbose_set_frequency(dongle.dev, dongle.freq); + fprintf(stderr, "Oversampling input by: %ix.\n", demod.downsample); + fprintf(stderr, "Oversampling output by: %ix.\n", demod.post_downsample); + fprintf(stderr, "Buffer size: %0.2fms\n", + 1000 * 0.5 * (float)ACTUAL_BUF_LENGTH / (float)dongle.rate); + + /* Set the sample rate */ + verbose_set_sample_rate(dongle.dev, dongle.rate); + fprintf(stderr, "Output at %u Hz.\n", demod.rate_in/demod.post_downsample); + + while (!exitflag) { + safe_cond_wait(&s->hop, &s->hop_m); + if (s->freq_len <= 1) { + continue;} + /* hacky hopping */ + s->freq_now = (s->freq_now + 1) % s->freq_len; + optimal_settings(s->freqs[s->freq_now], demod.rate_in); + rtlsdr_set_center_freq(dongle.dev, dongle.freq); + dongle.mute = BUFFER_DUMP; + } + return 0; +} + +void dongle_init(struct dongle_state *s) +{ + s->rate = DEFAULT_SAMPLE_RATE; + s->gain = AUTO_GAIN; // tenths of a dB + s->mute = 0; + s->direct_sampling = 0; + s->offset_tuning = 0; + s->demod_target = &demod; +} + +void demod_init(struct demod_state *s) +{ + s->rate_in = DEFAULT_SAMPLE_RATE; + s->rate_out = DEFAULT_SAMPLE_RATE; + s->squelch_level = 0; + s->conseq_squelch = 10; + s->terminate_on_squelch = 0; + s->squelch_hits = 11; + s->downsample_passes = 0; + s->comp_fir_size = 0; + s->prev_index = 0; + s->post_downsample = 1; // once this works, default = 4 + s->custom_atan = 0; + s->deemph = 0; + s->rate_out2 = -1; // flag for disabled + s->mode_demod = &fm_demod; + s->pre_j = s->pre_r = s->now_r = s->now_j = 0; + s->prev_lpr_index = 0; + s->deemph_a = 0; + s->now_lpr = 0; + s->dc_block = 0; + s->dc_avg = 0; + pthread_rwlock_init(&s->rw, NULL); + pthread_cond_init(&s->ready, NULL); + pthread_mutex_init(&s->ready_m, NULL); + s->output_target = &output; +} + +void demod_cleanup(struct demod_state *s) +{ + pthread_rwlock_destroy(&s->rw); + pthread_cond_destroy(&s->ready); + pthread_mutex_destroy(&s->ready_m); +} + +void output_init(struct output_state *s) +{ + s->rate = DEFAULT_SAMPLE_RATE; + pthread_rwlock_init(&s->rw, NULL); + pthread_cond_init(&s->ready, NULL); + pthread_mutex_init(&s->ready_m, NULL); +} + +void output_cleanup(struct output_state *s) +{ + pthread_rwlock_destroy(&s->rw); + pthread_cond_destroy(&s->ready); + pthread_mutex_destroy(&s->ready_m); +} + +void controller_init(struct controller_state *s) +{ + s->freqs[0] = 446000000; + s->freq_len = 0; + s->edge = 0; + s->wb_mode = 0; + pthread_cond_init(&s->hop, NULL); + pthread_mutex_init(&s->hop_m, NULL); +} + +void controller_cleanup(struct controller_state *s) +{ + pthread_cond_destroy(&s->hop); + pthread_mutex_destroy(&s->hop_m); +} + +void sanity_checks(void) +{ + if (controller.freq_len == 0) { + fprintf(stderr, "Please specify a frequency.\n"); + exit(1); + } + + if (controller.freq_len >= FREQUENCIES_LIMIT) { + fprintf(stderr, "Too many channels, maximum %i.\n", FREQUENCIES_LIMIT); + exit(1); + } + + if (controller.freq_len > 1 && demod.squelch_level == 0) { + fprintf(stderr, "Please specify a squelch level. Required for scanning multiple frequencies.\n"); + exit(1); + } + +} + +void rtlsdr_sighandler() +{ + fprintf(stderr, "Signal caught, exiting!\n"); + rtlsdr_cancel_async(dongle.dev); +} + +void open_rtlsdr_stream(dsd_opts *opts) +{ + struct sigaction sigact; + int r; + + dongle_init(&dongle); + demod_init(&demod); + output_init(&output); + controller_init(&controller); + + if (opts->rtlsdr_center_freq > 0) { + controller.freqs[controller.freq_len] = opts->rtlsdr_center_freq; + controller.freq_len++; + } + + if (opts->rtlsdr_ppm_error > 0) { + dongle.ppm_error = opts->rtlsdr_ppm_error; + } + + /* quadruple sample_rate to limit to Δθ to ±π/2 */ + demod.rate_in *= demod.post_downsample; + + if (!output.rate) output.rate = demod.rate_out; + + sanity_checks(); + + if (controller.freq_len > 1) demod.terminate_on_squelch = 0; + + ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH; + + r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index); + if (r < 0) + { + fprintf(stderr, "Failed to open rtlsdr device %d.\n", dongle.dev_index); + exit(1); + } + + // sigact.sa_handler = sighandler; + // sigemptyset(&sigact.sa_mask); + // sigact.sa_flags = 0; + // sigaction(SIGINT, &sigact, NULL); + // sigaction(SIGTERM, &sigact, NULL); + // sigaction(SIGQUIT, &sigact, NULL); + // sigaction(SIGPIPE, &sigact, NULL); + + if (demod.deemph) { + demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6))))); + } + + /* Set the tuner gain */ + if (dongle.gain == AUTO_GAIN) { + verbose_auto_gain(dongle.dev); + } else { + dongle.gain = nearest_gain(dongle.dev, dongle.gain); + verbose_gain_set(dongle.dev, dongle.gain); + } + + verbose_ppm_set(dongle.dev, dongle.ppm_error); + + /* Reset endpoint before we start reading from it (mandatory) */ + verbose_reset_buffer(dongle.dev); + + pthread_create(&controller.thread, NULL, controller_thread_fn, (void*)(&controller)); + usleep(100000); + pthread_create(&demod.thread, NULL, demod_thread_fn, (void*)(&demod)); + pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void*)(&dongle)); +} + +void cleanup_rtlsdr_stream() +{ + printf("cleaning up...\n"); + rtlsdr_cancel_async(dongle.dev); + pthread_join(dongle.thread, NULL); + safe_cond_signal(&demod.ready, &demod.ready_m); + pthread_join(demod.thread, NULL); + safe_cond_signal(&output.ready, &output.ready_m); + safe_cond_signal(&controller.hop, &controller.hop_m); + pthread_join(controller.thread, NULL); + + //dongle_cleanup(&dongle); + demod_cleanup(&demod); + output_cleanup(&output); + controller_cleanup(&controller); + + rtlsdr_close(dongle.dev); +} + +void get_rtlsdr_sample(int16_t *sample) +{ + if (output.queue.empty()) + { + safe_cond_wait(&output.ready, &output.ready_m); + } + pthread_rwlock_wrlock(&output.rw); + *sample = output.queue.front(); + output.queue.pop(); + pthread_rwlock_unlock(&output.rw); +}