P25 - added parsing of packet payload data for confirmed PDUs.

This commit is contained in:
dennis.sheirer
2015-01-19 13:17:11 +00:00
parent 51d6b328bd
commit cc5edd4790
16 changed files with 872 additions and 82 deletions

View File

@@ -107,12 +107,25 @@ public class BitSetBuffer extends BitSet
{
return mPointer;
}
/**
* Sets the pointer to a specific value
* @param index
*/
public void setPointer( int index )
{
mPointer = index;
}
/**
* Moves the current pointer position left (negative adjustment) or
* right (positive adjustment)
*/
public void adjustPointer( int adjustment )
{
mPointer += adjustment;
}
/**
* Static method to construct a new BitSetBuffer, preloaded with the bits
* from the preload parameter, and then filled with the bits from the

View File

@@ -0,0 +1,94 @@
package buffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import sample.Listener;
public class CircularBuffer<T> implements Listener<T>
{
private ArrayList<T> mElements;
private Listener<T> mListener;
private int mPointer;
private int mSize;
/**
* Circular Buffer - stores elements in an array list. Once the buffer is
* full, new samples replace older samples and older samples are broadcast
* to an optional listener.
*
* @param size - size of buffer
*/
public CircularBuffer( int size )
{
mSize = size;
mElements = new ArrayList<T>();
}
/**
* Returns the element stored at index. Note: the index argument refers to
* the internal storage index and does not indicate a temporal relationship
* among elements. For example, if you get indexes 1, 2, and 3, you may be
* getting ( oldest - 1, oldest, newest ) elements, if the internal pointer
* is currently pointing to and will insert the next element at index 4.
*
* @param index - element index
*
* @return element or null
*/
public T get( int index )
{
if( index < mElements.size() )
{
return mElements.get( index );
}
return null;
}
public int getSize()
{
return mSize;
}
public List<T> getElements()
{
return Collections.unmodifiableList( mElements );
}
@Override
public void receive( T element )
{
T previous = null;
if( mElements.size() > mPointer )
{
previous = mElements.get( mPointer );
mElements.set( mPointer, element );
}
else
{
mElements.add( element );
}
mPointer++;
if( mPointer >= mSize )
{
mPointer = 0;
}
if( previous != null && mListener != null )
{
mListener.receive( previous );
}
}
public void setListener( Listener<T> listener )
{
mListener = listener;
}
}

View File

