diff --git a/Makefile b/Makefile index f88446e..38414eb 100644 --- a/Makefile +++ b/Makefile @@ -186,13 +186,13 @@ showconfig: %.o: %.c *.h $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ -dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o ais_charset.o $(SDR_OBJ) $(COMPAT) $(CPUFEATURES_OBJS) $(STARCH_OBJS) +dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o ais_charset.o adaptive.o $(SDR_OBJ) $(COMPAT) $(CPUFEATURES_OBJS) $(STARCH_OBJS) $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) $(LIBS_CURSES) -view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o ais_charset.o $(COMPAT) +view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o ais_charset.o sdr_stub.o $(COMPAT) $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_CURSES) -faup1090: faup1090.o anet.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o ais_charset.o $(COMPAT) +faup1090: faup1090.o anet.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o ais_charset.o sdr_stub.o $(COMPAT) $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) starch-benchmark: cpu.o dsp/helpers/tables.o $(CPUFEATURES_OBJS) $(STARCH_OBJS) $(STARCH_BENCHMARK_OBJ) diff --git a/adaptive.c b/adaptive.c new file mode 100644 index 0000000..fa1df6a --- /dev/null +++ b/adaptive.c @@ -0,0 +1,551 @@ +// Part of dump1090, a Mode S message decoder for RTLSDR devices. +// +// adaptive.c: adaptive gain control +// +// Copyright (c) 2021 FlightAware, LLC +// +// This file is free software: you may copy, redistribute 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 file 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 "dump1090.h" +#include "adaptive.h" + +// +// gain limits +// +static int adaptive_gain_min; +static int adaptive_gain_max; + +// gain steps relative to current gain +static float adaptive_gain_up_db; +static float adaptive_gain_down_db; + +// +// block handling +// + +static unsigned adaptive_block_remaining; +static unsigned adaptive_block_size; + +void adaptive_init(); +void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decoded); +static void adaptive_update_single(uint16_t *buf, unsigned length, struct modesMessage *decoded); +static void adaptive_end_of_block(); + +// +// burst handling +// + +static unsigned adaptive_burst_window_size; +static unsigned adaptive_burst_window_remaining; +static unsigned adaptive_burst_window_counter; +static unsigned adaptive_burst_runlength; +static unsigned adaptive_burst_block_counter; +static unsigned adaptive_burst_block_loud_decodes; +static double adaptive_burst_smoothed; +static double adaptive_burst_loud_decodes_smoothed; +static unsigned adaptive_burst_change_delay; +static double adaptive_burst_loud_threshold; + +static void adaptive_burst_update(uint16_t *buf, unsigned length); +static void adaptive_burst_skip(unsigned length); +static unsigned adaptive_burst_count_samples(uint16_t *buf, unsigned n); +static void adaptive_burst_scan_windows(uint16_t *buf, unsigned windows); +static void adaptive_burst_end_of_window(unsigned counter); +static void adaptive_burst_end_of_block(); + +static void adaptive_burst_control_update(); + +// +// noise floor measurement (adaptive dynamic range) +// + +static unsigned *adaptive_range_radix; +static unsigned adaptive_range_counter; +static double adaptive_range_smoothed; +static enum { RANGE_SCAN_IDLE, RANGE_SCAN_UP, RANGE_SCAN_DOWN } adaptive_range_state = RANGE_SCAN_UP; +static unsigned adaptive_range_delay; + +static void adaptive_range_update(uint16_t *buf, unsigned length); +static void adaptive_range_end_of_block(); +static void adaptive_range_control_update(); + + +static bool adaptive_set_gain(int step, const char *why) +{ + if (step < adaptive_gain_min) + step = adaptive_gain_min; + if (step > adaptive_gain_max) + step = adaptive_gain_max; + + int current_gain = sdrGetGain(); + if (current_gain == step) + return false; + + fprintf(stderr, "adaptive: changing gain from %.1fdB (step %d) to %.1fdB (step %d) because: %s\n", + sdrGetGainDb(current_gain), current_gain, sdrGetGainDb(step), step, why); + + int new_gain = sdrSetGain(step); + return (current_gain != new_gain); +} + +static void adaptive_gain_changed() +{ + int new_gain = sdrGetGain(); + adaptive_gain_up_db = sdrGetGainDb(new_gain + 1) - sdrGetGainDb(new_gain); + adaptive_gain_down_db = sdrGetGainDb(new_gain) - sdrGetGainDb(new_gain - 1); + + double loud_threshold_dbfs = 0 - adaptive_gain_up_db - 3.0; + adaptive_burst_loud_threshold = pow(10, loud_threshold_dbfs / 10.0); +} + +void adaptive_init() +{ + int maxgain = sdrGetMaxGain(); + + // If the SDR doesn't support gain control, disable ourselves + if (maxgain < 0) { + if (Modes.adaptive_burst_control || Modes.adaptive_range_control) { + fprintf(stderr, "warning: adaptive gain control requested, but SDR gain control not available, ignored.\n"); + } + Modes.adaptive_burst_control = false; + Modes.adaptive_range_control = false; + } + + // If we're disabled, do nothing + if (!Modes.adaptive_burst_control && !Modes.adaptive_range_control) + return; + + // Look for 40us bursts + adaptive_burst_window_size = Modes.sample_rate / 25000; + adaptive_burst_window_remaining = adaptive_burst_window_size; + adaptive_burst_window_counter = 0; + adaptive_burst_change_delay = Modes.adaptive_burst_change_delay; + + // Use an overall block size that is an exact multiple of the burst window, close to 1 second long + adaptive_block_size = adaptive_burst_window_size * 25000; + adaptive_block_remaining = adaptive_block_size; + + adaptive_range_radix = calloc(sizeof(unsigned), 65536); + + adaptive_range_state = RANGE_SCAN_UP; + adaptive_range_delay = Modes.adaptive_range_scan_delay; + + // select and enforce gain limits + for (adaptive_gain_min = 0; adaptive_gain_min < maxgain; ++adaptive_gain_min) { + if (sdrGetGainDb(adaptive_gain_min) >= Modes.adaptive_min_gain_db) + break; + } + + for (adaptive_gain_max = maxgain; adaptive_gain_max > adaptive_gain_min; --adaptive_gain_max) { + if (sdrGetGainDb(adaptive_gain_max) <= Modes.adaptive_max_gain_db) + break; + } + + fprintf(stderr, "adaptive: enabled adaptive gain control with gain limits %.1fdB (step %d) .. %.1fdB (step %d)\n", + sdrGetGainDb(adaptive_gain_min), adaptive_gain_min, sdrGetGainDb(adaptive_gain_max), adaptive_gain_max); + if (Modes.adaptive_range_control) + fprintf(stderr, "adaptive: enabled dynamic range control, target dynamic range %.1fdB\n", Modes.adaptive_range_target); + if (Modes.adaptive_burst_control) + fprintf(stderr, "adaptive: enabled burst control\n"); + adaptive_set_gain(sdrGetGain(), "constraining gain to adaptive gain limits"); + adaptive_gain_changed(); +} + +// Feed some samples into the adaptive system. Any number of samples might be passed in. +void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decoded) +{ + if (!Modes.adaptive_burst_control && !Modes.adaptive_range_control) + return; + + // process samples up to a block boundary, then process the completed block + while (length >= adaptive_block_remaining) { + adaptive_update_single(buf, adaptive_block_remaining, decoded); + buf += adaptive_block_remaining; + length -= adaptive_block_remaining; + + adaptive_end_of_block(); + adaptive_block_remaining = adaptive_block_size; + } + + // process final samples that don't complete a block + if (length > 0) { + adaptive_update_single(buf, length, decoded); + adaptive_block_remaining -= length; + } +} + +// Feed some samples into the adaptive system. The samples are guaranteed to not cross a block boundary. +static void adaptive_update_single(uint16_t *buf, unsigned length, struct modesMessage *decoded) +{ + if (decoded) { + if (/* decoded->msgbits == 112 && */ decoded->signalLevel >= adaptive_burst_loud_threshold) + ++adaptive_burst_block_loud_decodes; + adaptive_burst_skip(length); + } else { + adaptive_burst_update(buf, length); + adaptive_range_update(buf, length); + } +} + +// Burst measurement: ignore the next 'length' samples (they are a successfully decoded message) +static void adaptive_burst_skip(unsigned length) +{ + if (!Modes.adaptive_burst_control) + return; + + // first window + if (length < adaptive_burst_window_remaining) { + // partial fill + adaptive_burst_window_remaining -= length; + return; + } + + // skip remainder of first window, dispatch it + adaptive_burst_end_of_window(adaptive_burst_window_counter); + length -= adaptive_burst_window_remaining; + + // skip remaining windows, dispatch them + unsigned windows = length / adaptive_burst_window_size; + unsigned samples = windows * adaptive_burst_window_size; + while (windows--) + adaptive_burst_end_of_window(0); + + length -= samples; + + // final partial window + adaptive_burst_window_counter = 0; + adaptive_burst_window_remaining = adaptive_burst_window_size - length; +} + +// Burst measurement: process 'length' samples from 'buf', look for loud bursts; +// the samples might cross burst window boundaries; +// the samples will not cross a block boundary. +static void adaptive_burst_update(uint16_t *buf, unsigned length) +{ + if (!Modes.adaptive_burst_control) + return; + + // first window + if (length < adaptive_burst_window_remaining) { + // partial fill + adaptive_burst_window_counter += adaptive_burst_count_samples(buf, length); + adaptive_burst_window_remaining -= length; + return; + } + + // complete fill of first partial window + unsigned n = adaptive_burst_window_remaining; + unsigned counter = adaptive_burst_window_counter + adaptive_burst_count_samples(buf, n); + adaptive_burst_end_of_window(counter); + buf += n; + length -= n; + + // remaining windows + unsigned windows = length / adaptive_burst_window_size; + unsigned samples = windows * adaptive_burst_window_size; + adaptive_burst_scan_windows(buf, windows); + buf += samples; + length -= samples; + + // final partial window + adaptive_burst_window_counter = adaptive_burst_count_samples(buf, length); + adaptive_burst_window_remaining = adaptive_burst_window_size - length; +} + +// Burst measurement: process 'windows' complete burst windows starting at 'buf'; +// 'buf' is aligned to the start of a burst window +static void adaptive_burst_scan_windows(uint16_t *buf, unsigned windows) +{ + while (windows--) { + unsigned counter = adaptive_burst_count_samples(buf, adaptive_burst_window_size); + buf += adaptive_burst_window_size; + adaptive_burst_end_of_window(counter); + } +} + +// Burst measurement: process 'n' samples from 'buf', look for loud samples; +// the samples are guaranteed not to cross window boundaries; +// return the number of loud samples seen +static inline unsigned adaptive_burst_count_samples(uint16_t *buf, unsigned n) +{ + unsigned counter = 0; + while (n--) { + if (buf[0] > 46395) // -3dBFS + ++counter; + ++buf; + } + return counter; +} + +// Burst measurement: we reached the end of a burst window with 'counter' +// loud samples seen, handle that window. +static void adaptive_burst_end_of_window(unsigned counter) +{ + if (counter > adaptive_burst_window_size / 4) { + // This window is loud, extend any existing run of loud windows + ++adaptive_burst_runlength; + } else { + // Quiet window. If we saw a run of loud windows >= 80us long, count + // that as a candidate for an over-amplified message that was + // not decoded. + if (adaptive_burst_runlength >= 2 && adaptive_burst_runlength <= 5) + ++adaptive_burst_block_counter; + adaptive_burst_runlength = 0; + } +} + +// Noise measurement: process 'length' samples from 'buf'. +// The samples will not cross a block boundary. +static void adaptive_range_update(uint16_t *buf, unsigned length) +{ + if (!Modes.adaptive_range_control) + return; + + adaptive_range_counter += length; + while (length--) { + // do a very simple radix sort of sample magnitudes + // so we can later find the Nth percentile value + ++adaptive_range_radix[buf[0]]; + ++buf; + } +} + +// Noise measurement: we reached the end of a block, update +// our noise estimate +static void adaptive_range_end_of_block() +{ + if (!Modes.adaptive_range_control) + return; + + unsigned n = 0, i = 0; + + // measure Nth percentile magnitude + unsigned count_n = adaptive_range_counter * Modes.adaptive_range_percentile / 100; + while (i < 65536 && n <= count_n) + n += adaptive_range_radix[i++]; + uint16_t percentile_n = i - 1; + + // maintain an EMA of the Nth percentile + adaptive_range_smoothed = adaptive_range_smoothed * (1 - Modes.adaptive_range_alpha) + percentile_n * Modes.adaptive_range_alpha; + // .. report to stats in dBFS + if (adaptive_range_smoothed > 0) { + Modes.stats_current.adaptive_noise_dbfs = 20 * log10(adaptive_range_smoothed / 65536.0); + } else { + Modes.stats_current.adaptive_noise_dbfs = 0; + } + + // reset radix sort for the next block + memset(adaptive_range_radix, 0, 65536 * sizeof(unsigned)); + adaptive_range_counter = 0; +} + +// Burst measurement: we reached the end of a block, update our burst rate estimate +static void adaptive_burst_end_of_block() +{ + if (!Modes.adaptive_burst_control) + return; + + // maintain an EMA of the number of bursts seen per block + Modes.stats_current.adaptive_loud_undecoded += adaptive_burst_block_counter; + adaptive_burst_smoothed = adaptive_burst_smoothed * (1 - Modes.adaptive_burst_alpha) + adaptive_burst_block_counter * Modes.adaptive_burst_alpha; + adaptive_burst_block_counter = 0; + + // maintain an EMA of the number of decoded, but loud, messages seen per block + Modes.stats_current.adaptive_loud_decoded += adaptive_burst_block_loud_decodes; + adaptive_burst_loud_decodes_smoothed = adaptive_burst_loud_decodes_smoothed * (1 - Modes.adaptive_burst_alpha) + adaptive_burst_block_loud_decodes * Modes.adaptive_burst_alpha; + adaptive_burst_block_loud_decodes = 0; +} + +// consecutive blocks with loud rate +static unsigned adaptive_burst_loud_blocks = 0; +// consecutive blocks with quiet rate +static unsigned adaptive_burst_quiet_blocks = 0; +// are we suppressing gain due to a burst? +static bool adaptive_burst_suppressing = false; +// what was the gain before we started suppressing? +static int adaptive_burst_orig_gain = 0; + +void flush_stats(uint64_t now); + +static void adaptive_increase_gain(const char *why) +{ + if (adaptive_set_gain(sdrGetGain() + 1, why)) + adaptive_gain_changed(); +} + +static void adaptive_decrease_gain(const char *why) +{ + if (adaptive_set_gain(sdrGetGain() - 1, why)) + adaptive_gain_changed(); +} + +// Adaptive gain: we reached a block boundary. Update measurements and act on them. +static void adaptive_end_of_block() +{ + adaptive_range_end_of_block(); + adaptive_burst_end_of_block(); + + adaptive_burst_control_update(); + adaptive_range_control_update(); + + unsigned current = Modes.stats_current.adaptive_gain = sdrGetGain(); + ++Modes.stats_current.adaptive_gain_seconds[current < STATS_GAIN_COUNT ? current : STATS_GAIN_COUNT-1]; + if (adaptive_burst_suppressing) + ++Modes.stats_current.adaptive_gain_reduced_seconds; +} + +static void adaptive_burst_control_update() +{ + if (!Modes.adaptive_burst_control) + return; + + if (adaptive_range_state != RANGE_SCAN_IDLE) + return; + + if (adaptive_burst_change_delay) + --adaptive_burst_change_delay; + + if (!adaptive_burst_change_delay) { + if (adaptive_burst_smoothed > Modes.adaptive_burst_loud_rate) { + adaptive_burst_quiet_blocks = 0; + ++adaptive_burst_loud_blocks; + } else if (adaptive_burst_loud_decodes_smoothed < Modes.adaptive_burst_quiet_rate) { + adaptive_burst_loud_blocks = 0; + ++adaptive_burst_quiet_blocks; + } else { + adaptive_burst_loud_blocks = 0; + adaptive_burst_quiet_blocks = 0; + } + + if (adaptive_burst_loud_blocks >= Modes.adaptive_burst_loud_runlength) { + // we need to reduce gain (further) + if (!adaptive_burst_suppressing) { + adaptive_burst_suppressing = true; + adaptive_burst_orig_gain = sdrGetGain(); + } + + adaptive_decrease_gain("saw a noisy period with many undecoded loud messages"); + adaptive_burst_loud_blocks = 0; + adaptive_burst_change_delay = Modes.adaptive_burst_change_delay; + } + + if (adaptive_burst_suppressing && adaptive_burst_quiet_blocks >= Modes.adaptive_burst_quiet_runlength) { + // we can relax the gain restriction + adaptive_increase_gain("saw a quiet period with few loud messages"); + adaptive_burst_quiet_blocks = 0; + adaptive_burst_change_delay = Modes.adaptive_burst_change_delay; + + if (sdrGetGain() >= adaptive_burst_orig_gain) + adaptive_burst_suppressing = false; + } + } +} + +static void adaptive_range_control_update() +{ + if (!Modes.adaptive_range_control) + return; + + if (adaptive_range_delay > 0) + --adaptive_range_delay; + + float available_range = -20 * log10(adaptive_range_smoothed / 65536.0); + + switch (adaptive_range_state) { + case RANGE_SCAN_UP: + if (adaptive_range_delay > 0) + break; + + if (available_range < Modes.adaptive_range_target) { + // Current gain fails to meet our target. Switch to downward scanning. + fprintf(stderr, "adaptive: available dynamic range (%.1fdB) < required dynamic range (%.1fdB), switching to downward scan\n", available_range, Modes.adaptive_range_target); + adaptive_decrease_gain("downwards dynamic range gain scan"); + adaptive_range_state = RANGE_SCAN_DOWN; + adaptive_range_delay = Modes.adaptive_range_scan_delay; + break; + } + + if (sdrGetGain() >= adaptive_gain_max) { + // We have reached our upper gain limit + fprintf(stderr, "adaptive: reached upper gain limit, halting dynamic range scan here\n"); + adaptive_range_state = RANGE_SCAN_IDLE; + adaptive_range_delay = Modes.adaptive_range_rescan_delay; + break; + } + + // This gain step is OK and we have more to try, try the next gain step up. + fprintf(stderr, "adaptive: available dynamic range (%.1fdB) >= required dynamic range (%.1fdB), continuing upward scan\n", available_range, Modes.adaptive_range_target); + adaptive_increase_gain("upwards dynamic range scan"); + adaptive_range_delay = Modes.adaptive_range_scan_delay; + break; + + case RANGE_SCAN_DOWN: + if (adaptive_range_delay > 0) + break; + + if (available_range >= Modes.adaptive_range_target) { + // Current gain meets our target; we are done with the scan. + fprintf(stderr, "adaptive: available dynamic range (%.1fdB) >= required dynamic range (%.1fdB), stopping downwards scan here\n", available_range, Modes.adaptive_range_target); + adaptive_range_state = RANGE_SCAN_IDLE; + adaptive_range_delay = Modes.adaptive_range_rescan_delay; + break; + } + + if (sdrGetGain() <= adaptive_gain_min) { + fprintf(stderr, "adaptive: reached lower gain limit, halting dynamic range scan here\n"); + adaptive_range_state = RANGE_SCAN_IDLE; + adaptive_range_delay = Modes.adaptive_range_rescan_delay; + break; + } + + // This gain step is too loud and we have more to try, try the next gain step down + fprintf(stderr, "adaptive: available dynamic range (%.1fdB) < required dynamic range (%.1fdB), continuing downwards scan\n", available_range, Modes.adaptive_range_target); + adaptive_decrease_gain("downwards dynamic range gain scan"); + adaptive_range_delay = Modes.adaptive_range_scan_delay; + break; + + case RANGE_SCAN_IDLE: + // Look for increased noise that could be compensated for by decreasing gain. + // Do this even if we're delaying. + if (available_range + adaptive_gain_down_db / 2 < Modes.adaptive_range_target && sdrGetGain() > adaptive_gain_min) { + fprintf(stderr, "adaptive: available dynamic range (%.1fdB) + half gain step down (%.1fdB) < required dynamic range (%.1fdB), starting downward scan\n", + available_range, Modes.adaptive_range_target, adaptive_gain_down_db); + adaptive_range_state = RANGE_SCAN_DOWN; + adaptive_range_delay = Modes.adaptive_range_scan_delay; + break; + } + + if (adaptive_range_delay > 0) + break; + + // Infrequently consider increasing gain to handle the case where we've selected a too-low gain where the noise floor is dominated by noise unrelated to the gain setting + if (available_range >= Modes.adaptive_range_target && sdrGetGain() < adaptive_gain_max) { + fprintf(stderr, "adaptive: start periodic scan for acceptable dynamic range at increased gain\n"); + adaptive_increase_gain("upwards dynamic range scan"); + adaptive_range_state = RANGE_SCAN_UP; + adaptive_range_delay = Modes.adaptive_range_scan_delay; + break; + } + + // Nothing to do for a while. + adaptive_range_delay = Modes.adaptive_range_rescan_delay; + break; + + default: + fprintf(stderr, "adaptive: in a weird state (%d), trying to fix it\n", adaptive_range_state); + adaptive_range_state = RANGE_SCAN_IDLE; + adaptive_range_delay = Modes.adaptive_range_scan_delay; + break; + } +} diff --git a/adaptive.h b/adaptive.h new file mode 100644 index 0000000..1ec728d --- /dev/null +++ b/adaptive.h @@ -0,0 +1,30 @@ +// Part of dump1090, a Mode S message decoder for RTLSDR devices. +// +// adaptive.h: adaptive gain control prototypes +// +// Copyright (c) 2021 FlightAware, LLC +// +// This file is free software: you may copy, redistribute 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 file 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 . + +#ifndef ADAPTIVE_H +#define ADAPTIVE_H + +#include + +struct modesMessage; + +void adaptive_init(); +void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decoded); + +#endif diff --git a/demod_2400.c b/demod_2400.c index 3ecf549..d6d90e0 100644 --- a/demod_2400.c +++ b/demod_2400.c @@ -103,10 +103,17 @@ void demodulate2400(struct mag_buf *mag) unsigned char msg1[MODES_LONG_MSG_BYTES], msg2[MODES_LONG_MSG_BYTES], *msg; uint32_t j; + static unsigned last_message_end = 0; + // initialize bitsets on first call if (!valid_df_short_bitset) init_bitsets(); + if (mag->flags & MAGBUF_DISCONTINUOUS) { + // gap, start from the very beginning + last_message_end = 0; + } + unsigned char *bestmsg; int bestscore, bestphase; @@ -120,7 +127,11 @@ void demodulate2400(struct mag_buf *mag) msg = msg1; - for (j = 0; j < mlen; j++) { + // sanity check + if (last_message_end > mlen) + last_message_end = mlen; + + for (j = last_message_end; j < mlen; j++) { uint16_t *preamble = &m[j]; int high; uint32_t base_signal, base_noise; @@ -391,13 +402,21 @@ void demodulate2400(struct mag_buf *mag) Modes.stats_current.strong_signal_count++; // signal power above -3dBFS } + // Feed "empty" sample to adaptive gain logic + if (j > last_message_end) + adaptive_update(&m[last_message_end], j - last_message_end, NULL); + + // Feed message samples to adaptive gain logic, update end pointer + last_message_end = j + (msglen + 8) * 12/5; + adaptive_update(&m[j], last_message_end - j, &mm); + // Skip over the message: // (we actually skip to 8 bits before the end of the message, // because we can often decode two messages that *almost* collide, // where the preamble of the second message clobbered the last // few bits of the first message, but the message bits didn't // overlap) - j += msglen*12/5; + j = last_message_end - 8*12/5; // Pass data to the next layer useModesMessage(&mm); @@ -409,8 +428,20 @@ void demodulate2400(struct mag_buf *mag) Modes.stats_current.noise_power_sum += (mag->mean_power * mlen - sum_signal_power); Modes.stats_current.noise_power_count += mlen; } -} + // feed trailing empty samples to adaptive gain logic + if (last_message_end < mlen) { + // trailing data from end of last message to start of overlap; + // on the next pass, start from the start of the overlap + adaptive_update(&m[last_message_end], mlen - last_message_end, NULL); + last_message_end = 0; + } else { + // last decoded message runs into the overlap region; + // on the next pass, start at the right place in the overlap; + // no trailing data to pass this time + last_message_end -= mlen; + } +} #ifdef MODEAC_DEBUG diff --git a/dump1090.c b/dump1090.c index 1a509dd..2ef773e 100644 --- a/dump1090.c +++ b/dump1090.c @@ -122,6 +122,24 @@ static void modesInitConfig(void) { Modes.maxRange = 1852 * 300; // 300NM default max range Modes.mode_ac_auto = 1; + // adaptive + Modes.adaptive_min_gain_db = 0; + Modes.adaptive_max_gain_db = 99999; + + Modes.adaptive_burst_control = false; + Modes.adaptive_burst_alpha = 2.0 / (5 + 1); + Modes.adaptive_burst_change_delay = 15; + Modes.adaptive_burst_loud_runlength = 10; + Modes.adaptive_burst_loud_rate = 5.0; + Modes.adaptive_burst_quiet_runlength = 10; + Modes.adaptive_burst_quiet_rate = 5.0; + + Modes.adaptive_range_control = false; + Modes.adaptive_range_alpha = 2.0 / (5 + 1); + Modes.adaptive_range_percentile = 40; + Modes.adaptive_range_scan_delay = 15; + Modes.adaptive_range_rescan_delay = 900; + sdrInitConfig(); } // @@ -300,62 +318,106 @@ static void showHelp(void) sdrShowHelp(); printf( -" Common options\n" +" Output modes\n" "\n" +// ------ 80 char limit ----------------------------------------------------------| +"--raw Show only messages hex values\n" +"--modeac Enable decoding of SSR Modes 3/A & 3/C\n" +"--mlat display raw messages in Beast ascii mode\n" +"--onlyaddr Show only ICAO addresses (testing purposes)\n" +"--metric Use metric units (meters, km/h, ...)\n" +"--gnss Show altitudes as HAE/GNSS when available\n" +"--quiet Disable output to stdout. Use for daemon applications\n" +"--show-only Show only messages from the given ICAO on stdout\n" +"--snip Strip IQ file removing samples < level\n" +"\n" +" Decoder settings\n" +"\n" +// ------ 80 char limit ----------------------------------------------------------| "--gain Set gain (default: max gain. Use -10 for auto-gain)\n" "--freq Set frequency (default: 1090 Mhz)\n" -"--interactive Interactive mode refreshing data on screen. Implies --throttle\n" -"--interactive-ttl Remove from list if idle for (default: 60)\n" -"--interactive-show-distance Show aircraft distance and bearing instead of lat/lon\n" -" (requires --lat and --lon)\n" -"--interactive-distance-units Distance units ('km', 'sm', 'nm') (default: 'nm')\n" -"--interactive-callsign-filter Only callsigns that match the prefix or regex will be displayed\n" -"--raw Show only messages hex values\n" +"--fix Enable single-bit error correction using CRC\n" +"--fix-2bit Enable two-bit error correction using CRC\n" +" (use with caution!)\n" +"--no-fix Disable error correction using CRC\n" +"--no-fix-df Disable error correction of the DF message field\n" +" (reduces CPU requirements)\n" +"--no-crc-check Disable messages with broken CRC (discouraged)\n" +"--enable-df24 Enable decoding of DF24 Comm-D ELM messages\n" +"--wisdom Read DSP wisdom from given path\n" +"--lat Reference/receiver latitude for surface positions\n" +"--lon Reference/receiver longitude for surface positions\n" +"--max-range Absolute maximum range for position decoding (in NM)\n" +"\n" +// ------ 80 char limit ----------------------------------------------------------| +" Adaptive gain\n" +"\n" +"--adaptive-burst Adjust gain for too-loud message bursts\n" +"--adaptive-burst-alpha Set burst rate smoothing factor\n" +" (0..1, smaller=more smoothing)\n" +"--adaptive-burst-loud-rate Set burst rate for gain decrease\n" +"--adaptive-burst-loud-runlength Set burst runlength for gain decrease\n" +"--adaptive-burst-quiet-rate Set burst rate for gain increase\n" +"--adaptive-burst-quiet-runlength Set burst runlength for gain increase\n" +"--adaptive-range Adjust gain for target dynamic range\n" +"--adaptive-range-target Set target dynamic range in dB\n" +"--adaptive-range-alpha Set dynamic range noise smoothing factor\n" +" (0..1, smaller=more smoothing)\n" +"--adaptive-range-percentile

