Merge branch 'master' into 39-polyphase-channelizer

# Conflicts:
#	src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java
This commit is contained in:
Denny
2018-01-15 04:56:16 -05:00
7 changed files with 398 additions and 253 deletions

View File

@@ -1,17 +1,52 @@
# sdrtrunk
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0)
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/images/ApplicationOverview_V0.3.0.png)
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/v0.3/images/ApplicationOverview_V0.3.0.png)
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
# Instructions:
# End User Instructions:
If you simply want to download and run the program, please follow these instructions.
## Install Java 8 (or newer)
## Download the latest sdrtrunk release
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
**(final)** These versions have been tested and are the current release version.
**(beta)** These versions are currently being tested for bugs and functionality.
## Run the application
Either double-click on the downloaded file (if supported on your operating system) or open a terminal/command window
and change to the directory where you downloaded the release file and type:
```
java -jar downloaded-jar-filename
```
Note: replace _downloaded-jar-filename_ with the actual name of the sdrtrunk release version that you downloaded
## Optional - P25 Audio
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions
for downloading the JMBE audio library source code and compiling the JMBE library. Copy the resulting JMBE audio library
jar file to the same folder containing the sdrtrunk application to use the library with sdrtrunk.
# Developer Instructions:
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
## Build the project
Project uses gradle as build system. You can build it with locally installed gradle or if you do not have/want install gradle you can use preconfigured linux/windows wrapper.
sdrtrunk uses gradle as the build system. You can build it with locally installed gradle or if you do not have/want
to install gradle you can use preconfigured linux/windows wrapper.
### Build with locally installed gradle 4.3.1
```
gradle clean buildSdr
@@ -35,20 +70,9 @@ version = '0.3.2'
```
## Run SDRTrunk
SDRTrunk is packed into single uber-jar file. There is need to have folders with dependecies and
SDRTrunk is packed into single uber-jar file. There is need to have folders with dependencies and
adding them with classpath. Just run with java.
### Use Java8
```
java -jar build/libs/sdr-trunk-all-0.3.2.jar
```
## TODO ITEMS
- [x] Move project to gradle build
- [x] Changed folders structure, based on java project
- [x] Dependencies are moved to gradle and versions are updated
- [x] Create uberJAR with all dependencies
- [ ] Some dependencies left in imports folder. Find them and include into gradle
- [ ] Move images into resources of project
- [ ] Add tests to project. This would guarantee that after refactoring part of functionality is not broken
- [ ] Create modular OOP system (radio-module, gui, web-ui, etc). This would allow to use RADIO modules to call from CLI, GUI or Web UI. And use SDRTrunk as dependency for other projects.

View File

