Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

View File

@@ -35,7 +35,6 @@ import instrument.tap.Tap;
import instrument.tap.stream.EyeDiagramData;
import instrument.tap.stream.EyeDiagramDataTap;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -44,24 +43,22 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.Provider;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import source.tuner.frequency.FrequencyChangeListener;
import buffer.FloatAveragingBuffer;
import dsp.filter.interpolator.QPSKInterpolator;
import dsp.filter.interpolator.RealInterpolator;
import dsp.symbol.Dibit;
/**
* Implements a CQPSK demodulator using a Gardner Detector to determine optimal
* sampling timing and a Costas Loop as a phase locked loop synchronized with
* the incoming signal carrier frequency.
* Implements a LSM (CQPSK) demodulator using a Gardner Detector to determine
* optimal sampling timing and a Costas Loop as a phase locked loop synchronized
* with the incoming signal carrier frequency.
*
* Sample Rate: 48000
* Symbol Rate: 4800
*/
public class CQPSKDemodulator implements Instrumentable,
Listener<ComplexSample>,
Provider<ComplexSample>
public class CQPSKDemodulator implements Instrumentable, ComplexSampleListener
{
private final static Logger mLog =
LoggerFactory.getLogger( CQPSKDemodulator.class );
@@ -70,12 +67,10 @@ public class CQPSKDemodulator implements Instrumentable,
public static final float THETA = (float)( Math.PI / 4.0d );
/* 45 degree point */
public static final ComplexSample POINT_45_DEGREES =
new ComplexSample( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
public static final Complex POINT_45_DEGREES =
new Complex( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
private DecimalFormat mDecimalFormat = new DecimalFormat( "0.000000" );
private Listener<ComplexSample> mListener;
private Listener<Complex> mSymbolListener;
private GardnerSymbolTiming mGardnerDetector = new GardnerSymbolTiming();
@@ -89,10 +84,20 @@ public class CQPSKDemodulator implements Instrumentable,
{
}
@Override
public void receive( ComplexSample sample )
public void setSymbolListener( Listener<Complex> listener )
{
mGardnerDetector.receive( sample );
mSymbolListener = listener;
}
public void removeSymbolListener()
{
mSymbolListener = null;
}
@Override
public void receive( float inphase, float quadrature )
{
mGardnerDetector.receive( inphase, quadrature );
}
/**
@@ -211,14 +216,15 @@ public class CQPSKDemodulator implements Instrumentable,
/**
* Gardner symbol timing detector
*/
public class GardnerSymbolTiming implements Listener<ComplexSample>
public class GardnerSymbolTiming implements ComplexSampleListener
{
public static final int HALF_SAMPLES_PER_SYMBOL = 5;
public static final int SAMPLES_PER_SYMBOL = 10;
public static final int TWICE_SAMPLES_PER_SYMBOL = 20;
private ComplexSample[] mDelayLine =
new ComplexSample[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineInphase = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineQuadrature = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private int mDelayLinePointer = 0;
/* Sampling point */
@@ -231,25 +237,21 @@ public class CQPSKDemodulator implements Instrumentable,
private float mOmegaRel = 0.005f;
private float mOmegaMid = 10.0f;
private QPSKInterpolator mInterpolator = new QPSKInterpolator( 1.0f );
private RealInterpolator mInterpolator = new RealInterpolator( 1.0f );
private Complex mPreviousSample;
private Complex mPreviousMiddleSample;
private Complex mPreviousSymbol;
private ComplexSample mPreviousSample = new ComplexSample( 0.0f, 0.0f );
private ComplexSample mPreviousMiddleSample = new ComplexSample( 0.0f, 0.0f );
private ComplexSample mPreviousSymbol = new ComplexSample( 0.0f, 0.0f );
/**
* Provides symbol sampling timing control
*/
public GardnerSymbolTiming()
{
for( int x = 0; x < ( 2 * TWICE_SAMPLES_PER_SYMBOL ); x++ )
{
mDelayLine[ x ] = new ComplexSample( 0.0f, 0.0f );
}
}
@Override
public void receive( ComplexSample sample )
public void receive( float inphase, float quadrature )
{
/* Count down samples per symbol until we calculate the symbol */
mMu--;
@@ -258,14 +260,20 @@ public class CQPSKDemodulator implements Instrumentable,
/* Mix incoming sample with costas loop to remove any rotation
* that is present from a mis-tuned carrier frequency */
ComplexSample derotatedSample = ComplexSample
.multiply( mCostasLoop.getCurrentVector(), sample );
Complex costas = mCostasLoop.getCurrentVector();
float derotatedInphase = Complex.multiplyInphase( inphase, quadrature, costas.inphase(), costas.quadrature() );
float derotatedQuadrature = Complex.multiplyQuadrature( inphase, quadrature, costas.inphase(), costas.quadrature() );
/* Fill up the delay line to use with the interpolator */
mDelayLine[ mDelayLinePointer ] = derotatedSample;
mDelayLine[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample;
mDelayLineInphase[ mDelayLinePointer ] = derotatedInphase;
mDelayLineInphase[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedInphase;
/* Imcrement pointer and keep pointer in bounds */
mDelayLineQuadrature[ mDelayLinePointer ] = derotatedQuadrature;
mDelayLineQuadrature[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedQuadrature;
/* Increment pointer and keep pointer in bounds */
mDelayLinePointer = ( mDelayLinePointer + 1 ) % TWICE_SAMPLES_PER_SYMBOL;
/* Calculate the symbol once we've stored enough samples */
@@ -282,19 +290,24 @@ public class CQPSKDemodulator implements Instrumentable,
}
/* Calculate interpolated middle sample and current sample */
ComplexSample middleSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer, mMu );
ComplexSample currentSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer + half_sps, half_mu );
/* Multiply current and previous samples to get symbols to use
* for gardner error feedback */
ComplexSample middleSymbol = ComplexSample.multiply(
middleSample, mPreviousMiddleSample.conjugate() );
float middleSampleInphase = mInterpolator.filter(
mDelayLineInphase, mDelayLinePointer, mMu );
float middleSampleQuadrature = mInterpolator.filter(
mDelayLineQuadrature, mDelayLinePointer, mMu );
ComplexSample currentSymbol = ComplexSample.multiply(
currentSample, mPreviousSample.conjugate() );
int index = mDelayLinePointer + half_sps;
float currentSampleInphase = mInterpolator.filter(
mDelayLineInphase, index, half_mu );
float currentSampleQuadrature = mInterpolator.filter(
mDelayLineQuadrature, index, half_mu );
/* Multiply current sample and conjugate (negated quadrature) of
* previous sample to get symbols to use for gardner error feedback */
Complex middleSymbol = Complex.multiply( middleSampleInphase,
middleSampleQuadrature, mPreviousMiddleSample.conjugate() );
Complex currentSymbol = Complex.multiply( currentSampleInphase,
currentSampleQuadrature, mPreviousSample.conjugate() );
/* Set gain to unity */
middleSymbol.normalize();
@@ -312,8 +325,10 @@ public class CQPSKDemodulator implements Instrumentable,
if( mEyeDiagramDataTap != null )
{
mEyeDiagramDataTap.receive(
new EyeDiagramData( Arrays.copyOfRange( mDelayLine,
0, 20 ), mMu, (float)half_sps + half_mu, gardnerError ) );
new EyeDiagramData(
Arrays.copyOfRange( mDelayLineInphase, 0, 20 ),
Arrays.copyOfRange( mDelayLineQuadrature, 0, 20 ),
mMu, (float)half_sps + half_mu, gardnerError ) );
}
/* mOmega is samples per symbol and is constrained to floating
@@ -325,8 +340,8 @@ public class CQPSKDemodulator implements Instrumentable,
mMu += mOmega + ( mGainMu * gardnerError );
/* Store current samples/symbols to use for the next period */
mPreviousSample = currentSample;
mPreviousMiddleSample = middleSample;
mPreviousSample.setValues( currentSampleInphase, currentSampleQuadrature );
mPreviousMiddleSample.setValues( middleSampleInphase, middleSampleQuadrature );
mPreviousSymbol = currentSymbol;
/* Update costas loop using phase error present in current
@@ -337,9 +352,9 @@ public class CQPSKDemodulator implements Instrumentable,
// ComplexSample.multiply( currentSymbol, POINT_45_DEGREES ) );
/* Dispatch the differentiated symbol to the registered listener */
if( mListener != null )
if( mSymbolListener != null )
{
mListener.receive( currentSymbol );
mSymbolListener.receive( currentSymbol );
}
}
}
@@ -356,7 +371,7 @@ public class CQPSKDemodulator implements Instrumentable,
* gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's
* OP25/cqpsk.py
*/
public class CostasLoop implements Listener<ComplexSample>
public class CostasLoop implements Listener<Complex>
{
public static final double TWO_PI = 2.0 * Math.PI;
@@ -442,19 +457,19 @@ public class CQPSKDemodulator implements Instrumentable,
/**
* Current vector of the loop rotated by THETA degrees.
*/
public ComplexSample getCurrentVector()
public Complex getCurrentVector()
{
// return ComplexSample.fromAngle( mLoopPhase + THETA );
return ComplexSample.fromAngle( mLoopPhase );
return Complex.fromAngle( mLoopPhase );
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex complex )
{
/* Calculate phase error */
float phaseError = getPhaseError( sample );
float phaseError = getPhaseError( complex );
Dibit d = QPSKPolarSlicer.decide( sample );
Dibit d = QPSKPolarSlicer.decide( complex );
// mLog.debug( "Dibit: " + d.name() + " Error: " + phaseError );
/* Adjust for phase error */
@@ -503,30 +518,30 @@ public class CQPSKDemodulator implements Instrumentable,
* with the carrier frequency. Provides error feedback to adjust
* mixer frequency.
*/
public float getPhaseError( ComplexSample sample )
public float getPhaseError( Complex complex )
{
float phase_error = 0;
if( Math.abs( sample.real() ) > Math.abs( sample.imaginary() ) )
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
{
if( sample.real() > 0 )
if( complex.inphase() > 0 )
{
phase_error = -sample.imaginary();
phase_error = -complex.quadrature();
}
else
{
phase_error = sample.imaginary();
phase_error = complex.quadrature();
}
}
else
{
if( sample.imaginary() > 0 )
if( complex.quadrature() > 0 )
{
phase_error = sample.real();
phase_error = complex.inphase();
}
else
{
phase_error = -sample.real();
phase_error = -complex.inphase();
}
}
@@ -534,18 +549,6 @@ public class CQPSKDemodulator implements Instrumentable,
}
}
@Override
public void setListener( Listener<ComplexSample> listener )
{
mListener = listener;
}
@Override
public void removeListener( Listener<ComplexSample> listener )
{
mListener = null;
}
@Override
public List<Tap> getTaps()
{

View File

@@ -45,10 +45,11 @@ import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.Provider;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import sample.complex.ComplexSampleListener;
import source.tuner.frequency.FrequencyChangeListener;
import buffer.FloatAveragingBuffer;
import dsp.filter.interpolator.QPSKInterpolator;
import dsp.filter.interpolator.RealInterpolator;
import dsp.symbol.Dibit;
/**
@@ -59,9 +60,8 @@ import dsp.symbol.Dibit;
* Sample Rate: 48000
* Symbol Rate: 4800
*/
public class CQPSKDemodulator2 implements Instrumentable,
Listener<ComplexSample>,
Provider<ComplexSample>
public class CQPSKDemodulator2 implements Instrumentable, ComplexSampleListener,
Provider<Complex>
{
private final static Logger mLog =
LoggerFactory.getLogger( CQPSKDemodulator2.class );
@@ -70,12 +70,12 @@ public class CQPSKDemodulator2 implements Instrumentable,
public static final float THETA = (float)( Math.PI / 4.0d );
/* 45 degree point */
public static final ComplexSample POINT_45_DEGREES =
new ComplexSample( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
public static final Complex POINT_45_DEGREES =
new Complex( (float)Math.sin( THETA ), (float)Math.cos( THETA ) );
private DecimalFormat mDecimalFormat = new DecimalFormat( "0.000000" );
private Listener<ComplexSample> mListener;
private Listener<Complex> mListener;
private EarlyLateSymbolTiming mGardnerDetector = new EarlyLateSymbolTiming();
@@ -90,9 +90,9 @@ public class CQPSKDemodulator2 implements Instrumentable,
}
@Override
public void receive( ComplexSample sample )
public void receive( float inphase, float quadrature )
{
mGardnerDetector.receive( sample );
mGardnerDetector.receive( inphase, quadrature );
}
/**
@@ -218,13 +218,14 @@ public class CQPSKDemodulator2 implements Instrumentable,
* attempting to find the largest magnitude value between two interpolated
* sample points during each symbol period.
*/
public class EarlyLateSymbolTiming implements Listener<ComplexSample>
public class EarlyLateSymbolTiming implements ComplexSampleListener
{
public static final int SAMPLES_PER_SYMBOL = 10;
public static final int TWICE_SAMPLES_PER_SYMBOL = 20;
private ComplexSample[] mDelayLine =
new ComplexSample[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineInphase = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private float[] mDelayLineQuadrature = new float[ 2 * TWICE_SAMPLES_PER_SYMBOL ];
private int mDelayLinePointer = 0;
/* Sampling point */
@@ -237,23 +238,19 @@ public class CQPSKDemodulator2 implements Instrumentable,
private float mOmegaRel = 0.005f;
private float mOmegaMid = 10.0f;
private QPSKInterpolator mInterpolator = new QPSKInterpolator( 1.0f );
private RealInterpolator mInterpolator = new RealInterpolator( 1.0f );
private ComplexSample mPreviousSample = new ComplexSample( 0.0f, 0.0f );
private Complex mPreviousSample = new Complex( 0.0f, 0.0f );
/**
* Provides symbol sampling timing control
*/
public EarlyLateSymbolTiming()
{
for( int x = 0; x < ( 2 * TWICE_SAMPLES_PER_SYMBOL ); x++ )
{
mDelayLine[ x ] = new ComplexSample( 0.0f, 0.0f );
}
}
@Override
public void receive( ComplexSample sample )
public void receive( float inphase, float quadrature )
{
/* Count down samples per symbol until we calculate the symbol */
mMu--;
@@ -262,29 +259,31 @@ public class CQPSKDemodulator2 implements Instrumentable,
/* Mix incoming sample with costas loop to remove any rotation
* that is present from a mis-tuned carrier frequency */
ComplexSample derotatedSample = ComplexSample
.multiply( mCostasLoop.getCurrentVector(), sample );
Complex derotatedSample = Complex.multiply(
mCostasLoop.getCurrentVector(), inphase, quadrature );
/* Fill up the delay line to use with the interpolator */
mDelayLine[ mDelayLinePointer ] = derotatedSample;
mDelayLine[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample;
mDelayLineInphase[ mDelayLinePointer ] = derotatedSample.inphase();
mDelayLineInphase[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample.inphase();
mDelayLineQuadrature[ mDelayLinePointer ] = derotatedSample.quadrature();
mDelayLineQuadrature[ mDelayLinePointer + TWICE_SAMPLES_PER_SYMBOL ] = derotatedSample.quadrature();
/* Imcrement pointer and keep pointer in bounds */
mDelayLinePointer = ( mDelayLinePointer + 1 ) % TWICE_SAMPLES_PER_SYMBOL;
/* Calculate the symbol once we've stored enough samples */
if( mMu <= 1.0f )
{
ComplexSample earlySample = mInterpolator
.filter( mDelayLine, mDelayLinePointer, mMu );
Complex earlySample = mInterpolator.filter( mDelayLineInphase,
mDelayLineQuadrature, mDelayLinePointer, mMu );
ComplexSample middleSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer + 1, mMu );
Complex middleSample = mInterpolator.filter( mDelayLineInphase,
mDelayLineQuadrature, mDelayLinePointer + 1, mMu );
ComplexSample lateSample = mInterpolator
.filter( mDelayLine, mDelayLinePointer + 2, mMu );
Complex lateSample = mInterpolator.filter( mDelayLineInphase,
mDelayLineQuadrature, mDelayLinePointer + 2, mMu );
ComplexSample currentSymbol = ComplexSample.multiply(
Complex currentSymbol = Complex.multiply(
middleSample, mPreviousSample.conjugate() );
/* Set symbol gain to unity */
@@ -295,10 +294,12 @@ public class CQPSKDemodulator2 implements Instrumentable,
if( mEyeDiagramDataTap != null )
{
mEyeDiagramDataTap.receive(
new EyeDiagramData( Arrays.copyOfRange( mDelayLine, 0, 20 ),
(float)mDelayLinePointer + 4.0f + mMu,
(float)mDelayLinePointer + 4.0f + mMu + 0.25f, earlyLateError ) );
mEyeDiagramDataTap.receive( new EyeDiagramData(
Arrays.copyOfRange( mDelayLineInphase, 0, 20 ),
Arrays.copyOfRange( mDelayLineQuadrature, 0, 20 ),
(float)mDelayLinePointer + 4.0f + mMu,
(float)mDelayLinePointer + 4.0f + mMu + 0.25f,
earlyLateError ) );
}
/* mOmega is samples per symbol and is constrained to floating
@@ -341,7 +342,7 @@ public class CQPSKDemodulator2 implements Instrumentable,
* gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's
* OP25/cqpsk.py
*/
public class CostasLoop implements Listener<ComplexSample>
public class CostasLoop implements Listener<Complex>
{
public static final double TWO_PI = 2.0 * Math.PI;
@@ -427,14 +428,14 @@ public class CQPSKDemodulator2 implements Instrumentable,
/**
* Current vector of the loop rotated by THETA degrees.
*/
public ComplexSample getCurrentVector()
public Complex getCurrentVector()
{
// return ComplexSample.fromAngle( mLoopPhase + THETA );
return ComplexSample.fromAngle( mLoopPhase );
return Complex.fromAngle( mLoopPhase );
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
/* Calculate phase error */
float phaseError = getPhaseError( sample );
@@ -488,7 +489,7 @@ public class CQPSKDemodulator2 implements Instrumentable,
* with the carrier frequency. Provides error feedback to adjust
* mixer frequency.
*/
public float getPhaseError( ComplexSample sample )
public float getPhaseError( Complex sample )
{
float phase_error = 0;
@@ -520,13 +521,13 @@ public class CQPSKDemodulator2 implements Instrumentable,
}
@Override
public void setListener( Listener<ComplexSample> listener )
public void setListener( Listener<Complex> listener )
{
mListener = listener;
}
@Override
public void removeListener( Listener<ComplexSample> listener )
public void removeListener( Listener<Complex> listener )
{
mListener = null;
}

View File

@@ -2,10 +2,27 @@ package dsp.psk;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import dsp.symbol.Dibit;
public class QPSKPolarSlicer implements Listener<ComplexSample>
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 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/>
******************************************************************************/
public class QPSKPolarSlicer implements Listener<Complex>
{
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
@@ -15,7 +32,7 @@ public class QPSKPolarSlicer implements Listener<ComplexSample>
*
* \ 00 /
* \ /
* 11 \/ 10
* 01 \/ 10
* /\
* / \
* / 11 \
@@ -41,16 +58,16 @@ public class QPSKPolarSlicer implements Listener<ComplexSample>
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex complex )
{
mBroadcaster.broadcast( decide( sample ) );
mBroadcaster.broadcast( decide( complex ) );
}
public static Dibit decide( ComplexSample sample )
public static Dibit decide( Complex complex )
{
if( sample.inPhaseAbsolute() > sample.quadratureAbsolute() )
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
{
if( sample.inphase() > 0 )
if( complex.inphase() > 0 )
{
return Dibit.D10_MINUS_1;
}
@@ -61,7 +78,7 @@ public class QPSKPolarSlicer implements Listener<ComplexSample>
}
else
{
if( sample.quadrature() > 0 )
if( complex.quadrature() > 0 )
{
return Dibit.D00_PLUS_1;
}

View File

@@ -2,10 +2,10 @@ package dsp.psk;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.complex.Complex;
import dsp.symbol.Dibit;
public class QPSKStarSlicer implements Listener<ComplexSample>
public class QPSKStarSlicer implements Listener<Complex>
{
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
@@ -38,12 +38,12 @@ public class QPSKStarSlicer implements Listener<ComplexSample>
}
@Override
public void receive( ComplexSample sample )
public void receive( Complex sample )
{
mBroadcaster.broadcast( decide( sample ) );
}
public static Dibit decide( ComplexSample sample )
public static Dibit decide( Complex sample )
{
if( sample.inphase() > 0 )
{