mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-08 09:36:02 -05:00
387 lines
12 KiB
C
387 lines
12 KiB
C
|
|
||
|
#define _USE_MATH_DEFINES
|
||
|
#include <math.h>
|
||
|
|
||
|
#include "dsd.h"
|
||
|
|
||
|
#include "p25p1_heuristics.h"
|
||
|
|
||
|
/**
|
||
|
* This module is dedicated to improve the accuracy of the digitizer. The digitizer is the piece of code that
|
||
|
* translates an analog value to an actual symbol, in the case of P25, a dibit.
|
||
|
* It implements a simple Gaussian classifier. It's based in the assumption that the analog values from the
|
||
|
* signal follow normal distributions, one single distribution for each symbol.
|
||
|
* Analog values for the dibit "0" will fit into a Gaussian bell curve with a characteristic mean and std
|
||
|
* distribution and the same goes for dibits "1", "2" and "3."
|
||
|
* Hopefully those bell curves are well separated from each other so we can accurately discriminate dibits.
|
||
|
* If we could model the Gaussian of each dibit, then given an analog value, the dibit whose Gaussian fits
|
||
|
* better is the most likely interpretation for that value. By better fit we can calculate the PDF
|
||
|
* (probability density function) for the Gaussian, the one with the highest value is the best fit.
|
||
|
*
|
||
|
* The approach followed here to model the Gaussian for each dibit is to use the error corrected information
|
||
|
* as precise oracles. P25 uses strong error correction, some dibits are doubly protected by Hamming/Golay
|
||
|
* and powerful Reed-Solomon codes. If a sequence of dibits clears the last Reed-Solomon check, we can be
|
||
|
* quite confident that those values are correct. We can use the analog values for those cleared dibits to
|
||
|
* calculate mean and std deviation of our Gaussians. With this we are ready to calculate the PDF of a new
|
||
|
* unknown analog value when required.
|
||
|
* Values that don't clear the Reed-Solomon check are discarded.
|
||
|
* This implementation uses a circular buffer to keep track of the N latest cleared analog dibits so we can
|
||
|
* adapt to changes in the signal.
|
||
|
* A modification was made to improve results for C4FM signals. See next block comment.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* In the C4FM P25 recorded files from the "samples" repository, it can be observed that there is a
|
||
|
* correlation between the correct dibit associated for a given analog value and the value of the previous
|
||
|
* dibit. For instance, in one P25 recording, the dibits "0" come with an average analog signal of
|
||
|
* 3829 when the previous dibit was also "0," but if the previous dibit was a "3" then the average
|
||
|
* analog signal is 6875. These are the mean and std deviations for the full 4x4 combinations of previous and
|
||
|
* current dibits:
|
||
|
*
|
||
|
* 00: count: 200 mean: 3829.12 sd: 540.43 <-
|
||
|
* 01: count: 200 mean: 13352.45 sd: 659.74
|
||
|
* 02: count: 200 mean: -5238.56 sd: 1254.70
|
||
|
* 03: count: 200 mean: -13776.50 sd: 307.41
|
||
|
* 10: count: 200 mean: 3077.74 sd: 1059.00
|
||
|
* 11: count: 200 mean: 11935.11 sd: 776.20
|
||
|
* 12: count: 200 mean: -6079.46 sd: 1003.94
|
||
|
* 13: count: 200 mean: -13845.43 sd: 264.42
|
||
|
* 20: count: 200 mean: 5574.33 sd: 1414.71
|
||
|
* 21: count: 200 mean: 13687.75 sd: 727.68
|
||
|
* 22: count: 200 mean: -4753.38 sd: 765.95
|
||
|
* 23: count: 200 mean: -12342.17 sd: 1372.77
|
||
|
* 30: count: 200 mean: 6875.23 sd: 1837.38 <-
|
||
|
* 31: count: 200 mean: 14527.99 sd: 406.85
|
||
|
* 32: count: 200 mean: -3317.61 sd: 1089.02
|
||
|
* 33: count: 200 mean: -12576.08 sd: 1161.77
|
||
|
* || | | |
|
||
|
* || | | \_std deviation
|
||
|
* || | |
|
||
|
* || | \_mean of the current dibit
|
||
|
* || |
|
||
|
* || \_number of dibits used to calculate mean and std deviation
|
||
|
* ||
|
||
|
* |\_current dibit
|
||
|
* |
|
||
|
* \_previous dibit
|
||
|
*
|
||
|
* This effect is not observed on QPSK or GFSK signals, there the mean values are quite consistent regardless
|
||
|
* of the previous dibit.
|
||
|
*
|
||
|
* The following define enables taking the previous dibit into account for C4FM signals. Comment out
|
||
|
* to disable.
|
||
|
*/
|
||
|
#define USE_PREVIOUS_DIBIT
|
||
|
|
||
|
/**
|
||
|
* There is a minimum of cleared analog values we need to produce a meaningful mean and std deviation.
|
||
|
*/
|
||
|
#define MIN_ELEMENTS_FOR_HEURISTICS 10
|
||
|
|
||
|
|
||
|
//Uncomment to disable the behaviour of this module.
|
||
|
//#define DISABLE_HEURISTICS
|
||
|
|
||
|
/**
|
||
|
* The value of the previous dibit is only taken into account on the C4FM modulation. QPSK and GFSK are
|
||
|
* not improved by this technique.
|
||
|
*/
|
||
|
static int use_previous_dibit(int rf_mod)
|
||
|
{
|
||
|
// 0: C4FM modulation
|
||
|
// 1: QPSK modulation
|
||
|
// 2: GFSK modulation
|
||
|
|
||
|
// Use previoud dibit information when on C4FM
|
||
|
return (rf_mod == 0)? 1 : 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the model of a symbol's Gaussian with new information.
|
||
|
* \param heuristics Pointer to the P25Heuristics module with all the needed state information.
|
||
|
* \param previous_dibit The cleared previous dibit value.
|
||
|
* \param original_dibit The current dibit as it was interpreted initially.
|
||
|
* \param dibit The current dibit. Will be different from original_dibit if the FEC fixed it.
|
||
|
* \param analog_value The actual analog signal value from which the original_dibit was derived.
|
||
|
*/
|
||
|
static void update_p25_heuristics(P25Heuristics* heuristics, int previous_dibit, int original_dibit, int dibit, int analog_value)
|
||
|
{
|
||
|
float mean;
|
||
|
int old_value;
|
||
|
float old_mean;
|
||
|
|
||
|
SymbolHeuristics* sh;
|
||
|
int number_errors;
|
||
|
|
||
|
#ifndef USE_PREVIOUS_DIBIT
|
||
|
previous_dibit = 0;
|
||
|
#endif
|
||
|
|
||
|
// Locate the Gaussian (SymbolHeuristics structure) we are going to update
|
||
|
sh = &(heuristics->symbols[previous_dibit][dibit]);
|
||
|
|
||
|
// Update the circular buffers of values
|
||
|
old_value = sh->values[sh->index];
|
||
|
old_mean = sh->means[sh->index];
|
||
|
|
||
|
// Update the BER statistics
|
||
|
number_errors = 0;
|
||
|
if (original_dibit != dibit) {
|
||
|
if ((original_dibit == 0 && dibit == 3) || (original_dibit == 3 && dibit == 0) ||
|
||
|
(original_dibit == 1 && dibit == 2) || (original_dibit == 2 && dibit == 1)) {
|
||
|
// Interpreting a "00" as "11", "11" as "00", "01" as "10" or "10" as "01" counts as 2 errors
|
||
|
number_errors = 2;
|
||
|
} else {
|
||
|
// The other 8 combinations count (where original_dibit != dibit) as 1 error.
|
||
|
number_errors = 1;
|
||
|
}
|
||
|
}
|
||
|
update_error_stats(heuristics, 2, number_errors);
|
||
|
|
||
|
// Update the running mean and variance. This is to calculate the PDF faster when required
|
||
|
if (sh->count >= HEURISTICS_SIZE) {
|
||
|
sh->sum -= old_value;
|
||
|
sh->var_sum -= (((float)old_value) - old_mean) * (((float)old_value) - old_mean);
|
||
|
}
|
||
|
sh->sum += analog_value;
|
||
|
|
||
|
sh->values[sh->index] = analog_value;
|
||
|
if (sh->count < HEURISTICS_SIZE) {
|
||
|
sh->count++;
|
||
|
}
|
||
|
mean = sh->sum / ((float)sh->count);
|
||
|
sh->means[sh->index] = mean;
|
||
|
if (sh->index >= (HEURISTICS_SIZE-1)) {
|
||
|
sh->index = 0;
|
||
|
} else {
|
||
|
sh->index++;
|
||
|
}
|
||
|
|
||
|
sh->var_sum += (((float)analog_value) - mean) * (((float)analog_value) - mean);
|
||
|
}
|
||
|
|
||
|
void contribute_to_heuristics(int rf_mod, P25Heuristics* heuristics, AnalogSignal* analog_signal_array, int count)
|
||
|
{
|
||
|
int i;
|
||
|
int use_prev_dibit;
|
||
|
|
||
|
#ifdef USE_PREVIOUS_DIBIT
|
||
|
use_prev_dibit = use_previous_dibit(rf_mod);
|
||
|
#else
|
||
|
use_prev_dibit = 0;
|
||
|
#endif
|
||
|
|
||
|
for (i=0; i<count; i++) {
|
||
|
int use;
|
||
|
int prev_dibit;
|
||
|
|
||
|
if (use_prev_dibit) {
|
||
|
if (analog_signal_array[i].sequence_broken) {
|
||
|
// The sequence of dibits was broken here so we don't have reliable information on the actual
|
||
|
// value of the previous dibit. Don't use this value.
|
||
|
use = 0;
|
||
|
} else {
|
||
|
use = 1;
|
||
|
// The previous dibit is the corrected_dibit of the previous element
|
||
|
prev_dibit = analog_signal_array[i-1].corrected_dibit;
|
||
|
}
|
||
|
} else {
|
||
|
use = 1;
|
||
|
prev_dibit = 0;
|
||
|
}
|
||
|
|
||
|
if (use) {
|
||
|
update_p25_heuristics(heuristics, prev_dibit, analog_signal_array[i].dibit,
|
||
|
analog_signal_array[i].corrected_dibit, analog_signal_array[i].value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes the symbol's heuristics state.
|
||
|
* \param sh The SymbolHeuristics structure to initialize.
|
||
|
*/
|
||
|
static void initialize_symbol_heuristics(SymbolHeuristics* sh)
|
||
|
{
|
||
|
sh->count = 0;
|
||
|
sh->index = 0;
|
||
|
sh->sum = 0;
|
||
|
sh->var_sum = 0;
|
||
|
}
|
||
|
|
||
|
void initialize_p25_heuristics(P25Heuristics* heuristics)
|
||
|
{
|
||
|
int i, j;
|
||
|
for (i=0; i<4; i++) {
|
||
|
for (j=0; j<4; j++) {
|
||
|
initialize_symbol_heuristics(&(heuristics->symbols[i][j]));
|
||
|
}
|
||
|
}
|
||
|
heuristics->bit_count = 0;
|
||
|
heuristics->bit_error_count = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Important method to calculate the PDF (probability density function) of the Gaussian.
|
||
|
* TODO: improve performance. Since we are calculating this value to compare it with other PDF we can
|
||
|
* simplify very much. We don't really need to know the actual PDF value, just which Gaussian's got the
|
||
|
* highest PDF, which is a simpler problem.
|
||
|
*/
|
||
|
static float evaluate_pdf(SymbolHeuristics* se, int value)
|
||
|
{
|
||
|
float x = (se->count*((float)value) - se->sum);
|
||
|
float y = -0.5F*x*x/(se->count*se->var_sum);
|
||
|
float pdf = sqrtf(se->count / se->var_sum) * expf(y) / sqrtf(2.0F * ((float) M_PI));
|
||
|
|
||
|
return pdf;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Logging of the internal PDF values for a given analog value and previous dibit.
|
||
|
*/
|
||
|
static void debug_log_pdf(P25Heuristics* heuristics, int previous_dibit, int analog_value)
|
||
|
{
|
||
|
int i;
|
||
|
float pdfs[4];
|
||
|
|
||
|
for (i=0; i<4; i++) {
|
||
|
pdfs[i] = evaluate_pdf(&(heuristics->symbols[previous_dibit][i]), analog_value);
|
||
|
}
|
||
|
|
||
|
fprintf(stderr, "v: %i, (%e, %e, %e, %e)\n", analog_value, pdfs[0], pdfs[1], pdfs[2], pdfs[3]);
|
||
|
}
|
||
|
|
||
|
int estimate_symbol(int rf_mod, P25Heuristics* heuristics, int previous_dibit, int analog_value, int* dibit)
|
||
|
{
|
||
|
int valid;
|
||
|
int i;
|
||
|
float pdfs[4];
|
||
|
|
||
|
|
||
|
#ifdef USE_PREVIOUS_DIBIT
|
||
|
int use_prev_dibit = use_previous_dibit(rf_mod);
|
||
|
|
||
|
if (use_prev_dibit == 0)
|
||
|
{
|
||
|
// Ignore
|
||
|
previous_dibit = 0;
|
||
|
}
|
||
|
#else
|
||
|
// Use previous_dibit as it comes.
|
||
|
#endif
|
||
|
|
||
|
valid = 1;
|
||
|
|
||
|
// Check if we have enough values to model the Gaussians for each symbol involved.
|
||
|
for (i=0; i<4; i++) {
|
||
|
if (heuristics->symbols[previous_dibit][i].count >= MIN_ELEMENTS_FOR_HEURISTICS) {
|
||
|
pdfs[i] = evaluate_pdf(&(heuristics->symbols[previous_dibit][i]), analog_value);
|
||
|
} else {
|
||
|
// Not enough data, we don't trust this result
|
||
|
valid = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (valid) {
|
||
|
// Find the highest pdf
|
||
|
int max_index;
|
||
|
float max;
|
||
|
|
||
|
max_index = 0;
|
||
|
max = pdfs[0];
|
||
|
for (i=1; i<4; i++) {
|
||
|
if (pdfs[i] > max) {
|
||
|
max_index = i;
|
||
|
max = pdfs[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The symbol is the one with the highest pdf
|
||
|
*dibit = max_index;
|
||
|
}
|
||
|
|
||
|
#ifdef DISABLE_HEURISTICS
|
||
|
valid = 0;
|
||
|
#endif
|
||
|
|
||
|
return valid;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Logs the internal state of the heuristic's state. Good for debugging.
|
||
|
*/
|
||
|
static void debug_print_symbol_heuristics(int previous_dibit, int dibit, SymbolHeuristics* sh)
|
||
|
{
|
||
|
float mean, sd;
|
||
|
int k;
|
||
|
int n;
|
||
|
|
||
|
n = sh->count;
|
||
|
if (n == 0)
|
||
|
{
|
||
|
mean = 0;
|
||
|
sd = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mean = sh->sum/n;
|
||
|
sd = sqrtf(sh->var_sum / ((float) n));
|
||
|
}
|
||
|
fprintf(stderr, "%i%i: count: %2i mean: % 10.2f sd: % 10.2f", previous_dibit, dibit, sh->count, mean, sd);
|
||
|
/*
|
||
|
fprintf(stderr, "(");
|
||
|
for (k=0; k<n; k++)
|
||
|
{
|
||
|
if (k != 0)
|
||
|
{
|
||
|
fprintf(stderr, ", ");
|
||
|
}
|
||
|
fprintf(stderr, "%i", sh->values[k]);
|
||
|
}
|
||
|
fprintf(stderr, ")");
|
||
|
*/
|
||
|
fprintf(stderr, "\n");
|
||
|
|
||
|
}
|
||
|
|
||
|
void debug_print_heuristics(P25Heuristics* heuristics)
|
||
|
{
|
||
|
int i,j;
|
||
|
|
||
|
fprintf(stderr, "\n");
|
||
|
|
||
|
for(i=0; i<4; i++)
|
||
|
{
|
||
|
for(j=0; j<4; j++)
|
||
|
{
|
||
|
debug_print_symbol_heuristics(i, j, &(heuristics->symbols[i][j]));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void update_error_stats(P25Heuristics* heuristics, int bits, int errors)
|
||
|
{
|
||
|
heuristics->bit_count += bits;
|
||
|
heuristics->bit_error_count += errors;
|
||
|
|
||
|
// Normalize to avoid overflow in the counters
|
||
|
if ((heuristics->bit_count & 1) == 0 && (heuristics->bit_error_count & 1) == 0) {
|
||
|
// We can divide both values by 2 safely. We just care about their ratio, not the actual value
|
||
|
heuristics->bit_count >>= 1;
|
||
|
heuristics->bit_error_count >>= 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float get_P25_BER_estimate(P25Heuristics* heuristics)
|
||
|
{
|
||
|
float ber;
|
||
|
if (heuristics->bit_count == 0) {
|
||
|
ber = 0.0F;
|
||
|
} else {
|
||
|
ber = ((float)heuristics->bit_error_count) * 100.0F / ((float)heuristics->bit_count);
|
||
|
}
|
||
|
return ber;
|
||
|
}
|