/*******************************************************************************
* 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/>
******************************************************************************/
package source.mixer;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer;
import source.SourceException;
public class ComplexMixer
{
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 )
{
mTargetDataLine = targetDataLine;
mName = name;
mAudioFormat = format;
mSampleAdapter = sampleAdapter;
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();
}
}
public void stop()
{
mBufferReader.stop();
}
@Override
public String toString()
{
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 */
mBufferSize = (int)( mAudioFormat.getSampleRate()
* 0.05 ) * mBytesPerFrame;
/* We'll reuse the same buffer for each read */
byte[] buffer = new byte[ mBufferSize ];
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 );
/* 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 )
{
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;
}
}