Set dynamic range noise percentile\n" +"--adaptive-range-scan-delay Set data collection interval for dynamic\n" +" range gain scanning (seconds)\n" +"--adaptive-range-rescan-delay Set rescan interval for dynamic range\n" +" gain scanning (seconds)\n" +"--adaptive-min-gain Set gain adjustment range lower limit (dB)\n" +"--adaptive-max-gain Set gain adjustment range upper limit (dB)\n" +"\n" +// ------ 80 char limit ----------------------------------------------------------| +" Network connections\n" +"\n" "--net Enable networking with default ports unless overridden\n" -"--modeac Enable decoding of SSR Modes 3/A & 3/C\n" -"--no-modeac-auto Don't enable Mode A/C if requested by a Beast connection\n" +"--no-modeac-auto Don't enable Mode A/C if requested by a net connection\n" "--net-only Enable just networking, no RTL device or file used\n" -"--net-bind-address IP address to bind to (default: Any; Use 127.0.0.1 for private)\n" +"--net-bind-address IP address to bind to (use 127.0.0.1 for private)\n" "--net-ri-port TCP raw input listen ports (default: 30001)\n" "--net-ro-port TCP raw output listen ports (default: 30002)\n" "--net-sbs-port TCP BaseStation output listen ports (default: 30003)\n" "--net-bi-port TCP Beast input listen ports (default: 30004,30104)\n" "--net-bo-port TCP Beast output listen ports (default: 30005)\n" -"--net-stratux-port TCP Stratux output listen ports (default: disabled)\n" +"--net-stratux-port TCP Stratux output listen ports (default: disabled)\n" "--net-ro-size TCP output minimum size (default: 0)\n" "--net-ro-interval TCP output memory flush rate in seconds (default: 0)\n" -"--net-heartbeat TCP heartbeat rate in seconds (default: 60 sec; 0 to disable)\n" +"--net-heartbeat TCP heartbeat rate in seconds\n" +" (default: 60 sec; 0 to disable)\n" "--net-buffer TCP buffer size 64Kb * (2^n) (default: n=0, 64Kb)\n" -"--net-verbatim Make Beast-format output connections default to verbatim mode\n" -" (forward all messages, without applying CRC corrections)\n" -"--forward-mlat Allow forwarding of received mlat results to output ports\n" -"--lat Reference/receiver latitude for surface posn (opt)\n" -"--lon Reference/receiver longitude for surface posn (opt)\n" -"--max-range Absolute maximum range for position decoding (in nm, default: 300)\n" -"--fix Enable single-bit error correction using CRC\n" -"--fix-2bit Enable two-bit error correction using CRC (use with caution)\n" -"--no-fix Disable error correction using CRC\n" -"--no-fix-df Disable error correction of the DF message field (reduces CPU requirements)\n" -"--no-crc-check Disable messages with broken CRC (discouraged)\n" -"--enable-df24 Enable decoding of DF24 Comm-D ELM messages\n" -"--mlat display raw messages in Beast ascii mode\n" -"--stats With --ifile print stats at exit. No other output\n" -"--stats-range Collect/show range histogram\n" +"--net-verbatim Make output connections default to verbatim mode\n" +" (forward all messages without correction)\n" +"--forward-mlat Allow forwarding of received mlat results\n" +"\n" +// ------ 80 char limit ----------------------------------------------------------| +" Stats and json output\n" +"\n" +"--stats Show stats summary at exit.\n" "--stats-every Show and reset stats every seconds\n" -"--onlyaddr Show only ICAO addresses (testing purposes)\n" -"--metric Use metric units (meters, km/h, ...)\n" -"--gnss Show altitudes as HAE/GNSS (with H suffix) when available\n" -"--snip Strip IQ file removing samples < level\n" -"--quiet Disable output to stdout. Use for daemon applications\n" -"--show-only Show only messages from the given ICAO on stdout\n" -"--write-json

