mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 10:07:26 -05:00
* Adopts same architecture and symbol processor as P1 C4FM/LSM, DMR and NXDN. * Soft symbol sync detection, processing and erro correction. * Scalar and vector soft sync detection and calibration * Improved time slot detection/prediction and ISCH processing. * Fixes bug in parsing sequential mac structures in SACCH * Vector calibration(s) cleanup across multiple calibrations * Error correction bug in ISCH * Renames differential demod shared with P1 LSM decoder. Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests the Reed-Solomon errors-and-erasures decoder against the P25 RS(63,35,29) code.
|
||||
*/
|
||||
public class ReedSolomonErasureDecoderTest
|
||||
{
|
||||
private static final int N = 63;
|
||||
private static final int K = 35;
|
||||
private static final int PARITY = N - K;
|
||||
private int[] randomCodeword(Random random)
|
||||
{
|
||||
int[] message = new int[K];
|
||||
|
||||
for(int i = 0; i < K; i++)
|
||||
{
|
||||
message[i] = random.nextInt(64);
|
||||
}
|
||||
|
||||
return ReedSolomonTestEncoder.encode(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrupts the codeword with the specified number of errors and erasures at distinct random positions.
|
||||
* @return erasure positions
|
||||
*/
|
||||
private int[] corrupt(int[] codeword, int errors, int erasures, Random random)
|
||||
{
|
||||
int[] positions = new int[N];
|
||||
|
||||
for(int i = 0; i < N; i++)
|
||||
{
|
||||
positions[i] = i;
|
||||
}
|
||||
|
||||
for(int i = N - 1; i > 0; i--)
|
||||
{
|
||||
int j = random.nextInt(i + 1);
|
||||
int t = positions[i];
|
||||
positions[i] = positions[j];
|
||||
positions[j] = t;
|
||||
}
|
||||
|
||||
for(int i = 0; i < errors; i++)
|
||||
{
|
||||
codeword[positions[i]] ^= 1 + random.nextInt(63);
|
||||
}
|
||||
|
||||
int[] erased = Arrays.copyOfRange(positions, errors, errors + erasures);
|
||||
|
||||
for(int position : erased)
|
||||
{
|
||||
codeword[position] = random.nextInt(64);
|
||||
}
|
||||
|
||||
return erased;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encoderProducesValidCodewords()
|
||||
{
|
||||
Random random = new Random(1);
|
||||
BerlekempMassey reference = new ReedSolomon_63_35_29_P25();
|
||||
|
||||
for(int trial = 0; trial < 100; trial++)
|
||||
{
|
||||
int[] codeword = randomCodeword(random);
|
||||
int[] output = new int[N];
|
||||
assertFalse(reference.decode(codeword, output));
|
||||
assertArrayEquals(codeword, output);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctsErrorsAndErasuresWithinCapacity()
|
||||
{
|
||||
Random random = new Random(2);
|
||||
ReedSolomonErasureDecoder decoder = ReedSolomonErasureDecoder.p25_63_35_29();
|
||||
|
||||
for(int erasures = 0; erasures <= PARITY; erasures++)
|
||||
{
|
||||
int maxErrors = (PARITY - erasures) / 2;
|
||||
|
||||
for(int errors = 0; errors <= maxErrors; errors++)
|
||||
{
|
||||
for(int trial = 0; trial < 20; trial++)
|
||||
{
|
||||
int[] codeword = randomCodeword(random);
|
||||
int[] received = codeword.clone();
|
||||
int[] erased = corrupt(received, errors, erasures, random);
|
||||
int[] output = new int[N];
|
||||
assertFalse(decoder.decode(received, output, erased, erased.length),
|
||||
"Failed with errors [" + errors + "] erasures [" + erasures + "]");
|
||||
assertArrayEquals(codeword, output, "Errors [" + errors + "] erasures [" + erasures + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsMostDecodingFailuresBeyondCapacity()
|
||||
{
|
||||
Random random = new Random(3);
|
||||
ReedSolomonErasureDecoder decoder = ReedSolomonErasureDecoder.p25_63_35_29();
|
||||
int miscorrections = 0;
|
||||
int trials = 1000;
|
||||
|
||||
for(int trial = 0; trial < trials; trial++)
|
||||
{
|
||||
int[] codeword = randomCodeword(random);
|
||||
int[] received = codeword.clone();
|
||||
int erasures = 6;
|
||||
int errors = (PARITY - erasures) / 2 + 3;
|
||||
int[] erased = corrupt(received, errors, erasures, random);
|
||||
int[] output = new int[N];
|
||||
|
||||
if(!decoder.decode(received, output, erased, erased.length))
|
||||
{
|
||||
//A miscorrection must still produce a valid (but incorrect) codeword
|
||||
assertFalse(Arrays.equals(codeword, output));
|
||||
int[] check = new int[N];
|
||||
assertFalse(new ReedSolomon_63_35_29_P25().decode(output, check));
|
||||
miscorrections++;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(miscorrections < trials / 100, "Miscorrections: " + miscorrections);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesErrorsOnlyDecoder()
|
||||
{
|
||||
Random random = new Random(4);
|
||||
ReedSolomonErasureDecoder decoder = ReedSolomonErasureDecoder.p25_63_35_29();
|
||||
BerlekempMassey reference = new ReedSolomon_63_35_29_P25();
|
||||
|
||||
for(int trial = 0; trial < 500; trial++)
|
||||
{
|
||||
int[] received = randomCodeword(random);
|
||||
corrupt(received, random.nextInt(18), 0, random);
|
||||
int[] output = new int[N];
|
||||
int[] referenceOutput = new int[N];
|
||||
boolean failed = decoder.decode(received, output, new int[0], 0);
|
||||
boolean referenceFailed = reference.decode(received, referenceOutput);
|
||||
assertEquals(referenceFailed, failed);
|
||||
|
||||
if(!failed)
|
||||
{
|
||||
assertArrayEquals(referenceOutput, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
/**
|
||||
* Systematic encoder for the P25 RS(63,35,29) code (and codes shortened from it) for unit tests. Parity symbols are
|
||||
* at indices 0-27 and message symbols at indices 28-62, with generator polynomial roots alpha^1 through alpha^28.
|
||||
*/
|
||||
public class ReedSolomonTestEncoder
|
||||
{
|
||||
private static final int N = 63;
|
||||
private static final int K = 35;
|
||||
private static final int PARITY = N - K;
|
||||
private static final int[] EXP = new int[63];
|
||||
private static final int[] LOG = new int[64];
|
||||
|
||||
static
|
||||
{
|
||||
int value = 1;
|
||||
|
||||
for(int i = 0; i < 63; i++)
|
||||
{
|
||||
EXP[i] = value;
|
||||
LOG[value] = i;
|
||||
value <<= 1;
|
||||
|
||||
if(value > 63)
|
||||
{
|
||||
value = (value & 63) ^ 3; //x^6 = x + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int multiply(int a, int b)
|
||||
{
|
||||
return (a == 0 || b == 0) ? 0 : EXP[(LOG[a] + LOG[b]) % 63];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the message symbols.
|
||||
* @param message symbols (35), where trailing zero symbols represent a shortened code
|
||||
* @return codeword (63 symbols)
|
||||
*/
|
||||
public static int[] encode(int[] message)
|
||||
{
|
||||
int[] generator = new int[PARITY + 1];
|
||||
generator[0] = 1;
|
||||
|
||||
for(int i = 1; i <= PARITY; i++)
|
||||
{
|
||||
int root = EXP[i % 63];
|
||||
|
||||
for(int j = i; j > 0; j--)
|
||||
{
|
||||
generator[j] = generator[j - 1] ^ multiply(generator[j], root);
|
||||
}
|
||||
|
||||
generator[0] = multiply(generator[0], root);
|
||||
}
|
||||
|
||||
//Remainder of x^28 * m(x) divided by g(x)
|
||||
int[] remainder = new int[N];
|
||||
System.arraycopy(message, 0, remainder, PARITY, K);
|
||||
|
||||
for(int i = N - 1; i >= PARITY; i--)
|
||||
{
|
||||
int coefficient = remainder[i];
|
||||
|
||||
if(coefficient != 0)
|
||||
{
|
||||
for(int j = 0; j <= PARITY; j++)
|
||||
{
|
||||
remainder[i - PARITY + j] ^= multiply(generator[j], coefficient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[] codeword = new int[N];
|
||||
System.arraycopy(remainder, 0, codeword, 0, PARITY);
|
||||
System.arraycopy(message, 0, codeword, PARITY, K);
|
||||
return codeword;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user