Compare commits

...

28 Commits

Author SHA1 Message Date
Denny
d95c78c0f1 Updates version to 0.4.0 alpha 5 2018-09-23 05:51:28 -04:00
Denny Sheirer
aee5ece8d2 Merge pull request #430 from DSheirer/429-polyphase-channelizer-25khz-channel-rate
429 polyphase channelizer 25khz channel rate
2018-09-23 05:50:18 -04:00
Denny
ba358a7d48 Adjusted costas loop gain values. Updated channel filter values to mitigate adjacent channel interference. 2018-09-23 05:34:41 -04:00
Denny
09b209496b Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 09:19:15 -04:00
Denny Sheirer
cc209683bd Merge pull request #428 from DSheirer/377-npe-in-trellis-decoder
377 NPE in P25 3/4 Rate Trellis Decoder
2018-09-22 02:53:16 -04:00
Denny
2771c28dba Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 02:52:36 -04:00
Denny
d752917073 Resolves #427. Moves the module(s) reset operation to the stop() method
so that the modules are reset after they are shut down, in order to
prepare for processing chain reuse.

Previously, the modules were reset upon startup, but after any traffic
channel metadata had already been transferred to the channel state,
causing the metadata to be lost and allowing initial audio packets to
stream without any metadata and related streaming/monitoring rules.
2018-09-20 04:01:45 -04:00
Denny
28bc02e172 Updates channel state to ensure teardown processing is completed during
a call end and/or during a commanded stop.
2018-09-19 04:25:37 -04:00
Denny
0f6044d48b Updates mutable metadata to reset the buffer overflow flag between uses. 2018-09-18 19:15:06 -04:00
Denny Sheirer
58ecb83e2c Merge pull request #425 from DSheirer/417-channel-overflow-state-processing
417 Channel Overflow State Processing - Part 2
2018-09-16 10:23:15 -04:00
Denny
9e1de922e4 Resolves issue with overflow state processing. Displays the overflow
state in the Message field of the channel metadata table column instead
of displaying it in the channel state column.
2018-09-16 10:22:27 -04:00
Denny Sheirer
84d0b827d8 Merge pull request #422 from DSheirer/421-nbfm-25-khz-channel-bandwidth
421 Add 25 kHz Channel Bandwidth Support to NBFM Decoder
2018-09-16 07:22:38 -04:00
Denny
a17132be0d Updates the NBFM decoder to allow user to select between 12.5 and 25.0
kHz channel bandwidth so that 25 kHz channels can be demodulated correctly.
2018-09-16 07:21:14 -04:00
Denny Sheirer
dae68515a5 Merge pull request #420 from DSheirer/419-heterdyne-channelizer-center-frequency-tuning
419 Heterodyne Channelizer Center Frequency Calculations
2018-09-16 06:04:09 -04:00
Denny
0aaec8ff54 Updates the heterodyne channelizer's center frequency calculator to
only update the center frequency when needed and to place the set of
decoding channels adjacent to the right side of the tuner's center
frequency when the complete set of channels fit into the usable half
bandwidth of the tuner.
2018-09-16 06:02:46 -04:00
Denny Sheirer
001883edc2 Merge pull request #418 from DSheirer/417-channel-overflow-zombie-state
417 Traffic Channels in Buffer Overflow state do not tear down correctly
2018-09-16 05:23:05 -04:00
Denny
335fb51456 Updates buffer overflow handling to occur coincident to other channel
states rather than being a channel state itself.  This was preventing
traffic channels that overflow from being torn down correctly.
2018-09-16 05:21:46 -04:00
Denny Sheirer
e94b6ae172 Merge pull request #416 from DSheirer/415-overflow-state
415 Buffer Overflow State Listener Registration
2018-09-14 06:46:34 -04:00
Denny
785125da4f Correctly implements the buffer overflow listener registration so that
decoding channels can be notified when the internal buffer goes into
and out of overflow state.
2018-09-14 06:45:37 -04:00
Denny
81b8f5a423 Updates build to version 0.4.0 alpha 4 2018-09-12 06:23:09 -04:00
Denny Sheirer
bf186a7408 Merge pull request #414 from DSheirer/405-airspy-tuner-waterfall-freeze
405 Airspy Spectrum/Waterfall Freezes
2018-09-12 06:20:36 -04:00
Denny
43fc326f93 Resolves #405
Adds 500 ms delay to initial registering for samples from all tuners to
feed the spectral display/waterfall.  The existing approach of immediately
registering for samples was causing the airspy tuner to lockup
aperiodically.  Investigation via debugger didn't show any application
thread locks ... usb master processor was continuing to service libusb
timeouts.  Unable to debug internal airspy processes.  Adding the delay
to startup seems to be a usable solution.
2018-09-12 06:19:11 -04:00
Denny Sheirer
7cdbb75b42 Merge pull request #413 from DSheirer/375-poor-p25-decode-performance
375 P25 Decode Performance
2018-09-10 04:22:40 -04:00
Denny
96a56cab68 Resolves #375.
Reverts PLL gain values to version 0.3.4beta2 values to restore P25
decoder performance.

This change in conjuction with recent channel sample rate change should
restore P25 channel decoding to comparable levels from 0.3.4b2.
2018-09-09 09:55:40 -04:00
Denny Sheirer
29d4b36cf7 Merge pull request #412 from DSheirer/411-variable-channel-sample-rates
#411 Update Tuner Source Manager to provide variable sample rate channels
2018-09-09 09:50:11 -04:00
Denny
b15a3e59a0 Resolves #411
Updates decoder types to specify channel sample rate and pass/stop
frequencies for requested channels.  Updates tuner channel source
managers to accept a channel specification for channels that are
allocated.
2018-09-09 09:49:05 -04:00
Denny Sheirer
9950e46e5a Merge pull request #408 from DSheirer/406-tuner-sample-delay-buffer
406 Tuner Sample Delay Buffer
2018-09-09 08:47:17 -04:00
Denny
570321da3e Implements a complex sample delay buffer integrated into the tuner
channel manager so that channel source requests can have a start
timestamp specified and the channel can be preloaded with samples from
the delay buffer.  If no timestamp is specified, current time is assumed.

This feature requires corresponding updates to any decoder that wishes
to implement the timestamped sample preloading functionality.
2018-09-09 08:46:21 -04:00
67 changed files with 1554 additions and 691 deletions

View File

@@ -15,7 +15,7 @@ repositories {
mavenCentral()
}
version = '0.4.0-alpha.3'
version = '0.4.0-alpha.5'
sourceCompatibility = 1.8
sourceSets {

View File

@@ -20,6 +20,7 @@ package io.github.dsheirer.channel.metadata;
public enum Attribute
{
BUFFER_OVERFLOW,
CHANNEL_CONFIGURATION_NAME,
CHANNEL_CONFIGURATION_SITE,
CHANNEL_CONFIGURATION_SYSTEM,

View File

@@ -116,6 +116,16 @@ public class AttributeChangeRequest<T>
return null;
}
public Boolean getBooleanValue()
{
if(mValue instanceof Boolean)
{
return (Boolean)mValue;
}
return false;
}
/**
* Returns the value as a DecoderType or null if the value is not a DecoderType instance.
*/

View File

@@ -1,19 +1,16 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* sdr-trunk
* Copyright (C) 2014-2018 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 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.
* 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/>
* 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.channel.metadata;
@@ -21,9 +18,11 @@ package io.github.dsheirer.channel.metadata;
import io.github.dsheirer.channel.state.State;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
@@ -32,6 +31,8 @@ import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataModel.class);
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
public static final int COLUMN_STATE = 0;
@@ -45,7 +46,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
public static final int COLUMN_CONFIGURATION_NAME = 8;
public static final int COLUMN_MESSAGE = 9;
private static final String[] COLUMNS = {"State", "Decoder", "Channel", "Frequency", "Primary From", "Primary To",
private static final String[] COLUMNS = {"Status", "Decoder", "Channel", "Frequency", "Primary From", "Primary To",
"Secondary From", "Secondary To", "Channel Name", "Message"};
private List<MutableMetadata> mChannelMetadata = new ArrayList();
@@ -184,7 +185,11 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
return null;
case COLUMN_MESSAGE:
if(metadata.hasMessageType() | metadata.hasMessage())
if(metadata.isBufferOverflow())
{
return "**OVERFLOW**";
}
else if(metadata.hasMessageType() || metadata.hasMessage())
{
StringBuilder sb = new StringBuilder();
if(metadata.hasMessageType())
@@ -248,6 +253,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
case CHANNEL_STATE:
fireTableCellUpdated(rowIndex, COLUMN_STATE);
break;
case BUFFER_OVERFLOW:
case MESSAGE:
case MESSAGE_TYPE:
fireTableCellUpdated(rowIndex, COLUMN_MESSAGE);

View File

@@ -120,8 +120,6 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
mForegroundColors.put(State.FADE, Color.DARK_GRAY);
mBackgroundColors.put(State.IDLE, Color.WHITE);
mForegroundColors.put(State.IDLE, Color.DARK_GRAY);
mBackgroundColors.put(State.OVERFLOW, Color.RED);
mForegroundColors.put(State.OVERFLOW, Color.YELLOW);
mBackgroundColors.put(State.RESET, Color.PINK);
mForegroundColors.put(State.RESET, Color.YELLOW);
mBackgroundColors.put(State.TEARDOWN, Color.DARK_GRAY);

View File

@@ -41,6 +41,7 @@ public class Metadata
protected boolean mUpdated;
protected DecoderType mPrimaryDecoderType;
protected boolean mSelected;
protected boolean mBufferOverflow;
protected State mState = State.IDLE;
protected String mChannelConfigurationSystem;
protected String mChannelConfigurationSite;
@@ -173,6 +174,14 @@ public class Metadata
return mSelected;
}
/**
* Indicates if the channel for this metadata is in a buffer overflow state.
*/
public boolean isBufferOverflow()
{
return mBufferOverflow;
}
/**
* List of de-duplicated broadcast channels aggregated from the primary and secondary TO/FROM aliases.
*/

View File

@@ -80,6 +80,10 @@ public class MutableMetadata extends Metadata implements Listener<AttributeChang
{
switch(request.getAttribute())
{
case BUFFER_OVERFLOW:
mBufferOverflow = request.getBooleanValue();
broadcast(Attribute.BUFFER_OVERFLOW);
break;
case CHANNEL_CONFIGURATION_SYSTEM:
mChannelConfigurationSystem = request.getStringValue();
broadcast(Attribute.CHANNEL_CONFIGURATION_SYSTEM);
@@ -164,6 +168,7 @@ public class MutableMetadata extends Metadata implements Listener<AttributeChang
public void resetTemporalAttributes()
{
mDoNotRecord = null;
mBufferOverflow = false;
if(hasMessage())
{

View File

@@ -135,21 +135,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override
public void stop()
{
mMutableMetadata.resetAllAttributes();
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
}
mTrafficChannelCallEvent = null;
processTeardownState();
mSquelchLocked = false;
setState(State.TEARDOWN);
}
public void dispose()
@@ -346,8 +333,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
}
}
@@ -370,8 +356,19 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
mSourceOverflow = overflow;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
mMutableMetadata.receive(new AttributeChangeRequest<Boolean>(Attribute.BUFFER_OVERFLOW, mSourceOverflow));
}
/**
* Indicates if this channel's sample buffer is in overflow state, meaning that the inbound sample
* stream is not being processed fast enough and samples are being thrown away until the processing can
* catch up.
*
* @return true if the channel is in overflow state.
*/
public boolean isOverflow()
{
return mSourceOverflow;
}
/**
@@ -410,9 +407,13 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
if(mTrafficChannelEndListener != null && mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
mTrafficChannelEndListener.callEnd(mTrafficChannelCallEvent.getChannel());
mTrafficChannelEndListener = null;
mTrafficChannelCallEvent = null;
}
}

View File

@@ -34,7 +34,7 @@ public enum State
@Override
public boolean canChangeTo(State state)
{
return state == CALL |
return state == CALL ||
state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
@@ -108,8 +108,7 @@ public enum State
public boolean canChangeTo(State state)
{
return state != FADE &&
state != RESET &&
state != OVERFLOW;
state != RESET;
}
},
/**
@@ -121,25 +120,10 @@ public enum State
public boolean canChangeTo(State state)
{
return state != TEARDOWN &&
state != RESET &&
state != OVERFLOW;
}
},
/**
* Indicates that the channel's sample buffer has overflowed and new buffer samples are no longer being
* delivered to the channel. This state persists until the processing catches up and reduces the number
* of samples in the input buffer below a threshold value. Transition to and from this state is exclusively
* managed by the Channel State and no other channel states can be processed from the channel decoders
* until the channel state is taken out of overflow.
*/
OVERFLOW("OVERFLOW")
{
@Override
public boolean canChangeTo(State state)
{
return false;
state != RESET;
}
},
/**
* Indicates that the channel has been reset and is ready to use for a new decoding session.
*/
@@ -191,4 +175,10 @@ public enum State
{
return mDisplayValue;
}
@Override
public String toString()
{
return mDisplayValue;
}
}

