#1190 Tuner management redesign. Updates all tuners with direct threaded support. Incorporates USB Hotplug support. Adds black/white listing of tuners. Updates all tuner editors to have a common look and feel. Changes location of tuner configuration file and changes format to JSON. (#1208)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-04-18 07:30:58 -04:00
committed by GitHub
parent 0fb148d31e
commit 96d3996bec
259 changed files with 11974 additions and 12454 deletions

View File

@@ -61,9 +61,9 @@ public class ComplexMixer
* Sets the buffer size in bytes per buffer for each read interval.
* @param bufferSize in bytes
*/
public void setBufferSize(int bufferSize)
public void setBufferSampleCount(int bufferSize)
{
mMixerReader.setBufferSize(bufferSize);
mMixerReader.setBufferSampleSize(bufferSize);
}
public void start()

View File

@@ -22,16 +22,12 @@ import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.ISampleAdapter;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import java.util.Arrays;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -39,23 +35,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
* the samples to an array of floats using the specified adapter. Dispatches float arrays to the registered
* buffer listener.
*/
public class MixerReader<T> implements Runnable
public class MixerReader<T>
{
private final static Logger mLog = LoggerFactory.getLogger(MixerReader.class);
private static final long BUFFER_PROCESSING_INTERVAL_MS = 50; //50 milliseconds, 20 reads per second
private TargetDataLine mTargetDataLine;
private byte[] mBuffer;
private int mBufferSize;
private AtomicBoolean mRunning = new AtomicBoolean();
private ScheduledFuture mScheduledFuture;
private int mBytesRead;
private ISampleAdapter<T> mSampleAdapter;
private Listener<T> mListener;
private Listener<SourceEvent> mSourceEventListener;
private AudioFormat mAudioFormat;
private HeartbeatManager mHeartbeatManager;
private Thread mReaderThread;
/**
* Constructs an instance
* @param audioFormat for the target mixer
* @param targetDataLine to read samples from
* @param abstractSampleAdapter to convert samples to native buffers
* @param heartbeatManager to ping before each buffer read for downstream consumers
*/
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine,
ISampleAdapter<T> abstractSampleAdapter, HeartbeatManager heartbeatManager)
{
@@ -64,10 +63,22 @@ public class MixerReader<T> implements Runnable
mSampleAdapter = abstractSampleAdapter;
mHeartbeatManager = heartbeatManager;
/* Set buffer size to 1/10 second of samples */
mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize();
if(mAudioFormat.getSampleRate() <= 96000)
{
setBufferSampleSize(4096);
}
else
{
setBufferSampleSize(8192);
}
}
/**
* Constructs an instance
* @param audioFormat for the target mixer
* @param targetDataLine to read samples from
* @param abstractSampleAdapter to convert samples to native buffers
*/
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, ISampleAdapter<T> abstractSampleAdapter)
{
this(audioFormat, targetDataLine, abstractSampleAdapter, new HeartbeatManager());
@@ -82,54 +93,12 @@ public class MixerReader<T> implements Runnable
}
/**
* Sets the size of buffers to use. This reader will read from target data line 20 times per second.
* @param bytesPerBuffer to size for each buffer read.
* Sets the number of samples per native buffer to produce.
* @param samples is the number of samples
*/
public void setBufferSize(int bytesPerBuffer)
public void setBufferSampleSize(int samples)
{
mBufferSize = bytesPerBuffer;
mBuffer = new byte[mBufferSize];
}
/**
* Opens the source mixer target data line
*
* @throws LineUnavailableException if the target data line is null or unavailable (from the OS).
*/
private void openTargetDataLine() throws LineUnavailableException
{
if(mTargetDataLine == null)
{
throw new LineUnavailableException("Source Mixer TargetDataLine is null");
}
mBuffer = new byte[mBufferSize];
mTargetDataLine.open(getAudioFormat());
mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning());
mLog.info("Format:" + mTargetDataLine.getFormat().toString());
mTargetDataLine.start();
mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning());
}
/**
* Closes the mixer source target data line
*/
private void closeTargetDataLine()
{
if(mTargetDataLine != null && mTargetDataLine.isOpen())
{
if(mTargetDataLine.isRunning())
{
mTargetDataLine.stop();
}
if(mTargetDataLine.isOpen())
{
mTargetDataLine.close();
}
}
mBufferSize = samples * mAudioFormat.getFrameSize();
}
/**
@@ -139,9 +108,17 @@ public class MixerReader<T> implements Runnable
{
if(mRunning.compareAndSet(false, true))
{
if(mTargetDataLine == null)
{
mRunning.set(false);
mLog.error("Attempt to start failed - target data line is null");
return;
}
try
{
openTargetDataLine();
mTargetDataLine.open(getAudioFormat());
mTargetDataLine.start();
}
catch(LineUnavailableException e)
{
@@ -150,15 +127,9 @@ public class MixerReader<T> implements Runnable
return;
}
//Cancel the scheduled thread if it wasn't cancelled previously ... this shouldn't happen
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
mScheduledFuture = null;
}
mScheduledFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this,
0, BUFFER_PROCESSING_INTERVAL_MS, TimeUnit.MILLISECONDS);
mReaderThread = new Thread(new DataLineReader());
mReaderThread.setName("sdrtrunk mixer sample reader");
mReaderThread.start();
}
else
{
@@ -173,66 +144,31 @@ public class MixerReader<T> implements Runnable
{
if(mRunning.compareAndSet(true, false))
{
if(mScheduledFuture != null)
if(mTargetDataLine != null && mTargetDataLine.isOpen())
{
mScheduledFuture.cancel(true);
mScheduledFuture = null;
if(mTargetDataLine.isRunning())
{
mTargetDataLine.stop();
}
if(mTargetDataLine.isOpen())
{
mTargetDataLine.close();
}
}
closeTargetDataLine();
}
else
{
mLog.warn("Attempt to stop an already stopped BufferReader instance - this shouldn't happen");
}
}
/**
* Sample processing thread. This method runs each time the timer fires.
*/
@Override
public void run()
{
if(mRunning.get())
{
if(mHeartbeatManager != null)
{
mHeartbeatManager.broadcast();
}
mReaderThread.interrupt();
try
{
mBytesRead = 0;
//Blocking read - waits until the buffer fills
mBytesRead = mTargetDataLine.read(mBuffer, 0, mBuffer.length);
if(mBytesRead == mBuffer.length)
{
//Sample adapter automatically sets initial listener count to one.
T reusableBuffer = mSampleAdapter.convert(mBuffer);
if(reusableBuffer != null && mListener != null)
{
mListener.receive(reusableBuffer);
}
}
else if(mBytesRead > 0)
{
//Sample adapter automatically sets initial listener count to one.
T reusableBuffer = mSampleAdapter.convert(Arrays.copyOf(mBuffer, mBytesRead));
if(reusableBuffer != null && mListener != null)
{
mListener.receive(reusableBuffer);
}
}
mReaderThread.join(500);
}
catch(Throwable t)
catch(InterruptedException ie)
{
mLog.error("MixerSource - error while reading from the mixer target data line", t);
stop();
//No-op ... we're shutting down
}
mReaderThread = null;
}
}
@@ -296,9 +232,49 @@ public class MixerReader<T> implements Runnable
public void dispose()
{
stop();
mHeartbeatManager = null;
mListener = null;
mSourceEventListener = null;
}
/**
* Runnable reader to read samples from the target data line, convert them to native buffers, and dispatch.
*/
public class DataLineReader implements Runnable
{
@Override
public void run()
{
while(mRunning.get())
{
if(mHeartbeatManager != null)
{
mHeartbeatManager.broadcast();
}
byte[] buffer = new byte[mBufferSize];
int bytesRead = 0;
try
{
while(bytesRead < buffer.length)
{
bytesRead += mTargetDataLine.read(buffer, bytesRead, buffer.length);
}
}
catch(ArrayIndexOutOfBoundsException aioobe)
{
//No-op ... this can happen during USB disconnect
}
//We'll always read the correct number of bytes until the data line is closed or stopped, and we're
//shutting down.
if(bytesRead == buffer.length && mListener != null)
{
mListener.receive(mSampleAdapter.convert(buffer));
}
}
}
}
}