#6 DMR Decoder (#947)

* #6 DMR decoder feature - work in progress

* #6 DMR Decoder.

* #6 DMR Decoder.  Code review updates.

Co-authored-by: Zhenyu Mao <maozhenyu@foxmail.com>
Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-10-12 04:57:59 -04:00
committed by GitHub
parent b061db1189
commit 7eec82deee
436 changed files with 36258 additions and 2409 deletions

View File

@@ -599,9 +599,9 @@ public class FilterFactory
* @param alpha - roll-off factor.
* @return - filter coefficients
*/
public static float[] getRootRaisedCosine(int samplesPerSymbol, int symbolCount, float alpha)
public static float[] getRootRaisedCosine(double samplesPerSymbol, int symbolCount, float alpha)
{
int taps = samplesPerSymbol * symbolCount;
int taps = (int)(samplesPerSymbol * symbolCount);
float scale = 0;

View File

@@ -22,8 +22,8 @@
package io.github.dsheirer.dsp.filter.design;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -83,41 +83,52 @@ public class FilterViewer extends Application
// .passBandRipple(0.01)
// .build();
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(50000.0)
.passBandCutoff(6500)
.passBandAmplitude(1.0)
.passBandRipple(0.005)
.stopBandAmplitude(0.0)
.stopBandStart(7200)
.stopBandRipple(0.01)
.build();
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
// .sampleRate(50000.0)
// .passBandCutoff(6500)
// .passBandAmplitude(1.0)
// .passBandRipple(0.005)
// .stopBandAmplitude(0.0)
// .stopBandStart(7200)
// .stopBandRipple(0.01)
// .build();
float[] taps = null;
// try
// {
// taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19);
// taps = FilterFactory.getSincFilter(25000.0, 12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
// }
// catch(FilterDesignException fde)
// {
// mLog.error("Error");
// }
boolean rrc = false;
try
if(rrc)
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
double sampleRate = 50000.0;
int symbolRate = 4800;
double samplesPerSymbol = sampleRate / symbolRate / 2;
float alpha = 0.25f;
int symbolCount = 20;
if(designer.isValid())
taps = FilterFactory.getRootRaisedCosine(samplesPerSymbol, symbolCount, alpha);
}
else
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(50000)
.passBandCutoff(4500)
.passBandAmplitude(1.0)
.passBandRipple(0.1)
.stopBandAmplitude(0.0)
.stopBandStart(7000)
.stopBandRipple(0.01)
.build();
try
{
taps = designer.getImpulseResponse();
taps = FilterFactory.getTaps(specification);//
}
catch(Exception fde) //FilterDesignException
{
mLog.error("Couldn't design low pass baseband filter for sample rate");
}
}
catch(FilterDesignException fde)
{
mLog.error("Filter design error", fde);
}
if(taps == null)
{

View File

@@ -0,0 +1,35 @@
package io.github.dsheirer.dsp.filter.design;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.stage.Stage;
public class FilterViewerLauncher
{
private JFXPanel mJFXPanel;
public FilterViewerLauncher()
{
mJFXPanel = new JFXPanel();
Platform.runLater(() ->
{
FilterViewer filterViewer = new FilterViewer();
Stage stage = new Stage();
try
{
filterViewer.start(stage);
}
catch(Exception e)
{
e.printStackTrace();
}
});
}
public static void main(String[] args)
{
FilterViewerLauncher filterViewerLauncher = new FilterViewerLauncher();
}
}

View File

@@ -24,22 +24,32 @@ package io.github.dsheirer.dsp.symbol;
public enum Dibit
{
D01_PLUS_3(false, true, 1, 4),
D00_PLUS_1(false, false, 0, 0),
D10_MINUS_1(true, false, 2, 8),
D11_MINUS_3(true, true, 3, 12);
D01_PLUS_3(false, true, 1, 4, 1),
D00_PLUS_1(false, false, 0, 0, 0),
D10_MINUS_1(true, false, 2, 8, 2),
D11_MINUS_3(true, true, 3, 12, 3);
private boolean mBit1;
private boolean mBit2;
private int mLowValue;
private int mHighValue;
private int mValue;
private Dibit(boolean bit1, boolean bit2, int lowValue, int highValue)
private Dibit(boolean bit1, boolean bit2, int lowValue, int highValue, int value)
{
mBit1 = bit1;
mBit2 = bit2;
mLowValue = lowValue;
mHighValue = highValue;
mValue = value;
}
/**
* Binary value of the symbol
*/
public int getValue()
{
return mValue;
}
/**

View File

@@ -0,0 +1,260 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.dsp.symbol;
import io.github.dsheirer.bits.BitSetFullException;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Circular buffer for storing and accessing dibits.
*/
public class DibitDelayBuffer
{
private final static Logger mLog = LoggerFactory.getLogger(DibitDelayBuffer.class);
protected Dibit[] mBuffer;
protected int mPointer;
/**
* Constructs a dibit delay buffer of the specified length
*/
public DibitDelayBuffer(int length)
{
Validate.isTrue(length > 0, "Dibit buffer length must be greater than 0");
mBuffer = new Dibit[length];
reset();
}
/**
* Resets the buffer and preloads the buffer with all zeros
*/
public void reset()
{
for(int x = 0; x < mBuffer.length; x++)
{
mBuffer[x] = Dibit.D00_PLUS_1;
}
}
/**
* Length of this circular buffer
*/
public int length()
{
return mBuffer.length;
}
/**
* Returns the dibit at the specified index
* @param index to get
* @return dibit at the specified index
*/
public Dibit get(int index)
{
if(index < 0 || index >= mBuffer.length)
{
throw new IllegalArgumentException("Index argument must be between 0 and buffer length [" + mBuffer.length + "]");
}
return mBuffer[index];
}
/**
* Sets the dibit at the specified index
* @param index of the dibit
* @param dibit to load at the specified index
*/
public void set(int index, Dibit dibit)
{
if(index < 0 || index >= mBuffer.length)
{
throw new IllegalArgumentException("Index argument must be between 0 and buffer length [" + mBuffer.length + "]");
}
mBuffer[index] = dibit;
}
/**
* Returns an ordered view of the internal circular buffer contents.
*/
public Dibit[] getBuffer()
{
Dibit[] transferBuffer = new Dibit[mBuffer.length];
int transferBufferPointer = 0;
int bufferPointer = mPointer;
while(transferBufferPointer < transferBuffer.length)
{
transferBuffer[transferBufferPointer++] = mBuffer[bufferPointer++];
if(bufferPointer >= mBuffer.length)
{
bufferPointer = 0;
}
}
return transferBuffer;
}
/**
* Retrieves a view of the buffer from the starting index to the specified length. Note: if the requested
* length exceeds the length of this buffer, the contents will be unwrapped from the circular buffer and repeated.
* @param start of the view where 0 is the first element.
* @param length of the dibit view
* @return an array of dibits
*/
public Dibit[] getBuffer(int start, int length)
{
Dibit[] transferBuffer = new Dibit[length];
int transferBufferPointer = 0;
int bufferPointer = (mPointer + start) % mBuffer.length;
while(transferBufferPointer < transferBuffer.length)
{
transferBuffer[transferBufferPointer++] = mBuffer[bufferPointer++];
if(bufferPointer >= mBuffer.length)
{
bufferPointer = 0;
}
}
return transferBuffer;
}
/**
* Extracts a corrected binary message from the dibit buffer
*
* @param start dibit index where 0 is the oldest dibit and the newest dibit is buffer length - 1
* @param dibitLength number of dibits to include in the message, making the message length = dibitLength * 2
* @return corrected binary message
*/
public CorrectedBinaryMessage getMessage(int start, int dibitLength)
{
CorrectedBinaryMessage message = new CorrectedBinaryMessage(dibitLength * 2);
int dibitCount = 0;
int bufferPointer = (mPointer + start) % mBuffer.length;
try
{
while(dibitCount < dibitLength)
{
Dibit dibit = mBuffer[bufferPointer++];
message.add(dibit.getBit1());
message.add(dibit.getBit2());
dibitCount++;
if(bufferPointer >= mBuffer.length)
{
bufferPointer = 0;
}
}
}
catch(BitSetFullException e)
{
//This shouldn't happen
mLog.error("Bit set full ??", e);
}
return message;
}
/**
* Utility method to view the buffer contents with the dibits decoded as an array of zeros and ones.
*/
public int[] getBufferAsArray()
{
Dibit[] dibits = getBuffer();
int[] bits = new int[dibits.length * 2];
for(int x = 0; x < dibits.length; x++)
{
if(dibits[x].getBit1())
{
bits[x * 2] = 1;
}
if(dibits[x].getBit2())
{
bits[x * 2 + 1] = 1;
}
}
return bits;
}
/**
* Places the dibit into the internal circular buffer, overwriting the oldest dibit.
*/
public void put(Dibit dibit)
{
mBuffer[mPointer++] = dibit;
if(mPointer >= mBuffer.length)
{
mPointer = 0;
}
}
/**
* Places the dibit into the internal circular buffer, overwriting and returning the
* oldest dibit.
*/
public Dibit getAndPut(Dibit dibit)
{
Dibit toReturn = mBuffer[mPointer];
put(dibit);
return toReturn;
}
/**
* Logs the contents of the buffer
*/
public void log()
{
StringBuilder sb = new StringBuilder();
int counter = 0;
int pointer = mPointer;
while(counter < mBuffer.length)
{
sb.append(mBuffer[pointer].getBit1() ? "1" : "0");
sb.append(mBuffer[pointer++].getBit2() ? "1" : "0");
if(pointer >= mBuffer.length)
{
pointer = 0;
}
counter++;
}
mLog.debug("BUFFER: " + sb + " Length:" + mBuffer.length);
}
}

View File

@@ -0,0 +1,120 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dsp.symbol;
/**
* QPSK carrier lock elements that indicate if a PLL is locked normally, or if it has locked at +/- 90 degrees,
* or inverted 180 degrees. Abnormal PLL lock is normally detected by rotated sync patterns.
*
* Each enumeration entry provides a correct(dibit) method that supports correcting dibits that were captured with a
* misaligned PLL lock to what they should be if the PLL were locked correctly. This allows detected (yet misaligned)
* messages to become usable through dibit correction even though the PLL was misaligned.
*/
public enum QPSKCarrierLock
{
/**
* Normal PLL lock indication
*/
NORMAL
{
@Override
public Dibit correct(Dibit dibit)
{
//No correction needed
return dibit;
}
},
/**
* PLL lock at plus 90 degrees to the carrier, requiring a PLL adjustment of -90 degrees or -PI/2.
*/
PLUS_90
{
@Override
public Dibit correct(Dibit dibit)
{
switch(dibit)
{
case D00_PLUS_1:
return Dibit.D10_MINUS_1;
case D01_PLUS_3:
return Dibit.D00_PLUS_1;
case D10_MINUS_1:
return Dibit.D11_MINUS_3;
case D11_MINUS_3:
default:
return Dibit.D01_PLUS_3;
}
}
},
/**
* PLL lock at minus 90 degrees to the carrier, requiring a PLL adjustment of +90 degrees, or +PI/2.
*/
MINUS_90
{
@Override
public Dibit correct(Dibit dibit)
{
switch(dibit)
{
case D00_PLUS_1:
return Dibit.D01_PLUS_3;
case D01_PLUS_3:
return Dibit.D11_MINUS_3;
case D10_MINUS_1:
return Dibit.D00_PLUS_1;
case D11_MINUS_3:
default:
return Dibit.D10_MINUS_1;
}
}
},
/**
* PLL lock at 180 degrees or inverted to the carrier, requiring a PLL adjustment of 180 degrees or PI.
*/
INVERTED
{
@Override
public Dibit correct(Dibit dibit)
{
switch(dibit)
{
case D00_PLUS_1:
return Dibit.D11_MINUS_3;
case D01_PLUS_3:
return Dibit.D10_MINUS_1;
case D10_MINUS_1:
return Dibit.D01_PLUS_3;
case D11_MINUS_3:
default:
return Dibit.D00_PLUS_1;
}
}
};
/**
* Method for each entry to provide dibit correction solutions
* @param dibit that was generated by a misaligned QPSK Phase locked loop
* @return dibit that is corrected to the dibit for a correctly aligned QPSK Phase Locked Loop
*/
public abstract Dibit correct(Dibit dibit);
}