#define _USE_MATH_DEFINES #include #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; icount = 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; kvalues[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; }