View File

@@ -186,7 +186,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
if(frequency > 0)
{
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(frequency,
mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
mDecodeConfiguration.getChannelSpecification().getBandwidth()));
if(channel != null)
{

View File

@@ -489,14 +489,14 @@ public class Channel extends Configuration implements Listener<SourceEvent>
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{
SourceConfigRecording config = (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
}

View File

@@ -212,7 +212,7 @@ public class ChannelProcessingManager implements ChannelEventListener
try
{
source = mSourceManager.getSource(channel.getSourceConfiguration(),
channel.getDecodeConfiguration().getDecoderType().getChannelBandwidth());
channel.getDecodeConfiguration().getChannelSpecification());
}
catch(SourceException se)
{

View File

@@ -63,7 +63,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 12500;
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));

View File

@@ -29,6 +29,7 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
@@ -62,7 +63,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0;
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
private static final int POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL = 9;
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
@@ -141,7 +142,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* @param tunerChannel specifying center frequency and bandwidth.
* @return source or null.
*/
public TunerChannelSource getChannel(TunerChannel tunerChannel)
public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{
PolyphaseChannelSource channelSource = null;
@@ -155,10 +156,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency);
try
{
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
mChannelSources.add(channelSource);
mChannelSources.add(channelSource);
}
catch(FilterDesignException fde)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
}
}
}
catch(IllegalArgumentException iae)

View File

@@ -16,22 +16,35 @@
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class PolyphaseChannelSource extends TunerChannelSource
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
private long mReplacementFrequency;
private ComplexFIRFilter2 mLowPassFilter;
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
@@ -46,15 +59,22 @@ public class PolyphaseChannelSource extends TunerChannelSource
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
* @param channelSampleRate for the downstream sample output
* @param centerFrequency of the incoming polyphase channel(s)
* @throws FilterDesignException if a channel low pass filter can't be designed to the channel specification
*/
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
long centerFrequency)
long centerFrequency, ChannelSpecification channelSpecification)
throws FilterDesignException
{
super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor;
mChannelSampleRate = channelSampleRate;
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(),
channelSpecification.getStopFrequency());
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
setFrequency(centerFrequency);
}
@@ -93,14 +113,33 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency)
{
//Lock on the channel output processor to block the channel results processor thread from servicing the
//channel output processor's buffer while we change out the processors.
synchronized(mPolyphaseChannelOutputProcessor)
//If this is the first time, simply assign the output processor
if(mPolyphaseChannelOutputProcessor == null)
{
mPolyphaseChannelOutputProcessor = outputProcessor;
}
//Otherwise, we have to swap out the processor on the sample processing thread
else
{
mReplacementPolyphaseChannelOutputProcessor = outputProcessor;
mReplacementFrequency = frequency;
}
}
/**
* Updates the output processor to use the new output processor provided by the external
* polyphase channel manager. This method should only be executed on the sample processing
* thread, within the processChannelResults() method.
*/
private void swapOutputProcessor()
{
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
mPolyphaseChannelOutputProcessor = outputProcessor;
mPolyphaseChannelOutputProcessor = mReplacementPolyphaseChannelOutputProcessor;
mReplacementPolyphaseChannelOutputProcessor = null;
//Remove the buffer overflow listener and clear any previous buffer overflow state
existingProcessor.setSourceOverflowListener(null);
@@ -109,18 +148,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
//Register to receive buffer overflow notifications so we can push them up through the source chain
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
//Fully process the residual channel results buffer of the previous channel output processor
if(existingProcessor != null)
{
existingProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
//to allow consumers to adjust and calculate a new frequency correction value, if needed.
mIndexCenterFrequency = frequency;
mChannelFrequencyCorrection = 0;
//Finally, setup the frequency offset for the output processor.
mIndexCenterFrequency = mReplacementFrequency;
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
}
@@ -133,12 +163,13 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
{
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
if(mPolyphaseChannelOutputProcessor != null)
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
swapOutputProcessor();
}
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
}
/**
@@ -173,7 +204,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.dispose();
mPolyphaseChannelOutputProcessor = null;
}
}
@@ -213,11 +243,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override
protected void processSamples()
{
//Lock on the output processor so that it can't be changed in the middle of processing
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
/**
@@ -236,10 +262,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
private void updateFrequencyOffset()
{
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
@Override
@@ -248,4 +271,42 @@ public class PolyphaseChannelSource extends TunerChannelSource
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency();
}
/**
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
*
* @param sampleRate for the final output channel rate
* @param passFrequency for half of the desired channel rate
* @param stopFrequency for the attenuated band
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//Use existing filter if we've already designed one
if(sLowPassFilters.containsKey((int)sampleRate))
{
return sLowPassFilters.get((int)sampleRate);
}
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.passBandCutoff(passFrequency)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopFrequency)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//This will throw an exception if the filter cannot be designed
float[] taps = designer.getImpulseResponse();
sLowPassFilters.put((int)sampleRate, taps);
return taps;
}
}

View File

@@ -16,29 +16,18 @@
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private static Map<Integer,float[]> sLowPassFilterKernelMap = new HashMap<>();
// private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private TwoChannelSynthesizerM2 mSynthesizer;
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
private ComplexFIRFilter2 mLowPassFilter;
private int mChannelOffset1;
private int mChannelOffset2;
@@ -58,28 +47,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
super(2, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes);
setSynthesisFilter(filter);
float[] lowPassFilter = sLowPassFilterKernelMap.get((int)sampleRate);
if(lowPassFilter == null)
{
try
{
lowPassFilter = getLowPassFilter(sampleRate);
sLowPassFilterKernelMap.put((int)sampleRate, lowPassFilter);
}
catch(FilterDesignException fde)
{
mLog.error("Couldn't design a low-pass filter for 2-channel synthesizer", fde);
throw new IllegalStateException("Cannot design low-pass filter for 2 channel synthesizer", fde);
}
}
mLowPassFilter = new ComplexFIRFilter2(lowPassFilter, 1.0f);
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
*
* @param frequencyOffset in hertz
*/
@Override
@@ -126,7 +98,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers)
for(ReusableChannelResultsBuffer buffer : channelResultsBuffers)
{
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
@@ -140,48 +112,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
synthesized.applyGain(getGain());
lowPassFiltered.applyGain(getGain());
reusableComplexBufferAssembler.receive(lowPassFiltered);
reusableComplexBufferAssembler.receive(synthesized);
buffer.decrementUserCount();
}
}
/**
* Creates a low-pass filter kernel for the specified sample rate.
* @param sampleRate for the channel
* @return filter
* @throws FilterDesignException if the filter cannot be designed
*/
private static float[] getLowPassFilter(double sampleRate) throws FilterDesignException
{
double cutoff = sampleRate / 4.0;
int passBandCutoff = (int)(cutoff * 0.92);
int stopBandStart = (int)(cutoff * 1.08);
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.oddLength(true)
.passBandCutoff((int)passBandCutoff)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart((int)stopBandStart)
.stopBandAmplitude(0.0)
.stopBandRipple(0.005) //Approximately 94 dB attenuation
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
return designer.getImpulseResponse();
}
throw new FilterDesignException("Couldn't design low-pass filter for sample rate: " + sampleRate +
" Pass End:" + passBandCutoff + " Stop Begin:" + stopBandStart);
}
}

View File

@@ -104,7 +104,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
* and specified pass/stop frequencies.
*/
public ComplexPrimeCICDecimate(double sampleRate, int decimation, int passFrequency, int stopFrequency)
public ComplexPrimeCICDecimate(double sampleRate, int decimation, double passFrequency, double stopFrequency)
throws FilterDesignException
{
Validate.isTrue(decimation <= PRIMES[PRIMES.length - 1]);
@@ -400,7 +400,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
private ComplexFIRFilter2 mLowPassFilter;
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
public Output(double outputSampleRate, int passFrequency, int stopFrequency) throws FilterDesignException
public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate);
@@ -461,7 +461,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, int passFrequency, int stopFrequency) throws FilterDesignException
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//Use existing filter if we've already designed one
if(sLowPassFilters.containsKey((int)sampleRate))

View File

@@ -1,7 +1,7 @@
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;
@@ -61,14 +61,15 @@ public class FilterViewer extends Application
// .passBandRipple(0.01)
// .build();
FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
.sampleRate(8000)
.stopFrequency1(1000)
.passFrequencyBegin(1100)
.passFrequencyEnd(1900)
.stopFrequency2(2000)
.stopRipple(0.000001)
.passRipple(0.00001)
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(25000.0)
.gridDensity(16)
.passBandCutoff(11800)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(12400)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
float[] taps = null;
@@ -81,37 +82,25 @@ public class FilterViewer extends Application
// catch(FilterDesignException fde)
// {
// mLog.error("Error");
// }
// try
// {
// RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//
// if(designer.isValid())
// {
// taps = designer.getImpulseResponse();
// }
// }
// catch(FilterDesignException fde)
// {
// mLog.error("Filter design error", fde);
// }
//
// if(taps == null)
// {
// throw new IllegalStateException("Couldn't design filter");
// }
try
{
// taps = FilterFactory.getSincM2Synthesizer(0.25, 2, 9);
// taps = FilterFactory.getSincM2Channelizer(12500.0, 2, 9, true);
taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 9);
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
taps = designer.getImpulseResponse();
}
}
catch(FilterDesignException e)
catch(FilterDesignException fde)
{
e.printStackTrace();
mLog.error("Filter design error", fde);
}
if(taps == null)
{
throw new IllegalStateException("Couldn't design filter");
}
return taps;

