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:
parent
f7b6f7aefc
commit
56625449e8
6
Makefile
6
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
37
demod_2400.c
37
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
|
||||
|
||||
|
|
|
|||
183
dump1090.c
183
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 <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);
|
||||
|
|
|
|||
26
dump1090.h
26
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;
|
||||
|
|
|
|||
129
net_io.c
129
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
64
sdr.c
64
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
6
sdr.h
6
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
146
sdr_rtlsdr.c
146
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
100
stats.c
|
|
@ -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
12
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 <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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue