mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 01:27:34 -05:00
Compare commits
2 Commits
v0.5.0-bet
...
v0.5.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
775ae00861 | ||
|
|
1b80f5f1b3 |
@@ -28,7 +28,7 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.5.0-beta1'
|
version = '0.5.0-beta2'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel processing manager handles all starting and stopping of channel decoding. A processing chain is created
|
* Channel processing manager handles all starting and stopping of channel decoding. A processing chain is created
|
||||||
@@ -74,7 +76,9 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelProcessingManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelProcessingManager.class);
|
||||||
private static final String TUNER_UNAVAILABLE_DESCRIPTION = "TUNER UNAVAILABLE";
|
private static final String TUNER_UNAVAILABLE_DESCRIPTION = "TUNER UNAVAILABLE";
|
||||||
private Map<Channel,ProcessingChain> mProcessingChains = new ConcurrentHashMap<>();
|
private Map<Channel,ProcessingChain> mProcessingChains = new ConcurrentHashMap<>();
|
||||||
|
private Lock mLock = new ReentrantLock();
|
||||||
|
|
||||||
|
private ChannelSourceEventErrorListener mSourceErrorListener = new ChannelSourceEventErrorListener();
|
||||||
private List<Listener<AudioSegment>> mAudioSegmentListeners = new CopyOnWriteArrayList<>();
|
private List<Listener<AudioSegment>> mAudioSegmentListeners = new CopyOnWriteArrayList<>();
|
||||||
private List<Listener<IDecodeEvent>> mDecodeEventListeners = new CopyOnWriteArrayList<>();
|
private List<Listener<IDecodeEvent>> mDecodeEventListeners = new CopyOnWriteArrayList<>();
|
||||||
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster();
|
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster();
|
||||||
@@ -170,9 +174,8 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
*
|
*
|
||||||
* @param event that requests either enable/start or disable/stop a channel.
|
* @param event that requests either enable/start or disable/stop a channel.
|
||||||
*/
|
*/
|
||||||
@Deprecated //TODO: convert all usage to start(ChannelStartProcessingRequest request) method
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void receive(ChannelEvent event)
|
public void receive(ChannelEvent event)
|
||||||
{
|
{
|
||||||
Channel channel = event.getChannel();
|
Channel channel = event.getChannel();
|
||||||
|
|
||||||
@@ -350,38 +353,9 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
processingChain.addDecodeEventListener(listener);
|
processingChain.addDecodeEventListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add a listener to detect source error state that indicates the channel should be shutdown
|
//Add a listener to detect source error state that indicates the channel should be shutdown.
|
||||||
processingChain.addSourceEventListener(sourceEvent ->
|
//Note: processing chain will only add this once.
|
||||||
{
|
processingChain.addSourceEventListener(mSourceErrorListener);
|
||||||
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE && sourceEvent.getSource() != null)
|
|
||||||
{
|
|
||||||
Channel toShutdown = null;
|
|
||||||
|
|
||||||
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
|
|
||||||
{
|
|
||||||
if(entry.getValue().hasSource(sourceEvent.getSource()))
|
|
||||||
{
|
|
||||||
toShutdown = entry.getKey();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(toShutdown != null)
|
|
||||||
{
|
|
||||||
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
stopProcessing(toShutdown);
|
|
||||||
}
|
|
||||||
catch(ChannelException ce)
|
|
||||||
{
|
|
||||||
mLog.error("Error stopping channel [" + channel.getName() + "] with source error - " +
|
|
||||||
ce.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Register this manager to receive channel events from traffic channel manager modules within
|
//Register this manager to receive channel events from traffic channel manager modules within
|
||||||
//the processing chain
|
//the processing chain
|
||||||
@@ -459,18 +433,90 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
|
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mProcessingChains.put(channel, processingChain);
|
if(addProcessingChain(channel, processingChain))
|
||||||
processingChain.start();
|
{
|
||||||
|
processingChain.start();
|
||||||
|
|
||||||
//This has to be done on the FX event thread when the playlist editor is constructed
|
//This has to be done on the FX event thread when the playlist editor is constructed
|
||||||
Platform.runLater(() -> channel.setProcessing(true));
|
Platform.runLater(() -> channel.setProcessing(true));
|
||||||
|
|
||||||
getChannelMetadataModel().add(new ChannelAndMetadata(channel, processingChain.getChannelState().getChannelMetadata()));
|
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.warn("Channel [" + channel.getName() + "] processing chain not added because it already exists");
|
||||||
|
processingChain.removeEventLoggingModules();
|
||||||
|
processingChain.removeRecordingModules();
|
||||||
|
processingChain.removeFrequencyChangeListener(channel);
|
||||||
|
channel.resetFrequencyCorrection();
|
||||||
|
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
||||||
|
mChannelEventBroadcaster.removeListener(processingChain);
|
||||||
|
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
||||||
|
processingChain.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START));
|
/**
|
||||||
|
* Thread-safe add processing chain and add channel metadata to channel metadata model.
|
||||||
|
* @param channel for the processing chain
|
||||||
|
* @param processingChain to add
|
||||||
|
* @return true if processing chain was added or false if it was not added due to there already
|
||||||
|
* being a processing chain registered for that channel.
|
||||||
|
*/
|
||||||
|
private boolean addProcessingChain(Channel channel, ProcessingChain processingChain)
|
||||||
|
{
|
||||||
|
boolean added = false;
|
||||||
|
|
||||||
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(!mProcessingChains.containsKey(channel))
|
||||||
|
{
|
||||||
|
added = true;
|
||||||
|
mProcessingChains.put(channel, processingChain);
|
||||||
|
getChannelMetadataModel().add(new ChannelAndMetadata(channel, processingChain.getChannelState().getChannelMetadata()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe remove processing chain.
|
||||||
|
* @param channel for identifying the processing chain
|
||||||
|
* @return the removed processing chain or null
|
||||||
|
*/
|
||||||
|
private ProcessingChain removeProcessingChain(Channel channel)
|
||||||
|
{
|
||||||
|
ProcessingChain removed = null;
|
||||||
|
|
||||||
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
removed = mProcessingChains.remove(channel);
|
||||||
|
|
||||||
|
if(removed != null)
|
||||||
|
{
|
||||||
|
for(ChannelMetadata channelMetadata: removed.getChannelState().getChannelMetadata())
|
||||||
|
{
|
||||||
|
getChannelMetadataModel().remove(channelMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -483,30 +529,34 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
//This has to be done on the FX event thread when the playlist editor is constructed
|
//This has to be done on the FX event thread when the playlist editor is constructed
|
||||||
Platform.runLater(() -> channel.setProcessing(false));
|
Platform.runLater(() -> channel.setProcessing(false));
|
||||||
|
|
||||||
if(mProcessingChains.containsKey(channel))
|
ProcessingChain processingChain = removeProcessingChain(channel);
|
||||||
|
|
||||||
|
if(processingChain != null)
|
||||||
{
|
{
|
||||||
ProcessingChain processingChain = mProcessingChains.remove(channel);
|
//Since the processing chain's source will block on stop(), throw it to the thread pool to run
|
||||||
|
ThreadPool.CACHED.submit(() -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
processingChain.stop();
|
||||||
|
processingChain.removeEventLoggingModules();
|
||||||
|
processingChain.removeRecordingModules();
|
||||||
|
|
||||||
for(ChannelMetadata channelMetadata: processingChain.getChannelState().getChannelMetadata())
|
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
||||||
{
|
processingChain.removeFrequencyChangeListener(channel);
|
||||||
getChannelMetadataModel().remove(channelMetadata);
|
channel.resetFrequencyCorrection();
|
||||||
}
|
|
||||||
|
|
||||||
processingChain.stop();
|
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
||||||
|
mChannelEventBroadcaster.removeListener(processingChain);
|
||||||
|
|
||||||
processingChain.removeEventLoggingModules();
|
//Unregister for event bus requests and notifications
|
||||||
processingChain.removeRecordingModules();
|
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
||||||
|
processingChain.dispose();
|
||||||
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
}
|
||||||
processingChain.removeFrequencyChangeListener(channel);
|
catch(Exception e)
|
||||||
channel.resetFrequencyCorrection();
|
{
|
||||||
|
mLog.error("Error during shutdown of processing chain for channel [" + channel.getName() + "}", e);
|
||||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
}
|
||||||
mChannelEventBroadcaster.removeListener(processingChain);
|
});
|
||||||
|
|
||||||
//Unregister for event bus requests and notifications
|
|
||||||
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
|
||||||
processingChain.dispose();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -653,4 +703,52 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors all channels for an error in the source event that would require the
|
||||||
|
* channel's processing chain to be stopped
|
||||||
|
*/
|
||||||
|
private class ChannelSourceEventErrorListener implements Listener<SourceEvent>
|
||||||
|
{
|
||||||
|
@Override public void receive(SourceEvent sourceEvent)
|
||||||
|
{
|
||||||
|
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE && sourceEvent.getSource() != null)
|
||||||
|
{
|
||||||
|
Channel toShutdown = null;
|
||||||
|
|
||||||
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
|
||||||
|
{
|
||||||
|
if(entry.getValue().hasSource(sourceEvent.getSource()))
|
||||||
|
{
|
||||||
|
toShutdown = entry.getKey();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(toShutdown != null)
|
||||||
|
{
|
||||||
|
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stopProcessing(toShutdown);
|
||||||
|
}
|
||||||
|
catch(ChannelException ce)
|
||||||
|
{
|
||||||
|
mLog.error("Error stopping channel [" + (toShutdown != null ? toShutdown.getName() : "unknown") +
|
||||||
|
"] with source error - " + ce.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,25 +23,29 @@ import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyphase Channelizer's Tuner Channel Source implementation. Wraps a ChannelOutputProcessor instance and
|
||||||
|
* provides tuner channel source functionality. Supports dynamic swapout of the underlying channel output processor
|
||||||
|
* as the upstream polyphase channelizer reconfigures during tuned center frequency changes.
|
||||||
|
*/
|
||||||
public class PolyphaseChannelSource extends TunerChannelSource implements Listener<ComplexSamples>
|
public class PolyphaseChannelSource extends TunerChannelSource implements Listener<ComplexSamples>
|
||||||
{
|
{
|
||||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||||
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
|
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
|
||||||
|
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
||||||
private long mReplacementFrequency;
|
private long mReplacementFrequency;
|
||||||
private double mChannelSampleRate;
|
private double mChannelSampleRate;
|
||||||
private long mIndexCenterFrequency;
|
private long mIndexCenterFrequency;
|
||||||
private long mChannelFrequencyCorrection;
|
private long mChannelFrequencyCorrection;
|
||||||
private Listener<ComplexSamples> mComplexSamplesListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polyphase channelizer tuner channel source implementation. Adapts the channel array output samples from the
|
* Constructs an instance
|
||||||
* polyphase channelizer into a single channel, or a channel synthesized from two adjacent channels that is
|
|
||||||
* frequency translated and decimated to a single channel.
|
|
||||||
*
|
*
|
||||||
* @param tunerChannel describing the desired channel frequency and bandwidth/minimum sample rate
|
* @param tunerChannel describing the desired channel frequency and bandwidth/minimum sample rate
|
||||||
* @param outputProcessor - to process polyphase channelizer channel results into a channel stream
|
* @param outputProcessor - to process polyphase channelizer channel results into a channel stream
|
||||||
@@ -58,7 +62,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
mPolyphaseChannelOutputProcessor = outputProcessor;
|
mPolyphaseChannelOutputProcessor = outputProcessor;
|
||||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||||
mChannelSampleRate = channelSampleRate;
|
mChannelSampleRate = channelSampleRate;
|
||||||
|
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
|
||||||
setFrequency(centerFrequency);
|
setFrequency(centerFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +71,8 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
{
|
{
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
mStreamHeartbeatProcessor.start();
|
||||||
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null)
|
if(mPolyphaseChannelOutputProcessor != null)
|
||||||
{
|
{
|
||||||
mPolyphaseChannelOutputProcessor.start();
|
mPolyphaseChannelOutputProcessor.start();
|
||||||
@@ -82,6 +88,8 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
{
|
{
|
||||||
mPolyphaseChannelOutputProcessor.stop();
|
mPolyphaseChannelOutputProcessor.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mStreamHeartbeatProcessor.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,19 +98,14 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
@Override
|
@Override
|
||||||
public void setListener(final Listener<ComplexSamples> listener)
|
public void setListener(final Listener<ComplexSamples> listener)
|
||||||
{
|
{
|
||||||
mComplexSamplesListener = listener;
|
mStreamHeartbeatProcessor.setListener(listener);
|
||||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive(ComplexSamples complexSamples)
|
public void receive(ComplexSamples complexSamples)
|
||||||
{
|
{
|
||||||
getHeartbeatManager().broadcast();
|
mStreamHeartbeatProcessor.receive(complexSamples);
|
||||||
|
|
||||||
if(mComplexSamplesListener != null)
|
|
||||||
{
|
|
||||||
mComplexSamplesListener.receive(complexSamples);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2022 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;
|
|
||||||
|
|
||||||
import io.github.dsheirer.buffer.INativeBuffer;
|
|
||||||
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
|
|
||||||
import io.github.dsheirer.dsp.filter.cic.PrimeCicDecimationFilter;
|
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
|
||||||
import io.github.dsheirer.dsp.mixer.ComplexMixer;
|
|
||||||
import io.github.dsheirer.dsp.mixer.ComplexMixerFactory;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamplesRepackager;
|
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
|
||||||
import io.github.dsheirer.util.Dispatcher;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complex sample source that provides a frequency-translated and decimated sample buffer stream. Uses a
|
|
||||||
* CIC decimation filter that requires the decimation rate to be an integer multiple. Sample buffer processing
|
|
||||||
* occurs on a scheduled runnable thread.
|
|
||||||
*/
|
|
||||||
public class CICTunerChannelSource<T extends INativeBuffer> extends TunerChannelSource implements Listener<T>
|
|
||||||
{
|
|
||||||
//Maximum number of filled buffers for the blocking queue
|
|
||||||
private static final int BUFFER_MAX_CAPACITY = 300;
|
|
||||||
|
|
||||||
//Threshold for resetting buffer overflow condition
|
|
||||||
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
|
|
||||||
|
|
||||||
//Samples repackager is set to 2048 (a power of 2) to align with channel processing chain requirements.
|
|
||||||
private final ComplexSamplesRepackager mSamplesRepackager = new ComplexSamplesRepackager(1024);
|
|
||||||
private Dispatcher<T> mBufferDispatcher;
|
|
||||||
private ComplexMixer mFrequencyCorrectionMixer;
|
|
||||||
private PrimeCicDecimationFilter mIDecimationFilter;
|
|
||||||
private PrimeCicDecimationFilter mQDecimationFilter;
|
|
||||||
private double mChannelSampleRate;
|
|
||||||
private long mChannelFrequencyCorrection = 0;
|
|
||||||
private long mTunerFrequency;
|
|
||||||
private Listener<ComplexSamples> mListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a frequency translating and CIC decimating channel source.
|
|
||||||
*
|
|
||||||
* @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 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, ChannelSpecification channelSpecification) throws FilterDesignException
|
|
||||||
{
|
|
||||||
super(producerSourceEventListener, tunerChannel);
|
|
||||||
|
|
||||||
int decimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate());
|
|
||||||
|
|
||||||
mIDecimationFilter = new PrimeCicDecimationFilter(sampleRate, decimation, channelSpecification.getPassFrequency(),
|
|
||||||
channelSpecification.getStopFrequency());
|
|
||||||
mQDecimationFilter = new PrimeCicDecimationFilter(sampleRate, decimation, channelSpecification.getPassFrequency(),
|
|
||||||
channelSpecification.getStopFrequency());
|
|
||||||
|
|
||||||
mBufferDispatcher = new Dispatcher(BUFFER_MAX_CAPACITY, "sdrtrunk cic channel " +
|
|
||||||
tunerChannel.getFrequency(), new NativeBufferPoisonPill());
|
|
||||||
mBufferDispatcher.setListener(new NativeBufferProcessor());
|
|
||||||
|
|
||||||
//Setup the frequency mixer to the current source frequency
|
|
||||||
mChannelSampleRate = sampleRate / (double)decimation;
|
|
||||||
mTunerFrequency = tunerChannel.getFrequency();
|
|
||||||
long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency();
|
|
||||||
|
|
||||||
mFrequencyCorrectionMixer = ComplexMixerFactory.getMixer(frequencyOffset, sampleRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
super.start();
|
|
||||||
mBufferDispatcher.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop()
|
|
||||||
{
|
|
||||||
super.stop();
|
|
||||||
mBufferDispatcher.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Primary interface for receiving incoming complex sample buffers to be frequency translated and decimated.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void receive(T complexSamples)
|
|
||||||
{
|
|
||||||
mBufferDispatcher.receive(complexSamples);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets/updates the center frequency for the sample streaming being sent from the producer.
|
|
||||||
*
|
|
||||||
* @param frequency in hertz
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setFrequency(long frequency)
|
|
||||||
{
|
|
||||||
mTunerFrequency = frequency;
|
|
||||||
updateMixerFrequencyOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current frequency correction being applied to the channel sample stream
|
|
||||||
*
|
|
||||||
* @return correction in hertz
|
|
||||||
*/
|
|
||||||
public long getFrequencyCorrection()
|
|
||||||
{
|
|
||||||
return mChannelFrequencyCorrection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
|
|
||||||
*
|
|
||||||
* @param correction current frequency correction value.
|
|
||||||
*/
|
|
||||||
public void setFrequencyCorrection(long correction)
|
|
||||||
{
|
|
||||||
mChannelFrequencyCorrection = correction;
|
|
||||||
updateMixerFrequencyOffset();
|
|
||||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the local mixer frequency offset from the tuned frequency,
|
|
||||||
* channel's requested frequency, and channel frequency correction.
|
|
||||||
*/
|
|
||||||
private void updateMixerFrequencyOffset()
|
|
||||||
{
|
|
||||||
long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
|
|
||||||
mFrequencyCorrectionMixer.setFrequency(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the sample rate of the incoming sample stream from the producer
|
|
||||||
*
|
|
||||||
* @param sampleRate in hertz
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void setSampleRate(double sampleRate)
|
|
||||||
{
|
|
||||||
//Not implemented. Sample rate changes are not permitted once sample stream starts
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the frequency correction for the outbound sample stream to the consumer
|
|
||||||
*
|
|
||||||
* @param correction in hertz
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void setChannelFrequencyCorrection(long correction)
|
|
||||||
{
|
|
||||||
mChannelFrequencyCorrection = correction;
|
|
||||||
broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction));
|
|
||||||
updateMixerFrequencyOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frequency correction value currently being applied to the outbound sample stream
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getChannelFrequencyCorrection()
|
|
||||||
{
|
|
||||||
return mChannelFrequencyCorrection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the listener to receive the complex buffer sample output from this channel
|
|
||||||
*
|
|
||||||
* @param listener to receive complex buffers
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setListener(Listener<ComplexSamples> listener)
|
|
||||||
{
|
|
||||||
mListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getSampleRate()
|
|
||||||
{
|
|
||||||
return mChannelSampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes native buffers received from the dispatcher and sends to the registered listener
|
|
||||||
*/
|
|
||||||
public class NativeBufferProcessor implements Listener<T>
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void receive(T nativeBuffer)
|
|
||||||
{
|
|
||||||
getHeartbeatManager().broadcast();
|
|
||||||
|
|
||||||
if(mListener != null)
|
|
||||||
{
|
|
||||||
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
|
||||||
|
|
||||||
while(iterator.hasNext())
|
|
||||||
{
|
|
||||||
ComplexSamples basebanded = mFrequencyCorrectionMixer.mix(iterator.next());
|
|
||||||
float[] i = mIDecimationFilter.decimate(basebanded.i());
|
|
||||||
float[] q = mQDecimationFilter.decimate(basebanded.q());
|
|
||||||
|
|
||||||
List<ComplexSamples> repackaged = mSamplesRepackager.process(i, q);
|
|
||||||
|
|
||||||
for(ComplexSamples samples: repackaged)
|
|
||||||
{
|
|
||||||
mListener.receive(samples);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -54,6 +54,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
private long mChannelFrequencyCorrection = 0;
|
private long mChannelFrequencyCorrection = 0;
|
||||||
private long mTunerFrequency;
|
private long mTunerFrequency;
|
||||||
private Listener<ComplexSamples> mListener;
|
private Listener<ComplexSamples> mListener;
|
||||||
|
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a frequency translating and CIC decimating channel source.
|
* Constructs a frequency translating and CIC decimating channel source.
|
||||||
@@ -70,6 +71,8 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
{
|
{
|
||||||
super(producerSourceEventListener, tunerChannel);
|
super(producerSourceEventListener, tunerChannel);
|
||||||
|
|
||||||
|
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
|
||||||
|
|
||||||
int desiredDecimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate());
|
int desiredDecimation = (int)(sampleRate / channelSpecification.getMinimumSampleRate());
|
||||||
int decimation = DecimationFilterFactory.getDecimationRate(desiredDecimation);
|
int decimation = DecimationFilterFactory.getDecimationRate(desiredDecimation);
|
||||||
|
|
||||||
@@ -91,6 +94,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
super.start();
|
super.start();
|
||||||
|
mStreamHeartbeatProcessor.start();
|
||||||
mBufferDispatcher.start();
|
mBufferDispatcher.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +103,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
{
|
{
|
||||||
super.stop();
|
super.stop();
|
||||||
mBufferDispatcher.stop();
|
mBufferDispatcher.stop();
|
||||||
|
mStreamHeartbeatProcessor.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -200,13 +205,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
@Override
|
@Override
|
||||||
public void setListener(Listener<ComplexSamples> listener)
|
public void setListener(Listener<ComplexSamples> listener)
|
||||||
{
|
{
|
||||||
if(listener == null)
|
mStreamHeartbeatProcessor.setListener(listener);
|
||||||
{
|
|
||||||
//Wait for the native buffer processor thread to die before we nullify the listener
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
mListener = listener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -223,21 +222,14 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
@Override
|
@Override
|
||||||
public void receive(T nativeBuffer)
|
public void receive(T nativeBuffer)
|
||||||
{
|
{
|
||||||
getHeartbeatManager().broadcast();
|
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
||||||
|
|
||||||
Listener<ComplexSamples> listener = mListener;
|
while(iterator.hasNext())
|
||||||
|
|
||||||
if(listener != null)
|
|
||||||
{
|
{
|
||||||
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
ComplexSamples basebanded = mFrequencyCorrectionMixer.mix(iterator.next());
|
||||||
|
float[] i = mIDecimationFilter.decimateReal(basebanded.i());
|
||||||
while(iterator.hasNext())
|
float[] q = mQDecimationFilter.decimateReal(basebanded.q());
|
||||||
{
|
mStreamHeartbeatProcessor.receive(new ComplexSamples(i, q));
|
||||||
ComplexSamples basebanded = mFrequencyCorrectionMixer.mix(iterator.next());
|
|
||||||
float[] i = mIDecimationFilter.decimateReal(basebanded.i());
|
|
||||||
float[] q = mQDecimationFilter.decimateReal(basebanded.q());
|
|
||||||
listener.receive(new ComplexSamples(i, q));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(PassThroughChannelSource.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PassThroughChannelSource.class);
|
||||||
private TunerController mTunerController;
|
private TunerController mTunerController;
|
||||||
private Dispatcher<INativeBuffer> mBufferDispatcher;
|
private Dispatcher<INativeBuffer> mBufferDispatcher;
|
||||||
private Listener<ComplexSamples> mComplexSamplesListener;
|
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -59,12 +59,14 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
|||||||
mBufferDispatcher = new Dispatcher<>(500, "sdrtrunk pass-through channel " +
|
mBufferDispatcher = new Dispatcher<>(500, "sdrtrunk pass-through channel " +
|
||||||
tunerChannel.getFrequency(), new NativeBufferPoisonPill());
|
tunerChannel.getFrequency(), new NativeBufferPoisonPill());
|
||||||
mBufferDispatcher.setListener(new BufferProcessor());
|
mBufferDispatcher.setListener(new BufferProcessor());
|
||||||
|
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
super.start();
|
super.start();
|
||||||
|
mStreamHeartbeatProcessor.stop();
|
||||||
mBufferDispatcher.start();
|
mBufferDispatcher.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,7 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
|||||||
{
|
{
|
||||||
super.stop();
|
super.stop();
|
||||||
mBufferDispatcher.stop();
|
mBufferDispatcher.stop();
|
||||||
|
mStreamHeartbeatProcessor.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -100,9 +103,9 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setListener(Listener<ComplexSamples> complexSamplesListener)
|
public void setListener(Listener<ComplexSamples> listener)
|
||||||
{
|
{
|
||||||
mComplexSamplesListener = complexSamplesListener;
|
mStreamHeartbeatProcessor.setListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -122,16 +125,11 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
|
|||||||
@Override
|
@Override
|
||||||
public void receive(INativeBuffer nativeBuffer)
|
public void receive(INativeBuffer nativeBuffer)
|
||||||
{
|
{
|
||||||
getHeartbeatManager().broadcast();
|
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
||||||
|
|
||||||
if(mComplexSamplesListener != null)
|
while(iterator.hasNext())
|
||||||
{
|
{
|
||||||
Iterator<ComplexSamples> iterator = nativeBuffer.iterator();
|
mStreamHeartbeatProcessor.receive(iterator.next());
|
||||||
|
|
||||||
while(iterator.hasNext())
|
|
||||||
{
|
|
||||||
mComplexSamplesListener.receive(iterator.next());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2022 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;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe implementation that coordinates the flow of data/buffer stream processing with a parallel, periodic
|
||||||
|
* heartbeat using an internal locking mechanism to coordinate the two activities. This class is intended to be used
|
||||||
|
* by TunerChannelSource implementations to coordinate the flow of sample buffers produced on a dispatch thread with a
|
||||||
|
* scheduled timer driven heartbeat mechanism, to ensure the downstream listener receives either samples or a
|
||||||
|
* heartbeat, but not both at the same time.
|
||||||
|
*
|
||||||
|
* Samples are processed as they arrive and are immediately followed by a heartbeat. A separate scheduled timer runs
|
||||||
|
* on a periodic schedule to optionally fire a heartbeat, independent of the sample stream rate. The scheduled
|
||||||
|
* heartbeat is optional and will skip a heartbeat interval if this processor is currently dispatching a sample buffer.
|
||||||
|
* Sample stream processing will block until a currently running scheduled heartbeat timer event completes, when these
|
||||||
|
* two events occur at the same time.
|
||||||
|
*/
|
||||||
|
public class StreamProcessorWithHeartbeat<T> implements Listener<T>
|
||||||
|
{
|
||||||
|
private ReentrantLock mLock = new ReentrantLock();
|
||||||
|
private HeartbeatManager mHeartbeatManager;
|
||||||
|
private long mHeartbeatInterval;
|
||||||
|
private Listener<T> mListener;
|
||||||
|
private ScheduledFuture<?> mHeartbeatTimerFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param heartbeatManager that will be invoked by an internal scheduled timer
|
||||||
|
* @param heartbeatInterval in milliseconds
|
||||||
|
*/
|
||||||
|
public StreamProcessorWithHeartbeat(HeartbeatManager heartbeatManager, long heartbeatInterval)
|
||||||
|
{
|
||||||
|
mHeartbeatManager = heartbeatManager;
|
||||||
|
mHeartbeatInterval = heartbeatInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the heartbeat timer
|
||||||
|
*/
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if(mHeartbeatTimerFuture == null)
|
||||||
|
{
|
||||||
|
mHeartbeatTimerFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> fireOptionalHeartbeat(),
|
||||||
|
mHeartbeatInterval, mHeartbeatInterval, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the heartbeat timer
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if(mHeartbeatTimerFuture != null)
|
||||||
|
{
|
||||||
|
mHeartbeatTimerFuture.cancel(true);
|
||||||
|
mHeartbeatTimerFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally fires a heartbeat if we're not currently processing a native buffer
|
||||||
|
*/
|
||||||
|
private void fireOptionalHeartbeat()
|
||||||
|
{
|
||||||
|
//Acquire lock only if it's not already locked, otherwise ignore heartbeat for this timer interval.
|
||||||
|
if(mLock.tryLock())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mHeartbeatManager.broadcast();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(T data)
|
||||||
|
{
|
||||||
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mHeartbeatManager.broadcast();
|
||||||
|
|
||||||
|
Listener<T> listener = mListener;
|
||||||
|
|
||||||
|
if(listener != null)
|
||||||
|
{
|
||||||
|
listener.receive(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive data/buffers for the defined generic type that are distributed to this processor.
|
||||||
|
* @param listener to register or null to deregister an existing listener
|
||||||
|
*/
|
||||||
|
public void setListener(Listener<T> listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,12 +31,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
public abstract class TunerChannelSource extends ComplexSource implements ISourceEventProcessor
|
public abstract class TunerChannelSource extends ComplexSource implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
|
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
|
||||||
private static final long BUFFER_PROCESSOR_RUN_INTERVAL_MILLISECONDS = 50;
|
protected static final long HEARTBEAT_INTERVAL_MS = 100;
|
||||||
private SourceEventListenerToProcessorAdapter mConsumerSourceEventListenerAdapter;
|
private SourceEventListenerToProcessorAdapter mConsumerSourceEventListenerAdapter;
|
||||||
protected TunerChannel mTunerChannel;
|
protected TunerChannel mTunerChannel;
|
||||||
private Listener<SourceEvent> mProducerSourceEventListener;
|
private Listener<SourceEvent> mProducerSourceEventListener;
|
||||||
private Listener<SourceEvent> mConsumerSourceEventListener;
|
private Listener<SourceEvent> mConsumerSourceEventListener;
|
||||||
// private ScheduledIntervalProcessor mScheduledIntervalProcessor = new ScheduledIntervalProcessor();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tuner Channel Source is a Digital Drop Channel (DDC) abstract class that defines the minimum functionality
|
* Tuner Channel Source is a Digital Drop Channel (DDC) abstract class that defines the minimum functionality
|
||||||
@@ -245,95 +244,4 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
|
|||||||
{
|
{
|
||||||
mConsumerSourceEventListener = null;
|
mConsumerSourceEventListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Processor to periodically invoke buffer sample processing on an interval timer. At each interval this processor
|
|
||||||
// * sends a heartbeat to the registered consumer and then commands the sub-class implementation to process any
|
|
||||||
// * queued buffers and distribute complex buffer sample(s) to the registered consumer.
|
|
||||||
// */
|
|
||||||
// public class ScheduledIntervalProcessor implements Runnable
|
|
||||||
// {
|
|
||||||
// private ScheduledFuture<?> mScheduledFuture;
|
|
||||||
// private boolean mStopped = false;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Commands this processor to do a shutdown at the end of this or the next iteration. Once successfully
|
|
||||||
// * shutdown, it will invoke the performDisposal() method to cleanup this instance.
|
|
||||||
// */
|
|
||||||
// public void stop()
|
|
||||||
// {
|
|
||||||
// mStopped = true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Starts buffer processing on a fixed interval using a scheduled thread pool
|
|
||||||
// */
|
|
||||||
// public void start()
|
|
||||||
// {
|
|
||||||
// if(mScheduledFuture == null)
|
|
||||||
// {
|
|
||||||
// mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this, 0,
|
|
||||||
// BUFFER_PROCESSOR_RUN_INTERVAL_MILLISECONDS, TimeUnit.MILLISECONDS);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Implementation of the Runnable interface to periodically send a heartbeat and then process buffer samples.
|
|
||||||
// */
|
|
||||||
// @Override
|
|
||||||
// public void run()
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// if(!mStopped)
|
|
||||||
// {
|
|
||||||
// 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)
|
|
||||||
// {
|
|
||||||
// if(mScheduledFuture != null)
|
|
||||||
// {
|
|
||||||
// //Set may-interrupt to false so that we can complete this iteration
|
|
||||||
// mScheduledFuture.cancel(false);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// mScheduledFuture = null;
|
|
||||||
//
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// getHeartbeatManager().broadcast();
|
|
||||||
// performDisposal();
|
|
||||||
// }
|
|
||||||
// catch(Throwable t)
|
|
||||||
// {
|
|
||||||
// mLog.error("Error during final shutdown processing of samples", t);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch(Throwable t)
|
|
||||||
// {
|
|
||||||
// mLog.error("Error during heartbeat processing", t);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user