Periodically write json output to (for serving by a separate webserver)\n" +"--stats-range Collect/show range histogram\n" +"--write-json Periodically write json output to \n" +" (for serving by a separate webserver)\n" "--write-json-every Write json aircraft output every t seconds (default 1)\n" "--json-stats-every Write json stats output every t seconds (default 60)\n" -"--json-location-accuracy Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact\n" -#if 0 -"--dcfilter Apply a 1Hz DC filter to input data (requires more CPU)\n" -#endif -"--wisdom Read DSP wisdom from given path\n" +"--json-location-accuracy Accuracy of receiver location in json metadata\n" +" (0=no location, 1=approximate, 2=exact)\n" +"\n" +" Interactive mode\n" +"\n" +"--interactive Interactive mode refreshing data on screen.\n" +" Implies --throttle\n" +"--interactive-ttl Remove from list if idle for \n" +"--interactive-show-distance Show aircraft distance and bearing\n" +" (requires --lat and --lon)\n" +"--interactive-distance-units Distance units ('km', 'sm', 'nm')\n" +"--interactive-callsign-filter Filter rows by callsign against regex\n" +"\n" +" Misc\n" +"\n" "--version Show version, build and DSP options\n" "--help Show this help\n" ); @@ -363,7 +425,8 @@ static void showHelp(void) // Accumulate stats data from stats_current to stats_periodic, stats_alltime and stats_latest; // reset stats_current -static void flush_stats(uint64_t now) +void flush_stats(uint64_t now); +void flush_stats(uint64_t now) { add_stats(&Modes.stats_current, &Modes.stats_periodic, &Modes.stats_periodic); add_stats(&Modes.stats_current, &Modes.stats_alltime, &Modes.stats_alltime); @@ -534,7 +597,7 @@ int main(int argc, char **argv) { } else if ( (!strcmp(argv[j], "--device") || !strcmp(argv[j], "--device-index")) && more) { Modes.dev_name = strdup(argv[++j]); } else if (!strcmp(argv[j],"--gain") && more) { - Modes.gain = (int) (atof(argv[++j])*10); // Gain is in tens of DBs + Modes.gain = atof(argv[++j]); } else if (!strcmp(argv[j],"--dcfilter")) { #if 0 Modes.dc_filter = 1; @@ -693,7 +756,37 @@ int main(int argc, char **argv) { fprintf(stderr, "Failed to read wisdom file %s: %s\n", argv[j], strerror(errno)); exit(1); - } + } + } else if (!strcmp(argv[j], "--adaptive-min-gain") && more) { + Modes.adaptive_min_gain_db = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-max-gain") && more) { + Modes.adaptive_max_gain_db = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-burst")) { + Modes.adaptive_burst_control = true; + } else if (!strcmp(argv[j], "--adaptive-burst-alpha") && more) { + Modes.adaptive_burst_alpha = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-burst-delay") && more) { + Modes.adaptive_burst_change_delay = atoi(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-burst-loud-rate") && more) { + Modes.adaptive_burst_loud_rate = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-burst-loud-runlength") && more) { + Modes.adaptive_burst_loud_runlength = atoi(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-burst-quiet-rate") && more) { + Modes.adaptive_burst_quiet_rate = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-burst-quiet-runlength") && more) { + Modes.adaptive_burst_quiet_runlength = atoi(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-range")) { + Modes.adaptive_range_control = true; + } else if (!strcmp(argv[j], "--adaptive-range-alpha") && more) { + Modes.adaptive_range_alpha = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-range-percentile") && more) { + Modes.adaptive_range_percentile = atoi(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-range-target") && more) { + Modes.adaptive_range_target = atof(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-range-scan-delay") && more) { + Modes.adaptive_range_scan_delay = atoi(argv[++j]); + } else if (!strcmp(argv[j], "--adaptive-range-rescan-delay") && more) { + Modes.adaptive_range_rescan_delay = atoi(argv[++j]); } else if (sdrHandleOption(argc, argv, &j)) { /* handled */ } else { @@ -724,7 +817,7 @@ int main(int argc, char **argv) { if (!sdrOpen()) { exit(1); } - + if (Modes.net) { modesInitNet(); } @@ -740,6 +833,8 @@ int main(int argc, char **argv) { for (j = 0; j < 15; ++j) Modes.stats_1min[j].start = Modes.stats_1min[j].end = Modes.stats_current.start; + adaptive_init(); + // write initial json files so they're not missing writeJsonToFile("receiver.json", generateReceiverJson); writeJsonToFile("stats.json", generateStatsJson); diff --git a/dump1090.h b/dump1090.h index 81a5b25..41799a7 100644 --- a/dump1090.h +++ b/dump1090.h @@ -92,7 +92,7 @@ #define MODES_RTL_BUF_SIZE (16*16384) // 256k #define MODES_MAG_BUF_SAMPLES (MODES_RTL_BUF_SIZE / 2) // Each sample is 2 bytes #define MODES_MAG_BUFFERS 12 // Number of magnitude buffers (should be smaller than RTL_BUFFERS for flowcontrol to work) -#define MODES_AUTO_GAIN -100 // Use automatic gain +#define MODES_AUTO_GAIN -10 // Use automatic gain #define MODES_MAX_GAIN 999999 // Use max available gain #define MODES_MSG_SQUELCH_DB 4.0 // Minimum SNR, in dB #define MODES_MSG_ENCODER_ERRS 3 // Maximum number of encoding errors @@ -272,6 +272,7 @@ typedef enum { #include "convert.h" #include "sdr.h" #include "fifo.h" +#include "adaptive.h" //======================== structure declarations ========================= @@ -296,9 +297,9 @@ struct _Modes { // Internal state // Sample conversion int dc_filter; // should we apply a DC filter? - // RTLSDR + // RTLSDR and some other SDRs char * dev_name; - int gain; + float gain; // value in dB, or MODES_AUTO_GAIN, or MODES_MAX_GAIN int freq; // Networking @@ -388,6 +389,25 @@ struct _Modes { // Internal state int stats_newest_1min; // Index into stats_1min of the most recent 1-minute window struct stats stats_5min; // Accumulated stats from the last 5 complete 1-minute windows struct stats stats_15min; // Accumulated stats from the last 15 complete 1-minute windows + + // Adaptive gain config + float adaptive_min_gain_db; + float adaptive_max_gain_db; + + bool adaptive_burst_control; + float adaptive_burst_alpha; + unsigned adaptive_burst_change_delay; + float adaptive_burst_loud_rate; + unsigned adaptive_burst_loud_runlength; + float adaptive_burst_quiet_rate; + unsigned adaptive_burst_quiet_runlength; + + bool adaptive_range_control; + float adaptive_range_alpha; + unsigned adaptive_range_percentile; + float adaptive_range_target; + unsigned adaptive_range_scan_delay; + unsigned adaptive_range_rescan_delay; }; extern struct _Modes Modes; diff --git a/net_io.c b/net_io.c index 7b2145f..438a34b 100644 --- a/net_io.c +++ b/net_io.c @@ -1803,64 +1803,85 @@ static char * appendStatsJson(char *p, p = safe_snprintf(p, end, "]}"); } - { - uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL; - uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL; - uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL; + uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL; + uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL; + uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL; - p = safe_snprintf(p, end, - ",\"cpr\":{\"surface\":%u" - ",\"airborne\":%u" - ",\"global_ok\":%u" - ",\"global_bad\":%u" - ",\"global_range\":%u" - ",\"global_speed\":%u" - ",\"global_skipped\":%u" - ",\"local_ok\":%u" - ",\"local_aircraft_relative\":%u" - ",\"local_receiver_relative\":%u" - ",\"local_skipped\":%u" - ",\"local_range\":%u" - ",\"local_speed\":%u" - ",\"filtered\":%u}" - ",\"altitude_suppressed\":%u" - ",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}" - ",\"tracks\":{\"all\":%u" - ",\"single_message\":%u" - ",\"unreliable\":%u}" - ",\"messages\":%u", - st->cpr_surface, - st->cpr_airborne, - st->cpr_global_ok, - st->cpr_global_bad, - st->cpr_global_range_checks, - st->cpr_global_speed_checks, - st->cpr_global_skipped, - st->cpr_local_ok, - st->cpr_local_aircraft_relative, - st->cpr_local_receiver_relative, - st->cpr_local_skipped, - st->cpr_local_range_checks, - st->cpr_local_speed_checks, - st->cpr_filtered, - st->suppressed_altitude_messages, - (unsigned long long)demod_cpu_millis, - (unsigned long long)reader_cpu_millis, - (unsigned long long)background_cpu_millis, - st->unique_aircraft, - st->single_message_aircraft, - st->unreliable_aircraft, - st->messages_total); + p = safe_snprintf(p, end, + ",\"cpr\":{\"surface\":%u" + ",\"airborne\":%u" + ",\"global_ok\":%u" + ",\"global_bad\":%u" + ",\"global_range\":%u" + ",\"global_speed\":%u" + ",\"global_skipped\":%u" + ",\"local_ok\":%u" + ",\"local_aircraft_relative\":%u" + ",\"local_receiver_relative\":%u" + ",\"local_skipped\":%u" + ",\"local_range\":%u" + ",\"local_speed\":%u" + ",\"filtered\":%u}" + ",\"altitude_suppressed\":%u" + ",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}" + ",\"tracks\":{\"all\":%u" + ",\"single_message\":%u" + ",\"unreliable\":%u}" + ",\"messages\":%u", + st->cpr_surface, + st->cpr_airborne, + st->cpr_global_ok, + st->cpr_global_bad, + st->cpr_global_range_checks, + st->cpr_global_speed_checks, + st->cpr_global_skipped, + st->cpr_local_ok, + st->cpr_local_aircraft_relative, + st->cpr_local_receiver_relative, + st->cpr_local_skipped, + st->cpr_local_range_checks, + st->cpr_local_speed_checks, + st->cpr_filtered, + st->suppressed_altitude_messages, + (unsigned long long)demod_cpu_millis, + (unsigned long long)reader_cpu_millis, + (unsigned long long)background_cpu_millis, + st->unique_aircraft, + st->single_message_aircraft, + st->unreliable_aircraft, + st->messages_total); - for (i = 0; i < 32; ++i) { - if (i == 0) - p = safe_snprintf(p, end, ",\"messages_by_df\":[%u", st->messages_by_df[i]); - else - p = safe_snprintf(p, end, ",%u", st->messages_by_df[i]); - } - p = safe_snprintf(p, end, "]}"); + for (i = 0; i < 32; ++i) { + if (i == 0) + p = safe_snprintf(p, end, ",\"messages_by_df\":[%u", st->messages_by_df[i]); + else + p = safe_snprintf(p, end, ",%u", st->messages_by_df[i]); } + p = safe_snprintf(p, end, "]"); + p = safe_snprintf(p, end, + ",\"adaptive\":" + "{\"gain_db\":%.1f" + ",\"gain_reduced_seconds\":%u" + ",\"loud_undecoded\":%u" + ",\"loud_decoded\":%u" + ",\"noise_dbfs\":%.1f" + ",\"gain_seconds\":[", + sdrGetGainDb(st->adaptive_gain), + st->adaptive_gain_reduced_seconds, + st->adaptive_loud_undecoded, + st->adaptive_loud_decoded, + st->adaptive_noise_dbfs); + bool first = true; + for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) { + if (st->adaptive_gain_seconds[i] > 0) { + p = safe_snprintf(p, end, "%s[%.1f,%u]", + first ? "" : ",", + sdrGetGainDb(i), st->adaptive_gain_seconds[i]); + first = false; + } + } + p = safe_snprintf(p, end, "]}}"); return p; } diff --git a/sdr.c b/sdr.c index 4c33de1..484302a 100644 --- a/sdr.c +++ b/sdr.c @@ -44,6 +44,10 @@ typedef struct { void (*run)(); void (*stop)(); void (*close)(); + int (*getgain)(); + int (*getmaxgain)(); + double (*getgaindb)(int); + int (*setgain)(int); } sdr_handler; static void noInitConfig() @@ -81,6 +85,28 @@ static void noClose() { } +static int noGetGain() +{ + return -1; +} + +static int noGetMaxGain() +{ + return -1; +} + +static double noGetGainDb(int step) +{ + MODES_NOTUSED(step); + return 0.0; +} + +static int noSetGain(int step) +{ + MODES_NOTUSED(step); + return 0; +} + static bool unsupportedOpen() { fprintf(stderr, "Support for this SDR type was not enabled in this build.\n"); @@ -89,24 +115,24 @@ static bool unsupportedOpen() static sdr_handler sdr_handlers[] = { #ifdef ENABLE_RTLSDR - { "rtlsdr", SDR_RTLSDR, rtlsdrInitConfig, rtlsdrShowHelp, rtlsdrHandleOption, rtlsdrOpen, rtlsdrRun, rtlsdrStop, rtlsdrClose }, + { "rtlsdr", SDR_RTLSDR, rtlsdrInitConfig, rtlsdrShowHelp, rtlsdrHandleOption, rtlsdrOpen, rtlsdrRun, rtlsdrStop, rtlsdrClose, rtlsdrGetGain, rtlsdrGetMaxGain, rtlsdrGetGainDb, rtlsdrSetGain }, #endif #ifdef ENABLE_BLADERF - { "bladerf", SDR_BLADERF, bladeRFInitConfig, bladeRFShowHelp, bladeRFHandleOption, bladeRFOpen, bladeRFRun, noStop, bladeRFClose }, + { "bladerf", SDR_BLADERF, bladeRFInitConfig, bladeRFShowHelp, bladeRFHandleOption, bladeRFOpen, bladeRFRun, noStop, bladeRFClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, #endif #ifdef ENABLE_HACKRF - { "hackrf", SDR_HACKRF, hackRFInitConfig, hackRFShowHelp, hackRFHandleOption, hackRFOpen, hackRFRun, noStop, hackRFClose }, + { "hackrf", SDR_HACKRF, hackRFInitConfig, hackRFShowHelp, hackRFHandleOption, hackRFOpen, hackRFRun, noStop, hackRFClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, #endif #ifdef ENABLE_LIMESDR - { "limesdr", SDR_LIMESDR, limesdrInitConfig, limesdrShowHelp, limesdrHandleOption, limesdrOpen, limesdrRun, noStop, limesdrClose }, + { "limesdr", SDR_LIMESDR, limesdrInitConfig, limesdrShowHelp, limesdrHandleOption, limesdrOpen, limesdrRun, noStop, limesdrClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, #endif - { "none", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, noOpen, noRun, noStop, noClose }, - { "ifile", SDR_IFILE, ifileInitConfig, ifileShowHelp, ifileHandleOption, ifileOpen, ifileRun, noStop, ifileClose }, + { "none", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, noOpen, noRun, noStop, noClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, + { "ifile", SDR_IFILE, ifileInitConfig, ifileShowHelp, ifileHandleOption, ifileOpen, ifileRun, noStop, ifileClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, - { NULL, SDR_NONE, NULL, NULL, NULL, NULL, NULL, NULL, NULL } /* must come last */ + { NULL, SDR_NONE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } /* must come last */ }; void sdrInitConfig() @@ -163,7 +189,7 @@ bool sdrHandleOption(int argc, char **argv, int *jptr) static sdr_handler *current_handler() { - static sdr_handler unsupported_handler = { "unsupported", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, unsupportedOpen, noRun, noStop, noClose }; + static sdr_handler unsupported_handler = { "unsupported", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, unsupportedOpen, noRun, noStop, noClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }; for (int i = 0; sdr_handlers[i].name; ++i) { if (Modes.sdr_type == sdr_handlers[i].sdr_type) { @@ -223,3 +249,25 @@ void sdrUpdateCPUTime(struct timespec *addTo) Modes.reader_cpu_accumulator.tv_nsec = 0; pthread_mutex_unlock(&Modes.reader_cpu_mutex); } + +int sdrGetGain() +{ + return current_handler()->getgain(); +} + +int sdrGetMaxGain() +{ + return current_handler()->getmaxgain(); +} + +double sdrGetGainDb(int step) +{ + return current_handler()->getgaindb(step); +} + +int sdrSetGain(int step) +{ + return current_handler()->setgain(step); +} + + diff --git a/sdr.h b/sdr.h index b73655b..9ab0ab0 100644 --- a/sdr.h +++ b/sdr.h @@ -31,6 +31,12 @@ void sdrRun(); void sdrStop(); void sdrClose(); +// Gain control +int sdrGetGain(); // return current gain step 0..N, or -1 if gain control is not supported +int sdrGetMaxGain(); // return maximum gain step, or -1 if gain control is not supported +double sdrGetGainDb(int step); // return gain in dB for the given gain step, or 0.0 if gain control is not supported +int sdrSetGain(int step); // set gain step; return actual gain step used, or -1 if gain control is not supported + // Call periodically from the SDR read thread to update reader thread CPU stats: void sdrMonitor(); // Retrieve CPU stats and add new CPU time to *addTo diff --git a/sdr_bladerf.c b/sdr_bladerf.c index 74d0a6f..5f7c726 100644 --- a/sdr_bladerf.c +++ b/sdr_bladerf.c @@ -254,7 +254,7 @@ bool bladeRFOpen() goto error; } - if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_RX, Modes.gain / 10.0)) < 0) { + if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_RX, Modes.gain)) < 0) { fprintf(stderr, "bladerf_set_gain(RX) failed: %s\n", bladerf_strerror(status)); goto error; } diff --git a/sdr_limesdr.c b/sdr_limesdr.c index 8809aa0..c935130 100644 --- a/sdr_limesdr.c +++ b/sdr_limesdr.c @@ -285,7 +285,7 @@ bool limesdrOpen(void) goto error; } } else { - if (LMS_SetGaindB(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, Modes.gain / 10)) { + if (LMS_SetGaindB(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, Modes.gain)) { limesdrLogHandler(LMS_LOG_ERROR, "unable to set gain"); goto error; } diff --git a/sdr_rtlsdr.c b/sdr_rtlsdr.c index fd192c2..e7a8c5f 100644 --- a/sdr_rtlsdr.c +++ b/sdr_rtlsdr.c @@ -66,6 +66,9 @@ static struct { uint8_t *bounce_buffer; iq_convert_fn converter; struct converter_state *converter_state; + int *gains; + int gain_steps; + int current_gain; } RTLSDR; // @@ -81,6 +84,9 @@ void rtlsdrInitConfig() RTLSDR.bounce_buffer = NULL; RTLSDR.converter = NULL; RTLSDR.converter_state = NULL; + RTLSDR.gains = NULL; + RTLSDR.gain_steps = 0; + RTLSDR.current_gain = 0; } static void show_rtlsdr_devices() @@ -173,7 +179,16 @@ bool rtlsdrHandleOption(int argc, char **argv, int *jptr) return true; } -bool rtlsdrOpen(void) { +// sort function to sort by increasing gain +static int sort_gains(const void *left, const void *right) +{ + const int *left_int = (const int *)left; + const int *right_int = (const int *)right; + return *left_int - *right_int; +} + +bool rtlsdrOpen(void) +{ if (!rtlsdr_get_device_count()) { fprintf(stderr, "rtlsdr: no supported devices found.\n"); return false; @@ -210,41 +225,48 @@ bool rtlsdrOpen(void) { if (RTLSDR.direct_sampling) { fprintf(stderr, "rtlsdr: direct sampling from input %d\n", RTLSDR.direct_sampling); rtlsdr_set_direct_sampling(RTLSDR.dev, RTLSDR.direct_sampling); + RTLSDR.gain_steps = 0; } else { - if (Modes.gain == MODES_AUTO_GAIN) { - fprintf(stderr, "rtlsdr: enabling tuner AGC\n"); - rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 0); - } else { - int *gains; - int numgains; + int *gains; + int numgains; - numgains = rtlsdr_get_tuner_gains(RTLSDR.dev, NULL); - if (numgains <= 0) { - fprintf(stderr, "rtlsdr: error getting tuner gains\n"); - return false; + numgains = rtlsdr_get_tuner_gains(RTLSDR.dev, NULL); + if (numgains <= 0) { + fprintf(stderr, "rtlsdr: error getting tuner gains\n"); + return false; } - gains = malloc(numgains * sizeof(int)); - if (rtlsdr_get_tuner_gains(RTLSDR.dev, gains) != numgains) { - fprintf(stderr, "rtlsdr: error getting tuner gains\n"); - free(gains); - return false; - } - - int target = (Modes.gain == MODES_MAX_GAIN ? 9999 : Modes.gain); - int closest = -1; - - for (int i = 0; i < numgains; ++i) { - if (closest == -1 || abs(gains[i] - target) < abs(gains[closest] - target)) - closest = i; - } - - rtlsdr_set_tuner_gain(RTLSDR.dev, gains[closest]); + gains = malloc((numgains + 1) * sizeof(int)); + if (rtlsdr_get_tuner_gains(RTLSDR.dev, gains) != numgains) { + fprintf(stderr, "rtlsdr: error getting tuner gains\n"); free(gains); - - fprintf(stderr, "rtlsdr: tuner gain set to %.1f dB\n", - rtlsdr_get_tuner_gain(RTLSDR.dev)/10.0); + return false; } + + qsort(gains, numgains, sizeof(gains[0]), sort_gains); + + // Fake an entry at slightly higher than max manual gain; + // we will use this for the "tuner AGC enabled" settings + // which due to librtlsdr quirks behaves like a "more than + // max" gain. :/ + gains[numgains] = gains[numgains-1] + 90; // +9.0dB + + RTLSDR.gain_steps = numgains + 1; + RTLSDR.gains = gains; + + int selected = -1; + if (Modes.gain == MODES_AUTO_GAIN) { + selected = numgains; + } else if (Modes.gain == MODES_MAX_GAIN) { + selected = numgains - 1; + } else { + for (int i = 0; i < numgains; ++i) { + if (selected == -1 || fabs(gains[i]/10.0 - Modes.gain) < fabs(gains[selected]/10.0 - Modes.gain)) + selected = i; + } + } + + rtlsdrSetGain(selected); } if (RTLSDR.digital_agc) { @@ -276,6 +298,9 @@ bool rtlsdrOpen(void) { } #endif + if (Modes.adaptive_range_target == 0) + Modes.adaptive_range_target = 30.0; + return true; } @@ -382,4 +407,65 @@ void rtlsdrClose() free(RTLSDR.bounce_buffer); RTLSDR.bounce_buffer = NULL; + + free(RTLSDR.gains); + RTLSDR.gains = NULL; } + +int rtlsdrGetGain() +{ + return RTLSDR.current_gain; +} + +int rtlsdrGetMaxGain() +{ + return RTLSDR.gain_steps - 1; +} + +double rtlsdrGetGainDb(int step) +{ + if (!RTLSDR.gains) + return 0.0; + + if (step < 0) + step = 0; + if (step >= RTLSDR.gain_steps) + step = RTLSDR.gain_steps - 1; + return RTLSDR.gains[step] / 10.0; +} + +int rtlsdrSetGain(int step) +{ + if (!RTLSDR.gains) + return -1; + + if (step < 0) + step = 0; + if (step >= RTLSDR.gain_steps) + step = RTLSDR.gain_steps - 1; + + if (step == RTLSDR.gain_steps - 1) { + if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 0) < 0) { + fprintf(stderr, "rtlsdr: failed to enable tuner AGC\n"); + return RTLSDR.current_gain; + } + + fprintf(stderr, "rtlsdr: tuner gain set to about %.1f dB (gain step %d) (tuner AGC enabled)\n", RTLSDR.gains[step] / 10.0, step); + } else { + if (rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 1) < 0) { + fprintf(stderr, "rtlsdr: failed to disable tuner AGC\n"); + return RTLSDR.current_gain; + } + + if (rtlsdr_set_tuner_gain(RTLSDR.dev, RTLSDR.gains[step]) < 0) { + fprintf(stderr, "rtlsdr: failed to set tuner gain to %.1fdB\n", RTLSDR.gains[step] / 10.0); + return RTLSDR.current_gain; + } + + fprintf(stderr, "rtlsdr: tuner gain set to %.1f dB (gain step %d)\n", RTLSDR.gains[step] / 10.0, step); + } + + RTLSDR.current_gain = step; + return step; +} + diff --git a/sdr_rtlsdr.h b/sdr_rtlsdr.h index af6c53d..a4d7d65 100644 --- a/sdr_rtlsdr.h +++ b/sdr_rtlsdr.h @@ -28,5 +28,9 @@ void rtlsdrRun(); void rtlsdrStop(); void rtlsdrClose(); bool rtlsdrHandleOption(int argc, char **argv, int *jptr); +int rtlsdrGetGain(); +int rtlsdrGetMaxGain(); +double rtlsdrGetGainDb(int step); +int rtlsdrSetGain(int step); #endif diff --git a/sdr_stub.c b/sdr_stub.c new file mode 100644 index 0000000..1ac69a7 --- /dev/null +++ b/sdr_stub.c @@ -0,0 +1,92 @@ +// Part of dump1090, a Mode S message decoder for RTLSDR devices. +// +// sdr_stub.c: generic SDR infrastructure, stubbed out to do nothing +// +// Copyright (c) 2021 FlightAware LLC +// +// This file is free software: you may copy, redistribute 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 file 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 "dump1090.h" + +void sdrInitConfig() +{ + /* nothing */ +} + +void sdrShowHelp() +{ + /* nothing */ +} + +bool sdrHandleOption(int argc, char **argv, int *jptr) +{ + MODES_NOTUSED(argc); + MODES_NOTUSED(argv); + MODES_NOTUSED(jptr); + return false; +} + +bool sdrOpen() +{ + return false; +} + +void sdrRun() +{ + /* nothing */ +} + +void sdrStop() +{ + /* nothing */ +} + +void sdrClose() +{ + /* nothing */ +} + +void sdrMonitor() +{ + /* nothing */ +} + +void sdrUpdateCPUTime(struct timespec *addTo) +{ + MODES_NOTUSED(addTo); +} + +int sdrGetGain() +{ + return -1; +} + +int sdrGetMaxGain() +{ + return -1; +} + +double sdrGetGainDb(int step) +{ + MODES_NOTUSED(step); + return 0.0; +} + +int sdrSetGain(int step) +{ + MODES_NOTUSED(step); + return -1; +} + + diff --git a/stats.c b/stats.c index d433746..0ebd64d 100644 --- a/stats.c +++ b/stats.c @@ -3,6 +3,7 @@ // stats.c: statistics helpers. // // Copyright (c) 2015 Oliver Jowett +// Copyright (c) 2021 FlightAware LLC // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the @@ -114,6 +115,46 @@ void display_stats(struct stats *st) { st->strong_signal_count); } + if (Modes.adaptive_burst_control || Modes.adaptive_range_control) { + printf("Adaptive gain:\n" + " %5u loud undecoded bursts\n" + " %5u loud decoded messages\n" + " %5.1f dBFS current noise floor\n" + " %5.1f dB current gain setting\n", + st->adaptive_loud_undecoded, + st->adaptive_loud_decoded, + st->adaptive_noise_dbfs, + sdrGetGainDb(st->adaptive_gain)); + + uint32_t total_seconds = 0; + for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) + total_seconds += st->adaptive_gain_seconds[i]; + + if (total_seconds) { + unsigned count = 0; + for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) { + count += st->adaptive_gain_seconds[i]; + if (count >= total_seconds/2) { + printf(" %5.1f dB median gain\n", sdrGetGainDb(i)); + break; + } + } + + printf(" %5u seconds (%5.1f%%) at reduced gain due to loud messages\n", + st->adaptive_gain_reduced_seconds, 100.0 * st->adaptive_gain_reduced_seconds / total_seconds); + + printf(" Gain histogram:\n"); + for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) { + unsigned seconds = st->adaptive_gain_seconds[i]; + if (seconds) { + printf(" %5.1f dB: %5u seconds (%5.1f%%)\n", + sdrGetGainDb(i), seconds, 100.0 * seconds / total_seconds); + } + } + + } + } + if (Modes.net) { printf("Messages from network clients:\n"); printf(" %8u Mode A/C messages received\n", st->remote_received_modeac); @@ -125,28 +166,29 @@ void display_stats(struct stats *st) { printf(" %8u accepted with %d-bit error repaired\n", st->remote_accepted[j], j); } - printf("%8u total usable messages\n", + printf("Decoder:\n" + " %8u total usable messages\n", st->messages_total); for (unsigned i = 0; i < 32; ++i) { if (st->messages_by_df[i]) - printf(" %8u DF%u messages\n", st->messages_by_df[i], i); + printf(" %8u DF%u messages\n", st->messages_by_df[i], i); } - printf("%8u surface position messages received\n" - "%8u airborne position messages received\n" - "%8u global CPR attempts with valid positions\n" - "%8u global CPR attempts with bad data\n" - " %8u global CPR attempts that failed the range check\n" - " %8u global CPR attempts that failed the speed check\n" - "%8u global CPR attempts with insufficient data\n" - "%8u local CPR attempts with valid positions\n" - " %8u aircraft-relative positions\n" - " %8u receiver-relative positions\n" - "%8u local CPR attempts that did not produce useful positions\n" - " %8u local CPR attempts that failed the range check\n" - " %8u local CPR attempts that failed the speed check\n" - "%8u CPR messages that look like transponder failures filtered\n", + printf(" %8u surface position messages received\n" + " %8u airborne position messages received\n" + " %8u global CPR attempts with valid positions\n" + " %8u global CPR attempts with bad data\n" + " %8u global CPR attempts that failed the range check\n" + " %8u global CPR attempts that failed the speed check\n" + " %8u global CPR attempts with insufficient data\n" + " %8u local CPR attempts with valid positions\n" + " %8u aircraft-relative positions\n" + " %8u receiver-relative positions\n" + " %8u local CPR attempts that did not produce useful positions\n" + " %8u local CPR attempts that failed the range check\n" + " %8u local CPR attempts that failed the speed check\n" + " %8u CPR messages that look like transponder failures filtered\n", st->cpr_surface, st->cpr_airborne, st->cpr_global_ok, @@ -162,10 +204,10 @@ void display_stats(struct stats *st) { st->cpr_local_speed_checks, st->cpr_filtered); - printf("%8u non-ES altitude messages from ES-equipped aircraft ignored\n", st->suppressed_altitude_messages); - printf("%8u unique aircraft tracks\n", st->unique_aircraft); - printf("%8u aircraft tracks where only one message was seen\n", st->single_message_aircraft); - printf("%8u aircraft tracks which were not marked reliable\n", st->unreliable_aircraft); + printf(" %8u non-ES altitude messages from ES-equipped aircraft ignored\n", st->suppressed_altitude_messages); + printf(" %8u unique aircraft tracks\n", st->unique_aircraft); + printf(" %8u aircraft tracks where only one message was seen\n", st->single_message_aircraft); + printf(" %8u aircraft tracks which were not marked reliable\n", st->unreliable_aircraft); { uint64_t demod_cpu_millis = (uint64_t)st->demod_cpu.tv_sec*1000UL + st->demod_cpu.tv_nsec/1000000UL; @@ -272,7 +314,14 @@ void add_stats(const struct stats *st1, const struct stats *st2, struct stats *t else target->start = st2->start; - target->end = st1->end > st2->end ? st1->end : st2->end; + const struct stats *newer; + if (st1->end > st2->end) { + newer = st1; + } else { + newer = st2; + } + + target->end = newer->end; target->demod_preambles = st1->demod_preambles + st2->demod_preambles; target->demod_rejected_bad = st1->demod_rejected_bad + st2->demod_rejected_bad; @@ -344,4 +393,13 @@ void add_stats(const struct stats *st1, const struct stats *st2, struct stats *t // range histogram for (i = 0; i < RANGE_BUCKET_COUNT; ++i) target->range_histogram[i] = st1->range_histogram[i] + st2->range_histogram[i]; + + // adaptive gain measurements + target->adaptive_gain = newer->adaptive_gain; + for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) + target->adaptive_gain_seconds[i] = st1->adaptive_gain_seconds[i] + st2->adaptive_gain_seconds[i]; + target->adaptive_gain_reduced_seconds = st1->adaptive_gain_reduced_seconds + st2->adaptive_gain_reduced_seconds; + target->adaptive_loud_undecoded = st1->adaptive_loud_undecoded + st2->adaptive_loud_undecoded; + target->adaptive_loud_decoded = st1->adaptive_loud_decoded + st2->adaptive_loud_decoded; + target->adaptive_noise_dbfs = newer->adaptive_noise_dbfs; } diff --git a/stats.h b/stats.h index 1935b12..387cb17 100644 --- a/stats.h +++ b/stats.h @@ -1,8 +1,9 @@ // Part of dump1090, a Mode S message decoder for RTLSDR devices. // -// stats.c: statistics structures and prototypes. +// stats.h: statistics structures and prototypes. // // Copyright (c) 2015 Oliver Jowett +// Copyright (c) 2021 FlightAware LLC // // This file is free software: you may copy, redistribute and/or modify it // under the terms of the GNU General Public License as published by the @@ -128,6 +129,15 @@ struct stats { // range histogram #define RANGE_BUCKET_COUNT 76 uint32_t range_histogram[RANGE_BUCKET_COUNT]; + + // adaptive gain measurements +#define STATS_GAIN_COUNT 64 + unsigned adaptive_gain; // Current gain step in use + uint32_t adaptive_gain_seconds[STATS_GAIN_COUNT]; // Seconds spent at each gain step + uint32_t adaptive_gain_reduced_seconds; // Seconds spent at a reduced gain due to burst detection + uint32_t adaptive_loud_undecoded; // Total number of loud, undecoded bursts + uint32_t adaptive_loud_decoded; // Total number of loud, decoded messages + double adaptive_noise_dbfs; // Current adaptive-dynamic-range smoothed noise measurement, dBFS }; void add_stats(const struct stats *st1, const struct stats *st2, struct stats *target);