@@ -43,8 +43,8 @@ public class ComplexSampleBufferAssembler
*/
public void put( ComplexSample sample )
{
mByteBuffer.putShort( (short)sample.left() );
mByteBuffer.putShort( (short)sample.right() );
mByteBuffer.putShort( (short)( sample.left() * Short.MAX_VALUE ) );
mByteBuffer.putShort( (short)( sample.right() * Short.MAX_VALUE ) );
}
/**

View File

@@ -144,7 +144,7 @@ public class P25Interleave
int start = 0;
int end = 196;
String interleaved = "0010110111100011001010101100001000011110101110000110001000010010111100101101100100100101011101100011000101110010111100101110111100100111101010111011011001010111001011100001011100101000000010100100";
String interleaved = "0010010100000000000000000000000000000000000010100011001011111010101010101010101010101010010111101010100011010000000000000000000000000000110001011010011010101010101010101010101010101010100101100101";
BitSetBuffer b = new BitSetBuffer( end );

View File

@@ -121,7 +121,6 @@ public class Trellis_3_4_Rate
* and the flushing 000 node, otherwise there was an error */
if( path != null && path.getNodes().size() == 50 )
{
mLog.debug( path.toString() );
/* Clear the original message bits */
message.clear( start, end );
@@ -641,7 +640,7 @@ public class Trellis_3_4_Rate
*/
public static void main( String[] args )
{
String raw = "0010001001110110000100100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000001101000100011101011011000001110110000001101111";
String raw = "12:58:51.635 DEBUG dsp.fsk.P25MessageFramer - AFTER DEINTERLEAVE: 00100110000011001010010101111101000101010110100111111100101011000111011011000110000000000000000000000111111101011000001000001111000110000000000010101011110110010000000001011011000000010100011110110001000010100100011111000000000100000000000000000000010000000000001100000000000000011010101010101010101010100010001010000110010111111101101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000101000001010000010100000010111001001000011100101011010101010101001010011";
BitSetBuffer message = BitSetBuffer.load( raw );

View File

@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BitSetBuffer;
import decode.p25.reference.DataUnitID;
import decode.p25.reference.Vendor;
import edac.CRC;
import edac.CRCP25;
@@ -14,6 +15,12 @@ public class PDUConfirmedMessage extends PDUMessage
public final static Logger mLog =
LoggerFactory.getLogger( PDUConfirmedMessage.class );
public static final int SEQUENCE_RESET_FLAG = 128; //SYN
public static final int[] PACKET_SEQUENCE_NUMBER = { 129,130,131 };
public static final int FINAL_FRAGMENT_FLAG = 132;
public static final int[] FRAGMENT_SEQUENCE_NUMBER = { 133,134,135 };
public static final int PACKET_DATA_START = 176;
public PDUConfirmedMessage( BitSetBuffer message, DataUnitID duid,
AliasList aliasList )
{
@@ -57,29 +64,162 @@ public class PDUConfirmedMessage extends PDUMessage
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
sb.append( " CRC " );
sb.append( " " );
sb.append( getDirection() );
sb.append( " CRC[" );
sb.append( getErrorStatus() );
sb.append( " LLID:" );
sb.append( "] LLID:" );
sb.append( getLogicalLinkID() );
sb.append( " " );
sb.append( getConfirmation() );
sb.append( " " );
sb.append( getDirection() );
sb.append( " FMT:" );
sb.append( getFormat().getLabel() );
sb.append( " SAP:" );
sb.append( getServiceAccessPoint().name() );
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
sb.append( " BLKS TO FOLLOW:" );
sb.append( getServiceAccessPoint().getLabel() );
if( getVendor() != Vendor.STANDARD )
{
sb.append( " VEND:" );
sb.append( getVendor().getLabel() );
}
sb.append( " PACKET #" );
sb.append( getPacketSequenceNumber() );
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
{
sb.append( ".C" );
}
else
{
sb.append( "." );
sb.append( getFragmentSequenceNumber() );
if( isFinalFragment() )
{
sb.append( "C" );
}
}
sb.append( " " );
sb.append( getPacket() );
sb.append( " BLOCKS:" );
sb.append( getBlocksToFollowCount() );
return sb.toString();
}
/**
* Number of bytes/octets contained in the message, exclusive of the data
* header.
*/
public int getOctetCount()
{
/* 16 octets per block, and 12 octets in final block */
int count = getBlocksToFollowCount() * 16 - 4;
count -= getPadOctetCount();
count -= getDataHeaderOffset();
return count;
}
/**
* Indicates that the receiver should accept this packet, despite the
* apparent out-of-order packet and fragment sequence numbering. This
* indicates that something happened to the synchronization between
* the tower and the radio and now the tower commands the radio to reset
* the sequence numbers and accept this packet/fragment instead of discarding
* it as a duplicate.
*/
public boolean isSequenceReset()
{
return mMessage.get( SEQUENCE_RESET_FLAG );
}
/**
* Packet sequence number. Numbers run from 0 to 7 and will continually
* roll-over if there are more than 8 total packets.
*/
public int getPacketSequenceNumber()
{
return mMessage.getInt( PACKET_SEQUENCE_NUMBER );
}
/**
* Indicates that this packet fragment is the final fragment
*/
public boolean isFinalFragment()
{
return mMessage.get( FINAL_FRAGMENT_FLAG );
}
/**
* Packet fragment sequence number
*/
public int getFragmentSequenceNumber()
{
return mMessage.getInt( FRAGMENT_SEQUENCE_NUMBER );
}
/**
* Hex string representation of the packet data, including packet header
* data if present.
*/
public String getPacket()
{
StringBuilder sb = new StringBuilder();
int dataHeaderOffset = getDataHeaderOffset();
if( dataHeaderOffset > 0 )
{
sb.append( "HDR:" );
for( int x = 0; x < dataHeaderOffset; x++ )
{
sb.append( getPacketOctet( x ) );
}
sb.append( " " );
}
int counter = 0;
/* Append octets inserting a space between each 32-bit value */
for( int x = 0; x < getOctetCount(); x++ )
{
sb.append( getPacketOctet( x + dataHeaderOffset ) );
counter++;
if( counter >= 4 )
{
sb.append( " " );
counter = 0;
}
}
return sb.toString();
}
/**
* Returns the octet identified by the 0-indexed 'octet' argument location.
* If there is a Data Header Offset, the 0 index points to the first octet
* of the data header. Otherwise, the 0 index points to the first octet
* of the packet. You must account for the data header offset when
* specifying the octet.
*/
public String getPacketOctet( int octet )
{
int block = (int)( octet / 16 );
int offset = octet % 16;
int start = PACKET_DATA_START + ( block * 144 ) + ( offset * 8 );
return mMessage.getHex( start, start + 7, 2 );
}
public String toString()
{

View File

@@ -133,12 +133,7 @@ public class PDUMessage extends P25Message
public int getPadOctetCount()
{
if( getFormat() == PDUFormat.UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL )
{
return mMessage.getInt( PAD_OCTET_COUNT );
}
return 0;
return mMessage.getInt( PAD_OCTET_COUNT );
}
public Opcode getOpcode()
@@ -153,11 +148,6 @@ public class PDUMessage extends P25Message
public int getDataHeaderOffset()
{
if( getFormat() == PDUFormat.UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL )
{
return mMessage.getInt( DATA_HEADER_OFFSET );
}
return 0;
return mMessage.getInt( DATA_HEADER_OFFSET );
}
}

View File

@@ -2,13 +2,13 @@ package decode.p25.reference;
public enum ServiceAccessPoint
{
UNENCRYPTED_USER_DATA( "0", 0 ),
ENCRYPTED_USER_DATA( "1", 1 ),
CIRCUIT_DATA( "2", 2 ),
CIRCUIT_DATA_CONTROL( "3", 3 ),
PACKET_DATA( "4", 4 ),
ADDRESS_RESOLUTION_PROTOCOL( "5", 5 ),
SNDCP_PACKET_DATA_CONTROL( "6", 6 ),
UNENCRYPTED_USER_DATA( "USER DATA", 0 ),
ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ),
CIRCUIT_DATA( "CIRCUIT DATA", 2 ),
CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ),
PACKET_DATA( "PACKET DATA", 4 ),
ADDRESS_RESOLUTION_PROTOCOL( "ADDRESS RESOLUTION PROTOCOL", 5 ),
SNDCP_PACKET_DATA_CONTROL( "SNDCP PACKET DATA CONTROL", 6 ),
SAP_7( "7", 7 ),
SAP_8( "8", 8 ),
SAP_9( "9", 9 ),
@@ -33,17 +33,17 @@ public enum ServiceAccessPoint
SAP_28( "28", 28 ),
SAP_29( "29", 29 ),
SAP_30( "30", 30 ),
EXTENDED_ADDRESS( "31", 31 ),
REGISTRATION_AND_AUTHORIZATION( "32", 32 ),
CHANNEL_REASSIGNMENT( "33", 33 ),
SYSTEM_CONFIGURATION( "34", 34 ),
MR_LOOPBACK( "35", 35 ),
MR_STATISTICS( "36", 36 ),
MR_OUT_OF_SERVICE( "37", 37 ),
MR_PAGING( "38", 38 ),
MR_CONFIGURATION( "39", 39 ),
UNENCRYPTED_KEY_MANAGEMENT_MESSAGE( "40", 40 ),
ENCRYPTED_KEY_MANAGEMENT_MESSAGE( "41", 41 ),
EXTENDED_ADDRESS( "EXTENDED ADDRESS", 31 ),
REGISTRATION_AND_AUTHORIZATION( "REGISTRATION AND AUTHORIZATION", 32 ),
CHANNEL_REASSIGNMENT( "CHANNEL REASSIGNMENT", 33 ),
SYSTEM_CONFIGURATION( "SYSTEM CONFIGURATION", 34 ),
MR_LOOPBACK( "MOBILE RADIO LOOPBACK", 35 ),
MR_STATISTICS( "MOBILE RADIO STATISTICS", 36 ),
MR_OUT_OF_SERVICE( "MOBILE RADIO OUT OF SERVICE", 37 ),
MR_PAGING( "MOBILE RADIO PAGING", 38 ),
MR_CONFIGURATION( "MOBILE RADIO CONFIGURATION", 39 ),
UNENCRYPTED_KEY_MANAGEMENT_MESSAGE( "UNENCRYPTED KEY MANAGEMENT", 40 ),
ENCRYPTED_KEY_MANAGEMENT_MESSAGE( "ENCRYPTED KEY MANAGEMENT", 41 ),
SAP_42( "42", 42 ),
SAP_43( "43", 43 ),
SAP_44( "44", 44 ),
@@ -63,7 +63,7 @@ public enum ServiceAccessPoint
SAP_58( "58", 58 ),
SAP_59( "59", 59 ),
SAP_60( "60", 60 ),
UNENCRYPTED_TRUNKING_CONTROL( "UNENCRYPTED TRUNKING CONTROL", 61 ),
UNENCRYPTED_TRUNKING_CONTROL( "TRUNKING CONTROL", 61 ),
SAP_62( "62", 62 ),
ENCRYPTED_TRUNKING_CONTROL( "ENCRYPTED TRUNKING CONTROL", 63 ),
UKNOWN( "UNKN", -1 );

View File

@@ -399,8 +399,10 @@ public class P25MessageFramer implements Listener<Dibit>
if( mThreeQuarterRate.decode( mMessage,
mMessage.size() - 196, mMessage.size() ) )
{
/* Resize the message to account for removing 48 + 4 parity bits */
/* Resize the message and adjust the message pointer
* to account for removing 48 + 4 parity bits */
mMessage.setSize( mMessage.size() - 52 );
mMessage.adjustPointer( -52 );
int blocks = mMessage.getInt( PDUMessage.BLOCKS_TO_FOLLOW );

View File

@@ -332,7 +332,7 @@ public class CRCP25
*/
public static CRC checkCRC9( BitSetBuffer message, int messageStart )
{
int calculated = 0x1FF; //Initial fill of all ones
int calculated = 0x0; //Initial fill of all ones
/* Iterate the set bits and XOR running checksum with lookup value */
for (int i = message.nextSetBit( messageStart );
@@ -353,7 +353,16 @@ public class CRCP25
int checksum = message.getInt( messageStart + 7, messageStart + 15 );
return ( calculated ^ checksum ) == 0 ? CRC.PASSED : CRC.FAILED_CRC;
int residual = calculated ^ checksum;
// mLog.debug( "CALC:" + calculated + " CHECK:" + checksum + " RESID:" + residual );
if( residual == 0 || residual == 0x1FF )
{
return CRC.PASSED;
}
return CRC.FAILED_CRC;
}
@@ -432,7 +441,7 @@ public class CRCP25
public static void main( String[] args )
{
String raw = "000000010110100101010001000000000100010100000000000000000010000000011001001011100000000000000000011111110001000101001011100101110000101000110011";
String raw = "000000001000001100000001010001111011000100001010010001111100000000000101000000000000000001000000000000110000000000000001101010101010101010101010";
BitSetBuffer message = BitSetBuffer.load( raw );

View File

@@ -0,0 +1,198 @@
package gui.control;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Ellipse2D;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexSample;
import buffer.CircularBuffer;
public class ConstellationViewer extends JPanel
implements Listener<ComplexSample>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( ConstellationViewer.class );
private int mSampleRate;
private int mSymbolRate;
private float mSamplesPerSymbol;
private float mCounter = 0;
private float mOffset = 0;
private CircularBuffer<ComplexSample> mBuffer =
new CircularBuffer<ComplexSample>( 5000 );
private ComplexSample mPrevious = new ComplexSample( 1, 1 );
public ConstellationViewer( int sampleRate, int symbolRate )
{
mSampleRate = sampleRate;
mSymbolRate = symbolRate;
mSamplesPerSymbol = (float)mSampleRate / (float)mSymbolRate;
initGui();
}
private void initGui()
{
setPreferredSize( new Dimension( 200,200 ) );
addMouseListener( new MouseListener()
{
@Override
public void mouseClicked( MouseEvent e )
{
if( SwingUtilities.isRightMouseButton( e ) )
{
JPopupMenu menu = new JPopupMenu();
menu.add( new TimingOffsetItem( (int)( mSamplesPerSymbol * 10 ), (int)( mOffset * 10 ) ) );
menu.show( ConstellationViewer.this, e.getX(), e.getY() );
}
}
public void mouseReleased( MouseEvent e ) {}
public void mousePressed( MouseEvent e ) {}
public void mouseExited( MouseEvent e ) {}
public void mouseEntered( MouseEvent e ) {}
} );
}
@Override
public void receive( ComplexSample sample )
{
mBuffer.receive( sample );
repaint();
}
/**
* Sets the timing offset ( 0 <> SamplesPerSymbol ) for selecting which
* sample to plot, within the symbol timeframe. Values greater than the
* samples per symbol value will simply wrap or delay into the next symbol
* period.
*
* @param offset
*/
public void setOffset( float offset )
{
mOffset = offset;
repaint();
}
@Override
public void paintComponent( Graphics g )
{
super.paintComponent( g );
Graphics2D graphics = (Graphics2D) g;
// graphics.setBackground( Color.BLACK );
graphics.setColor( Color.BLUE );
List<ComplexSample> samples = mBuffer.getElements();
double centerX = (double)getHeight() / 2.0d;
double centerY = (double)getWidth() / 2.0d;
// double scale = centerX / 2.0d;
double scale = 0.5d;
mCounter = 0;
for( ComplexSample sample: samples )
{
if( mCounter > ( mOffset + mSamplesPerSymbol ) )
{
/**
* Multiply the current sample against the complex conjugate of the
* previous sample to derive the phase delta between the two samples
*
* Negating the previous sample quadrature produces the conjugate
*/
double i = ( sample.inphase() * mPrevious.inphase() ) -
( sample.quadrature() * -mPrevious.quadrature() );
double q = ( sample.quadrature() * mPrevious.inphase() ) +
( sample.inphase() * -mPrevious.quadrature() );
mLog.debug( "i:" + i + " q:" + q );
double angle;
//Check for divide by zero
if( i == 0 )
{
angle = 0.0;
}
else
{
/**
* Use the arcus tangent of imaginary (q) divided by real (i) to
* get the phase angle (+/-) which was directly manipulated by the
* original message waveform during the modulation. This value now
* serves as the instantaneous amplitude of the demodulated signal
*/
double denominator = 1.0d / i;
angle = Math.atan( (double)q * denominator );
}
Ellipse2D.Double ellipse =
new Ellipse2D.Double( centerX - ( i * scale ),
centerY - ( q * scale ), 4, 4 );
graphics.draw( ellipse );
mCounter -= mSamplesPerSymbol;
}
mCounter++;
// mLog.debug( "Plotting x:" + ( centerX - ( sample.x() * scale ) ) +
// " y:" + ( centerY - ( sample.y() * scale ) ) + " for sample " + sample.toString() );
}
}
public class TimingOffsetItem extends JSlider implements ChangeListener
{
private static final long serialVersionUID = 1L;
public TimingOffsetItem( int maxValue, int currentValue )
{
super( JSlider.HORIZONTAL, 0, maxValue, currentValue );
setMajorTickSpacing( 10 );
setMinorTickSpacing( 5 );
setPaintTicks( true );
setPaintLabels( true );
addChangeListener( this );
}
@Override
public void stateChanged( ChangeEvent event )
{
int value = ((JSlider)event.getSource()).getValue();
setOffset( (float)value / 10.0f );
}
}
}

View File

@@ -55,11 +55,11 @@ import decode.p25.P25Decoder;
import decode.passport.PassportDecoder;
import decode.tait.Tait1200Decoder;
public class WaveSourceFrame extends JInternalFrame implements PositionListener
public class AudioSourceFrame extends JInternalFrame implements PositionListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( WaveSourceFrame.class );
LoggerFactory.getLogger( AudioSourceFrame.class );
private WaveSource mSource;
private JDesktopPane mDesktop;
@@ -67,7 +67,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
private JLabel mCurrentPosition = new JLabel( "0" );
private JComboBox<DecoderType> mComboDecoders;
public WaveSourceFrame( WaveSource source, JDesktopPane desktop )
public AudioSourceFrame( WaveSource source, JDesktopPane desktop )
{
mSource = source;
mSource.addListener( this );
@@ -79,7 +79,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
private void initGui()
{
setTitle( "Wave Source [" + mSource.getSampleType().toString() + "]" );
setTitle( "Audio Source [" + mSource.getSampleType().toString() + "]" );
setPreferredSize( new Dimension( 600, 150 ) );
setSize( 600, 150 );
@@ -125,6 +125,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
public class AddDecoderButton extends JButton
{
private static final long serialVersionUID = 1L;
public AddDecoderButton()
{
super( "Add" );
@@ -167,6 +169,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
case TAIT_1200:
decoder = new Tait1200Decoder( null );
break;
default:
break;
}
if( decoder != null )
@@ -184,6 +188,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
public class NextSampleButton extends JButton
{
private static final long serialVersionUID = 1L;
private WaveSource mSource;
private int mCount;
@@ -207,7 +213,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
mLog.error( "Viewer - error trying to fetch next [" +
mCount + "] samples", e );
JOptionPane.showMessageDialog( WaveSourceFrame.this,
JOptionPane.showMessageDialog( AudioSourceFrame.this,
"Cannot read " + mCount + " more samples [" +
e.getLocalizedMessage() + "]",
"Wave File Error",
@@ -220,6 +226,8 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
public class JumpToField extends JTextField
{
private static final long serialVersionUID = 1L;
private WaveSource mSource;
public JumpToField( WaveSource source )
@@ -248,7 +256,7 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener
mLog.error( "WaveSourceFrame - exception during jump "
+ "to", e );
JOptionPane.showMessageDialog( WaveSourceFrame.this,
JOptionPane.showMessageDialog( AudioSourceFrame.this,
"Can't jump to position [" + getText() + "]",
"Error",
JOptionPane.ERROR_MESSAGE );

View File

@@ -0,0 +1,240 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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 instrument.gui;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.IOException;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexSample;
import source.wave.ComplexWaveSource;
import source.wave.WaveSource;
import source.wave.WaveSource.PositionListener;
public class BasebandSourceFrame extends JInternalFrame implements PositionListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( BasebandSourceFrame.class );
private ComplexWaveSource mSource;
private JDesktopPane mDesktop;
private JLabel mCurrentPosition = new JLabel( "0" );
private JComboBox<Panels> mComboPanels;
public BasebandSourceFrame( ComplexWaveSource source, JDesktopPane desktop )
{
mSource = source;
mSource.addListener( this );
mDesktop = desktop;
initGui();
}
private void initGui()
{
setTitle( "Baseband Source [" + mSource.getSampleType().toString() + "]" );
setPreferredSize( new Dimension( 600, 150 ) );
setSize( 600, 150 );
setResizable( true );
setClosable( true );
setIconifiable( true );
setMaximizable( false );
JPanel panel = new JPanel();
panel.setLayout( new MigLayout() );
JLabel fileLabel = new JLabel( "File: " + mSource.getFile().getName() );
fileLabel.setToolTipText( mSource.getFile().getName() );
panel.add( fileLabel, "span,wrap" );
panel.add( new JLabel( "Jump:" ) );
panel.add( new JumpToField( mSource ) );
panel.add( new NextSampleButton( mSource, ">", 1 ) );
panel.add( new NextSampleButton( mSource, "> 100 >", 100 ) );
panel.add( new NextSampleButton( mSource, "> 1000 >", 1000 ) );
panel.add( new JLabel( "Posn:" ) );
panel.add( mCurrentPosition, "wrap" );
/* ComboBox: Viewer Panels */
mComboPanels = new JComboBox<Panels>(
new DefaultComboBoxModel<Panels>( Panels.values() ) );
panel.add( new JLabel( "Panels:" ) );
panel.add( mComboPanels, "span 3,grow" );
panel.add( new AddPanelButton(), "grow" );
add( panel );
}
public class AddPanelButton extends JButton
{
private static final long serialVersionUID = 1L;
public AddPanelButton()
{
super( "Add" );
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
Panels selected =
(Panels)mComboPanels.getSelectedItem();
mLog.info( "Selected:" + selected.name() );
switch( selected )
{
case CONSTELLATION:
ConstellationPanel con = new ConstellationPanel();
mDesktop.add( con );
con.setVisible( true );
if( mSource != null )
{
mSource.addListener( (Listener<ComplexSample>)con );
}
break;
default:
break;
}
}
} );
}
}
public class NextSampleButton extends JButton
{
private static final long serialVersionUID = 1L;
private WaveSource mSource;
private int mCount;
public NextSampleButton( WaveSource source, String label, int count )
{
super( label );
mSource = source;
mCount = count;
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent arg0 )
{
try
{
mSource.next( mCount );
}
catch ( IOException e )
{
mLog.error( "Viewer - error trying to fetch next [" +
mCount + "] samples", e );
JOptionPane.showMessageDialog( BasebandSourceFrame.this,
"Cannot read " + mCount + " more samples [" +
e.getLocalizedMessage() + "]",
"Wave File Error",
JOptionPane.ERROR_MESSAGE );
}
}
} );
}
}
public class JumpToField extends JTextField
{
private static final long serialVersionUID = 1L;
private WaveSource mSource;
public JumpToField( WaveSource source )
{
super( "0" );
mSource = source;
setMinimumSize( new Dimension( 100, getHeight() ) );
addFocusListener( new FocusListener()
{
@Override
public void focusGained( FocusEvent arg0 ) {}
@Override
public void focusLost( FocusEvent arg0 )
{
try
{
long position = Long.parseLong( getText() );
mSource.jumpTo( position );
}
catch( Exception e )
{
mLog.error( "WaveSourceFrame - exception during jump "
+ "to", e );
JOptionPane.showMessageDialog( BasebandSourceFrame.this,
"Can't jump to position [" + getText() + "]",
"Error",
JOptionPane.ERROR_MESSAGE );
}
}
} );
}
}
@Override
public void positionUpdated( long position, boolean reset )
{
mCurrentPosition.setText( String.valueOf( position ) );
if( reset )
{
}
}
public enum Panels
{
CONSTELLATION;
}
}

View File

@@ -0,0 +1,76 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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 instrument.gui;
import gui.control.ConstellationViewer;
import java.awt.Dimension;
import javax.swing.JInternalFrame;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.complex.ComplexSample;
import source.wave.WaveSource.PositionListener;
public class ConstellationPanel extends JInternalFrame
implements PositionListener, Listener<ComplexSample>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( ConstellationPanel.class );
private ConstellationViewer mConstellation =
new ConstellationViewer( 48000, 4800 );
public ConstellationPanel()
{
initGui();
}
private void initGui()
{
setLayout( new MigLayout( "insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]" ) );
setTitle( "Constellation View" );
setPreferredSize( new Dimension( 300, 300 ) );
setSize( 300, 300 );
setResizable( true );
setClosable( true );
setIconifiable( true );
setMaximizable( false );
add( mConstellation );
}
@Override
public void positionUpdated( long position, boolean reset )
{
}
@Override
public void receive( ComplexSample sample )
{
mConstellation.receive( sample );
}
}

View File

@@ -51,21 +51,11 @@ public class Viewer
public Viewer()
{
initGUI();
EventQueue.invokeLater( new Runnable()
{
public void run()
{
try
{
mFrame.setVisible( true );
}
catch( Exception e )
{
e.printStackTrace();
}
}
} );
}
public void setVisible( boolean visible )
{
mFrame.setVisible( visible );
}
/**
@@ -146,10 +136,15 @@ public class Viewer
source = floatSource;
mLog.info( "File opened as float wave file" );
AudioSourceFrame sourcePanel =
new AudioSourceFrame( source, mDesktop );
sourcePanel.setVisible( true );
mDesktop.add( sourcePanel );
}
catch( Exception e )
{
mLog.error( "Couldn't open file as float source", e);
mLog.error( "Couldn't open file as float single-channel source" );
}
if( source == null )
@@ -163,6 +158,13 @@ public class Viewer
source = complex;
mLog.info( "File opened as complex wave file" );
BasebandSourceFrame sourcePanel =
new BasebandSourceFrame( complex, mDesktop );
sourcePanel.setVisible( true );
mDesktop.add( sourcePanel );
}
catch( Exception e )
{
@@ -174,20 +176,34 @@ public class Viewer
{
mSource = source;
WaveSourceFrame sourcePanel =
new WaveSourceFrame( source, mDesktop );
sourcePanel.setVisible( true );
mDesktop.add( sourcePanel );
}
}
public static void main( String[] args )
{
final Viewer viewer = new Viewer();
EventQueue.invokeLater( new Runnable()
{
public void run()
{
try
{
viewer.setVisible( true );
}
catch( Exception e )
{
e.printStackTrace();
}
}
} );
}
public class SourceFileItem extends JMenuItem
{
private static final long serialVersionUID = 1L;
public SourceFileItem()
{
super( "Open" );

View File

@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
@@ -127,10 +128,14 @@ public class ComplexWaveSource extends WaveSource
mBytesPerFrame = mInputStream.getFormat().getFrameSize();
if( mBytesPerFrame != 2 || mInputStream.getFormat().getChannels() != 2 )
AudioFormat format = mInputStream.getFormat();
if( format.getChannels() != 2 || format.getSampleSizeInBits() != 16 )
{
throw new IOException( "Unsupported Wave Format - requires two "
+ "channels with 16-bit samples" );
throw new IOException( "Unsupported Wave Format - EXPECTED: 2 " +
"channels 16-bit samples FOUND: " +
mInputStream.getFormat().getChannels() + " channels " +
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" );
}
mBuffer = new byte[ mBytesPerFrame * BUFFER_SAMPLE_SIZE ];