@@ -15,7 +15,7 @@ repositories {
mavenCentral()
}
version = '0.3.3-beta.2'
version = '0.3.3-beta.3'
sourceCompatibility = 1.8
sourceSets {

View File

@@ -81,16 +81,23 @@ public class Icon implements Comparable<Icon>
{
try
{
URL imageURL = Icon.class.getResource(mPath);
if(imageURL == null && !mPath.startsWith("/"))
if(mPath.startsWith("/"))
{
imageURL = (Icon.class.getResource("/" + mPath));
mImageIcon = new ImageIcon(mPath);
}
if(imageURL != null)
else
{
mImageIcon = new ImageIcon(imageURL);
URL imageURL = Icon.class.getResource(mPath);
if(imageURL == null && !mPath.startsWith("/"))
{
imageURL = (Icon.class.getResource("/" + mPath));
}
if(imageURL != null)
{
mImageIcon = new ImageIcon(imageURL);
}
}
}
catch(Exception e)

View File

@@ -129,7 +129,7 @@ public class IconManager
/**
* All icons in a sorted array
*/
public io.github.dsheirer.icon.Icon[] getIcons()
public Icon[] getIcons()
{
return getModel().getIconsAsArray();
}
@@ -159,7 +159,11 @@ public class IconManager
Icon icon = getModel().getIcon(name);
ImageIcon scaledIcon = getScaledIcon(icon.getIcon(), height);
mResizedIcons.put(scaledIconName, scaledIcon);
if(scaledIcon != null)
{
mResizedIcons.put(scaledIconName, scaledIcon);
}
return scaledIcon;
}
@@ -173,14 +177,19 @@ public class IconManager
*/
public static ImageIcon getScaledIcon(ImageIcon original, int height)
{
double scale = (double) original.getIconHeight() / (double) height;
if(original != null)
{
double scale = (double) original.getIconHeight() / (double) height;
int scaledWidth = (int) ((double) original.getIconWidth() / scale);
int scaledWidth = (int) ((double) original.getIconWidth() / scale);
Image scaledImage = original.getImage().getScaledInstance(scaledWidth,
height, java.awt.Image.SCALE_SMOOTH);
Image scaledImage = original.getImage().getScaledInstance(scaledWidth,
height, java.awt.Image.SCALE_SMOOTH);
return new ImageIcon(scaledImage);
return new ImageIcon(scaledImage);
}
return null;
}
/**
@@ -191,9 +200,14 @@ public class IconManager
*/
public static ImageIcon getScaledIcon(String path, int height)
{
Icon icon = new Icon("", path);
if(path != null)
{
Icon icon = new Icon("", path);
return getScaledIcon(icon.getIcon(), height);
return getScaledIcon(icon.getIcon(), height);
}
return null;
}
/**

View File

@@ -20,10 +20,16 @@ package io.github.dsheirer.icon;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
@@ -119,7 +125,7 @@ public class IconSelector extends JDialog implements DocumentListener
String name = mNameText.getText();
String path = mFileLabel.getText();
io.github.dsheirer.icon.Icon icon = new io.github.dsheirer.icon.Icon(name, path);
Icon icon = new Icon(name, path);
mIconTableModel.add(icon);
IconSelector.this.dispose();

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
* 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
@@ -18,72 +18,53 @@ package io.github.dsheirer.spectrum;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Buffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.complex.ComplexBuffer;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProcessor;
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
import io.github.dsheirer.util.ThreadPool;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Processes both complex samples or float samples and dispatches a float array
* of DFT results, using configurable fft size and output dispatch timelines.
* Processes both complex samples or float samples and dispatches a float array of DFT results, using configurable fft
* size and output dispatch timelines.
*/
public class DFTProcessor implements Listener<ComplexBuffer>, ISourceEventProcessor, IDFTWidthChangeProcessor
public class DFTProcessor implements Listener<ComplexBuffer>, IFrequencyChangeProcessor, IDFTWidthChangeProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(DFTProcessor.class);
private CopyOnWriteArrayList<DFTResultsConverter> mListeners = new CopyOnWriteArrayList<DFTResultsConverter>();
//Fixed size sample queue. We set the size at 12 based on the highest
//sample rate tuner (10 MHz) producing 153 frames per second and the lowest
//DFT processing frame rate of 12 frames per second. This means that the
//maximum size needed is 153 / 12 = 11 frames per DFT processing iteration
//so we set it at 12.
private BlockingQueue<ComplexBuffer> mQueue = new ArrayBlockingQueue<>(12);
private ScheduledFuture<?> mProcessorTaskHandle;
public static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate";
private static final Logger mLog = LoggerFactory.getLogger(DFTProcessor.class);
private static final int BUFFER_QUEUE_MAX_SIZE = 12;
private static final int BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD = 6;
private static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate";
//The Cosine and Hanning windows seem to offer the best spectral display with minimal bin leakage/smearing
private WindowType mWindowType = Window.WindowType.HANNING;
private double[] mWindow;
private DFTSize mDFTSize = DFTSize.FFT04096;
private DFTSize mNewDFTSize = DFTSize.FFT04096;
private double[] mWindow;
/* The Cosine and Hanning windows seem to offer the best spectral display
* with minimal bin leakage/smearing */
private WindowType mWindowType = WindowType.BLACKMAN_HARRIS_7;
private FloatFFT_1D mFFT = new FloatFFT_1D(mDFTSize.getSize());
private int mFrameRate;
private int mSampleRate;
private int mFFTFloatsPerFrame;
private float mNewFloatsPerFrame;
private float mNewFloatResidual;
private float[] mPreviousFrame = new float[8192];
private float[] mCurrentBuffer;
private int mCurrentBufferPointer = 0;
private int mSampleRate = 2400000; //Initial high value until we receive update from tuner
private int mFrameSize;
private int mFrameFlushCount;
private int mFrameOverlapCount;
private SampleType mSampleType;
private AtomicBoolean mRunning = new AtomicBoolean();
private ScheduledFuture<?> mProcessorTaskHandle;
private CopyOnWriteArrayList<DFTResultsConverter> mListeners = new CopyOnWriteArrayList<DFTResultsConverter>();
private OverflowableBufferStream mOverflowableBufferStream = new OverflowableBufferStream(BUFFER_QUEUE_MAX_SIZE,
BUFFER_QUEUE_OVERFLOW_RESET_THRESHOLD, mDFTSize.getSize());
private float[] mPreviousSamples;
public DFTProcessor(SampleType sampleType)
{
@@ -98,9 +79,8 @@ public class DFTProcessor implements Listener<ComplexBuffer>, ISourceEventProces
stop();
mListeners.clear();
mQueue.clear();
mOverflowableBufferStream.clear();
mWindow = null;
mCurrentBuffer = null;
}
public WindowType getWindowType()
@@ -158,14 +138,10 @@ public class DFTProcessor implements Listener<ComplexBuffer>, ISourceEventProces
public void setFrameRate(int framesPerSecond)
{
//TODO: make sure frame rate & sample rate sample requirement doesn't
//expect overlap greater than the previous frame length
if(framesPerSecond < 1 || framesPerSecond > 1000)
{
throw new IllegalArgumentException("DFTProcessor cannot run "
+ "more than 1000 times per second -- requested setting:"
+ framesPerSecond);
throw new IllegalArgumentException("DFTProcessor cannot run more than 1000 times per second -- requested " +
"setting:" + framesPerSecond);
}
mFrameRate = framesPerSecond;
@@ -202,168 +178,46 @@ public class DFTProcessor implements Listener<ComplexBuffer>, ISourceEventProces
start();
}
public int getCalculationsPerSecond()
{
return mFrameRate;
}
/**
* Places the sample into a transfer queue for future processing.
*/
@Override
public void receive(ComplexBuffer sampleBuffer)
{
mQueue.offer(sampleBuffer);
}
private void getNextBuffer()
{
mCurrentBuffer = null;
try
{
Buffer buffer = mQueue.take();
mCurrentBuffer = buffer.getSamples();
}
catch(InterruptedException e)
{
mCurrentBuffer = null;
}
mCurrentBufferPointer = 0;
}
private float[] getSamples()
{
int remaining = (int) mFFTFloatsPerFrame;
float[] currentFrame = new float[remaining];
int currentFramePointer = 0;
float integralFloatsToConsume = mNewFloatsPerFrame + mNewFloatResidual;
int newFloatsToConsumeThisFrame = (int) integralFloatsToConsume;
mNewFloatResidual = integralFloatsToConsume - newFloatsToConsumeThisFrame;
/* If the number of required floats for the fft is greater than the
* consumption rate per frame, we have to reach into the previous
* frame to makeup the difference. */
if(newFloatsToConsumeThisFrame < remaining)
{
int previousFloatsRequired = remaining - newFloatsToConsumeThisFrame;
System.arraycopy(mPreviousFrame,
mPreviousFrame.length - previousFloatsRequired,
currentFrame,
currentFramePointer,
previousFloatsRequired);
remaining -= previousFloatsRequired;
currentFramePointer += previousFloatsRequired;
}
/* Fill the rest of the buffer with new samples */
while(mRunning.get() && remaining > 0)
{
if(mCurrentBuffer == null ||
mCurrentBufferPointer >= mCurrentBuffer.length)
{
getNextBuffer();
}
/* If we don't have new samples to use, send the current frame with
* the remaining values as zero */
if(mCurrentBuffer == null)
{
remaining = 0;
}
else
{
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
while(remaining > 0 && samplesAvailable > 0)
{
currentFrame[currentFramePointer++] =
(float) mCurrentBuffer[mCurrentBufferPointer++];
samplesAvailable--;
remaining--;
newFloatsToConsumeThisFrame--;
}
}
}
/* If the incoming float rate is greater than the fft consumption rate,
* then we have to purge some floats, otherwise, store the previous
* frame, because we have overlapping frames */
if(newFloatsToConsumeThisFrame > 0)
{
purge(newFloatsToConsumeThisFrame);
}
else
{
mPreviousFrame = Arrays.copyOf(currentFrame, currentFrame.length);
}
return currentFrame;
mOverflowableBufferStream.offer(sampleBuffer);
}
private void calculate()
{
float[] samples = getSamples();
Window.apply(mWindow, samples);
if(mSampleType == SampleType.REAL)
try
{
mFFT.realForward(samples);
}
else
{
mFFT.complexForward(samples);
}
dispatch(samples);
}
private void purge(int samplesToPurge)
{
if(samplesToPurge <= 0)
{
throw new IllegalArgumentException("DFTProcessor - cannot purge "
+ "negative sample amount");
}
while(mRunning.get() && samplesToPurge > 0)
{
if(mCurrentBuffer == null ||
mCurrentBufferPointer >= mCurrentBuffer.length)
if(mFrameFlushCount > 0)
{
getNextBuffer();
mOverflowableBufferStream.flush(mFrameFlushCount);
}
if(mCurrentBuffer != null)
{
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
//If this throws an IO exception, the buffer queue is (temporarily) empty and we return from the method
float[] samples = mOverflowableBufferStream.get(mFrameSize, mFrameOverlapCount);
if(samplesAvailable >= samplesToPurge)
{
mCurrentBufferPointer += samplesToPurge;
samplesToPurge = 0;
}
else
{
samplesToPurge -= samplesAvailable;
mCurrentBufferPointer = mCurrentBuffer.length;
}
Window.apply(mWindow, samples);
if(mSampleType == SampleType.REAL)
{
mFFT.realForward(samples);
}
else
{
samplesToPurge = 0;
mFFT.complexForward(samples);
}
dispatch(samples);
mPreviousSamples = samples;
}
catch(IOException ioe)
{
//No new data, dispatch the previous samples again
dispatch(mPreviousSamples);
}
}
@@ -386,11 +240,6 @@ public class DFTProcessor implements Listener<ComplexBuffer>, ISourceEventProces
mListeners.add(listener);
}
public void removeConverter(DFTResultsConverter listener)
{
mListeners.remove(listener);
}
private class DFTCalculationTask implements Runnable
{
@Override
@@ -430,26 +279,17 @@ public class DFTProcessor implements Listener<ComplexBuffer>, ISourceEventProces
setWindowType(mWindowType);
if(mSampleType == SampleType.COMPLEX)
{
mPreviousFrame = new float[mDFTSize.getSize() * 2];
}
else
{
mPreviousFrame = new float[mDFTSize.getSize()];
}
mFFT = new FloatFFT_1D(mDFTSize.getSize());
}
}
public void clearBuffer()
{
mQueue.clear();
mOverflowableBufferStream.clear();
}
@Override
public void process(SourceEvent event)
public void frequencyChanged(FrequencyChangeEvent event)
{
switch(event.getEvent())
{
@@ -463,17 +303,69 @@ public class DFTProcessor implements Listener<ComplexBuffer>, ISourceEventProces
}
/**
*
* Calculates the frame size, flush count and overlap count to use for each calculation cycle.
*/
private void calculateConsumptionRate()
{
mNewFloatResidual = 0.0f;
int floatsPerSample = mSampleType == SampleType.COMPLEX ? 2 : 1;
mNewFloatsPerFrame = ((float) mSampleRate / (float) mFrameRate) *
(mSampleType == SampleType.COMPLEX ? 2.0f : 1.0f);
mFrameSize = mDFTSize.getSize() * floatsPerSample;
mPreviousSamples = new float[mFrameSize];
mFFTFloatsPerFrame = (mSampleType == SampleType.COMPLEX ?
mDFTSize.getSize() * 2 :
mDFTSize.getSize());
int productionRate = mSampleRate * floatsPerSample;
int consumptionRate = mFrameRate * mFrameSize;
int residual = productionRate - consumptionRate;
if(residual < 0)
{
mFrameFlushCount = 0;
mFrameOverlapCount = -residual / mFrameRate;
if(mFrameOverlapCount * mFrameRate < -residual)
{
mFrameOverlapCount ++;
}
}
else if(residual > 0)
{
mFrameOverlapCount = 0;
mFrameFlushCount = residual / mFrameRate;
if(mFrameFlushCount * mFrameRate < residual)
{
mFrameFlushCount++;
}
}
//Ensure we're flushing or overlapping by even multiples of the sample size
if(mFrameOverlapCount % floatsPerSample != 0)
{
mFrameOverlapCount++;
}
if(mFrameFlushCount % floatsPerSample != 0)
{
mFrameFlushCount++;
}
//If the overlap size is greater than the frame size, we can't do that. Automatically decrease the frame rate
//until we reach a legitimate overlap value.
if(mFrameOverlapCount >= mFrameSize)
{
mLog.warn("Unable to provide frame rate [" + mFrameRate + "] for current DFT size [" + mDFTSize.getSize() +
"] - reducing frame rate");
mFrameRate--;
if(mFrameRate <= 0)
{
//Consider reducing the FFT size as a possible alternative corrective measure
throw new IllegalStateException("Unable to determine a viable frame rate based on incoming sample " +
"rate, DFT size and DFT frame rate");
}
//Recursively call this method to test the new frame rate
calculateConsumptionRate();
}
}
}

