Misc adaptive gain changes:

Update adaptive gain control loop to handle interactions between burst detection and dynamic range control.

Internal doc updates & naming cleanups.

Stats tweaks.
This commit is contained in:
Oliver Jowett 2021-07-07 20:57:34 +08:00
parent fd8f2d77e1
commit ac97423249
6 changed files with 215 additions and 188 deletions

View File

@ -34,28 +34,31 @@ static float adaptive_gain_down_db;
// block handling // block handling
// //
static unsigned adaptive_block_remaining; static unsigned adaptive_block_remaining; // samples in each block
static unsigned adaptive_block_size; static unsigned adaptive_block_size; // samples remaining in the current block
void adaptive_init(); void adaptive_init();
void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decoded); 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_update_single(uint16_t *buf, unsigned length, struct modesMessage *decoded);
static void adaptive_end_of_block(); static void adaptive_end_of_block();
static void adaptive_control_update();
// //
// burst handling // burst handling
// //
static unsigned adaptive_burst_window_size; static unsigned adaptive_burst_window_size; // samples in each burst window
static unsigned adaptive_burst_window_remaining; static unsigned adaptive_burst_window_remaining; // samples remaining in the current burst window
static unsigned adaptive_burst_window_counter; static unsigned adaptive_burst_window_counter; // loud samples seen in current burst window
static unsigned adaptive_burst_runlength; static unsigned adaptive_burst_runlength; // consecutive loud burst windows seen
static unsigned adaptive_burst_block_counter; static unsigned adaptive_burst_block_loud_undecoded; // loud undecoded bursts seen in this block so far
static unsigned adaptive_burst_block_loud_decodes; static unsigned adaptive_burst_block_loud_decoded; // loud decoded messages seen in this block so far
static double adaptive_burst_smoothed; static double adaptive_burst_loud_undecoded_smoothed; // smoothed rate of loud misdecodes per block
static double adaptive_burst_loud_decodes_smoothed; static double adaptive_burst_loud_decoded_smoothed; // smoothed rate of loud successful decodes per block
static unsigned adaptive_burst_change_delay; static unsigned adaptive_burst_change_timer; // countdown inhibiting control after changing gain
static double adaptive_burst_loud_threshold; static double adaptive_burst_loud_threshold; // current signal level threshold for a "loud decode"
static unsigned adaptive_burst_loud_blocks = 0; // consecutive blocks with loud rate
static unsigned adaptive_burst_quiet_blocks = 0; // consecutive blocks with quiet rate
static void adaptive_burst_update(uint16_t *buf, unsigned length); static void adaptive_burst_update(uint16_t *buf, unsigned length);
static void adaptive_burst_skip(unsigned length); static void adaptive_burst_skip(unsigned length);
@ -64,23 +67,23 @@ 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_window(unsigned counter);
static void adaptive_burst_end_of_block(); static void adaptive_burst_end_of_block();
static void adaptive_burst_control_update();
// //
// noise floor measurement (adaptive dynamic range) // noise floor measurement (adaptive dynamic range)
// //
static unsigned *adaptive_range_radix; static unsigned *adaptive_range_radix; // radix-sort buckets for current block
static unsigned adaptive_range_counter; static unsigned adaptive_range_radix_counter; // sum of all radix-sort buckets (= number of samples sorted)
static double adaptive_range_smoothed; static double adaptive_range_smoothed; // smoothed noise floor estimate, dBFS
static enum { RANGE_SCAN_IDLE, RANGE_SCAN_UP, RANGE_SCAN_DOWN } adaptive_range_state = RANGE_SCAN_UP; static enum { RANGE_SCAN_IDLE, RANGE_SCAN_UP, RANGE_SCAN_DOWN } adaptive_range_state = RANGE_SCAN_UP;
static unsigned adaptive_range_delay; static unsigned adaptive_range_change_timer; // countdown inhibiting control after changing gain
static unsigned adaptive_range_rescan_timer; // countdown to next upwards gain reprobe
static int adaptive_range_gain_limit; // probed maximum gain step with acceptable dynamic range
static void adaptive_range_update(uint16_t *buf, unsigned length); static void adaptive_range_update(uint16_t *buf, unsigned length);
static void adaptive_range_end_of_block(); static void adaptive_range_end_of_block();
static void adaptive_range_control_update();
// Try to change the SDR gain to 'step' and tell the user about it,
// with 'why' as the reason to show. Return true if the gain actually changed.
static bool adaptive_set_gain(int step, const char *why) static bool adaptive_set_gain(int step, const char *why)
{ {
if (step < adaptive_gain_min) if (step < adaptive_gain_min)
@ -96,9 +99,14 @@ static bool adaptive_set_gain(int step, const char *why)
sdrGetGainDb(current_gain), current_gain, sdrGetGainDb(step), step, why); sdrGetGainDb(current_gain), current_gain, sdrGetGainDb(step), step, why);
int new_gain = sdrSetGain(step); int new_gain = sdrSetGain(step);
return (current_gain != new_gain); bool changed = (current_gain != new_gain);
if (changed)
++Modes.stats_current.adaptive_gain_changes;
return changed;
} }
// Update internal state to reflect a gain change
// (usually after adaptive_set_gain returns true, but also called during init)
static void adaptive_gain_changed() static void adaptive_gain_changed()
{ {
int new_gain = sdrGetGain(); int new_gain = sdrGetGain();
@ -107,8 +115,14 @@ static void adaptive_gain_changed()
double loud_threshold_dbfs = 0 - adaptive_gain_up_db - 3.0; double loud_threshold_dbfs = 0 - adaptive_gain_up_db - 3.0;
adaptive_burst_loud_threshold = pow(10, loud_threshold_dbfs / 10.0); adaptive_burst_loud_threshold = pow(10, loud_threshold_dbfs / 10.0);
adaptive_range_change_timer = Modes.adaptive_range_change_delay;
adaptive_burst_change_timer = Modes.adaptive_burst_change_delay;
adaptive_burst_loud_blocks = 0;
adaptive_burst_quiet_blocks = 0;
} }
// External init entry point
void adaptive_init() void adaptive_init()
{ {
int maxgain = sdrGetMaxGain(); int maxgain = sdrGetMaxGain();
@ -130,16 +144,13 @@ void adaptive_init()
adaptive_burst_window_size = Modes.sample_rate / 25000; adaptive_burst_window_size = Modes.sample_rate / 25000;
adaptive_burst_window_remaining = adaptive_burst_window_size; adaptive_burst_window_remaining = adaptive_burst_window_size;
adaptive_burst_window_counter = 0; 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 // 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_size = adaptive_burst_window_size * 25000;
adaptive_block_remaining = adaptive_block_size; adaptive_block_remaining = adaptive_block_size;
adaptive_range_radix = calloc(sizeof(unsigned), 65536); adaptive_range_radix = calloc(sizeof(unsigned), 65536);
adaptive_range_state = RANGE_SCAN_UP; adaptive_range_state = RANGE_SCAN_UP;
adaptive_range_delay = Modes.adaptive_range_scan_delay;
// select and enforce gain limits // select and enforce gain limits
for (adaptive_gain_min = 0; adaptive_gain_min < maxgain; ++adaptive_gain_min) { for (adaptive_gain_min = 0; adaptive_gain_min < maxgain; ++adaptive_gain_min) {
@ -160,6 +171,8 @@ void adaptive_init()
fprintf(stderr, "adaptive: enabled burst control\n"); fprintf(stderr, "adaptive: enabled burst control\n");
adaptive_set_gain(sdrGetGain(), "constraining gain to adaptive gain limits"); adaptive_set_gain(sdrGetGain(), "constraining gain to adaptive gain limits");
adaptive_gain_changed(); adaptive_gain_changed();
adaptive_range_gain_limit = sdrGetGain();
} }
// Feed some samples into the adaptive system. Any number of samples might be passed in. // Feed some samples into the adaptive system. Any number of samples might be passed in.
@ -190,7 +203,7 @@ static void adaptive_update_single(uint16_t *buf, unsigned length, struct modesM
{ {
if (decoded) { if (decoded) {
if (/* decoded->msgbits == 112 && */ decoded->signalLevel >= adaptive_burst_loud_threshold) if (/* decoded->msgbits == 112 && */ decoded->signalLevel >= adaptive_burst_loud_threshold)
++adaptive_burst_block_loud_decodes; ++adaptive_burst_block_loud_decoded;
adaptive_burst_skip(length); adaptive_burst_skip(length);
} else { } else {
adaptive_burst_update(buf, length); adaptive_burst_update(buf, length);
@ -300,7 +313,7 @@ static void adaptive_burst_end_of_window(unsigned counter)
// that as a candidate for an over-amplified message that was // that as a candidate for an over-amplified message that was
// not decoded. // not decoded.
if (adaptive_burst_runlength >= 2 && adaptive_burst_runlength <= 5) if (adaptive_burst_runlength >= 2 && adaptive_burst_runlength <= 5)
++adaptive_burst_block_counter; ++adaptive_burst_block_loud_undecoded;
adaptive_burst_runlength = 0; adaptive_burst_runlength = 0;
} }
} }
@ -312,7 +325,7 @@ static void adaptive_range_update(uint16_t *buf, unsigned length)
if (!Modes.adaptive_range_control) if (!Modes.adaptive_range_control)
return; return;
adaptive_range_counter += length; adaptive_range_radix_counter += length;
while (length--) { while (length--) {
// do a very simple radix sort of sample magnitudes // do a very simple radix sort of sample magnitudes
// so we can later find the Nth percentile value // so we can later find the Nth percentile value
@ -331,7 +344,7 @@ static void adaptive_range_end_of_block()
unsigned n = 0, i = 0; unsigned n = 0, i = 0;
// measure Nth percentile magnitude // measure Nth percentile magnitude
unsigned count_n = adaptive_range_counter * Modes.adaptive_range_percentile / 100; unsigned count_n = adaptive_range_radix_counter * Modes.adaptive_range_percentile / 100;
while (i < 65536 && n <= count_n) while (i < 65536 && n <= count_n)
n += adaptive_range_radix[i++]; n += adaptive_range_radix[i++];
uint16_t percentile_n = i - 1; uint16_t percentile_n = i - 1;
@ -347,7 +360,7 @@ static void adaptive_range_end_of_block()
// reset radix sort for the next block // reset radix sort for the next block
memset(adaptive_range_radix, 0, 65536 * sizeof(unsigned)); memset(adaptive_range_radix, 0, 65536 * sizeof(unsigned));
adaptive_range_counter = 0; adaptive_range_radix_counter = 0;
} }
// Burst measurement: we reached the end of a block, update our burst rate estimate // Burst measurement: we reached the end of a block, update our burst rate estimate
@ -356,25 +369,17 @@ static void adaptive_burst_end_of_block()
if (!Modes.adaptive_burst_control) if (!Modes.adaptive_burst_control)
return; return;
// maintain an EMA of the number of bursts seen per block // maintain an EMA of the number of undecoded loud bursts seen per block
Modes.stats_current.adaptive_loud_undecoded += adaptive_burst_block_counter; Modes.stats_current.adaptive_loud_undecoded += adaptive_burst_block_loud_undecoded;
adaptive_burst_smoothed = adaptive_burst_smoothed * (1 - Modes.adaptive_burst_alpha) + adaptive_burst_block_counter * Modes.adaptive_burst_alpha; adaptive_burst_loud_undecoded_smoothed = adaptive_burst_loud_undecoded_smoothed * (1 - Modes.adaptive_burst_alpha) + adaptive_burst_block_loud_undecoded * Modes.adaptive_burst_alpha;
adaptive_burst_block_counter = 0; adaptive_burst_block_loud_undecoded = 0;
// maintain an EMA of the number of decoded, but loud, messages seen per block // 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; Modes.stats_current.adaptive_loud_decoded += adaptive_burst_block_loud_decoded;
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_loud_decoded_smoothed = adaptive_burst_loud_decoded_smoothed * (1 - Modes.adaptive_burst_alpha) + adaptive_burst_block_loud_decoded * Modes.adaptive_burst_alpha;
adaptive_burst_block_loud_decodes = 0; adaptive_burst_block_loud_decoded = 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); void flush_stats(uint64_t now);
@ -396,32 +401,41 @@ static void adaptive_end_of_block()
adaptive_range_end_of_block(); adaptive_range_end_of_block();
adaptive_burst_end_of_block(); adaptive_burst_end_of_block();
adaptive_burst_control_update(); adaptive_control_update();
adaptive_range_control_update();
Modes.stats_current.adaptive_valid = true; Modes.stats_current.adaptive_valid = true;
unsigned current = Modes.stats_current.adaptive_gain = sdrGetGain(); unsigned current = Modes.stats_current.adaptive_gain = sdrGetGain();
Modes.stats_current.adaptive_range_gain_limit = adaptive_range_gain_limit;
++Modes.stats_current.adaptive_gain_seconds[current < STATS_GAIN_COUNT ? current : STATS_GAIN_COUNT-1]; ++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() static void adaptive_control_update()
{ {
if (!Modes.adaptive_burst_control) // votes for what to do with the gain
return; // "gain_not_up" overlaps somewhat with "gain_down", but they are not identical;
// burst control may want to prevent gain from increasing, but not necessarily
// decrease gain.
if (adaptive_range_state != RANGE_SCAN_IDLE) bool gain_up = false;
return; const char *gain_up_reason = NULL;
bool gain_down = false;
if (adaptive_burst_change_delay) const char *gain_down_reason = NULL;
--adaptive_burst_change_delay; bool gain_not_up = false;
if (!adaptive_burst_change_delay) { int current_gain = sdrGetGain();
if (adaptive_burst_smoothed > Modes.adaptive_burst_loud_rate) {
if (adaptive_burst_change_timer)
--adaptive_burst_change_timer;
if (adaptive_range_change_timer > 0)
--adaptive_range_change_timer;
if (adaptive_range_rescan_timer > 0)
--adaptive_range_rescan_timer;
if (Modes.adaptive_burst_control && !adaptive_burst_change_timer) {
if (adaptive_burst_loud_undecoded_smoothed > Modes.adaptive_burst_loud_rate) {
adaptive_burst_quiet_blocks = 0; adaptive_burst_quiet_blocks = 0;
++adaptive_burst_loud_blocks; ++adaptive_burst_loud_blocks;
} else if (adaptive_burst_loud_decodes_smoothed < Modes.adaptive_burst_quiet_rate) { } else if (adaptive_burst_loud_decoded_smoothed < Modes.adaptive_burst_quiet_rate) {
adaptive_burst_loud_blocks = 0; adaptive_burst_loud_blocks = 0;
++adaptive_burst_quiet_blocks; ++adaptive_burst_quiet_blocks;
} else { } else {
@ -431,122 +445,128 @@ static void adaptive_burst_control_update()
if (adaptive_burst_loud_blocks >= Modes.adaptive_burst_loud_runlength) { if (adaptive_burst_loud_blocks >= Modes.adaptive_burst_loud_runlength) {
// we need to reduce gain (further) // we need to reduce gain (further)
if (!adaptive_burst_suppressing) { gain_down = gain_not_up = true;
adaptive_burst_suppressing = true; gain_down_reason = "high rate of loud undecoded messages";
adaptive_burst_orig_gain = sdrGetGain();
// if we're currently doing a downward scan, reducing gain further may confuse it;
// stop that scan and restart it once we are no longer in a reduced-gain state
if (adaptive_range_state == RANGE_SCAN_DOWN) {
adaptive_range_state = RANGE_SCAN_IDLE;
adaptive_range_rescan_timer = 0;
}
} else if (adaptive_burst_quiet_blocks < Modes.adaptive_burst_quiet_runlength) {
// we're OK at the current gain, but should not increase it
gain_not_up = true;
} else if (current_gain < adaptive_range_gain_limit) {
// we're OK at the current gain, and can increase gain to the previously discovered
// dynamic range limit
gain_up = true;
gain_up_reason = "low loud message rate and gain below dynamic range limit";
}
}
if (Modes.adaptive_range_control && !adaptive_range_change_timer) {
float available_range = -20 * log10(adaptive_range_smoothed / 65536.0);
// allow the gain limit to increase if this gain setting is acceptable
// (decreasing the limit is done separately in SCAN_UP / IDLE states when we decide to reduce gain)
if (available_range >= Modes.adaptive_range_target && current_gain > adaptive_range_gain_limit)
adaptive_range_gain_limit = current_gain;
switch (adaptive_range_state) {
case RANGE_SCAN_UP:
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);
gain_down = gain_not_up = true;
gain_down_reason = "probing dynamic range gain lower bound";
adaptive_range_state = RANGE_SCAN_DOWN;
if (adaptive_range_gain_limit >= current_gain)
adaptive_range_gain_limit = current_gain - 1;
break;
} }
adaptive_decrease_gain("saw a noisy period with many undecoded loud messages"); if (sdrGetGain() >= adaptive_gain_max) {
adaptive_burst_loud_blocks = 0; // We have reached our upper gain limit
adaptive_burst_change_delay = Modes.adaptive_burst_change_delay; fprintf(stderr, "adaptive: reached upper gain limit, halting dynamic range scan here\n");
} adaptive_range_state = RANGE_SCAN_IDLE;
adaptive_range_rescan_timer = Modes.adaptive_range_rescan_delay;
break;
}
if (adaptive_burst_suppressing && adaptive_burst_quiet_blocks >= Modes.adaptive_burst_quiet_runlength) { // This gain step is OK and we have more to try, try the next gain step up.
// we can relax the gain restriction // (But if burst detection has inhibited increasing gain, don't do anything yet, just try again next block)
adaptive_increase_gain("saw a quiet period with few loud messages"); if (!gain_not_up) {
adaptive_burst_quiet_blocks = 0; fprintf(stderr, "adaptive: available dynamic range (%.1fdB) >= required dynamic range (%.1fdB), continuing upward scan\n", available_range, Modes.adaptive_range_target);
adaptive_burst_change_delay = Modes.adaptive_burst_change_delay; gain_up = true;
gain_up_reason = "probing dynamic range gain upper bound";
}
break;
if (sdrGetGain() >= adaptive_burst_orig_gain) case RANGE_SCAN_DOWN:
adaptive_burst_suppressing = false; 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_rescan_timer = 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_rescan_timer = 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);
gain_down = gain_not_up = true;
gain_down_reason = "probing dynamic range gain lower bound";
break;
case RANGE_SCAN_IDLE:
// Look for increased noise that could be compensated for by decreasing gain.
// Do this even if we're waiting to rescan or if burst control is also active
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);
if (current_gain >= adaptive_range_gain_limit)
adaptive_range_gain_limit = current_gain - 1;
adaptive_range_state = RANGE_SCAN_DOWN;
gain_down = gain_not_up = true;
gain_down_reason = "dynamic range fell below target value";
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.
// But don't do this while burst control is preventing gain increases.
if (!adaptive_range_rescan_timer && !gain_not_up) {
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");
gain_up = true;
gain_up_reason = "periodic re-probing of dynamic range gain upper bound";
adaptive_range_state = RANGE_SCAN_UP;
break;
}
// Nothing to do for a while.
adaptive_range_rescan_timer = 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_rescan_timer = Modes.adaptive_range_rescan_delay;
break;
} }
} }
}
// now actually perform any gain changes
static void adaptive_range_control_update()
{ if (gain_down)
if (!Modes.adaptive_range_control) adaptive_decrease_gain(gain_down_reason);
return; else if (gain_up && !gain_not_up)
adaptive_increase_gain(gain_up_reason);
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;
}
} }

