Merges from master branch.

This commit is contained in:
Denny
2017-12-27 10:10:51 -05:00
parent 667c50174d
commit 236df8f3f4
262 changed files with 7701 additions and 49122 deletions

View File

@@ -0,0 +1,222 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 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.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.Buffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import io.github.dsheirer.sample.complex.Complex;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.Tuner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class CICTunerChannelSource extends TunerChannelSource
{
private final static Logger mLog = LoggerFactory.getLogger(CICTunerChannelSource.class);
//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;
private static double CHANNEL_RATE = 48000.0;
private static int CHANNEL_PASS_FREQUENCY = 12000;
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
private Oscillator mFrequencyCorrectionMixer;
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener;
private List<ComplexBuffer> mSampleBuffers = new ArrayList<ComplexBuffer>();
private long mTunerFrequency = 0;
private double mTunerSampleRate;
private long mChannelFrequencyCorrection = 0;
/**
* Implements a heterodyne/decimate Digital Drop Channel (DDC) to decimate the IQ output from a tuner down to a
* fixed 48 kHz IQ channel rate.
*
* Note: this class can only be used once (started and stopped) and a new tuner channel source must be requested
* from the tuner once this object has been stopped. This is because channels are managed dynamically and center
* tuned frequency may have changed since this source was obtained and thus the tuner might no longer be able to
* source this channel once it has been stopped.
*
* @param tunerChannel specifying the center frequency for the DDC
*/
public CICTunerChannelSource(Tuner tuner, TunerChannel tunerChannel)
{
super(tuner.getTunerController().getSourceEventListener(), tunerChannel);
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
mBuffer.setSourceOverflowListener(this);
//Setup the frequency translator to the current source frequency
mTunerFrequency = tuner.getTunerController().getFrequency();
long frequencyOffset = mTunerFrequency - getTunerChannel().getFrequency();
mFrequencyCorrectionMixer = new Oscillator(frequencyOffset, tuner.getTunerController().getSampleRate());
//Setup the decimation filter chain
setSampleRate(tuner.getTunerController().getSampleRate());
}
/**
* Sets the center frequency for the incoming sample buffers
* @param frequency in hertz
*/
@Override
protected void setFrequency(long frequency)
{
mTunerFrequency = frequency;
//Reset frequency correction so that consumer components can adjust
setFrequencyCorrection(0);
updateMixerFrequencyOffset();
}
/**
* Current frequency correction being applied to the channel sample stream
* @return correction in hertz
*/
@Override
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.
*/
protected void setFrequencyCorrection(long correction)
{
mChannelFrequencyCorrection = correction;
updateMixerFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
/**
* Primary input method for receiving complex sample buffers from the wideband source (ie tuner)
* @param buffer to receive and eventually process
*/
public void receive(ComplexBuffer buffer)
{
mBuffer.offer(buffer);
}
@Override
public void setListener(Listener<ComplexBuffer> listener)
{
/* Save a pointer to the listener so that if we have to change the
* decimation filter, we can re-add the listener */
mListener = listener;
mDecimationFilter.setListener(listener);
}
@Override
public void removeListener(Listener<ComplexBuffer> listener)
{
mDecimationFilter.removeListener();
}
/**
* Updates the sample rate to the requested value and notifies any downstream components of the change
* @param sampleRate to set
*/
protected void setSampleRate(double sampleRate)
{
if(mTunerSampleRate != sampleRate)
{
mFrequencyCorrectionMixer.setSampleRate(sampleRate);
/* Get new decimation filter */
mDecimationFilter = FilterFactory.getDecimationFilter((int)sampleRate, (int)CHANNEL_RATE, 1,
CHANNEL_PASS_FREQUENCY, 60, Window.WindowType.HAMMING);
/* re-add the original output listener */
mDecimationFilter.setListener(mListener);
mTunerSampleRate = sampleRate;
broadcastConsumerSourceEvent(SourceEvent.channelSampleRateChange(getSampleRate()));
}
}
/**
* 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);
}
public double getSampleRate()
{
return CHANNEL_RATE;
}
@Override
protected void processSamples()
{
mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers)
{
float[] samples = buffer.getSamples();
/* We make a copy of the buffer so that we don't affect
* anyone else that is using the same buffer, like other
* channels or the spectral display */
float[] translated = new float[samples.length];
/* Perform frequency translation */
for(int x = 0; x < samples.length; x += 2)
{
mFrequencyCorrectionMixer.rotate();
translated[x] = Complex.multiplyInphase(
samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
translated[x + 1] = Complex.multiplyQuadrature(
samples[x], samples[x + 1], mFrequencyCorrectionMixer.inphase(), mFrequencyCorrectionMixer.quadrature());
}
final ComplexPrimeCICDecimate filter = mDecimationFilter;
if(filter != null)
{
filter.receive(new ComplexBuffer(translated));
}
}
mSampleBuffers.clear();
}
}

