From be3c9930f1e9242a301b28cd4c5f14ad248bc477 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 15 Jul 2021 16:44:26 +0800 Subject: [PATCH] Add --adaptive-duty-cycle option. This makes the adaptive gain code only process some fraction of all samples, rather than every sample. This helps a lot with CPU on slower machines. We default to a 50% duty cycle to save some CPU without really affecting the behavior much. We split the one-second adaptive gain blocks into 20* 50ms "subblocks" Each subblock is either entirely processed or not processed at all, based on the configured duty cycle. (This means that the duty cycle can only be specified in approx 5% increments, but that should be fine) For adaptive dynamic range, there's no further complications - we just look at fewer samples to compute the noise floor. For adaptive burst, we need to scale up the measured burst rate by 1/duty cycle to account for the samples that we didn't process. It's also more likely to cause functional changes as it's quite possible for a relatively small number of loud messages being missed to affect the computed rate substantially. Not many options there other than increasing the duty cycle when adaptive burst handling is wanted. --- adaptive.c | 133 +++++++++++++++++++++++++++++++++++++++-------------- dump1090.c | 5 ++ dump1090.h | 2 + 3 files changed, 106 insertions(+), 34 deletions(-) diff --git a/adaptive.c b/adaptive.c index 1a5a4d0..15ce920 100644 --- a/adaptive.c +++ b/adaptive.c @@ -33,13 +33,44 @@ static float adaptive_gain_down_db; // // block handling // +// 1 block = approx 1 second of samples. Control updates are done at the end of each block only. +// Each block is made up of an integer number of subblocks (currently 20) +// +// 1 subblock = approx 50ms of samples. Duty cycle decisions are made at the subblock level; +// either the whole subblock is processed, or the whole subblock is skipped. +// Each subblock is made up of an integer number of windows (currently 1250) +// +// 1 window = approx 40us of samples. Burst measurements are made by counting samples within each window. +// +// All three levels are aligned, i.e. every block boundary is also a subblock boundary; +// every subblock boundary is also a window boundary. -static unsigned adaptive_block_remaining; // samples in each block -static unsigned adaptive_block_size; // samples remaining in the current block + +static const unsigned adaptive_subblocks_per_block = 20; // subblocks per block +static unsigned adaptive_subblocks_remaining; // subblocks remaining in the current block + +// Duty cycle is expressed as N/D +// where N = adaptive_subblbock_dutycycle_N = adaptive_subblocks_per_block * Modes.adaptive_duty_cycle +// and D = adaptive_subblocks_dutycycle_D = adaptive_subblocks_per_block +// +// i.e. within each block, there are exactly N active subblocks out of D total subblocks +// +// The active subblocks are distributed evenly across the block by increasing a counter by N on each +// subblock, modulo D, and marking the subblock as active each time the counter rolls over. + +static unsigned adaptive_subblock_dutycycle_N; // subblock duty cycle numerator N +static const unsigned adaptive_subblock_dutycycle_D = adaptive_subblocks_per_block; // subblock duty cycle denominator D +static unsigned adaptive_subblock_dutycycle_counter; // subblock duty cycle counter (modulo D) +static bool adaptive_subblock_active; // is the current subblock active i.e. samples should be processed, not skipped? + +static unsigned adaptive_samples_per_subblock; // samples per subblock +static unsigned adaptive_subblock_samples_remaining; // samples remaining in the current subblock + +static unsigned adaptive_samples_per_window; // samples per window 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_update_subblock(uint16_t *buf, unsigned length, struct modesMessage *decoded); static void adaptive_end_of_block(); static void adaptive_control_update(); @@ -47,7 +78,6 @@ static void adaptive_control_update(); // burst handling // -static unsigned adaptive_burst_window_size; // samples in each burst window static unsigned adaptive_burst_window_remaining; // samples remaining in the current burst window static unsigned adaptive_burst_window_counter; // loud samples seen in current burst window static unsigned adaptive_burst_runlength; // consecutive loud burst windows seen @@ -140,14 +170,27 @@ void adaptive_init() if (!Modes.adaptive_burst_control && !Modes.adaptive_range_control) return; + // Set up window, subblock, and block sizes // 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_samples_per_window = Modes.sample_rate / 25000; - // 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; + // Use ~50ms subblocks; ensure it's an exact multiple of window size + adaptive_samples_per_subblock = adaptive_samples_per_window * 1250; + + adaptive_subblocks_remaining = adaptive_subblocks_per_block; + adaptive_subblock_samples_remaining = adaptive_samples_per_subblock; + adaptive_subblock_active = false; + + float N = roundf(adaptive_subblock_dutycycle_D * Modes.adaptive_duty_cycle); + if (N <= 0 || N > adaptive_subblock_dutycycle_D) { + fprintf(stderr, "warning: --adaptive-duty-cycle value %.1f%% is out of range, using 100%% instead\n", + Modes.adaptive_duty_cycle * 100.0); + N = adaptive_subblock_dutycycle_D; + } + adaptive_subblock_dutycycle_N = (unsigned)N; + + adaptive_burst_window_remaining = adaptive_samples_per_window; + adaptive_burst_window_counter = 0; adaptive_range_radix = calloc(sizeof(unsigned), 65536); adaptive_range_state = RANGE_SCAN_UP; @@ -180,26 +223,44 @@ void adaptive_update(uint16_t *buf, unsigned length, struct modesMessage *decode { 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 complete subblocks + while (length >= adaptive_subblock_samples_remaining) { + if (adaptive_subblock_active) + adaptive_update_subblock(buf, adaptive_subblock_samples_remaining, decoded); + + buf += adaptive_subblock_samples_remaining; + length -= adaptive_subblock_samples_remaining; + adaptive_subblock_samples_remaining = adaptive_samples_per_subblock; + + adaptive_subblock_dutycycle_counter += adaptive_subblock_dutycycle_N; + if (adaptive_subblock_dutycycle_counter >= adaptive_subblock_dutycycle_D) { + adaptive_subblock_dutycycle_counter -= adaptive_subblock_dutycycle_D; + adaptive_subblock_active = true; + } else { + adaptive_subblock_active = false; + // fake a quiet window to reset any existing run + adaptive_burst_end_of_window(0); + } + + if (!--adaptive_subblocks_remaining) { + // Block completed, do a control update + adaptive_subblocks_remaining = adaptive_subblocks_per_block; + adaptive_end_of_block(); + } } - // process final samples that don't complete a block + // process final samples that don't complete a subblock if (length > 0) { - adaptive_update_single(buf, length, decoded); - adaptive_block_remaining -= length; + if (adaptive_subblock_active) + adaptive_update_subblock(buf, length, decoded); + adaptive_subblock_samples_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) +// Feed some samples into the adaptive system. The samples are guaranteed to not cross a subblock boundary. +// The samples should be processsed (i.e. duty cycle is in the active part) +static void adaptive_update_subblock(uint16_t *buf, unsigned length, struct modesMessage *decoded) { if (decoded) { if (/* decoded->msgbits == 112 && */ decoded->signalLevel >= adaptive_burst_loud_threshold) @@ -229,8 +290,8 @@ static void adaptive_burst_skip(unsigned length) length -= adaptive_burst_window_remaining; // skip remaining windows, dispatch them - unsigned windows = length / adaptive_burst_window_size; - unsigned samples = windows * adaptive_burst_window_size; + unsigned windows = length / adaptive_samples_per_window; + unsigned samples = windows * adaptive_samples_per_window; while (windows--) adaptive_burst_end_of_window(0); @@ -238,7 +299,7 @@ static void adaptive_burst_skip(unsigned length) // final partial window adaptive_burst_window_counter = 0; - adaptive_burst_window_remaining = adaptive_burst_window_size - length; + adaptive_burst_window_remaining = adaptive_samples_per_window - length; } // Burst measurement: process 'length' samples from 'buf', look for loud bursts; @@ -265,15 +326,15 @@ static void adaptive_burst_update(uint16_t *buf, unsigned length) length -= n; // remaining windows - unsigned windows = length / adaptive_burst_window_size; - unsigned samples = windows * adaptive_burst_window_size; + unsigned windows = length / adaptive_samples_per_window; + unsigned samples = windows * adaptive_samples_per_window; 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; + adaptive_burst_window_remaining = adaptive_samples_per_window - length; } // Burst measurement: process 'windows' complete burst windows starting at 'buf'; @@ -281,8 +342,8 @@ static void adaptive_burst_update(uint16_t *buf, unsigned length) 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; + unsigned counter = adaptive_burst_count_samples(buf, adaptive_samples_per_window); + buf += adaptive_samples_per_window; adaptive_burst_end_of_window(counter); } } @@ -301,7 +362,7 @@ static inline unsigned adaptive_burst_count_samples(uint16_t *buf, unsigned n) // loud samples seen, handle that window. static void adaptive_burst_end_of_window(unsigned counter) { - if (counter > adaptive_burst_window_size / 4) { + if (counter > adaptive_samples_per_window / 4) { // This window is loud, extend any existing run of loud windows ++adaptive_burst_runlength; } else { @@ -365,14 +426,18 @@ static void adaptive_burst_end_of_block() if (!Modes.adaptive_burst_control) return; + // scale rates based on the actual duty cycle fraction + // (e.g. if we are only inspecting 2/5 of samples, then scale the rate by 5/2) + double scale = (double)adaptive_subblock_dutycycle_D / adaptive_subblock_dutycycle_N; + // maintain an EMA of the number of undecoded loud bursts seen per block Modes.stats_current.adaptive_loud_undecoded += adaptive_burst_block_loud_undecoded; - 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_loud_undecoded_smoothed = adaptive_burst_loud_undecoded_smoothed * (1 - Modes.adaptive_burst_alpha) + scale * adaptive_burst_block_loud_undecoded * Modes.adaptive_burst_alpha; adaptive_burst_block_loud_undecoded = 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_decoded; - 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_loud_decoded_smoothed = adaptive_burst_loud_decoded_smoothed * (1 - Modes.adaptive_burst_alpha) + scale * adaptive_burst_block_loud_decoded * Modes.adaptive_burst_alpha; adaptive_burst_block_loud_decoded = 0; } diff --git a/dump1090.c b/dump1090.c index 611abf1..ac84ff5 100644 --- a/dump1090.c +++ b/dump1090.c @@ -126,6 +126,8 @@ static void modesInitConfig(void) { Modes.adaptive_min_gain_db = 0; Modes.adaptive_max_gain_db = 99999; + Modes.adaptive_duty_cycle = 0.5; + Modes.adaptive_burst_control = false; Modes.adaptive_burst_alpha = 2.0 / (5 + 1); Modes.adaptive_burst_change_delay = 5; @@ -372,6 +374,7 @@ static void showHelp(void) " gain scanning (seconds)\n" "--adaptive-min-gain Set gain adjustment range lower limit (dB)\n" "--adaptive-max-gain Set gain adjustment range upper limit (dB)\n" +"--adaptive-duty-cycle

Set adaptive gain duty cycle %% (1..100)\n" "\n" // ------ 80 char limit ----------------------------------------------------------| " Network connections\n" @@ -764,6 +767,8 @@ int main(int argc, char **argv) { 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-duty-cycle") && more) { + Modes.adaptive_duty_cycle = atof(argv[++j]) / 100.0; } else if (!strcmp(argv[j], "--adaptive-burst")) { Modes.adaptive_burst_control = true; } else if (!strcmp(argv[j], "--adaptive-burst-alpha") && more) { diff --git a/dump1090.h b/dump1090.h index c0eb1bc..8bf6918 100644 --- a/dump1090.h +++ b/dump1090.h @@ -394,6 +394,8 @@ struct _Modes { // Internal state float adaptive_min_gain_db; float adaptive_max_gain_db; + float adaptive_duty_cycle; + bool adaptive_burst_control; float adaptive_burst_alpha; unsigned adaptive_burst_change_delay;