View File

@ -128,7 +128,7 @@ static void modesInitConfig(void) {
Modes.adaptive_burst_control = false; Modes.adaptive_burst_control = false;
Modes.adaptive_burst_alpha = 2.0 / (5 + 1); Modes.adaptive_burst_alpha = 2.0 / (5 + 1);
Modes.adaptive_burst_change_delay = 15; Modes.adaptive_burst_change_delay = 5;
Modes.adaptive_burst_loud_runlength = 10; Modes.adaptive_burst_loud_runlength = 10;
Modes.adaptive_burst_loud_rate = 5.0; Modes.adaptive_burst_loud_rate = 5.0;
Modes.adaptive_burst_quiet_runlength = 10; Modes.adaptive_burst_quiet_runlength = 10;
@ -137,7 +137,7 @@ static void modesInitConfig(void) {
Modes.adaptive_range_control = false; Modes.adaptive_range_control = false;
Modes.adaptive_range_alpha = 2.0 / (5 + 1); Modes.adaptive_range_alpha = 2.0 / (5 + 1);
Modes.adaptive_range_percentile = 40; Modes.adaptive_range_percentile = 40;
Modes.adaptive_range_scan_delay = 15; Modes.adaptive_range_change_delay = 10;
Modes.adaptive_range_rescan_delay = 900; Modes.adaptive_range_rescan_delay = 900;
sdrInitConfig(); sdrInitConfig();
@ -353,6 +353,8 @@ static void showHelp(void)
" Adaptive gain\n" " Adaptive gain\n"
"\n" "\n"
"--adaptive-burst Adjust gain for too-loud message bursts\n" "--adaptive-burst Adjust gain for too-loud message bursts\n"
"--adaptive-range-change-delay <s> Set delay after changing gain before\n"
" resuming burst control (seconds)\n"
"--adaptive-burst-alpha <a> Set burst rate smoothing factor\n" "--adaptive-burst-alpha <a> Set burst rate smoothing factor\n"
" (0..1, smaller=more smoothing)\n" " (0..1, smaller=more smoothing)\n"
"--adaptive-burst-loud-rate <r> Set burst rate for gain decrease\n" "--adaptive-burst-loud-rate <r> Set burst rate for gain decrease\n"
@ -364,8 +366,8 @@ static void showHelp(void)
"--adaptive-range-alpha <a> Set dynamic range noise smoothing factor\n" "--adaptive-range-alpha <a> Set dynamic range noise smoothing factor\n"
" (0..1, smaller=more smoothing)\n" " (0..1, smaller=more smoothing)\n"
"--adaptive-range-percentile <p> Set dynamic range noise percentile\n" "--adaptive-range-percentile <p> Set dynamic range noise percentile\n"
"--adaptive-range-scan-delay <s> Set data collection interval for dynamic\n" "--adaptive-range-change-delay <s> Set delay after changing gain before\n"
" range gain scanning (seconds)\n" " resuming dynamic range control (seconds)\n"
"--adaptive-range-rescan-delay <s> Set rescan interval for dynamic range\n" "--adaptive-range-rescan-delay <s> Set rescan interval for dynamic range\n"
" gain scanning (seconds)\n" " gain scanning (seconds)\n"
"--adaptive-min-gain <g> Set gain adjustment range lower limit (dB)\n" "--adaptive-min-gain <g> Set gain adjustment range lower limit (dB)\n"
@ -765,7 +767,7 @@ int main(int argc, char **argv) {
Modes.adaptive_burst_control = true; Modes.adaptive_burst_control = true;
} else if (!strcmp(argv[j], "--adaptive-burst-alpha") && more) { } else if (!strcmp(argv[j], "--adaptive-burst-alpha") && more) {
Modes.adaptive_burst_alpha = atof(argv[++j]); Modes.adaptive_burst_alpha = atof(argv[++j]);
} else if (!strcmp(argv[j], "--adaptive-burst-delay") && more) { } else if (!strcmp(argv[j], "--adaptive-burst-change-delay") && more) {
Modes.adaptive_burst_change_delay = atoi(argv[++j]); Modes.adaptive_burst_change_delay = atoi(argv[++j]);
} else if (!strcmp(argv[j], "--adaptive-burst-loud-rate") && more) { } else if (!strcmp(argv[j], "--adaptive-burst-loud-rate") && more) {
Modes.adaptive_burst_loud_rate = atof(argv[++j]); Modes.adaptive_burst_loud_rate = atof(argv[++j]);
@ -783,8 +785,8 @@ int main(int argc, char **argv) {
Modes.adaptive_range_percentile = atoi(argv[++j]); Modes.adaptive_range_percentile = atoi(argv[++j]);
} else if (!strcmp(argv[j], "--adaptive-range-target") && more) { } else if (!strcmp(argv[j], "--adaptive-range-target") && more) {
Modes.adaptive_range_target = atof(argv[++j]); Modes.adaptive_range_target = atof(argv[++j]);
} else if (!strcmp(argv[j], "--adaptive-range-scan-delay") && more) { } else if (!strcmp(argv[j], "--adaptive-range-change-delay") && more) {
Modes.adaptive_range_scan_delay = atoi(argv[++j]); Modes.adaptive_range_change_delay = atoi(argv[++j]);
} else if (!strcmp(argv[j], "--adaptive-range-rescan-delay") && more) { } else if (!strcmp(argv[j], "--adaptive-range-rescan-delay") && more) {
Modes.adaptive_range_rescan_delay = atoi(argv[++j]); Modes.adaptive_range_rescan_delay = atoi(argv[++j]);
} else if (sdrHandleOption(argc, argv, &j)) { } else if (sdrHandleOption(argc, argv, &j)) {

View File

@ -406,7 +406,7 @@ struct _Modes { // Internal state
float adaptive_range_alpha; float adaptive_range_alpha;
unsigned adaptive_range_percentile; unsigned adaptive_range_percentile;
float adaptive_range_target; float adaptive_range_target;
unsigned adaptive_range_scan_delay; unsigned adaptive_range_change_delay;
unsigned adaptive_range_rescan_delay; unsigned adaptive_range_rescan_delay;
}; };

View File

@ -1863,13 +1863,15 @@ static char * appendStatsJson(char *p,
p = safe_snprintf(p, end, p = safe_snprintf(p, end,
",\"adaptive\":" ",\"adaptive\":"
"{\"gain_db\":%.1f" "{\"gain_db\":%.1f"
",\"gain_reduced_seconds\":%u" ",\"dynamic_range_limit_db\":%.1f"
",\"gain_changes\":%u"
",\"loud_undecoded\":%u" ",\"loud_undecoded\":%u"
",\"loud_decoded\":%u" ",\"loud_decoded\":%u"
",\"noise_dbfs\":%.1f" ",\"noise_dbfs\":%.1f"
",\"gain_seconds\":[", ",\"gain_seconds\":[",
sdrGetGainDb(st->adaptive_gain), sdrGetGainDb(st->adaptive_gain),
st->adaptive_gain_reduced_seconds, sdrGetGainDb(st->adaptive_range_gain_limit),
st->adaptive_gain_changes,
st->adaptive_loud_undecoded, st->adaptive_loud_undecoded,
st->adaptive_loud_decoded, st->adaptive_loud_decoded,
st->adaptive_noise_dbfs); st->adaptive_noise_dbfs);

14
stats.c
View File

@ -120,11 +120,15 @@ void display_stats(struct stats *st) {
" %5u loud undecoded bursts\n" " %5u loud undecoded bursts\n"
" %5u loud decoded messages\n" " %5u loud decoded messages\n"
" %5.1f dBFS current noise floor\n" " %5.1f dBFS current noise floor\n"
" %5.1f dB current gain setting\n", " %5.1f dB current gain setting\n"
" %5.1f dB current dynamic range gain upper limit\n"
" %5u gain changes caused by adaptive gain control\n",
st->adaptive_loud_undecoded, st->adaptive_loud_undecoded,
st->adaptive_loud_decoded, st->adaptive_loud_decoded,
st->adaptive_noise_dbfs, st->adaptive_noise_dbfs,
sdrGetGainDb(st->adaptive_gain)); sdrGetGainDb(st->adaptive_gain),
sdrGetGainDb(st->adaptive_range_gain_limit),
st->adaptive_gain_changes);
uint32_t total_seconds = 0; uint32_t total_seconds = 0;
for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i)
@ -140,9 +144,6 @@ void display_stats(struct stats *st) {
} }
} }
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"); printf(" Gain histogram:\n");
for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) { for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) {
unsigned seconds = st->adaptive_gain_seconds[i]; unsigned seconds = st->adaptive_gain_seconds[i];
@ -408,8 +409,9 @@ void add_stats(const struct stats *st1, const struct stats *st2, struct stats *t
target->adaptive_gain = adaptive_best->adaptive_gain; target->adaptive_gain = adaptive_best->adaptive_gain;
for (unsigned i = 0; i < STATS_GAIN_COUNT; ++i) 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_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_undecoded = st1->adaptive_loud_undecoded + st2->adaptive_loud_undecoded;
target->adaptive_loud_decoded = st1->adaptive_loud_decoded + st2->adaptive_loud_decoded; target->adaptive_loud_decoded = st1->adaptive_loud_decoded + st2->adaptive_loud_decoded;
target->adaptive_gain_changes = st1->adaptive_gain_changes + st2->adaptive_gain_changes;
target->adaptive_noise_dbfs = adaptive_best->adaptive_noise_dbfs; target->adaptive_noise_dbfs = adaptive_best->adaptive_noise_dbfs;
target->adaptive_range_gain_limit = adaptive_best->adaptive_range_gain_limit;
} }

View File

@ -135,10 +135,11 @@ struct stats {
bool adaptive_valid; // is the following data valid? bool adaptive_valid; // is the following data valid?
int adaptive_gain; // Current gain step in use int 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_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_undecoded; // Total number of loud, undecoded bursts
uint32_t adaptive_loud_decoded; // Total number of loud, decoded messages uint32_t adaptive_loud_decoded; // Total number of loud, decoded messages
uint32_t adaptive_gain_changes; // Total number of gain changes caused by adaptive gain control
double adaptive_noise_dbfs; // Current adaptive-dynamic-range smoothed noise measurement, dBFS double adaptive_noise_dbfs; // Current adaptive-dynamic-range smoothed noise measurement, dBFS
int adaptive_range_gain_limit; // Current adaptive-dynamic-range gain step limit
}; };
void add_stats(const struct stats *st1, const struct stats *st2, struct stats *target); void add_stats(const struct stats *st1, const struct stats *st2, struct stats *target);