View File

@@ -0,0 +1,93 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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;
public class TunerChannel implements Comparable<TunerChannel>
{
private long mFrequency;
private int mBandwidth;
public TunerChannel(long frequency, int bandwidth)
{
mFrequency = frequency;
mBandwidth = bandwidth;
}
public long getFrequency()
{
return mFrequency;
}
public void setFrequency(long frequency)
{
mFrequency = frequency;
}
public int getBandwidth()
{
return mBandwidth;
}
public void setBandwidth(int bandwidth)
{
mBandwidth = bandwidth;
}
public long getMinFrequency()
{
return mFrequency - (mBandwidth / 2);
}
public long getMaxFrequency()
{
return mFrequency + (mBandwidth / 2);
}
/**
* Indicates if any part of this tuner channel overlaps the specified range
*/
public boolean overlaps(long minimum, long maximum)
{
long channelMin = getMinFrequency();
long channelMax = getMaxFrequency();
return ((minimum <= channelMin && channelMin <= maximum) ||
(minimum <= channelMax && channelMax <= maximum) ||
(minimum <= channelMin && channelMax <= maximum) ||
(channelMin <= minimum && maximum <= channelMax));
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Channel: ");
sb.append(getMinFrequency());
sb.append("-");
sb.append(getMaxFrequency());
return sb.toString();
}
@Override
public int compareTo(TunerChannel otherLock)
{
return Long.compare(getFrequency(), otherLock.getFrequency());
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 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.source.SourceException;
public interface TunerChannelProvider
{
/**
* Returns a tuner frequency channel source, tuned to the correct frequency
*
* @return - source for 48k sample rate
*/
public TunerChannelSource getChannel( TunerChannel channel ) throws SourceException;
/**
* Releases the tuned channel resources
*/
public void releaseChannel( TunerChannelSource source );
}

View File

@@ -0,0 +1,306 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.ComplexSource;
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.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public abstract class TunerChannelSource extends ComplexSource implements ISourceEventProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(TunerChannelSource.class);
private static final long BUFFER_PROCESSOR_RUN_INTERVAL_MILLISECONDS = 50;
private SourceEventListenerToProcessorAdapter mConsumerSourceEventListenerAdapter;
private TunerChannel mTunerChannel;
private Listener<SourceEvent> mProducerSourceEventListener;
private Listener<SourceEvent> mConsumerSourceEventListener;
private BufferProcessor mBufferProcessor = new BufferProcessor();
/**
* Tuner Channel Source is a Digital Drop Channel (DDC) abstract class that defines the minimum functionality
* requirements for a channel sample source.
*
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
* @param tunerChannel describing the desired channel frequency and bandwidth/minimum sample rate
*/
public TunerChannelSource(Listener<SourceEvent> producerSourceEventListener, TunerChannel tunerChannel)
{
mProducerSourceEventListener = producerSourceEventListener;
mTunerChannel = tunerChannel;
mConsumerSourceEventListenerAdapter = new SourceEventListenerToProcessorAdapter(this);
}
@Override
public long getFrequency()
{
return mTunerChannel.getFrequency();
}
/**
* Sets the center frequency for the sample streaming being sent from the producer.
* @param frequency in hertz
*/
protected abstract void setFrequency(long frequency);
/**
* Sets the sample rate of the incoming sample stream from the producer
* @param sampleRate in hertz
*/
protected abstract void setSampleRate(double sampleRate);
/**
* Sets the frequency correction for the outbound sample stream to the consumer
* @param correction in hertz
*/
protected abstract void setFrequencyCorrection(long correction);
/**
* Frequency correction value currently being applied to the outbound sample stream
*/
public abstract long getFrequencyCorrection();
/**
* Sets the listener to receive the complex buffer sample output from this channel
* @param complexBufferListener to receive complex buffers
*/
public abstract void setListener(Listener<ComplexBuffer> complexBufferListener);
/**
* Commands sub-class to process queued samples and distribute them to the consumer. This method will be invoked
* by an interval timer.
*/
protected abstract void processSamples();
/**
* Tuner channel for this tuner channel source
*/
public TunerChannel getTunerChannel()
{
return mTunerChannel;
}
@Override
public void dispose()
{
stop();
}
/**
* Starts this tuner channel source producing sample stream.
*/
public void start()
{
//Broadcast current frequency and sample rate so consumer can configure correctly
broadcastConsumerSourceEvent(SourceEvent.frequencyChange(getFrequency()));
broadcastConsumerSourceEvent(SourceEvent.sampleRateChange(getSampleRate()));
if(mProducerSourceEventListener != null)
{
mProducerSourceEventListener.receive(SourceEvent.startSampleStream(this));
}
mBufferProcessor.start();
}
/**
* Stops this tuner channel source from producing a sample stream. Note: tuner channel sources are one-time usage
* only. Invoking this method will also tell the source manager to dispose of this source.
*/
public void stop()
{
if(mProducerSourceEventListener != null)
{
mProducerSourceEventListener.receive(SourceEvent.stopSampleStream(this));
mProducerSourceEventListener.receive(SourceEvent.sourceDisposeRequest(this));
}
mBufferProcessor.stop();
}
@Override
public void reset()
{
//Reset is not valid for a tuner channel source - ignored
}
/**
* This method is invoked after the buffer processor is completely shutdown so that this instance can perform any
* cleanup operations needed to dispose of this instance.
*/
protected void performDisposal()
{
mProducerSourceEventListener = null;
mConsumerSourceEventListener = null;
mConsumerSourceEventListenerAdapter.dispose();
}
/**
* Processes source event notifications received from the producer/parent and source event requests received from
* the consumer.
*
* @param sourceEvent to process
* @throws SourceException if there is an error while processing the source event
*/
@Override
public void process(SourceEvent sourceEvent) throws SourceException
{
switch(sourceEvent.getEvent())
{
//Notification events from the producer
case NOTIFICATION_FREQUENCY_CHANGE:
setFrequency(sourceEvent.getValue().longValue());
break;
case NOTIFICATION_SAMPLE_RATE_CHANGE:
setSampleRate(sourceEvent.getValue().doubleValue());
break;
//Request events from the consumer
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setFrequencyCorrection(sourceEvent.getValue().longValue());
break;
default:
mLog.error("Ignoring unrecognized source event: " + sourceEvent.getEvent() + " from [" +
sourceEvent.getSource().getClass() + "]");
break;
}
}
/**
* Broadcasts the source event to the incoming sample stream producer.
* @param sourceEvent to broadcast
*/
protected void broadcastProducerSourceEvent(SourceEvent sourceEvent)
{
if(mProducerSourceEventListener != null)
{
mProducerSourceEventListener.receive(sourceEvent);
}
}
/**
* Broadcasts the source event to the outbound sample stream consumer.
* @param sourceEvent to broadcast
*/
protected void broadcastConsumerSourceEvent(SourceEvent sourceEvent)
{
if(mConsumerSourceEventListener != null)
{
mConsumerSourceEventListener.receive(sourceEvent);
}
}
/**
* Implements the ISourceEventListener interface for receiving source events (ie requests) from a consumer.
*
* Note: source events are simply transferred to the process() method in this class.
*/
@Override
public Listener<SourceEvent> getSourceEventListener()
{
return mConsumerSourceEventListenerAdapter;
}
/**
* Implements the ISourceEventProvider interface to set the consumer listener for source events.
* @param listener to receive consumer source events
*/
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mConsumerSourceEventListener = listener;
}
/**
* Implements the ISourceEventProvider interface to remove the consumer listener for source events.
*/
@Override
public void removeSourceEventListener()
{
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 BufferProcessor 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()
{
if(!mStopped)
{
getHeartbeatManager().broadcast();
processSamples();
}
if(mStopped)
{
if(mScheduledFuture != null)
{
//Set may-interrupt to false so that we can complete this iteration
mScheduledFuture.cancel(false);
}
mScheduledFuture = null;
performDisposal();
}
}
}
}