[WIP] Updates the sample adapters to natively use ReusableBuffers.

Refactors a MixerReader instance from the RealMixerSource so that it
can be reused for both real and complex data outputs.

Updates RealMixerSource to use refactored MixerReader and use a default
sample rate of 8 kHz.

Updates the ComplexMixer to use the refactored MixerReader.

Updates the FCD 1/2 tuners for compatibility with the new ComplexMixer.

Removes several unused classes.
This commit is contained in:
Denny
2018-06-24 09:50:36 -04:00
parent 19fa256c60
commit a621a31aa9
19 changed files with 672 additions and 760 deletions

View File

@@ -16,69 +16,74 @@
package io.github.dsheirer.source.mixer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.adapter.IComplexSampleAdapter;
import io.github.dsheirer.sample.adapter.ComplexSampleAdapter;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import java.util.concurrent.atomic.AtomicBoolean;
public class ComplexMixer
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexMixer.class);
private long mFrequency = 0;
private BufferReader mBufferReader = new BufferReader();
private TargetDataLine mTargetDataLine;
private AudioFormat mAudioFormat;
private MixerReader<ReusableComplexBuffer> mMixerReader;
private String mName;
private int mBytesPerFrame = 0;
private IComplexSampleAdapter mSampleAdapter;
private Listener<ReusableComplexBuffer> mListener;
private ReusableComplexBufferQueue mReusableComplexBufferQueue = new ReusableComplexBufferQueue("ComplexMixer");
/**
* 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.
* format (sample size, sample rate ) and broadcasts complex (I/Q) sample buffers to the registered listener.
*
* @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)
ComplexSampleAdapter complexSampleAdapter)
{
mTargetDataLine = targetDataLine;
mMixerReader = new MixerReader<>(format, targetDataLine, complexSampleAdapter);
mName = name;
mAudioFormat = format;
mSampleAdapter = complexSampleAdapter;
mListener = listener;
}
/**
* Sets the buffer size in bytes per buffer for each read interval.
* @param bufferSize in bytes
*/
public void setBufferSize(int bufferSize)
{
mMixerReader.setBufferSize(bufferSize);
}
public void start()
{
if(!mBufferReader.isRunning())
{
Thread thread = new Thread(mBufferReader);
thread.setDaemon(true);
thread.setName(mName + " Complex Sample Reader");
thread.start();
}
mMixerReader.start();
}
public void stop()
{
mBufferReader.stop();
mMixerReader.stop();
}
public void setBufferListener(Listener<ReusableComplexBuffer> listener)
{
mMixerReader.setBufferListener(listener);
}
public void removeBufferListener()
{
mMixerReader.removeBufferListener();
}
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mMixerReader.setSourceEventListener(listener);
}
public void removeSourceEventListener()
{
mMixerReader.removeSourceEventListener();
}
@Override
@@ -87,144 +92,8 @@ public class ComplexMixer
return mName;
}
public int getSampleRate()
{
if(mTargetDataLine != null)
{
return (int)mTargetDataLine.getFormat().getSampleRate();
}
else
{
return 0;
}
}
/**
* Returns the frequency of this source. Default is 0.
*/
public long getFrequency() throws SourceException
{
return mFrequency;
}
/**
* Specify the frequency that will be returned from this source. This may
* 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)
{
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
public void run()
{
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;
if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED)
{
mBytesPerFrame = 2;
}
//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
{
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);
/* Dispatch samples to registered listeners */
if(mListener != null)
{
ReusableComplexBuffer reusableComplexBuffer = mReusableComplexBufferQueue.getBuffer(sampleCount);
//Convert samples and load into reusable buffer
mSampleAdapter.convertAndLoad(buffer, reusableComplexBuffer);
mListener.receive(reusableComplexBuffer);
}
}
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();
}
}
/**
* Indicates if the reader thread is running
*/
public boolean isRunning()
{
return mRunning.get();
}
}
public void dispose()
{
if(mBufferReader != null)
{
mBufferReader.stop();
}
mListener = null;
mMixerReader.dispose();
}
}