mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
[WIP] Implements reusable sample buffers across all components. This
produces a significant reduction in memory usage, especially for tuners like the airspy and hackrf that produce 10/20 MSPS sample rates. This is observed to be around 66% reduction in peak memory usage and provides a more stable memory utilization across time reducing stress on the garbage collector. Use of these reusable memory buffers will be pushed further down into the processing chain modules to ensure we keep new object creation to the lowest levels possible.
This commit is contained in:
@@ -1,25 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.source.mixer;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.adapter.ISampleAdapter;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.adapter.IComplexSampleAdapter;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.complex.reusable.ReusableComplexBuffer;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -32,87 +31,72 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ComplexMixer
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( ComplexMixer.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexMixer.class);
|
||||
|
||||
private long mFrequency = 0;
|
||||
private int mBufferSize = 16384;
|
||||
|
||||
private BufferReader mBufferReader = new BufferReader();
|
||||
private TargetDataLine mTargetDataLine;
|
||||
private AudioFormat mAudioFormat;
|
||||
private String mName;
|
||||
private int mBytesPerFrame = 0;
|
||||
private ISampleAdapter mSampleAdapter;
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
|
||||
/**
|
||||
* Complex Mixer - constructs a reader on the mixer/sound card target
|
||||
* data line using the specified audio format (sample size, sample rate )
|
||||
* and broadcasts complex (I/Q) sample buffers to all registered listeners.
|
||||
* Reads sample buffers sized to 10% of the sample rate specified in audio
|
||||
* format.
|
||||
*
|
||||
* @param targetDataLine - mixer or sound card to be used
|
||||
*
|
||||
* @param format - audio format
|
||||
*
|
||||
* @param name - token name to use for this mixer
|
||||
*
|
||||
* @param sampleAdapter - adapter to convert byte array data read from the
|
||||
* mixer into ComplexBuffer. The adapter can optionally invert the channel
|
||||
* data if the left/right stereo channels are inverted.
|
||||
*/
|
||||
public ComplexMixer( TargetDataLine targetDataLine,
|
||||
AudioFormat format,
|
||||
String name,
|
||||
ISampleAdapter sampleAdapter,
|
||||
Listener<ComplexBuffer> listener )
|
||||
private long mFrequency = 0;
|
||||
|
||||
private BufferReader mBufferReader = new BufferReader();
|
||||
private TargetDataLine mTargetDataLine;
|
||||
private AudioFormat mAudioFormat;
|
||||
private String mName;
|
||||
private int mBytesPerFrame = 0;
|
||||
private IComplexSampleAdapter mSampleAdapter;
|
||||
private Listener<ReusableComplexBuffer> mListener;
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue();
|
||||
|
||||
/**
|
||||
* Complex Mixer - constructs a reader on the mixer/sound card target data line using the specified audio
|
||||
* format (sample size, sample rate ) and broadcasts complex (I/Q) sample buffers to all registered listeners.
|
||||
* Reads sample buffers sized to 10% of the sample rate specified in audio format.
|
||||
*
|
||||
* @param targetDataLine - mixer or sound card to be used
|
||||
* @param format - audio format
|
||||
* @param name - token name to use for this mixer
|
||||
* @param complexSampleAdapter - adapter to convert byte array data read from the mixer into ComplexBuffer.
|
||||
* @param listener to receive generated complex buffers
|
||||
*/
|
||||
public ComplexMixer(TargetDataLine targetDataLine, AudioFormat format, String name,
|
||||
IComplexSampleAdapter complexSampleAdapter, Listener<ReusableComplexBuffer> listener)
|
||||
{
|
||||
mTargetDataLine = targetDataLine;
|
||||
mName = name;
|
||||
mTargetDataLine = targetDataLine;
|
||||
mName = name;
|
||||
mAudioFormat = format;
|
||||
mSampleAdapter = sampleAdapter;
|
||||
mSampleAdapter = complexSampleAdapter;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public TargetDataLine getTargetDataLine()
|
||||
{
|
||||
return mTargetDataLine;
|
||||
}
|
||||
|
||||
|
||||
public void start()
|
||||
{
|
||||
if( !mBufferReader.isRunning() )
|
||||
{
|
||||
Thread thread = new Thread( mBufferReader );
|
||||
thread.setDaemon( true );
|
||||
thread.setName( mName + " Complex Sample Reader" );
|
||||
thread.start();
|
||||
}
|
||||
if(!mBufferReader.isRunning())
|
||||
{
|
||||
Thread thread = new Thread(mBufferReader);
|
||||
thread.setDaemon(true);
|
||||
thread.setName(mName + " Complex Sample Reader");
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
mBufferReader.stop();
|
||||
mBufferReader.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
public int getSampleRate()
|
||||
{
|
||||
if( mTargetDataLine != null )
|
||||
{
|
||||
return (int)mTargetDataLine.getFormat().getSampleRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if(mTargetDataLine != null)
|
||||
{
|
||||
return (int)mTargetDataLine.getFormat().getSampleRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +104,7 @@ public class ComplexMixer
|
||||
*/
|
||||
public long getFrequency() throws SourceException
|
||||
{
|
||||
return mFrequency;
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,125 +112,119 @@ public class ComplexMixer
|
||||
* be useful if you are streaming an external audio source in through the
|
||||
* sound card and you want to specify a frequency for that source
|
||||
*/
|
||||
public void setFrequency( long frequency )
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mFrequency = frequency;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reader thread. Performs blocking read against the mixer target data
|
||||
* line, converts the samples to an array of floats using the specified
|
||||
* adapter. Dispatches float arrays to all registered listeners.
|
||||
*/
|
||||
public class BufferReader implements Runnable
|
||||
{
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Reader thread. Performs blocking read against the mixer target data
|
||||
* line, converts the samples to an array of floats using the specified
|
||||
* adapter. Dispatches float arrays to all registered listeners.
|
||||
*/
|
||||
public class BufferReader implements Runnable
|
||||
{
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if( mRunning.compareAndSet( false, true ) )
|
||||
{
|
||||
if( mTargetDataLine == null )
|
||||
{
|
||||
mRunning.set( false );
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
if(mTargetDataLine == null)
|
||||
{
|
||||
mRunning.set(false);
|
||||
mLog.error("ComplexMixerSource - mixer target data line is null");
|
||||
}
|
||||
else
|
||||
{
|
||||
mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
|
||||
|
||||
mLog.error( "ComplexMixerSource - mixer target data line"
|
||||
+ " is null" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
|
||||
|
||||
if( mBytesPerFrame == AudioSystem.NOT_SPECIFIED )
|
||||
{
|
||||
mBytesPerFrame = 2;
|
||||
}
|
||||
|
||||
/* Set buffer size to 1/10 second of samples */
|
||||
mBufferSize = (int)( mAudioFormat.getSampleRate()
|
||||
* 0.05 ) * mBytesPerFrame;
|
||||
|
||||
/* We'll reuse the same buffer for each read */
|
||||
byte[] buffer = new byte[ mBufferSize ];
|
||||
|
||||
try
|
||||
if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED)
|
||||
{
|
||||
mTargetDataLine.open( mAudioFormat );
|
||||
|
||||
mTargetDataLine.start();
|
||||
mBytesPerFrame = 2;
|
||||
}
|
||||
catch ( LineUnavailableException e )
|
||||
|
||||
//Set buffer size to 1/10 second of samples
|
||||
int sampleCount = (int)(mAudioFormat.getSampleRate() * 0.10) * 2;
|
||||
int bufferSize = sampleCount * mBytesPerFrame;
|
||||
|
||||
//We'll reuse the same buffer for each read
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
try
|
||||
{
|
||||
mLog.error( "ComplexMixerSource - mixer target data line"
|
||||
+ "not available to read data from", e );
|
||||
|
||||
mRunning.set( false );
|
||||
mTargetDataLine.open(mAudioFormat);
|
||||
mTargetDataLine.start();
|
||||
}
|
||||
catch(LineUnavailableException e)
|
||||
{
|
||||
mLog.error("ComplexMixerSource - mixer target data line not available to read data from", e);
|
||||
mRunning.set(false);
|
||||
}
|
||||
|
||||
while( mRunning.get() )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Blocking read - waits until the buffer fills */
|
||||
mTargetDataLine.read( buffer, 0, buffer.length );
|
||||
|
||||
/* Convert samples to float array */
|
||||
float[] samples =
|
||||
mSampleAdapter.convert( buffer );
|
||||
|
||||
/* Dispatch samples to registered listeners */
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( new ComplexBuffer( samples ) );
|
||||
}
|
||||
}
|
||||
catch ( Exception e )
|
||||
while(mRunning.get())
|
||||
{
|
||||
try
|
||||
{
|
||||
mLog.error( "error while reading"
|
||||
+ "from the mixer target data line", e );
|
||||
|
||||
mRunning.set( false );
|
||||
//Blocking read - waits until the buffer fills
|
||||
mTargetDataLine.read(buffer, 0, buffer.length);
|
||||
|
||||
/* Dispatch samples to registered listeners */
|
||||
if(mListener != null)
|
||||
{
|
||||
ReusableComplexBuffer reusableComplexBuffer = mReusableBufferQueue.getBuffer(sampleCount);
|
||||
|
||||
//Convert samples and load into reusable buffer
|
||||
mSampleAdapter.convertAndLoad(buffer, reusableComplexBuffer);
|
||||
|
||||
mListener.receive(reusableComplexBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the data line if it is still open */
|
||||
if( mTargetDataLine != null && mTargetDataLine.isOpen() )
|
||||
{
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("error while reading from the mixer target data line", e);
|
||||
mRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the data line if it is still open */
|
||||
if(mTargetDataLine != null && mTargetDataLine.isOpen())
|
||||
{
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the reader thread
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if( mRunning.compareAndSet( true, false ) )
|
||||
{
|
||||
mTargetDataLine.stop();
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Stops the reader thread
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
mTargetDataLine.stop();
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the reader thread is running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Indicates if the reader thread is running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
if( mBufferReader != null )
|
||||
{
|
||||
mBufferReader.stop();
|
||||
}
|
||||
|
||||
mListener = null;
|
||||
if(mBufferReader != null)
|
||||
{
|
||||
mBufferReader.stop();
|
||||
}
|
||||
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user