diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c1663c..23eb391 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,6 +21,12 @@ if(PORTAUDIO_FOUND) add_definitions(-DUSE_PORTAUDIO) endif(PORTAUDIO_FOUND) +if(RTLSDR_FOUND) + include_directories(SYSTEM ${RTLSDR_INCLUDE_DIRS}) + list(APPEND LIBS ${RTLSDR_LIBRARIES}) + add_definitions(-DUSE_RTLSDR) +endif(RTLSDR_FOUND) + FILE(GLOB SRCS src/*.c src/*.cpp) FILE(GLOB HEADERS include/*.h include/*.hpp) diff --git a/cmake/FindLibSndFile.cmake b/cmake/FindLibSndFile.cmake index b12b358..d976921 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 0000000..8c18b49 --- /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/include/dsd.h b/include/dsd.h index 22ef3e9..59dbe40 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -58,6 +58,10 @@ #define PA_LATENCY_OUT 0.100 #endif +#ifdef USE_RTLSDR +#include +#endif + /* * global variables */ @@ -85,7 +89,10 @@ typedef struct #ifdef USE_PORTAUDIO PaStream* audio_in_pa_stream; #endif - int audio_in_type; // 0 for device, 1 for file, 2 for portaudio +#ifdef USE_RTLSDR + rtlsdr_dev_t *rtlSdrDev; +#endif + 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 +324,8 @@ void processDSTAR_HD (dsd_opts * opts, dsd_state * state); short dmr_filter(short sample); short nxdn_filter(short sample); +void open_rtlsdr_stream(); +void cleanup_rtlsdr_stream(); +void get_rtlsdr_sample(); + #endif // DSD_H diff --git a/src/dsd_audio.c b/src/dsd_audio.c index 993add2..6e75b85 100644 --- a/src/dsd_audio.c +++ b/src/dsd_audio.c @@ -500,6 +500,13 @@ openAudioInDevice (dsd_opts * opts) exit(1); #endif } + else if(strncmp(opts->audio_in_dev, "rtl:", 3) == 0) + { + printf("USING RTL SDR\n"); + opts->audio_in_type = 3; + + // Starting the stream will open the device. + } else { struct stat stat_buf; diff --git a/src/dsd_main.c b/src/dsd_main.c index 4069ce0..450180d 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -327,6 +327,13 @@ liveScanner (dsd_opts * opts, dsd_state * state) return; } } +#endif +#ifdef USE_RTLSDR + if(opts->audio_in_type == 3) + { + printf("starting rtl stream\n"); + open_rtlsdr_stream(); + } #endif while (1) { @@ -411,6 +418,13 @@ cleanupAndExit (dsd_opts * opts, dsd_state * state) } #endif +#ifdef USE_RTLSDR + if(opts->audio_in_type == 3) + { + // TODO: cleanup demod threads + } +#endif + printf("\n"); printf("Total audio errors: %i\n", state->debug_audio_errors); printf("Total header errors: %i\n", state->debug_header_errors); diff --git a/src/dsd_symbol.c b/src/dsd_symbol.c index f286ae2..088486a 100644 --- a/src/dsd_symbol.c +++ b/src/dsd_symbol.c @@ -88,18 +88,26 @@ 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); + printf("sample: %d\n", 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 0000000..c99b88f --- /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 + +#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 volatile int do_exit = 0; +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 exit_flag; + pthread_t thread; + FILE *file; + char *filename; + int16_t result[MAXIMUM_BUF_LENGTH]; + int result_len; + 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; + +static void sighandler(int signum) +{ + fprintf(stderr, "Signal caught, exiting!\n"); + do_exit = 1; + rtlsdr_cancel_async(dongle.dev); +} + +#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 (do_exit) { + 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) +{ + printf("starting dongle thread\n"); + 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) +{ + printf("starting demod thread\n"); + struct demod_state *d = static_cast(arg); + struct output_state *o = d->output_target; + while (!do_exit) { + 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) { + do_exit = 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); + memcpy(o->result, d->result, 2*d->result_len); + o->result_len = d->result_len; + pthread_rwlock_unlock(&o->rw); + safe_cond_signal(&o->ready, &o->ready_m); + } + return 0; +} + +static void *output_thread_fn(void *arg) +{ + printf("starting output thread\n"); + struct output_state *s = static_cast(arg); + while (!do_exit) { + // use timedwait and pad out under runs + safe_cond_wait(&s->ready, &s->ready_m); + pthread_rwlock_rdlock(&s->rw); + printf("result_len: %i\n", s->result_len); + for (int i = 0; i < s->result_len; i++) + { + s->queue.push(reinterpret_cast(s->result[i * 2])); + } + printf("queue size: %lu\n", s->queue.size()); + // fwrite(s->result, 2, s->result_len, s->file); + pthread_rwlock_unlock(&s->rw); + } + 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) +{ + printf("starting controller thread\n"); + // 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 (!do_exit) { + 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] = 451800000; + 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); +} + +extern "C" void open_rtlsdr_stream(); +void open_rtlsdr_stream() +{ + printf("+++++ OPEN STREAM +++++\n"); + + struct sigaction sigact; + int r; + + dongle_init(&dongle); + demod_init(&demod); + output_init(&output); + controller_init(&controller); + + /* quadruple sample_rate to limit to Δθ to ±π/2 */ + demod.rate_in *= demod.post_downsample; + + if (!output.rate) output.rate = demod.rate_out; + + // sanity_checks(); + + 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(&output.thread, NULL, output_thread_fn, (void*)(&output)); + pthread_create(&demod.thread, NULL, demod_thread_fn, (void*)(&demod)); + pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void*)(&dongle)); + printf("done opening stream\n"); +} + +void cleanup_rtlsdr_stream() +{ + 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); + pthread_join(output.thread, NULL); + 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); +} + +extern "C" void get_rtlsdr_sample(int16_t *sample); +void get_rtlsdr_sample(int16_t *sample) +{ + printf("getting sample\n"); + safe_cond_wait(&output.ready, &output.ready_m); + *sample = output.queue.front(); + output.queue.pop(); +}