Adaptive gain, first pass (#134)

This PR adds basic adaptive gain support, which adjusts SDR gain on the fly based on the noise & signal levels seen.

There are two control mechanisms:

Dynamic range control is enabled by the --adaptive-range option. This adjusts SDR gain to try to achieve a minimum dynamic range, regardless of the exact hardware in the RF path.

Burst (loud message) control is enabled by the --adaptive-burst option. This decreases SDR gain when undecodable loud messages are heard, allowing for better reception of nearby aircraft at the expense of range.

This is only the basic implementation - see the PR for remaining work to do.
This commit is contained in:
Oliver Jowett 2021-06-29 20:11:13 +08:00 committed by GitHub
parent f7b6f7aefc
commit 56625449e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1221 additions and 169 deletions

View File

@ -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)

551
adaptive.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#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;
}
}

30
adaptive.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#ifndef ADAPTIVE_H
#define ADAPTIVE_H
#include <inttypes.h>
struct modesMessage;
void adaptive_init();
void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decoded);
#endif

View File

@ -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

View File

@ -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 <addr> Show only messages from the given ICAO on stdout\n"
"--snip <level> Strip IQ file removing samples < level\n"
"\n"
" Decoder settings\n"
"\n"
// ------ 80 char limit ----------------------------------------------------------|
"--gain <db> Set gain (default: max gain. Use -10 for auto-gain)\n"
"--freq <hz> Set frequency (default: 1090 Mhz)\n"
"--interactive Interactive mode refreshing data on screen. Implies --throttle\n"
"--interactive-ttl <sec> Remove from list if idle for <sec> (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 <path> Read DSP wisdom from given path\n"
"--lat <latitude> Reference/receiver latitude for surface positions\n"
"--lon <longitude> Reference/receiver longitude for surface positions\n"
"--max-range <distance> 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 <a> Set burst rate smoothing factor\n"
" (0..1, smaller=more smoothing)\n"
"--adaptive-burst-loud-rate <r> Set burst rate for gain decrease\n"
"--adaptive-burst-loud-runlength <l> Set burst runlength for gain decrease\n"
"--adaptive-burst-quiet-rate <r> Set burst rate for gain increase\n"
"--adaptive-burst-quiet-runlength <l> Set burst runlength for gain increase\n"
"--adaptive-range Adjust gain for target dynamic range\n"
"--adaptive-range-target <db> Set target dynamic range in dB\n"
"--adaptive-range-alpha <a> Set dynamic range noise smoothing factor\n"
" (0..1, smaller=more smoothing)\n"
"--adaptive-range-percentile <p> Set dynamic range noise percentile\n"
"--adaptive-range-scan-delay <s> Set data collection interval for dynamic\n"
" range gain scanning (seconds)\n"
"--adaptive-range-rescan-delay <s> Set rescan interval for dynamic range\n"
" gain scanning (seconds)\n"
"--adaptive-min-gain <g> Set gain adjustment range lower limit (dB)\n"
"--adaptive-max-gain <g> 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> IP address to bind to (default: Any; Use 127.0.0.1 for private)\n"
"--net-bind-address <ip> IP address to bind to (use 127.0.0.1 for private)\n"
"--net-ri-port <ports> TCP raw input listen ports (default: 30001)\n"
"--net-ro-port <ports> TCP raw output listen ports (default: 30002)\n"
"--net-sbs-port <ports> TCP BaseStation output listen ports (default: 30003)\n"
"--net-bi-port <ports> TCP Beast input listen ports (default: 30004,30104)\n"
"--net-bo-port <ports> TCP Beast output listen ports (default: 30005)\n"
"--net-stratux-port <ports> TCP Stratux output listen ports (default: disabled)\n"
"--net-stratux-port <ports> TCP Stratux output listen ports (default: disabled)\n"
"--net-ro-size <size> TCP output minimum size (default: 0)\n"
"--net-ro-interval <rate> TCP output memory flush rate in seconds (default: 0)\n"
"--net-heartbeat <rate> TCP heartbeat rate in seconds (default: 60 sec; 0 to disable)\n"
"--net-heartbeat <rate> TCP heartbeat rate in seconds\n"
" (default: 60 sec; 0 to disable)\n"
"--net-buffer <n> 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 <latitude> Reference/receiver latitude for surface posn (opt)\n"
"--lon <longitude> Reference/receiver longitude for surface posn (opt)\n"
"--max-range <distance> 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 <seconds> Show and reset stats every <seconds> 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 <level> Strip IQ file removing samples < level\n"
"--quiet Disable output to stdout. Use for daemon applications\n"
"--show-only <addr> Show only messages from the given ICAO on stdout\n"
"--write-json <dir> Periodically write json output to <dir> (for serving by a separate webserver)\n"
"--stats-range Collect/show range histogram\n"
"--write-json <dir> Periodically write json output to <dir>\n"
" (for serving by a separate webserver)\n"
"--write-json-every <t> Write json aircraft output every t seconds (default 1)\n"
"--json-stats-every <t> Write json stats output every t seconds (default 60)\n"
"--json-location-accuracy <n> 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 <path> Read DSP wisdom from given path\n"
"--json-location-accuracy <n> 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 <sec> Remove from list if idle for <sec>\n"
"--interactive-show-distance Show aircraft distance and bearing\n"
" (requires --lat and --lon)\n"
"--interactive-distance-units <u> Distance units ('km', 'sm', 'nm')\n"
"--interactive-callsign-filter <r> 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);

View File

@ -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;

129
net_io.c
View File

@ -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;
}

64
sdr.c
View File

@ -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);
}

6
sdr.h
View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

92
sdr_stub.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#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;
}

100
stats.c
View File

@ -3,6 +3,7 @@
// stats.c: statistics helpers.
//
// Copyright (c) 2015 Oliver Jowett <oliver@mutability.co.uk>
// 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;
}

12
stats.h
View File

@ -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 <oliver@mutability.co.uk>
// 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);