View File

@@ -0,0 +1,202 @@
/*******************************************************************************
* 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.spectrum;
import io.github.dsheirer.sample.Buffer;
import io.github.dsheirer.sample.OverflowableTransferQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.LinkedList;
public class OverflowableBufferStream extends OverflowableTransferQueue<Buffer>
{
private static final Logger mLog = LoggerFactory.getLogger(OverflowableBufferStream.class);
private int mFlushCount = 0;
private Buffer mCurrentBuffer;
private int mCurrentBufferPointer = 0;
private FloatBuffer mFloatBuffer;
private LinkedList<Buffer> mBufferList = new LinkedList<>();
private int mBufferFetchLimit;
/**
* Stream for receiving Buffers from a producer thread and providing access to a consumer thread to read the
* sample data. Extends the overflowable transfer queue to define maximum and reset thresholds to ensure the
* queue doesn't grow too large.
*
* This stream is designed for consistent sample size requests each time the get() samples method is invoked, such
* as might be needed for an FFT processor. It uses an internal float buffer that must be resized whenever the
* get(sample size) argument changes, so consider using another option if you anticipate that your sample size
* argument during get() operations will vary.
*
* @param maxSize of the internal buffer queue - defines the overflow threshold
* @param resetThreshold of the internal buffer queue - defines when the overflow state will be cleared once the
* queue size has reduced below this threshold
* @param arraySize of the anticipated samples to fetch using the get(x,y) method.
*/
public OverflowableBufferStream(int maxSize, int resetThreshold, int arraySize)
{
super(maxSize, resetThreshold);
mFloatBuffer = FloatBuffer.allocate(arraySize);
mBufferFetchLimit = maxSize = resetThreshold;
}
/**
* Retrieves the specified number of float samples from the stream. When a non-zero overlap is specified, a
* quantity of samples from the previous buffer are preloaded in the buffer and new samples are added to make a
* full array count.
*
* @param sampleCount of float samples to pull from the stream
* @param overlap to reuse part of the previous buffer's samples in this fetch - must be less than sample count
* @return an array of float samples from the stream
* @throws IOException if the buffer queue is/becomes empty and the samples cannot be provided
* @throws IllegalArgumentException if the overlap argument is not less than the sample count argument
*/
public float[] get(int sampleCount, int overlap) throws IOException
{
if(overlap >= sampleCount)
{
throw new IllegalArgumentException("Overlap must be less than the requested sample count");
}
if(mCurrentBuffer == null)
{
//This will throw an IO exception if the queue is empty
getNextBuffer();
}
//Resize the float buffer if the request size changes
if(mFloatBuffer.capacity() != sampleCount)
{
mFloatBuffer = FloatBuffer.allocate(sampleCount);
}
while(mFloatBuffer.hasRemaining())
{
int available = mCurrentBuffer.getSamples().length - mCurrentBufferPointer;
if(available <= 0)
{
getNextBuffer();
}
else
{
//Fill the buffer to capacity
int toCopy = mFloatBuffer.remaining();
if(available < toCopy)
{
toCopy = available;
}
if(toCopy > 0)
{
mFloatBuffer.put(mCurrentBuffer.getSamples(), mCurrentBufferPointer, toCopy);
mCurrentBufferPointer += toCopy;
}
}
}
//If we get to here, the float buffer is full. Get the samples and then refill with overlap if non-zero
mFloatBuffer.rewind();
float[] samples = new float[sampleCount];
mFloatBuffer.get(samples);
mFloatBuffer.clear();
if(overlap > 0)
{
mFloatBuffer.put(samples, samples.length - overlap, overlap);
}
try
{
performFlush();
}
catch(IOException ioe)
{
//Do nothing if we get an IO exception ... the buffer is empty
}
//Reset flush count to zero, even if we ran out of buffers
mFlushCount = 0;
return samples;
}
/**
* Throws away incoming sample data until mFlushCount is zeroized or the queue is empty.
*
* @throws IOException if the queue is emptied while performing flush.
*/
private void performFlush() throws IOException
{
//Flush sample data as requested
while(mFlushCount > 0)
{
int available = mCurrentBuffer.getSamples().length - mCurrentBufferPointer;
if(available <= mFlushCount)
{
mFlushCount -= available;
getNextBuffer();
}
else
{
mCurrentBufferPointer += mFlushCount;
mFlushCount = 0;
}
}
}
/**
* Flushes the specified number of samples from the incoming stream.
* @param sampleCount to throw away
*/
public void flush(int sampleCount)
{
mFlushCount += sampleCount;
}
/**
* Fetches the next buffer from the queue.
*
* @throws IOException if the queue is currently empty
*/
private void getNextBuffer() throws IOException
{
mCurrentBuffer = null;
mCurrentBufferPointer = 0;
if(mBufferList.isEmpty())
{
drainTo(mBufferList, mBufferFetchLimit);
}
if(!mBufferList.isEmpty())
{
mCurrentBuffer = mBufferList.poll();
}
if(mCurrentBuffer == null)
{
throw new IOException("Buffer queue is (currently) empty");
}
}
}