mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
P25 audio work in progress - started work on exposing 88-bit (11-byte) IMBE voice frames from LDU messages. Added deinterleave, derandomize and the Golay(23,12,7) error detection and correction to LDU voice messages. Hamming edac still remains to be implemented. Plan to create a separate project to produce an imbe FormatConversionProvider that will convert 88-bit imbe frames to PCM audio that is then in a standard format for audio playback, recording, or conversion to another format like shoutcast.
This commit is contained in:
@@ -71,8 +71,6 @@ public class DirectGainControl implements GainController, RealSampleListener
|
||||
{
|
||||
mGain = mMaximum;
|
||||
}
|
||||
|
||||
mLog.debug( "Gain Increase: " + mGain );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,7 +82,6 @@ public class DirectGainControl implements GainController, RealSampleListener
|
||||
{
|
||||
mGain = mMinimum;
|
||||
}
|
||||
mLog.debug( "Gain Decrease: " + mGain );
|
||||
}
|
||||
|
||||
public void setListener( RealSampleListener listener )
|
||||
|
||||
@@ -36,6 +36,10 @@ import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.Provider;
|
||||
import sample.complex.ComplexSample;
|
||||
import source.tuner.FrequencyChangeEvent;
|
||||
import source.tuner.FrequencyChangeEvent.Attribute;
|
||||
import source.tuner.FrequencyChangeListener;
|
||||
import buffer.FloatAveragingBuffer;
|
||||
import dsp.filter.interpolator.QPSKInterpolator;
|
||||
|
||||
/**
|
||||
@@ -51,11 +55,13 @@ public class CQPSKDemodulator implements Listener<ComplexSample>,
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( CQPSKDemodulator.class );
|
||||
|
||||
|
||||
private Listener<ComplexSample> mListener;
|
||||
|
||||
private GardnerDetector mGardnerDetector = new GardnerDetector();
|
||||
|
||||
private FrequencyChangeListener mFrequencyChangeListener;
|
||||
|
||||
public CQPSKDemodulator()
|
||||
{
|
||||
}
|
||||
@@ -82,6 +88,75 @@ public class CQPSKDemodulator implements Listener<ComplexSample>,
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void addListener( FrequencyChangeListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener( FrequencyChangeListener listener )
|
||||
{
|
||||
mFrequencyChangeListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current loop frequency of the costas loop and broadcasts
|
||||
* tuner frequency offset adjustments 10 times a second as needed to keep
|
||||
* the frequency offset below half of the maximum frequency
|
||||
*/
|
||||
public class FrequencyControl
|
||||
{
|
||||
/* Set the trigger threshold at half of the maximum costas loop control
|
||||
* frequency */
|
||||
private static final float CORRECTION_THRESHOLD_FREQUENCY =
|
||||
( 2.0f * (float)Math.PI * 600.0f ) / 48000.0f;
|
||||
|
||||
private FloatAveragingBuffer mBuffer = new FloatAveragingBuffer( 40 );
|
||||
|
||||
private long mFrequencyError = 0;
|
||||
|
||||
private int mCounter = 0;
|
||||
|
||||
public void receive( float frequency )
|
||||
{
|
||||
// mCounter++;
|
||||
//
|
||||
// if( mCounter >= 480 )
|
||||
// {
|
||||
// float average = mBuffer.get( frequency );
|
||||
// mLog.debug( "Avg:" + average + " Threshold:" + CORRECTION_THRESHOLD_FREQUENCY);
|
||||
//
|
||||
// boolean correctionNeeded = false;
|
||||
//
|
||||
// if( average > CORRECTION_THRESHOLD_FREQUENCY )
|
||||
// {
|
||||
// mFrequencyError--;
|
||||
//
|
||||
// correctionNeeded = true;
|
||||
// }
|
||||
// else if( average < -CORRECTION_THRESHOLD_FREQUENCY )
|
||||
// {
|
||||
// mFrequencyError++;
|
||||
//
|
||||
// correctionNeeded = true;
|
||||
// }
|
||||
//
|
||||
// if( correctionNeeded && mFrequencyChangeListener != null )
|
||||
// {
|
||||
// mLog.debug( "Issuing Frequency Correction: " + mFrequencyError );
|
||||
// mFrequencyChangeListener.frequencyChanged(
|
||||
// new FrequencyChangeEvent( Attribute.FREQUENCY_ERROR,
|
||||
// mFrequencyError ) );
|
||||
// }
|
||||
//
|
||||
// mCounter = 0;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// mBuffer.get( frequency );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gardner symbol timing detector
|
||||
@@ -209,10 +284,15 @@ public class CQPSKDemodulator implements Listener<ComplexSample>,
|
||||
}
|
||||
|
||||
/**
|
||||
* Costas Loop - phase locked loop synchronized to the incoming signal.
|
||||
* Costas Loop - phase locked loop synchronized to the frequency offset of
|
||||
* the incoming signal to enable the signal to be mixed down to zero offset
|
||||
* for proper synchronization. The mFrequency value indicates the detected
|
||||
* frequency offset. We attempt to keep that value close to zero by issuing
|
||||
* frequency adjustments to the tuner channel source.
|
||||
*
|
||||
* Ported from gnuradio/control_loop and gnuradio/mpsk_receiver_cc
|
||||
* using initialization values from KA1RBI's OP25/cqpsk.py
|
||||
* Most of the costas loop code was ported from gnuradio/control_loop and
|
||||
* gnuradio/mpsk_receiver_cc using initialization values from KA1RBI's
|
||||
* OP25/cqpsk.py
|
||||
*/
|
||||
public class CostasLoop implements Listener<ComplexSample>
|
||||
{
|
||||
@@ -221,6 +301,9 @@ public class CQPSKDemodulator implements Listener<ComplexSample>,
|
||||
/* 45 degree rotation angle */
|
||||
public static final float THETA = (float)( Math.PI / 4.0d );
|
||||
|
||||
private static final float MAXIMUM_FREQUENCY = ( 2.0f * (float)Math.PI *
|
||||
1200.0f ) / 48000.0f;
|
||||
|
||||
/* http://www.trondeau.com/blog/2011/8/13/control-loop-gain-values.html */
|
||||
private float mDamping = (float)Math.sqrt( 2.0 ) / 2.0f;
|
||||
|
||||
@@ -238,9 +321,8 @@ public class CQPSKDemodulator implements Listener<ComplexSample>,
|
||||
private float mLoopPhase = 0.0f;
|
||||
|
||||
private float mLoopFrequency = 0.0f;
|
||||
|
||||
private float mMaximumFrequency = ( 2.0f * (float)Math.PI * 1200.0f ) /
|
||||
48000.0f;
|
||||
|
||||
private FrequencyControl mFrequencyControl = new FrequencyControl();
|
||||
|
||||
public CostasLoop()
|
||||
{
|
||||
@@ -289,12 +371,6 @@ public class CQPSKDemodulator implements Listener<ComplexSample>,
|
||||
|
||||
/* Adjust for phase error */
|
||||
adjust( phaseError );
|
||||
|
||||
/* Maintain phase between +/- 2 * PI */
|
||||
unwrapPhase();
|
||||
|
||||
/* Limit frequency to +/- maximum frequency */
|
||||
limitFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,18 +383,27 @@ public class CQPSKDemodulator implements Listener<ComplexSample>,
|
||||
{
|
||||
mLoopFrequency += mBetaGain * phase_error;
|
||||
mLoopPhase += mLoopFrequency + mAlphaGain * phase_error;
|
||||
|
||||
/* Maintain phase between +/- 2 * PI */
|
||||
unwrapPhase();
|
||||
|
||||
/* Limit frequency to +/- maximum frequency */
|
||||
limitFrequency();
|
||||
}
|
||||
|
||||
private void limitFrequency()
|
||||
{
|
||||
if( mLoopFrequency > mMaximumFrequency )
|
||||
/* Check for and issue tuner offset correction */
|
||||
mFrequencyControl.receive( mLoopFrequency );
|
||||
|
||||
if( mLoopFrequency > MAXIMUM_FREQUENCY )
|
||||
{
|
||||
mLoopFrequency = mMaximumFrequency;
|
||||
mLoopFrequency = MAXIMUM_FREQUENCY;
|
||||
}
|
||||
|
||||
if( mLoopFrequency < -mMaximumFrequency )
|
||||
if( mLoopFrequency < -MAXIMUM_FREQUENCY )
|
||||
{
|
||||
mLoopFrequency = -mMaximumFrequency;
|
||||
mLoopFrequency = -MAXIMUM_FREQUENCY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user