View File

@@ -299,8 +299,8 @@ public class FIRFilterSpecification
private int mOrder;
private Boolean mOddLength;
private int mGridDensity = 16;
private int mPassBandEndFrequency;
private int mStopBandStartFrequency;
private double mPassBandEndFrequency;
private double mStopBandStartFrequency;
private double mPassBandRipple;
private double mStopBandRipple;
private double mPassBandAmplitude = 1.0;
@@ -342,13 +342,13 @@ public class FIRFilterSpecification
return this;
}
public LowPassBuilder passBandCutoff(int frequencyHz)
public LowPassBuilder passBandCutoff(double frequencyHz)
{
mPassBandEndFrequency = frequencyHz;
return this;
}
public LowPassBuilder stopBandStart(int frequencyHz)
public LowPassBuilder stopBandStart(double frequencyHz)
{
mStopBandStartFrequency = frequencyHz;
return this;
@@ -905,7 +905,7 @@ public class FIRFilterSpecification
* @param stopBandRipple stop band ripple in dB
* @return estimated filter length
*/
public static int estimateFilterOrder(double sampleRate, int frequency1, int frequency2,
public static int estimateFilterOrder(double sampleRate, double frequency1, double frequency2,
double passBandRipple, double stopBandRipple)
{
double df = Math.abs(frequency2 - frequency1) / sampleRate;
@@ -1013,13 +1013,13 @@ public class FIRFilterSpecification
* @param amplitude of the frequency band relative to unity (0.0 - 1.0)
* @param ripple of the frequency band specified as db ripple
*/
public FrequencyBand(double sampleRate, int start, int end, double amplitude, double ripple)
public FrequencyBand(double sampleRate, double start, double end, double amplitude, double ripple)
{
this((double)start / sampleRate, (double)end / sampleRate,
this(start / sampleRate, end / sampleRate,
amplitude, ripple);
}
public FrequencyBand(double sampleRate, int start, int end, double amplitude, double ripple, double weight)
public FrequencyBand(double sampleRate, double start, double end, double amplitude, double ripple, double weight)
{
this(sampleRate, start, end, amplitude, ripple);
mWeight = weight;

View File

@@ -95,10 +95,45 @@ public class DQPSKDecisionDirectedSymbolEvaluator implements IPSKSymbolEvaluator
//Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
//arctan of the error angle relative to 0 radians and this provides our error value
mPhaseError = -mEvaluationSymbol.quadrature();
float errorNormalized = normalize(mEvaluationSymbol.quadrature(), 0.3f);
mPhaseError = -errorNormalized;
//Timing error is the same as phase error with the sign corrected according to the vector's angular rotation
mTimingError = mEvaluationSymbol.quadrature() * mTimingErrorPolarity;
mTimingError = errorNormalized * mTimingErrorPolarity;
}
/**
* Constrains value to the range of ( -maximum <> maximum )
*/
private static float clip(float value, float maximum)
{
if(value > maximum)
{
return maximum;
}
else if(value < -maximum)
{
return -maximum;
}
return value;
}
/**
* Constrains timing error to +/- the maximum value and corrects any
* floating point invalid numbers
*/
private float normalize(float error, float maximum)
{
if(Float.isNaN(error))
{
return 0.0f;
}
else
{
return clip(error, maximum);
}
}
/**

View File

@@ -100,7 +100,7 @@ public class DQPSKGardnerSymbolEvaluator implements IPSKSymbolEvaluator<Dibit>
//Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
//arctan of the error angle relative to 0 radians and this provides our error value
mPhaseError = -mEvaluationSymbol.quadrature();
mPhaseError = normalize(-mEvaluationSymbol.quadrature(), 0.3f);
}
/**

View File

@@ -20,11 +20,13 @@ package io.github.dsheirer.dsp.psk.pll;
*/
public enum PLLGain
{
LEVEL_1(50.0, 0, 1),
LEVEL_2(75.0, 2, 4),
LEVEL_3(100.0, 5, 6),
LEVEL_4(125.0, 7, 8),
LEVEL_5(150.0, 9, 10);
//NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2
LEVEL_1(150.0, 0, 1),
LEVEL_2(170.0, 2, 4),
LEVEL_3(190.0, 5, 6),
LEVEL_4(200.0, 7, 8),
LEVEL_5(200.0, 9, 10);
private double mLoopBandwidth;
private int mRangeStart;

View File

@@ -260,7 +260,7 @@ public class ChannelizerViewer extends JFrame
for(int x = 0; x < mChannelCount; x++)
{
TunerChannel tunerChannel = new TunerChannel(100000000, 12500);
TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x);
channelPanel.setDFTSize(mChannelPanelDFTSize);
@@ -362,7 +362,7 @@ public class ChannelizerViewer extends JFrame
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
if(mSource != null)
{
@@ -556,7 +556,7 @@ public class ChannelizerViewer extends JFrame
{
if(sourceCount < maxSourceCount)
{
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel);
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel, null);
if(source != null)
{

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
@@ -376,10 +377,11 @@ public class ChannelizerViewer2 extends JFrame
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, new ChannelSpecification(50000, 12500, 6000, 7000));
if(mSource != null)
{
mLog.debug("Channel: " + mSource.getTunerChannel() + " Rate:" + mSource.getSampleRate());
mSource.setListener(new Listener<ReusableComplexBuffer>()
{
@Override

View File

@@ -34,6 +34,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.prefs.Preferences;
/**
* Application for viewing and working with demodulators and recording files.
@@ -42,6 +43,9 @@ public class DemodulatorViewerFX extends Application
{
private final static Logger mLog = LoggerFactory.getLogger(DemodulatorViewerFX.class);
private static final String PREFERENCE_FILE_OPEN_DEFAULT_FOLDER = "default.folder";
private Preferences mPreferences = Preferences.userNodeForPackage(DemodulatorViewerFX.class);
private Stage mStage;
private MenuBar mMenuBar;
private RecentFilesMenu mRecentFilesMenu;
@@ -117,9 +121,22 @@ public class DemodulatorViewerFX extends Application
{
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open an I/Q recording file");
String directory = mPreferences.get(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, null);
if(directory != null)
{
fileChooser.setInitialDirectory(new File(directory));
}
File file = fileChooser.showOpenDialog(mStage);
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
if(file != null)
{
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
mPreferences.put(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, file.getParent());
}
}
});

View File

@@ -26,6 +26,7 @@ import java.util.prefs.Preferences;
public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
{
private static final String PREFERENCE_RECENT_FILE = ".recent.file.";
private Preferences mPreferences = Preferences.userNodeForPackage(RecentFilesMenu.class);
private IFileSelectionListener mFileSelectionListener;
private String mIdentifier;
@@ -111,7 +112,7 @@ public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
*/
private String getKey(int index)
{
return mIdentifier + ".recent.file." + index;
return mIdentifier + PREFERENCE_RECENT_FILE + index;
}
/**

View File

@@ -21,6 +21,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ValueAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +43,24 @@ public class DoubleLineChart extends LineChart implements Listener<Double>
init(length);
}
/**
* Sets the minimum displayable value for the y axis
* @param min
*/
public void setMin(double min)
{
((ValueAxis)getYAxis()).setLowerBound(min);
}
/**
* Sets the maximum displayable value for the y axis
* @param max
*/
public void setMax(double max)
{
((ValueAxis)getYAxis()).setUpperBound(max);
}
private void init(int length)
{
mDoubleCircularBuffer = new DoubleCircularBuffer(length);

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.gui.instrument.chart;
import javafx.collections.ObservableList;
public class SamplesPerSymbolChart extends DoubleLineChart
{
private double mFractionalSamplesPerSymbol;
private double mPreviousSamplesPerSymbol;
public SamplesPerSymbolChart(int length, double samplesPerSymbol)
{
super("Samples Per Symbol", -1.0, 1.0, 0.1, length);
setSamplesPerSymbol(samplesPerSymbol);
}
public void setSamplesPerSymbol(double samplesPerSymbol)
{
mFractionalSamplesPerSymbol = samplesPerSymbol - ((int)samplesPerSymbol);
mPreviousSamplesPerSymbol = samplesPerSymbol;
ObservableList<Series> seriesList = getData();
seriesList.get(0).setName("Samples Per Symbol:" + samplesPerSymbol);
}
@Override
public void receive(Double samplesPerSymbol)
{
double delta = samplesPerSymbol - mPreviousSamplesPerSymbol - mFractionalSamplesPerSymbol;
while(delta < (-1.0 + mFractionalSamplesPerSymbol))
{
delta++;
}
while(delta > 1.0)
{
delta--;
}
super.receive(delta);
mPreviousSamplesPerSymbol = samplesPerSymbol;
}
}

View File

@@ -19,6 +19,7 @@ import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
import io.github.dsheirer.gui.instrument.chart.SamplesPerSymbolChart;
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.decode.DecoderType;
@@ -34,14 +35,14 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class);
private HBox mSampleChartBox;
private ComplexSampleLineChart mSampleLineChart;
private HBox mTopChartBox;
private ComplexSampleLineChart mDifferentialDemodulatedSamplesChartBox;
private EyeDiagramChart mEyeDiagramChart;
private HBox mDecoderChartBox;
private SymbolChart mSymbolChart;
private HBox mBottomChartBox;
private SymbolChart mSymbolConstellationChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private SamplesPerSymbolChart mSamplesPerSymbolLineChart;
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
private P25DecoderLSMInstrumented mDecoder = new P25DecoderLSMInstrumented(null);
@@ -56,16 +57,16 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
addListener(getDecoder());
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolChart());
getDecoder().setComplexSymbolListener(getSymbolConstellationChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
mFilteredBufferBroadcaster.addListener(getDifferentialDemodulatedSamplesChartBox());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
HBox.setHgrow(getTopChartBox(), Priority.ALWAYS);
HBox.setHgrow(getBottomChartBox(), Priority.ALWAYS);
getChildren().addAll(getTopChartBox(), getBottomChartBox());
mDecoder.setMessageListener(new Listener<Message>()
{
@@ -86,7 +87,8 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getSampleLineChart().setSamplesPerSymbol((int)samplesPerSymbol);
getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int)samplesPerSymbol);
getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol);
}
private P25DecoderLSMInstrumented getDecoder()
@@ -94,69 +96,69 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
return mDecoder;
}
private SymbolChart getSymbolChart()
private SymbolChart getSymbolConstellationChart()
{
if(mSymbolChart == null)
if(mSymbolConstellationChart == null)
{
mSymbolChart = new SymbolChart(10);
mSymbolConstellationChart = new SymbolChart(10);
}
return mSymbolChart;
return mSymbolConstellationChart;
}
private HBox getDecoderChartBox()
private HBox getBottomChartBox()
{
if(mDecoderChartBox == null)
if(mBottomChartBox == null)
{
mDecoderChartBox = new HBox();
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
mBottomChartBox = new HBox();
mBottomChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolConstellationChart().setMaxWidth(Double.MAX_VALUE);
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
HBox.setHgrow(getSymbolConstellationChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
mBottomChartBox.getChildren().addAll(getSymbolConstellationChart(), getPLLPhaseErrorLineChart(),
getPLLFrequencyLineChart());
}
return mDecoderChartBox;
return mBottomChartBox;
}
private HBox getSampleChartBox()
private HBox getTopChartBox()
{
if(mSampleChartBox == null)
if(mTopChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
mTopChartBox = new HBox();
mTopChartBox.setMaxHeight(Double.MAX_VALUE);
getDifferentialDemodulatedSamplesChartBox().setMaxWidth(Double.MAX_VALUE);
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
HBox.setHgrow(getDifferentialDemodulatedSamplesChartBox(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
mTopChartBox.getChildren().addAll(getDifferentialDemodulatedSamplesChartBox(), getEyeDiagramChart(),
getSamplesPerSymbolLineChart());
}
return mSampleChartBox;
return mTopChartBox;
}
private ComplexSampleLineChart getSampleLineChart()
private ComplexSampleLineChart getDifferentialDemodulatedSamplesChartBox()
{
if(mSampleLineChart == null)
if(mDifferentialDemodulatedSamplesChartBox == null)
{
mSampleLineChart = new ComplexSampleLineChart(100, 10);
mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart(100, 10);
}
return mSampleLineChart;
return mDifferentialDemodulatedSamplesChartBox;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Middle: 3-4 / Symbol: 8-9");
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol/Eye Diagram");
}
return mEyeDiagramChart;
@@ -182,11 +184,11 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
return mPLLFrequencyLineChart;
}
private DoubleLineChart getSamplesPerSymbolLineChart()
private SamplesPerSymbolChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new DoubleLineChart( "Sample Point", 4.5, 7.0, 0.1, 40);
mSamplesPerSymbolLineChart = new SamplesPerSymbolChart(40, 6.25);
}
return mSamplesPerSymbolLineChart;

View File

@@ -565,12 +565,6 @@ public class ProcessingChain implements IChannelEventListener
{
if(mSource != null)
{
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
//Broadcast the source sample rate so that each of the modules can self-configure
mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup"));
@@ -652,6 +646,12 @@ public class ProcessingChain implements IChannelEventListener
{
module.stop();
}
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
}
}

View File

@@ -347,7 +347,6 @@ public class DecoderFactory
filters.add(new MPT1327MessageFilter());
break;
case P25_PHASE1:
case P25_PHASE2:
filters.add(new P25MessageFilterSet());
break;
case PASSPORT:

View File

@@ -1,19 +1,17 @@
/*******************************************************************************
* 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/>
* sdr-trunk
* Copyright (C) 2014-2018 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.module.decode;
@@ -25,37 +23,28 @@ import java.util.EnumSet;
*/
public enum DecoderType
{
ACARS( "ACARS", "ACARS", "images/acars.png", 12500 ),
AIS( "AIS", "AIS", "images/ais.png", 12500 ),
AM( "AM", "AM", "images/am.png", 10000 ),
APRS( "APRS", "APRS", "images/aprs.png", 12500 ),
DMR( "DMR", "DMR", "images/dmr.png", 12500 ),
FLEETSYNC2( "Fleetsync II", "Fleetsync2", "images/fm.png", 12500 ),
LJ_1200( "LJ1200 173.075", "LJ1200", "images/lj1200.png", 12500 ),
LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr.png", 10000 ),
LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net.png", 12500 ),
MDC1200( "MDC1200", "MDC1200", "images/fm.png", 12500 ),
MOTOROLA_TYPE_2( "MOTO T2", "MOTO T2", "images/motorola_type_2.png", 15000 ),
MPT1327( "MPT1327", "MPT1327", "images/mpt1327.png", 12500 ),
NXDN( "NXDN", "NXDN", "images/nxdn.png", 8000 ),
NBFM( "NBFM", "NBFM", "images/fm.png", 12500 ),
PASSPORT( "Passport", "Passport", "images/passport.png", 12500 ),
P25_PHASE1( "P25 Phase I", "P25-1", "images/p25_1.png", 12500 ),
P25_PHASE2( "P25 Phase II", "P25-2", "images/p25_2.png", 15000 ),
TAIT_1200( "Tait 1200", "Tait 1200", "images/tait.png", 12500 );
//Primary Decoders
AM("AM", "AM"),
LTR_STANDARD("LTR-Standard", "LTR"),
LTR_NET("LTR-Net", "LTR-Net"),
MPT1327("MPT1327", "MPT1327"),
NBFM("NBFM", "NBFM"),
PASSPORT("Passport", "Passport"),
P25_PHASE1("P25 Phase I", "P25-1"),
//Auxiliary Decoders
FLEETSYNC2("Fleetsync II", "Fleetsync2"),
LJ_1200("LJ1200 173.075", "LJ1200"),
MDC1200("MDC1200", "MDC1200"),
TAIT_1200("Tait 1200", "Tait 1200");
private String mDisplayString;
private String mShortDisplayString;
private String mIconFilename;
private int mChannelBandwidth;
DecoderType( String displayString, String shortDisplayString,
String iconFilename, int bandwidth )
DecoderType(String displayString, String shortDisplayString)
{
mDisplayString = displayString;
mShortDisplayString = shortDisplayString;
mIconFilename = iconFilename;
mChannelBandwidth = bandwidth;
}
/**
@@ -63,68 +52,43 @@ public enum DecoderType
*/
public static EnumSet<DecoderType> getPrimaryDecoders()
{
return EnumSet.of( DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
DecoderType.PASSPORT );
return EnumSet.of(DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
DecoderType.PASSPORT);
}
/**
* Decoders that can be used in the viewer application
*/
public static EnumSet<DecoderType> getInstrumentableDecoders()
{
return EnumSet.of( DecoderType.FLEETSYNC2,
DecoderType.LJ_1200,
DecoderType.LTR_NET,
DecoderType.MDC1200,
DecoderType.MPT1327,
DecoderType.PASSPORT,
DecoderType.P25_PHASE1,
DecoderType.TAIT_1200 );
}
/**
* Available auxiliary decoders.
*/
public static ArrayList<DecoderType> getAuxDecoders()
{
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add( DecoderType.FLEETSYNC2 );
decoders.add( DecoderType.LJ_1200 );
decoders.add( DecoderType.MDC1200 );
decoders.add( DecoderType.TAIT_1200 );
return decoders;
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add(DecoderType.FLEETSYNC2);
decoders.add(DecoderType.LJ_1200);
decoders.add(DecoderType.MDC1200);
decoders.add(DecoderType.TAIT_1200);
return decoders;
}
public String getDisplayString()
{
return mDisplayString;
}
public String getShortDisplayString()
{
return mShortDisplayString;
return mShortDisplayString;
}
public String getIconFilename()
{
return mIconFilename;
}
public int getChannelBandwidth()
{
return mChannelBandwidth;
}
@Override
public String toString()
{
return mDisplayString;
return mDisplayString;
}
}

View File

@@ -17,9 +17,11 @@
******************************************************************************/
package io.github.dsheirer.module.decode.am;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigAM extends DecodeConfiguration
{
@@ -27,6 +29,17 @@ public class DecodeConfigAM extends DecodeConfiguration
{
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
3000, 3000.0, 5000.0);
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public DecoderType getDecoderType()
{

View File

@@ -29,6 +29,7 @@ import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@@ -58,4 +59,7 @@ public abstract class DecodeConfiguration extends Configuration
@JsonIgnore
public abstract DecoderType getDecoderType();
@JsonIgnore
public abstract ChannelSpecification getChannelSpecification();
}

View File

@@ -30,10 +30,17 @@ import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
@@ -86,64 +93,6 @@ public class MessageActivityPanel extends JPanel implements Listener<ProcessingC
});
}
// @Override
// public void channelChanged(ChannelEvent event)
// {
// boolean changed = false;
//
// if(event.getEvent() == Event.NOTIFICATION_SELECTION_CHANGE &&
// event.getChannel().isSelected())
// {
// if(mDisplayedChannel == null || (mDisplayedChannel != null && mDisplayedChannel != event.getChannel()))
// {
// mDisplayedChannel = event.getChannel();
//
// ProcessingChain chain = mChannelProcessingManager.getProcessingChain(mDisplayedChannel);
//
// if(chain != null)
// {
// mDisplayedModel = chain.getMessageActivityModel();
// }
// else
// {
// mDisplayedModel = EMPTY_MODEL;
// }
//
// mMessageTable.setModel(mDisplayedModel);
//
// if(mDisplayedChannel != null)
// {
// mManagementPanel.enableButtons();
// }
// else
// {
// mManagementPanel.disableButtons();
// }
//
// changed = true;
// }
// }
// else if(event.getEvent() == Event.NOTIFICATION_PROCESSING_STOP || event.getEvent() == Event.REQUEST_DISABLE)
// {
// if(mDisplayedChannel != null && mDisplayedChannel == event.getChannel())
// {
// mDisplayedChannel = null;
//
// mMessageTable.setModel(EMPTY_MODEL);
//
// mManagementPanel.disableButtons();
//
// changed = true;
// }
// }
//
// if(changed)
// {
// revalidate();
// repaint();
// }
// }
public class MessageManagementPanel extends JPanel
{
private static final long serialVersionUID = 1L;

View File

@@ -17,10 +17,12 @@
******************************************************************************/
package io.github.dsheirer.module.decode.ltrnet;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRNet extends DecodeConfiguration
{
@@ -46,4 +48,15 @@ public class DecodeConfigLTRNet extends DecodeConfiguration
{
mMessageDirection = direction;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

View File

@@ -17,10 +17,12 @@
******************************************************************************/
package io.github.dsheirer.module.decode.ltrstandard;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRStandard extends DecodeConfiguration
{
@@ -47,4 +49,15 @@ public class DecodeConfigLTRStandard extends DecodeConfiguration
{
mMessageDirection = direction;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

View File

@@ -17,9 +17,11 @@
******************************************************************************/
package io.github.dsheirer.module.decode.mpt1327;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigMPT1327 extends DecodeConfiguration
{
@@ -102,4 +104,15 @@ public class DecodeConfigMPT1327 extends DecodeConfiguration
{
mTrafficChannelPoolSize = size;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

View File

@@ -17,12 +17,16 @@
******************************************************************************/
package io.github.dsheirer.module.decode.nbfm;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigNBFM extends DecodeConfiguration
{
private Bandwidth mBandwidth = Bandwidth.BW_12_5;
public DecodeConfigNBFM()
{
}
@@ -33,4 +37,53 @@ public class DecodeConfigNBFM extends DecodeConfiguration
return DecoderType.NBFM;
}
/**
* Channel bandwidth
*/
@JacksonXmlProperty(isAttribute = true, localName = "bandwidth")
public Bandwidth getBandwidth()
{
return mBandwidth;
}
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
if(mBandwidth == Bandwidth.BW_12_5)
{
return new ChannelSpecification(25000.0, 12500, 6000.0, 7000.0);
}
else
{
return new ChannelSpecification(50000.0, 25000, 12500.0, 13500.0);
}
}
/**
* Sets the channel bandwidth
*/
public void setBandwidth(Bandwidth bandwidth)
{
mBandwidth = bandwidth;
}
public enum Bandwidth
{
BW_12_5("12.5 kHz"),
BW_25_0("25.0 kHz");
private String mLabel;
Bandwidth(String label)
{
mLabel = label;
}
@Override
public String toString()
{
return mLabel;
}
};
}

View File

@@ -1,19 +1,17 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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/>
* sdr-trunk
* Copyright (C) 2014-2018 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.module.decode.nbfm;
@@ -24,70 +22,94 @@ import io.github.dsheirer.gui.editor.ValidatingEditor;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import net.miginfocom.swing.MigLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class NBFMDecoderEditor extends ValidatingEditor<Channel>
{
private static final long serialVersionUID = 1L;
public NBFMDecoderEditor()
{
init();
}
private void init()
{
setLayout( new MigLayout( "insets 0 0 0 0,wrap 2", "[right][grow,fill]", "" ) );
add( new JLabel( "Narrow Band FM Decoder" ) );
}
@Override
public void validate( Editor<Channel> editor ) throws EditorValidationException
{
//No validation
}
private JComboBox<DecodeConfigNBFM.Bandwidth> mComboBandwidth;
@Override
public void save()
{
if( hasItem() && isModified() )
{
DecodeConfigNBFM nbfm = new DecodeConfigNBFM();
getItem().setDecodeConfiguration( nbfm );
}
setModified( false );
}
public NBFMDecoderEditor()
{
init();
}
private void setControlsEnabled( boolean enabled )
{
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0,wrap 2", "[right][grow,fill]", ""));
@Override
public void setItem( Channel item )
{
super.setItem( item );
if( hasItem() )
{
setControlsEnabled( true );
DecodeConfiguration config = getItem().getDecodeConfiguration();
if( config instanceof DecodeConfigNBFM )
{
DecodeConfigNBFM nbfm = (DecodeConfigNBFM)config;
setModified( false );
}
else
{
setModified( true );
}
}
else
{
setControlsEnabled( false );
setModified( false );
}
}
mComboBandwidth = new JComboBox<>();
mComboBandwidth.setModel(new DefaultComboBoxModel<DecodeConfigNBFM.Bandwidth>(
DecodeConfigNBFM.Bandwidth.values()));
mComboBandwidth.setEnabled(false);
mComboBandwidth.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(new JLabel("Bandwidth:"));
add(mComboBandwidth);
}
@Override
public void validate(Editor<Channel> editor) throws EditorValidationException
{
//No validation
}
@Override
public void save()
{
if(hasItem() && isModified())
{
DecodeConfigNBFM nbfm = new DecodeConfigNBFM();
nbfm.setBandwidth((DecodeConfigNBFM.Bandwidth)mComboBandwidth.getSelectedItem());
getItem().setDecodeConfiguration(nbfm);
}
setModified(false);
}
private void setControlsEnabled(boolean enabled)
{
mComboBandwidth.setEnabled(enabled);
}
@Override
public void setItem(Channel item)
{
super.setItem(item);
if(hasItem())
{
setControlsEnabled(true);
DecodeConfiguration config = getItem().getDecodeConfiguration();
if(config instanceof DecodeConfigNBFM)
{
DecodeConfigNBFM nbfm = (DecodeConfigNBFM)config;
mComboBandwidth.setSelectedItem(nbfm.getBandwidth());
setModified(false);
}
else
{
setModified(true);
}
}
else
{
setControlsEnabled(false);
setModified(false);
}
}
}

View File

@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigP25Phase1 extends DecodeConfiguration
{
@@ -104,4 +105,14 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration
{
mTrafficChannelPoolSize = size;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(50000.0, 12500, 5750.0, 6500.0);
}
}

View File

@@ -350,9 +350,13 @@ public class Trellis_3_4_Rate
public void add( Con con )
{
Node current = getLastNode();
Tribit input = INPUT_FROM_CONSTELLATION_MAP
.get( con )[ current.getState().getValue() ];
Tribit input = null;
if(current != null)
{
input = INPUT_FROM_CONSTELLATION_MAP.get( con )[ current.getState().getValue() ];
}
/* A non-null input tribit from the lookup table is valid, otherwise
* create 8 alternate branches from the original path to explore

View File

@@ -1,7 +1,6 @@
/*
* *********************************************************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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
@@ -12,13 +11,15 @@
*
* 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.module.decode.passport;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigPassport extends DecodeConfiguration
{
@@ -31,4 +32,14 @@ public class DecodeConfigPassport extends DecodeConfiguration
{
return DecoderType.PASSPORT;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0, 12500, 6000.0, 7000.0);
}
}

View File

@@ -0,0 +1,268 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.sample.buffer;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.LinkedTransferQueue;
/**
* Delay buffer/queue for reusable complex buffers with support for pre-loading new buffer listeners (ie channels)
* with delayed sample buffers and broadcasting of incoming sample buffers to a list of listeners.
*
* This class is designed to compensate/account for any delays in processing of a control channel and subsequent
* channel grants so that when a new channel is allocated, that channel can be pre-loaded with time delayed sample
* buffers that start as close as possible to the channel grant timestamp, in order to avoid cutting off the
* beginning of a channel grant transmission.
*
* New channel listeners are enqueued with a request timestamp and as new buffers arrive, the new channel listeners
* are preloaded with delayed buffers from the queue and then added to an internal broadcaster so that they
* receive all subsequent buffers as normal.
*
* This class is designed to be controlled by the sample producer thread for adding new listeners and pre-loading
* delayed buffers. Any listeners that are added to this class are expected to implement a non-blocking receive method
* so as not to delay the stream of sample buffers. Channel listeners are expected to implement buffer queue processing
* on another thread.
*/
public class ReusableComplexDelayBuffer implements Listener<ReusableComplexBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexDelayBuffer.class);
private ReusableBufferBroadcaster<ReusableComplexBuffer> mBroadcaster = new ReusableBufferBroadcaster<>();
private LinkedTransferQueue<ActionRequest> mActionRequestQueue = new LinkedTransferQueue<>();
private ActionRequest mActionRequest;
private ReusableComplexBuffer[] mDelayBuffer;
private int mDelayBufferPointer = 0;
private long mBufferDuration;
/**
* Creates a new delay buffer with the specified delay length and the buffer sample rate.
*
* @param size of the delay queue
* @param bufferDuration in milliseconds for each complex buffer processed by this instance
*/
public ReusableComplexDelayBuffer(int size, long bufferDuration)
{
mDelayBuffer = new ReusableComplexBuffer[size];
mBufferDuration = bufferDuration;
}
/**
* Prepares this instance for disposal by releasing all stored sample buffers.
*/
public void dispose()
{
clearBuffer();
}
public void clear()
{
//Submit a clear buffer request to be processed upon the next buffer that arrives
mActionRequestQueue.offer(new ActionRequest());
}
/**
* Clears any delayed/enqueued sample buffers.
*/
private void clearBuffer()
{
for(int x = 0; x < mDelayBuffer.length; x++)
{
if(mDelayBuffer[x] != null)
{
mDelayBuffer[x].decrementUserCount();
mDelayBuffer[x] = null;
}
}
mDelayBufferPointer = 0;
}
@Override
public synchronized void receive(ReusableComplexBuffer reusableComplexBuffer)
{
mActionRequest = mActionRequestQueue.poll();
while(mActionRequest != null)
{
switch(mActionRequest.getAction())
{
case ADD_USER:
processNewListener(mActionRequest);
break;
case REMOVE_USER:
mBroadcaster.removeListener(mActionRequest.getListener());
break;
case CLEAR_BUFFER:
clearBuffer();
break;
}
mActionRequest = mActionRequestQueue.poll();
}
//Increment user count before we hand it to the broadcaster so that we can keep it as a copy for the
//delay buffer
reusableComplexBuffer.incrementUserCount();
mBroadcaster.receive(reusableComplexBuffer);
//Decrement the user count on the oldest buffer before we replace it in the delay queue
if(mDelayBuffer[mDelayBufferPointer] != null)
{
mDelayBuffer[mDelayBufferPointer].decrementUserCount();
}
//Store the new buffer in the delay queue and increment the pointer
mDelayBuffer[mDelayBufferPointer++] = reusableComplexBuffer;
//Wrap the delay buffer pointer as needed
if(mDelayBufferPointer >= mDelayBuffer.length)
{
mDelayBufferPointer = 0;
}
}
/**
* Indicates if any listeners are registered with this delay buffer
*/
public boolean hasListeners()
{
return mBroadcaster.hasListeners();
}
/**
* Processes any newly added listeners by checking all buffers in the delay queue and pre-loading the
* listeners with any buffers that occur on or after the listener's requested start timestamp.
*/
private void processNewListener(ActionRequest listenerToAdd)
{
ReusableComplexBuffer bufferToEvaluate;
int pointer = mDelayBufferPointer;
int preloadedBufferCount = 0;
for(int x = 0; x < mDelayBuffer.length; x++)
{
bufferToEvaluate = mDelayBuffer[pointer];
if(bufferToEvaluate != null &&
(bufferToEvaluate.getTimestamp() >= listenerToAdd.getTimestamp() ||
bufferToEvaluate.getTimestamp() + mBufferDuration >= listenerToAdd.getTimestamp()))
{
bufferToEvaluate.incrementUserCount();
listenerToAdd.getListener().receive(bufferToEvaluate);
preloadedBufferCount++;
}
pointer++;
if(pointer >= mDelayBuffer.length)
{
pointer = 0;
}
}
mBroadcaster.addListener(listenerToAdd.getListener());
}
/**
* Adds the listener to receive a copy of each buffer. The listener will be preloaded with delayed buffers
* that are at or after the specified timestamp.
*
* @param listener to add
* @param timestamp of the oldest sample buffers to preload to the listener
*/
public void addListener(Listener<ReusableComplexBuffer> listener, long timestamp)
{
mActionRequestQueue.add(new ActionRequest(listener, timestamp));
}
/**
* Removes the listener from receiving sample buffers
*/
public void removeListener(Listener<ReusableComplexBuffer> listener)
{
mActionRequestQueue.add(new ActionRequest(listener));
}
private enum Action{ADD_USER, REMOVE_USER, CLEAR_BUFFER};
/**
* Actions that must be completed on the incoming sample stream thread.
*/
public class ActionRequest
{
private Action mAction;
private Listener<ReusableComplexBuffer> mListener;
private long mTimestamp;
/**
* Creates an add listener request
* @param listener to add
* @param timestamp for the preloading buffers to the listener from the delay queue
*/
public ActionRequest(Listener<ReusableComplexBuffer> listener, long timestamp)
{
mAction = Action.ADD_USER;
mListener = listener;
mTimestamp = timestamp;
}
/**
* Creates a remove listener request
* @param listener
*/
public ActionRequest(Listener<ReusableComplexBuffer> listener)
{
mAction = Action.REMOVE_USER;
mListener = listener;
}
/**
* Creates a clearBuffer buffer request
*/
public ActionRequest()
{
mAction = Action.CLEAR_BUFFER;
}
public Action getAction()
{
return mAction;
}
/**
* Listener to be added/removed
*/
public Listener<ReusableComplexBuffer> getListener()
{
return mListener;
}
/**
* Requested timestamp for buffers to be preloaded.
*/
public long getTimestamp()
{
return mTimestamp;
}
}
}

View File

@@ -56,20 +56,28 @@ public class SourceEvent
/**
* Private constructor. Use the static constructor methods to create an event.
*/
private SourceEvent(Event event, Number value, String eventDescription)
private SourceEvent(Event event, Source source, Number value, String eventDescription)
{
mEvent = event;
mSource = source;
mValue = value;
mEventDescription = eventDescription;
}
/**
* Private constructor. Use the static constructor methods to create an event.
*/
private SourceEvent(Event event, Number value, String eventDescription)
{
this(event, null, value, eventDescription);
}
/**
* Private constructor. Use the static constructor methods to create an event.
*/
private SourceEvent(Event event, Number value)
{
mEvent = event;
mValue = value;
this(event, value, null);
}
/**
@@ -77,8 +85,12 @@ public class SourceEvent
*/
private SourceEvent(Event event, Source source)
{
mEvent = event;
mSource = source;
this(event, source, 0);
}
private SourceEvent(Event event, Source source, Number value)
{
this(event, source, value, null);
}
/**
@@ -283,11 +295,22 @@ public class SourceEvent
}
/**
* Creates a new start sample stream request event
* Creates a new start sample stream request event. This method uses the current system time in
* milliseconds as the requested sample start time.
*/
public static SourceEvent startSampleStreamRequest(Source source)
{
return new SourceEvent(Event.REQUEST_START_SAMPLE_STREAM, source);
return startSampleStreamRequest(source, System.currentTimeMillis());
}
/**
* Creates a new start sample stream request event and requests that samples (when available) for the
* specified timestamp be pre-loaded into the source. The timestamp only applies for sources that incorporate
* a sample delay buffer.
*/
public static SourceEvent startSampleStreamRequest(Source source, long timestamp)
{
return new SourceEvent(Event.REQUEST_START_SAMPLE_STREAM, source, timestamp);
}
/**
@@ -299,7 +322,7 @@ public class SourceEvent
}
/**
* Creates a new stop sample stream request event
* Creates a new stop sample stream notification event
*/
public static SourceEvent stopSampleStreamNotification(Source source)
{

View File

@@ -25,6 +25,7 @@ import io.github.dsheirer.source.mixer.MixerManager;
import io.github.dsheirer.source.recording.RecordingSourceManager;
import io.github.dsheirer.source.tuner.TunerManager;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class SourceManager
{
@@ -73,8 +74,7 @@ public class SourceManager
return mTunerModel;
}
public Source getSource(SourceConfiguration config, int bandwidth)
throws SourceException
public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification) throws SourceException
{
Source retVal = null;
@@ -84,10 +84,10 @@ public class SourceManager
retVal = mMixerManager.getSource(config);
break;
case TUNER:
retVal = mTunerModel.getSource((SourceConfigTuner) config, bandwidth);
retVal = mTunerModel.getSource((SourceConfigTuner) config, channelSpecification);
break;
case RECORDING:
retVal = mRecordingSourceManager.getSource(config, bandwidth);
retVal = mRecordingSourceManager.getSource(config, channelSpecification);
case NONE:
default:
break;

View File

@@ -47,6 +47,14 @@ public class ComplexMixer
mName = name;
}
/**
* Audio format used by the underlying mixer reader
*/
public AudioFormat getAudioFormat()
{
return mMixerReader.getAudioFormat();
}
/**
* Sets the buffer size in bytes per buffer for each read interval.
* @param bufferSize in bytes

View File

@@ -72,6 +72,14 @@ public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
this(audioFormat, targetDataLine, abstractSampleAdapter, null);
}
/**
* Audio format used by this reader
*/
public AudioFormat getAudioFormat()
{
return mAudioFormat;
}
/**
* Sets the size of buffers to use. This reader will read from target data line 20 times per second.
* @param bytesPerBuffer to size for each buffer read.

View File

@@ -1,19 +1,17 @@
/*******************************************************************************
* 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/>
* sdr-trunk
* Copyright (C) 2014-2018 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.source.recording;
@@ -22,6 +20,7 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
@@ -33,64 +32,64 @@ import java.util.List;
public class RecordingSourceManager
{
private final static Logger mLog = LoggerFactory.getLogger( RecordingSourceManager.class );
private final static Logger mLog = LoggerFactory.getLogger(RecordingSourceManager.class);
private ArrayList<Recording> mRecordings = new ArrayList<Recording>();
private ArrayList<Recording> mRecordings = new ArrayList<Recording>();
private SettingsManager mSettingsManager;
private SettingsManager mSettingsManager;
public RecordingSourceManager( SettingsManager settingsManager )
{
mSettingsManager = settingsManager;
loadRecordings();
}
public RecordingSourceManager(SettingsManager settingsManager)
{
mSettingsManager = settingsManager;
loadRecordings();
}
/**
* Iterates current recordings to get a tuner channel source for the frequency
* specified in the channel config's source config object
*/
public Source getSource( SourceConfiguration config, int bandwidth )
throws SourceException
public Source getSource(SourceConfiguration config, ChannelSpecification channelSpecification)
throws SourceException
{
TunerChannelSource retVal = null;
TunerChannelSource retVal = null;
if( config instanceof SourceConfigRecording )
{
SourceConfigRecording configRecording = (SourceConfigRecording)config;
TunerChannel tunerChannel = configRecording.getTunerChannel();
tunerChannel.setBandwidth( bandwidth );
if(config instanceof SourceConfigRecording)
{
SourceConfigRecording configRecording = (SourceConfigRecording)config;
Recording recording = getRecordingFromAlias(
configRecording.getRecordingAlias() );
if( recording != null )
{
retVal = recording.getChannel( tunerChannel );
}
else
{
throw new SourceException( "Recording with alias name [" +
configRecording.getRecordingAlias() + "] is not currently available" );
}
}
return retVal;
TunerChannel tunerChannel = configRecording.getTunerChannel();
tunerChannel.setBandwidth(channelSpecification.getBandwidth());
Recording recording = getRecordingFromAlias(
configRecording.getRecordingAlias());
if(recording != null)
{
retVal = recording.getChannel(tunerChannel);
}
else
{
throw new SourceException("Recording with alias name [" +
configRecording.getRecordingAlias() + "] is not currently available");
}
}
return retVal;
}
public Recording getRecordingFromAlias( String alias )
public Recording getRecordingFromAlias(String alias)
{
for( Recording recording: mRecordings )
{
if( recording.getRecordingConfiguration()
.getAlias().contentEquals( alias ) )
{
return recording;
}
}
return null;
for(Recording recording : mRecordings)
{
if(recording.getRecordingConfiguration()
.getAlias().contentEquals(alias))
{
return recording;
}
}
return null;
}
/**
@@ -98,47 +97,47 @@ public class RecordingSourceManager
*/
public List<Recording> getRecordings()
{
Collections.sort( mRecordings );
return mRecordings;
}
public void addRecording( Recording recording )
{
mRecordings.add( recording );
}
public Recording addRecording( RecordingConfiguration config )
{
Recording recording = new Recording( mSettingsManager, config );
Collections.sort(mRecordings);
mRecordings.add( recording );
return recording;
return mRecordings;
}
public void removeRecording( Recording recording )
public void addRecording(Recording recording)
{
/* Remove the config from the settings manager */
mSettingsManager.removeRecordingConfiguration(
recording.getRecordingConfiguration() );
mRecordings.remove( recording );
mRecordings.add(recording);
}
public Recording addRecording(RecordingConfiguration config)
{
Recording recording = new Recording(mSettingsManager, config);
mRecordings.add(recording);
return recording;
}
public void removeRecording(Recording recording)
{
/* Remove the config from the settings manager */
mSettingsManager.removeRecordingConfiguration(
recording.getRecordingConfiguration());
mRecordings.remove(recording);
}
private void loadRecordings()
{
List<RecordingConfiguration> recordingConfigurations =
mSettingsManager.getRecordingConfigurations();
List<RecordingConfiguration> recordingConfigurations =
mSettingsManager.getRecordingConfigurations();
mLog.info( "RecordingSourceManager - discovered [" +
recordingConfigurations.size() + "] recording configurations" );
for( RecordingConfiguration config: recordingConfigurations )
{
Recording recording = new Recording( mSettingsManager, config );
mRecordings.add( recording );
}
}
mLog.info("RecordingSourceManager - discovered [" +
recordingConfigurations.size() + "] recording configurations");
for(RecordingConfiguration config : recordingConfigurations)
{
Recording recording = new Recording(mSettingsManager, config);
mRecordings.add(recording);
}
}
}

View File

@@ -21,7 +21,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.manager.ChannelSourceManager;
import io.github.dsheirer.source.tuner.manager.HeterodyneChannelSourceManager;
import io.github.dsheirer.source.tuner.manager.PolyphaseChannelSourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,8 +51,8 @@ public abstract class Tuner implements ISourceEventProcessor
//Register to receive frequency and sample rate change notifications
mTunerController.addListener(this::process);
// mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController);
mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
mChannelSourceManager = new PolyphaseChannelSourceManager(mTunerController);
// mChannelSourceManager = new HeterodyneChannelSourceManager(mTunerController);
//Register to receive channel count change notifications
mChannelSourceManager.addSourceEventListener(this::process);

View File

@@ -26,12 +26,15 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceEventListenerToProcessorAdapter;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.frequency.FrequencyController;
import io.github.dsheirer.source.tuner.frequency.FrequencyController.Tunable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.SortedSet;
public abstract class TunerController implements Tunable, ISourceEventProcessor, ISourceEventListener,
IReusableComplexBufferProvider, Listener<ReusableComplexBuffer>
{
@@ -67,6 +70,21 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
public abstract void dispose();
/**
* Number of samples contained in each complex buffer provided by this tuner.
*
* @return number of complex sample in each buffer.
*/
public abstract int getBufferSampleCount();
/**
* Duration in milliseconds for each sample buffer provided by this tuner
*/
public long getBufferDuration()
{
return (long)(1000.0 / (getSampleRate() / (double)getBufferSampleCount()));
}
/**
* Implements the ISourceEventListener interface to receive requests from sample consumers
*/
@@ -346,4 +364,85 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
{
broadcast(complexBuffer);
}
/**
* Indicates if the current center frequency and bandwidth is correct to source the tuner channel
*
* @param tunerChannel to test
* @return true if the current center frequency and bandwidth is correct for the channel
*/
public boolean isTunedFor(TunerChannel tunerChannel)
{
try
{
if(tunerChannel.getMinFrequency() < getMinTunedFrequency())
{
return false;
}
if(tunerChannel.getMaxFrequency() > getMaxTunedFrequency())
{
return false;
}
if(hasMiddleUnusableBandwidth())
{
long minAvoid = getFrequency() - getMiddleUnusableHalfBandwidth();
long maxAvoid = getFrequency() + getMiddleUnusableHalfBandwidth();
if(tunerChannel.overlaps(minAvoid, maxAvoid))
{
return false;
}
}
}
catch(SourceException se)
{
return false;
}
return true;
}
/**
* Indicates if the current center frequency and bandwidth is correct to source the tuner channel set
*
* @param tunerChannels to test
* @return true if the current center frequency and bandwidth is correct for the channel set
*/
public boolean isTunedFor(SortedSet<TunerChannel> tunerChannels)
{
try
{
if(tunerChannels.first().getMinFrequency() < getMinTunedFrequency())
{
return false;
}
if(tunerChannels.last().getMaxFrequency() > getMaxTunedFrequency())
{
return false;
}
if(hasMiddleUnusableBandwidth())
{
long minAvoid = getFrequency() - getMiddleUnusableHalfBandwidth();
long maxAvoid = getFrequency() + getMiddleUnusableHalfBandwidth();
for(TunerChannel tunerChannel: tunerChannels)
{
if(tunerChannel.overlaps(minAvoid, maxAvoid))
{
return false;
}
}
}
}
catch(SourceException se)
{
return false;
}
return true;
}
}

View File

@@ -71,6 +71,10 @@ public class TunerManager
*/
public void dispose()
{
mLog.info("Shutting down LibUsb timeout processor ...");
LIBUSB_TRANSFER_PROCESSOR.shutdown();
mLog.info("Shutting down LibUsb ...");
try
{

View File

@@ -21,11 +21,13 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.tuner.TunerEvent.Event;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import io.github.dsheirer.spectrum.SpectralDisplayPanel;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,6 +36,7 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TunerModel extends AbstractTableModel implements Listener<TunerEvent>
{
@@ -201,7 +204,17 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
if(enabled && mTuners.size() > 0)
{
broadcast(new TunerEvent(mTuners.get(0), Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
//Hack: the airspy tuner would lockup aperiodically and refuse to produce
//transfer buffers ... delaying registering for buffers for 500 ms seems
//to allow the airspy to stabilize before we start asking for samples.
ThreadPool.SCHEDULED.schedule(new Runnable()
{
@Override
public void run()
{
broadcast(new TunerEvent(mTuners.get(0), Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
}
}, 500, TimeUnit.MILLISECONDS);
}
else
{
@@ -327,13 +340,13 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
*
* Returns null if no tuner can source the channel
*/
public Source getSource(SourceConfigTuner config, int bandwidth)
public Source getSource(SourceConfigTuner config, ChannelSpecification channelSpecification)
{
TunerChannelSource retVal = null;
TunerChannel tunerChannel = config.getTunerChannel();
tunerChannel.setBandwidth(bandwidth);
tunerChannel.setBandwidth(channelSpecification.getBandwidth());
Iterator<Tuner> it = mTuners.iterator();
@@ -347,7 +360,7 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
{
try
{
retVal = tuner.getChannelSourceManager().getSource(tunerChannel);
retVal = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
if(retVal != null)
{
@@ -370,7 +383,7 @@ public class TunerModel extends AbstractTableModel implements Listener<TunerEven
try
{
retVal = tuner.getChannelSourceManager().getSource(tunerChannel);
retVal = tuner.getChannelSourceManager().getSource(tunerChannel, channelSpecification);
}
catch(Exception e)
{

View File

@@ -107,6 +107,13 @@ public class AirspyTunerController extends USBTunerController
mDevice = device;
}
@Override
public int getBufferSampleCount()
{
//Each airspy complex sample is 4 bytes, so sample count is buffer size / 4
return USB_TRANSFER_BUFFER_SIZE / 4;
}
public void init() throws SourceException
{
mDeviceHandle = new DeviceHandle();

View File

@@ -19,6 +19,7 @@ import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.LowPhaseNoiseOscillator;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
@@ -44,17 +45,10 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene
//Threshold for resetting buffer overflow condition
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
//Pass band cutoff frequency for the complex channel - target = 12.5 kHz channel
private static final int CHANNEL_PASS_FREQUENCY = 6000;
//Stop band frequency for the complex channel
private static final int CHANNEL_STOP_FREQUENCY = 6700;
private OverflowableReusableBufferTransferQueue<ReusableComplexBuffer> mBuffer;
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("CICTunerChannelSource");
private IOscillator mFrequencyCorrectionMixer;
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ReusableComplexBuffer> mListener;
private List<ReusableComplexBuffer> mSampleBuffers = new ArrayList<>();
private double mChannelSampleRate;
private long mChannelFrequencyCorrection = 0;
@@ -66,18 +60,22 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene
* @param producerSourceEventListener to receive sample stream start/stop requests
* @param tunerChannel that details the desired channel frequency and bandwidth
* @param sampleRate of the incoming sample stream
* @param decimation to use in the CIC decimation filter.
* @param channelSpecification for the requested channel.
* @throws FilterDesignException if a final cleanup filter cannot be designed using the remez filter
* designer and the filter parameters.
*/
public CICTunerChannelSource(Listener<SourceEvent> producerSourceEventListener, TunerChannel tunerChannel,
double sampleRate, int decimation) throws FilterDesignException
double sampleRate, ChannelSpecification channelSpecification) throws FilterDesignException
{
super(producerSourceEventListener, tunerChannel);
mDecimationFilter = new ComplexPrimeCICDecimate(sampleRate, decimation, CHANNEL_PASS_FREQUENCY, CHANNEL_STOP_FREQUENCY);
int decimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate());
mDecimationFilter = new ComplexPrimeCICDecimate(sampleRate, decimation, channelSpecification.getPassFrequency(),
channelSpecification.getStopFrequency());
mBuffer = new OverflowableReusableBufferTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
//Setup the frequency mixer to the current source frequency
mChannelSampleRate = sampleRate / (double)decimation;
mTunerFrequency = tunerChannel.getFrequency();
@@ -86,6 +84,18 @@ public class CICTunerChannelSource extends TunerChannelSource implements Listene
mFrequencyCorrectionMixer = new LowPhaseNoiseOscillator(frequencyOffset, sampleRate);
}
/**
* Registers an overflow listener to be notified in the internal buffer is in an
* overflow state.
*
* @param listener to receiver updates to the source overflow state
*/
@Override
public void setOverflowListener(IOverflowListener listener)
{
mBuffer.setOverflowListener(listener);
}
@Override
public void dispose()
{

View File

@@ -0,0 +1,74 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.source.tuner.channel;
/**
* Channel source specification
*/
public class ChannelSpecification
{
private double mMinimumSampleRate;
private int mBandwidth;
private double mPassFrequency;
private double mStopFrequency;
/**
* Constructs a channel specification instance.
* @param minimumSampleRate requested for the channel
* @param bandwidth
* @param passFrequency for the pass band of the channel
* @param stopFrequency for the stop band of teh channel
*/
public ChannelSpecification(double minimumSampleRate, int bandwidth, double passFrequency, double stopFrequency)
{
mMinimumSampleRate = minimumSampleRate;
mBandwidth = bandwidth;
mPassFrequency = passFrequency;
mStopFrequency = stopFrequency;
}
/**
* Minimum requested sample rate for the channel
*/
public double getMinimumSampleRate()
{
return mMinimumSampleRate;
}
/**
* Bandwidth of the channel in Hertz
*/
public int getBandwidth()
{
return mBandwidth;
}
/**
* Pass band frequency for filtering the channel
*/
public double getPassFrequency()
{
return mPassFrequency;
}
/**
* Stop band frequency for filtering the channel
*/
public double getStopFrequency()
{
return mStopFrequency;
}
}

View File

@@ -286,8 +286,26 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
{
if(!mStopped)
{
getHeartbeatManager().broadcast();
processSamples();
try
{
getHeartbeatManager().broadcast();
}
catch(Throwable t)
{
mLog.error("Error while sending heartbeat", t);
}
}
if(!mStopped)
{
try
{
processSamples();
}
catch(Throwable t)
{
mLog.error("Error while processing samples", t);
}
}
if(mStopped)
@@ -300,7 +318,15 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
mScheduledFuture = null;
performDisposal();
try
{
getHeartbeatManager().broadcast();
performDisposal();
}
catch(Throwable t)
{
mLog.error("Error during final shutdown processing of samples", t);
}
}
}
}

View File

@@ -90,11 +90,17 @@ public abstract class FCDTunerController extends TunerController
mComplexMixer = new ComplexMixer( mixerTDL.getTargetDataLine(), audioFormat, tunerName,
new ComplexShortAdapter(mixerTDL.getMixerTunerType().getDisplayString()));
mComplexMixer.setBufferSize(audioFormat.getFrameSize() * (int)(audioFormat.getSampleRate() * 0.1));
mComplexMixer.setBufferSize(audioFormat.getFrameSize() * getBufferSampleCount());
mComplexMixer.setBufferListener(mReusableBufferBroadcaster);
}
@Override
public int getBufferSampleCount()
{
return (int)(mComplexMixer.getAudioFormat().getSampleRate() * 0.1);
}
/**
* Overrides the super class functionality to auto-start the complex mixer and provide samples to listeners
*/

View File

@@ -109,6 +109,12 @@ public class HackRFTunerController extends USBTunerController
mDeviceDescriptor = descriptor;
}
@Override
public int getBufferSampleCount()
{
return USB_TRANSFER_BUFFER_SIZE / 2;
}
public void init() throws SourceException
{
mDeviceHandle = new DeviceHandle();

View File

@@ -54,48 +54,57 @@ public class CenterFrequencyCalculator
//Start by placing the highest frequency channel at the high end of the spectrum
candidateFrequency = maxChannelFrequency - (tunerController.getUsableBandwidth() / 2);
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
if(tunerController.getMiddleUnusableHalfBandwidth() > 0)
if(maxChannelFrequency - minChannelFrequency <= tunerController.getUsableHalfBandwidth())
{
boolean processingRequired = true;
while(isValidCandidateFrequency && processingRequired)
candidateFrequency = minChannelFrequency - tunerController.getMiddleUnusableHalfBandwidth();
isValidCandidateFrequency = true;
}
else
{
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
if(tunerController.getMiddleUnusableHalfBandwidth() > 0)
{
processingRequired = false;
boolean processingRequired = true;
long minAvoid = candidateFrequency - tunerController.getMiddleUnusableHalfBandwidth();
long maxAvoid = candidateFrequency + tunerController.getMiddleUnusableHalfBandwidth();
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
for(TunerChannel channel : channels)
while(isValidCandidateFrequency && processingRequired)
{
if(channel.overlaps(minAvoid, maxAvoid))
processingRequired = false;
long minAvoid = candidateFrequency - tunerController.getMiddleUnusableHalfBandwidth();
long maxAvoid = candidateFrequency + tunerController.getMiddleUnusableHalfBandwidth();
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
for(TunerChannel channel : channels)
{
//Calculate a tuned frequency adjustment that places this overlapping channel just to the
//left of the central DC spike avoid zone
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
if(candidateFrequency + adjustment - (tunerController.getUsableBandwidth() / 2) <= minChannelFrequency)
if(channel.overlaps(minAvoid, maxAvoid))
{
candidateFrequency += adjustment;
processingRequired = true;
}
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
else
{
isValidCandidateFrequency = false;
}
//Calculate a tuned frequency adjustment that places this overlapping channel just to the
//left of the central DC spike avoid zone
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
//break out of the for/each loop, so that we can start over again with all of the channels
break;
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
if(candidateFrequency + adjustment - (tunerController.getUsableBandwidth() / 2) <= minChannelFrequency)
{
candidateFrequency += adjustment;
processingRequired = true;
}
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
else
{
isValidCandidateFrequency = false;
}
//break out of the for/each loop, so that we can start over again with all of the channels
break;
}
}
}
}
}
}
if(isValidCandidateFrequency)

View File

@@ -19,6 +19,7 @@ import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
@@ -50,9 +51,10 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
* resources allocated for the tuner channel source.
*
* @param tunerChannel for requested source
* @param channelSpecification for the requested channel
* @return tuner channel source or null
*/
public abstract TunerChannelSource getSource(TunerChannel tunerChannel);
public abstract TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification);
/**
* Adds a listener to receive source events

View File

@@ -17,10 +17,12 @@ package io.github.dsheirer.source.tuner.manager;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableComplexDelayBuffer;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.CICTunerChannelSource;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
@@ -38,12 +40,13 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
{
private final static Logger mLog = LoggerFactory.getLogger(HeterodyneChannelSourceManager.class);
private final static int OBJECTIVE_CHANNEL_SAMPLE_RATE = 30000;
private final static int DELAY_BUFFER_DURATION_MILLISECONDS = 2000;
private List<CICTunerChannelSource> mChannelSources = new CopyOnWriteArrayList<>();
private SortedSet<TunerChannel> mTunerChannels = new TreeSet<>();
private TunerController mTunerController;
private ChannelSourceEventProcessor mChannelSourceEventProcessor = new ChannelSourceEventProcessor();
private ReusableComplexDelayBuffer mSampleDelayBuffer;
public HeterodyneChannelSourceManager(TunerController tunerController)
{
@@ -64,17 +67,15 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
}
@Override
public TunerChannelSource getSource(TunerChannel tunerChannel)
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{
if(CenterFrequencyCalculator.canTune(tunerChannel, mTunerController, mTunerChannels))
{
int decimation = mTunerController.getBandwidth() / OBJECTIVE_CHANNEL_SAMPLE_RATE;
try
{
//Attempt to create the channel source first, in case we get a filter design exception
CICTunerChannelSource tunerChannelSource = new CICTunerChannelSource(mChannelSourceEventProcessor,
tunerChannel, mTunerController.getSampleRate(), decimation);
tunerChannel, mTunerController.getSampleRate(), channelSpecification);
//Add to the list of channel sources so that it will receive the tuner frequency change
mChannelSources.add(tunerChannelSource);
@@ -107,23 +108,26 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
*/
private void updateTunerFrequency()
{
long centerFrequency = CenterFrequencyCalculator.getCenterFrequency(mTunerController, getTunerChannels());
if(centerFrequency == CenterFrequencyCalculator.INVALID_FREQUENCY)
if(!mTunerController.isTunedFor(getTunerChannels()))
{
mLog.error("Couldn't calculate center frequency for tuner and tuner channels");
return;
}
long centerFrequency = CenterFrequencyCalculator.getCenterFrequency(mTunerController, getTunerChannels());
if(centerFrequency != mTunerController.getFrequency())
{
try
if(centerFrequency == CenterFrequencyCalculator.INVALID_FREQUENCY)
{
mTunerController.setFrequency(centerFrequency);
mLog.error("Couldn't calculate center frequency for tuner and tuner channels");
return;
}
catch(SourceException se)
if(centerFrequency != mTunerController.getFrequency())
{
mLog.error("Couldn't update tuner center frequency to " + centerFrequency, se);
try
{
mTunerController.setFrequency(centerFrequency);
}
catch(SourceException se)
{
mLog.error("Couldn't update tuner center frequency to " + centerFrequency, se);
}
}
}
}
@@ -136,6 +140,12 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
case NOTIFICATION_FREQUENCY_CHANGE:
//Tuner center frequency has changed - update channels
updateTunerFrequency(tunerSourceEvent.getValue().longValue());
//Clear the delay buffer since any delayed samples will be centered on the previous frequency
if(mSampleDelayBuffer != null)
{
mSampleDelayBuffer.clear();
}
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//The tuner is self-correcting for PPM error - relay to channels
@@ -156,7 +166,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
*/
private void broadcastToChannels(SourceEvent sourceEvent)
{
for(CICTunerChannelSource channelSource: mChannelSources)
for(CICTunerChannelSource channelSource : mChannelSources)
{
try
{
@@ -182,6 +192,39 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
}
}
/**
* Creates a complex sample delay buffer and registers it with the tuner controller to start the flow
* of complex sample buffers from the tuner.
*/
private void startDelayBuffer()
{
if(mSampleDelayBuffer == null)
{
int delayBufferSize = (int)(DELAY_BUFFER_DURATION_MILLISECONDS / mTunerController.getBufferDuration());
mSampleDelayBuffer = new ReusableComplexDelayBuffer(delayBufferSize, mTunerController.getBufferDuration());
mLog.debug("Created/registered complex sample delay buffer of size [" + delayBufferSize +
"] buffers and delay duration [" + DELAY_BUFFER_DURATION_MILLISECONDS +
"ms] for tuner controller [" + mTunerController.getClass().getName() +
"] with buffer duration [" + mTunerController.getBufferDuration() + "]");
mTunerController.addBufferListener(mSampleDelayBuffer);
}
}
/**
* Deregisters the complex sample delay buffer and disposes of any queued reusable buffers.
*/
private void stopDelayBuffer()
{
if(mSampleDelayBuffer != null && !mSampleDelayBuffer.hasListeners())
{
mTunerController.removeBufferListener(mSampleDelayBuffer);
mSampleDelayBuffer.dispose();
mSampleDelayBuffer = null;
}
}
/**
* Processes channel source events
*/
@@ -195,13 +238,20 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
case REQUEST_START_SAMPLE_STREAM:
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
{
mTunerController.addBufferListener((CICTunerChannelSource)sourceEvent.getSource());
startDelayBuffer();
//The start sample stream request contains a start timestamp and the delay buffer
//will preload the channel with delayed sample buffers that either contain the
//timestamp or occur later/newer than the timestamp.
mSampleDelayBuffer.addListener((CICTunerChannelSource)sourceEvent.getSource(),
sourceEvent.getValue().longValue());
}
break;
case REQUEST_STOP_SAMPLE_STREAM:
if(sourceEvent.getSource() instanceof CICTunerChannelSource)
{
mTunerController.removeBufferListener((CICTunerChannelSource)sourceEvent.getSource());
mSampleDelayBuffer.removeListener((CICTunerChannelSource)sourceEvent.getSource());
stopDelayBuffer();
}
break;
case REQUEST_SOURCE_DISPOSE:

View File

@@ -19,6 +19,7 @@ import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelManager;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
@@ -390,7 +391,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
* @return allocated DDC tuner channel source, or null if the channel cannot be provided by this source manager
*/
@Override
public TunerChannelSource getSource(TunerChannel tunerChannel)
public TunerChannelSource getSource(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{
if(isTunable(tunerChannel))
{
@@ -416,7 +417,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
}
//If we're successful to here, allocate the channel
return mPolyphaseChannelManager.getChannel(tunerChannel);
return mPolyphaseChannelManager.getChannel(tunerChannel, channelSpecification);
}
catch(SourceException se)
{

View File

@@ -95,6 +95,12 @@ public abstract class RTL2832TunerController extends USBTunerController
mDeviceDescriptor = deviceDescriptor;
}
@Override
public int getBufferSampleCount()
{
return getUSBTransferBufferSize(getSampleRate()) / 2; //2 bytes per complex sample
}
public void init() throws SourceException
{
@@ -987,17 +993,23 @@ public abstract class RTL2832TunerController extends USBTunerController
if(mUSBTransferProcessor != null)
{
if(sampleRate.getRate() > 300000)
{
mUSBTransferProcessor.setBufferSize(USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE);
}
else
{
mUSBTransferProcessor.setBufferSize(USB_TRANSFER_BUFFER_SIZE_LOW_SAMPLE_RATE);
}
mUSBTransferProcessor.setBufferSize(getUSBTransferBufferSize(sampleRate.getRate()));
}
}
/**
* Determines the size of USB transfer buffers according to the sample rate
*/
private int getUSBTransferBufferSize(double sampleRate)
{
if(sampleRate > 300000)
{
return USB_TRANSFER_BUFFER_SIZE_HIGH_SAMPLE_RATE;
}
return USB_TRANSFER_BUFFER_SIZE_LOW_SAMPLE_RATE;
}
public void setSampleRateFrequencyCorrection(int ppm) throws SourceException
{
int offset = -ppm * TWO_TO_22_POWER / 1000000;

View File

@@ -32,7 +32,7 @@ public class TestTunerController extends TunerController
public static final long MINIMUM_FREQUENCY = 1l;
public static final long MAXIMUM_FREQUENCY = 1000000000l;
public static final int SAMPLE_RATE = 50000;
public static final int SAMPLE_RATE = 10000000;
public static final int DC_NOISE_BANDWIDTH = 0;
public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00;
@@ -65,6 +65,12 @@ public class TestTunerController extends TunerController
}
}
@Override
public int getBufferSampleCount()
{
return SAMPLE_RATE / SPECTRAL_FRAME_RATE;
}
@Override
public void dispose()
{

View File

@@ -81,7 +81,7 @@ public class USBMasterProcessor
if(mRunning.compareAndSet(false, true))
{
//Set periodicity to an odd multiple to avoid contention with transfer buffer receivers
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 0L, 5L, TimeUnit.MILLISECONDS);
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 0L, 9L, TimeUnit.MILLISECONDS);
}
}
@@ -100,6 +100,14 @@ public class USBMasterProcessor
}
}
/**
* Stops the libusb timeout processor and prepares for shutdown.
*/
public void shutdown()
{
stop();
}
/**
* Runnable to invoke LibUSB timeout handler. All downstream completed transfer processing will occur on this
* thread instance.

View File

@@ -209,6 +209,11 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
if(broadcast && mListener != null)
{
if(samplesRead < 0)
{
throw new IOException("End of file reached");
}
if(samplesRead < buffer.length)
{
buffer = Arrays.copyOf(buffer, samplesRead);