mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 00:57:31 -05:00
Updates pull request from Roman Tsypuk to convert the build system from
ant to gradle. Removes linux and windows application launch scripts and produces an executable jar with all resources included.
This commit is contained in:
252
src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java
Normal file
252
src/main/java/io/github/dsheirer/source/mixer/ComplexMixer.java
Normal file
@@ -0,0 +1,252 @@
|
||||
/*******************************************************************************
|
||||
* 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 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.source.SourceException;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*******************************************************************************
|
||||
* 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 io.github.dsheirer.source.mixer;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public enum MixerChannel
|
||||
{
|
||||
LEFT( "LEFT" ),
|
||||
RIGHT( "RIGHT" ),
|
||||
MONO( "MONO" ),
|
||||
STEREO( "STEREO" );
|
||||
|
||||
private String mLabel;
|
||||
|
||||
private MixerChannel( String label )
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixer Target Data Line (ie sample source) channel options
|
||||
*/
|
||||
public static EnumSet<MixerChannel> getTargetChannels()
|
||||
{
|
||||
return EnumSet.of( LEFT,RIGHT,MONO );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixer Source Data Line (ie sound card output) channel options
|
||||
*/
|
||||
public static EnumSet<MixerChannel> getSourceChannels()
|
||||
{
|
||||
return EnumSet.of( MONO,STEREO );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.github.dsheirer.source.mixer;
|
||||
|
||||
import javax.sound.sampled.Mixer;
|
||||
|
||||
public class MixerChannelConfiguration
|
||||
{
|
||||
private Mixer mMixer;
|
||||
private MixerChannel mMixerChannel;
|
||||
|
||||
public MixerChannelConfiguration( Mixer mixer, MixerChannel channel )
|
||||
{
|
||||
mMixer = mixer;
|
||||
mMixerChannel = channel;
|
||||
}
|
||||
|
||||
public Mixer getMixer()
|
||||
{
|
||||
return mMixer;
|
||||
}
|
||||
|
||||
public MixerChannel getMixerChannel()
|
||||
{
|
||||
return mMixerChannel;
|
||||
}
|
||||
|
||||
public boolean matches( String mixer, String channels )
|
||||
{
|
||||
return mixer != null &&
|
||||
channels != null &&
|
||||
mMixer.getMixerInfo().getName().contentEquals( mixer ) &&
|
||||
mMixerChannel.name().contentEquals( channels );
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( mMixer.getMixerInfo().getName() );
|
||||
sb.append( " - " );
|
||||
sb.append( mMixerChannel.name() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ( ( mMixer == null ) ? 0 : mMixer.hashCode() );
|
||||
result = prime * result
|
||||
+ ( ( mMixerChannel == null ) ? 0 : mMixerChannel.hashCode() );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object obj )
|
||||
{
|
||||
if ( this == obj )
|
||||
return true;
|
||||
if ( obj == null )
|
||||
return false;
|
||||
if ( getClass() != obj.getClass() )
|
||||
return false;
|
||||
MixerChannelConfiguration other = (MixerChannelConfiguration) obj;
|
||||
if ( mMixer == null )
|
||||
{
|
||||
if ( other.mMixer != null )
|
||||
return false;
|
||||
} else if ( !mMixer.equals( other.mMixer ) )
|
||||
return false;
|
||||
if ( mMixerChannel != other.mMixerChannel )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
436
src/main/java/io/github/dsheirer/source/mixer/MixerManager.java
Normal file
436
src/main/java/io/github/dsheirer/source/mixer/MixerManager.java
Normal file
@@ -0,0 +1,436 @@
|
||||
/*******************************************************************************
|
||||
* 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 io.github.dsheirer.source.mixer;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import io.github.dsheirer.sample.adapter.ChannelShortAdapter;
|
||||
import io.github.dsheirer.sample.adapter.ShortAdapter;
|
||||
import io.github.dsheirer.source.config.SourceConfigMixer;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.MixerTunerDataLine;
|
||||
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.Port;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class MixerManager
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( MixerManager.class );
|
||||
|
||||
private List<InputMixerConfiguration> mInputMixers = new ArrayList<>();
|
||||
private List<MixerChannelConfiguration> mOutputMixers = new ArrayList<>();
|
||||
private HashMap<String,MixerTunerDataLine> mMixerTuners = new HashMap<>();
|
||||
|
||||
public MixerManager()
|
||||
{
|
||||
loadMixers();
|
||||
}
|
||||
|
||||
public RealMixerSource getSource( SourceConfiguration config )
|
||||
{
|
||||
RealMixerSource retVal = null;
|
||||
|
||||
if( config instanceof SourceConfigMixer)
|
||||
{
|
||||
SourceConfigMixer mixerConfig = (SourceConfigMixer)config;
|
||||
|
||||
String mixerName = mixerConfig.getMixer();
|
||||
|
||||
if( mixerName != null )
|
||||
{
|
||||
InputMixerConfiguration mixer = getInputMixer( mixerName );
|
||||
|
||||
if( mixer != null )
|
||||
{
|
||||
MixerChannel channel = mixerConfig.getChannel();
|
||||
|
||||
if( mixer.supportsChannel( channel ) )
|
||||
{
|
||||
|
||||
if( channel == MixerChannel.MONO )
|
||||
{
|
||||
DataLine.Info info =
|
||||
new DataLine.Info(TargetDataLine.class,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
|
||||
|
||||
TargetDataLine dataLine;
|
||||
|
||||
try
|
||||
{
|
||||
dataLine = (TargetDataLine)mixer
|
||||
.getMixer().getLine( info );
|
||||
|
||||
if( dataLine != null )
|
||||
{
|
||||
return new RealMixerSource( dataLine,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO,
|
||||
new ShortAdapter() );
|
||||
}
|
||||
}
|
||||
catch ( LineUnavailableException e )
|
||||
{
|
||||
mLog.error( "couldn't get mixer data line " +
|
||||
"for [" + mixerName + "] for channel [" +
|
||||
channel.name() + "]", e );
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
DataLine.Info info =
|
||||
new DataLine.Info(TargetDataLine.class,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO );
|
||||
|
||||
TargetDataLine dataLine;
|
||||
|
||||
try
|
||||
{
|
||||
dataLine = (TargetDataLine)mixer
|
||||
.getMixer().getLine( info );
|
||||
|
||||
if( dataLine != null )
|
||||
{
|
||||
return new RealMixerSource( dataLine,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO,
|
||||
new ChannelShortAdapter(
|
||||
mixerConfig.getChannel() ) );
|
||||
}
|
||||
}
|
||||
catch ( LineUnavailableException e )
|
||||
{
|
||||
mLog.error( "couldn't get mixer data line " +
|
||||
"for [" + mixerName + "] for channel [" +
|
||||
channel.name() + "]", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public InputMixerConfiguration[] getInputMixers()
|
||||
{
|
||||
return mInputMixers.toArray( new InputMixerConfiguration[ mInputMixers.size() ] );
|
||||
}
|
||||
|
||||
public InputMixerConfiguration getInputMixer( String name )
|
||||
{
|
||||
for( InputMixerConfiguration mixer: mInputMixers )
|
||||
{
|
||||
if( mixer.getMixerName().contentEquals( name ) )
|
||||
{
|
||||
return mixer;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public MixerChannelConfiguration[] getOutputMixers()
|
||||
{
|
||||
return mOutputMixers.toArray( new MixerChannelConfiguration[ mOutputMixers.size() ] );
|
||||
}
|
||||
|
||||
public Collection<MixerTunerDataLine> getMixerTunerDataLines()
|
||||
{
|
||||
if( mMixerTuners.isEmpty() )
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return mMixerTuners.values();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMixers()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "loading system mixer devices\n" );
|
||||
|
||||
for( Mixer.Info mixerInfo: AudioSystem.getMixerInfo() )
|
||||
{
|
||||
//Sort between the mixers and the tuner mixers, and load each
|
||||
MixerTunerType mixerTunerType = MixerTunerType.getMixerTunerType( mixerInfo );
|
||||
|
||||
if( mixerTunerType == MixerTunerType.UNKNOWN )
|
||||
{
|
||||
Mixer mixer = AudioSystem.getMixer( mixerInfo );
|
||||
|
||||
if( mixer != null )
|
||||
{
|
||||
EnumSet<MixerChannel> inputChannels = getSupportedTargetChannels( mixer );
|
||||
|
||||
if( inputChannels != null )
|
||||
{
|
||||
mInputMixers.add(
|
||||
new InputMixerConfiguration( mixer, inputChannels ) );
|
||||
|
||||
sb.append( "\t[LOADED] Input: " + mixerInfo.getName() +
|
||||
" CHANNELS: " + inputChannels + "\n" );
|
||||
}
|
||||
|
||||
EnumSet<MixerChannel> outputChannels = getSupportedSourceChannels( mixer );
|
||||
|
||||
if( outputChannels != null )
|
||||
{
|
||||
for( MixerChannel channel: outputChannels )
|
||||
{
|
||||
mOutputMixers.add( new MixerChannelConfiguration( mixer, channel ) );
|
||||
}
|
||||
|
||||
sb.append( "\t[LOADED] Output: " + mixerInfo.getName() +
|
||||
" CHANNELS: " + outputChannels + "\n" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "\t[NOT LOADED] Mixer:" + mixerInfo.getName() +
|
||||
" - couldn't get mixer\n" );
|
||||
}
|
||||
}
|
||||
else //Process as a Mixer-based tuner data line
|
||||
{
|
||||
TargetDataLine tdl =
|
||||
getTargetDataLine( mixerInfo,
|
||||
mixerTunerType.getAudioFormat() );
|
||||
|
||||
if( tdl != null )
|
||||
{
|
||||
switch( mixerTunerType )
|
||||
{
|
||||
case FUNCUBE_DONGLE_PRO:
|
||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
||||
mMixerTuners.put( mixerInfo.getName(),
|
||||
new MixerTunerDataLine( tdl, mixerTunerType ) );
|
||||
sb.append( "\t[LOADED] FunCube Dongle Mixer Tuner:" + mixerInfo.getName() + "[" + mixerTunerType.getDisplayString() + "]\n" );
|
||||
break;
|
||||
case UNKNOWN:
|
||||
default:
|
||||
sb.append( "\t[NOT LOADED] Tuner:" + mixerInfo.getName() + " - unrecognized tuner type\n" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mLog.info( sb.toString() );
|
||||
}
|
||||
|
||||
public HashMap<String,MixerTunerDataLine> getMixerTuners()
|
||||
{
|
||||
return mMixerTuners;
|
||||
}
|
||||
|
||||
private TargetDataLine getTargetDataLine( Mixer.Info mixerInfo, AudioFormat format )
|
||||
{
|
||||
TargetDataLine retVal = null;
|
||||
|
||||
Mixer mixer = AudioSystem.getMixer( mixerInfo );
|
||||
|
||||
if( mixer != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
DataLine.Info datalineInfo=
|
||||
new DataLine.Info(TargetDataLine.class, format );
|
||||
|
||||
retVal = (TargetDataLine) mixer.getLine( datalineInfo );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
//Do nothing ... we couldn't get the TDL
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private EnumSet<MixerChannel> getSupportedTargetChannels( Mixer mixer )
|
||||
{
|
||||
DataLine.Info stereoInfo=
|
||||
new DataLine.Info(TargetDataLine.class,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO );
|
||||
|
||||
boolean stereoSupported = mixer.isLineSupported( stereoInfo );
|
||||
|
||||
DataLine.Info monoInfo=
|
||||
new DataLine.Info(TargetDataLine.class,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
|
||||
|
||||
boolean monoSupported = mixer.isLineSupported( monoInfo );
|
||||
|
||||
if( stereoSupported && monoSupported )
|
||||
{
|
||||
return EnumSet.of( MixerChannel.LEFT,
|
||||
MixerChannel.RIGHT,
|
||||
MixerChannel.MONO );
|
||||
}
|
||||
else if( stereoSupported )
|
||||
{
|
||||
return EnumSet.of( MixerChannel.LEFT,
|
||||
MixerChannel.RIGHT,
|
||||
MixerChannel.MONO );
|
||||
}
|
||||
else if( monoSupported )
|
||||
{
|
||||
return EnumSet.of( MixerChannel.MONO );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enumset of SourceDataLine (audio output) channels
|
||||
* (MONO and/or STEREO) supported by the mixer, or null if the mixer doesn't
|
||||
* have any source data lines.
|
||||
*/
|
||||
private EnumSet<MixerChannel> getSupportedSourceChannels( Mixer mixer )
|
||||
{
|
||||
DataLine.Info stereoInfo=
|
||||
new DataLine.Info(SourceDataLine.class,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO );
|
||||
|
||||
boolean stereoSupported = mixer.isLineSupported( stereoInfo );
|
||||
|
||||
DataLine.Info monoInfo=
|
||||
new DataLine.Info(SourceDataLine.class,
|
||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
|
||||
|
||||
boolean monoSupported = mixer.isLineSupported( monoInfo );
|
||||
|
||||
if( stereoSupported && monoSupported )
|
||||
{
|
||||
return EnumSet.of( MixerChannel.MONO,
|
||||
MixerChannel.STEREO );
|
||||
}
|
||||
else if( stereoSupported )
|
||||
{
|
||||
return EnumSet.of( MixerChannel.STEREO );
|
||||
}
|
||||
else if( monoSupported )
|
||||
{
|
||||
return EnumSet.of( MixerChannel.MONO );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getMixerDevices()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for( Mixer.Info mixerInfo: AudioSystem.getMixerInfo() )
|
||||
{
|
||||
sb.append( "\n--------------------------------------------------" );
|
||||
sb.append( "\nMIXER name:" + mixerInfo.getName() +
|
||||
"\n desc:" + mixerInfo.getDescription() +
|
||||
"\n vendor:" + mixerInfo.getVendor() +
|
||||
"\n version:" + mixerInfo.getVersion() +
|
||||
"\n" );
|
||||
|
||||
Mixer mixer = AudioSystem.getMixer( mixerInfo );
|
||||
|
||||
Line.Info[] sourceLines = mixer.getSourceLineInfo();
|
||||
|
||||
for( Line.Info lineInfo: sourceLines )
|
||||
{
|
||||
sb.append( " SOURCE LINE desc:" + lineInfo.toString() +
|
||||
"\n class:" + lineInfo.getClass() +
|
||||
"\n lineclass:" + lineInfo.getLineClass() +
|
||||
"\n" );
|
||||
}
|
||||
|
||||
Line.Info[] targetLines = mixer.getTargetLineInfo();
|
||||
|
||||
for( Line.Info lineInfo: targetLines )
|
||||
{
|
||||
sb.append( " TARGET LINE desc:" + lineInfo.toString() +
|
||||
"\n class:" + lineInfo.getClass() +
|
||||
"\n lineclass:" + lineInfo.getLineClass() +
|
||||
"\n" );
|
||||
}
|
||||
|
||||
Line.Info portInfo = new Line.Info( Port.class );
|
||||
|
||||
if( mixer.isLineSupported( portInfo ) )
|
||||
{
|
||||
sb.append( "**PORT LINE IS SUPPORTED BY THIS MIXER***\n" );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public class InputMixerConfiguration
|
||||
{
|
||||
private Mixer mMixer;
|
||||
private EnumSet<MixerChannel> mChannels;
|
||||
|
||||
public InputMixerConfiguration( Mixer mixer, EnumSet<MixerChannel> channels )
|
||||
{
|
||||
mMixer = mixer;
|
||||
mChannels = channels;
|
||||
}
|
||||
|
||||
public Mixer getMixer()
|
||||
{
|
||||
return mMixer;
|
||||
}
|
||||
|
||||
public String getMixerName()
|
||||
{
|
||||
return mMixer.getMixerInfo().getName();
|
||||
}
|
||||
|
||||
public EnumSet<MixerChannel> getChannels()
|
||||
{
|
||||
return mChannels;
|
||||
}
|
||||
|
||||
public boolean supportsChannel( MixerChannel channel )
|
||||
{
|
||||
return mChannels.contains( channel );
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mMixer.getMixerInfo().getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 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.controller.channel.Channel;
|
||||
import io.github.dsheirer.gui.editor.Editor;
|
||||
import io.github.dsheirer.source.SourceManager;
|
||||
import io.github.dsheirer.source.config.SourceConfigMixer;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class MixerSourceEditor extends Editor<Channel>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private JComboBox<MixerManager.InputMixerConfiguration> mComboMixers;
|
||||
private JComboBox<MixerChannel>mComboChannels;
|
||||
|
||||
private SourceManager mSourceManager;
|
||||
|
||||
public MixerSourceEditor( SourceManager sourceManager )
|
||||
{
|
||||
mSourceManager = sourceManager;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "insets 0 0 0 0,wrap 4", "[right][grow,fill][right][grow,fill]", "" ) );
|
||||
|
||||
mComboMixers = new JComboBox<MixerManager.InputMixerConfiguration>();
|
||||
MixerManager.InputMixerConfiguration[] inputMixers = mSourceManager.getMixerManager().getInputMixers();
|
||||
mComboMixers.setModel( new DefaultComboBoxModel<MixerManager.InputMixerConfiguration>( inputMixers ) );
|
||||
mComboMixers.setEnabled( false );
|
||||
mComboMixers.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
MixerManager.InputMixerConfiguration selected = (MixerManager.InputMixerConfiguration)mComboMixers.getSelectedItem();
|
||||
|
||||
EnumSet<MixerChannel> channels = selected.getChannels();
|
||||
|
||||
mComboChannels.setModel( new DefaultComboBoxModel<MixerChannel>(
|
||||
channels.toArray( new MixerChannel[ channels.size() ] ) ) );
|
||||
|
||||
repaint();
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Mixer:" ) );
|
||||
add( mComboMixers );
|
||||
|
||||
mComboChannels = new JComboBox<MixerChannel>();
|
||||
|
||||
MixerManager.InputMixerConfiguration selected = (MixerManager.InputMixerConfiguration)mComboMixers.getSelectedItem();
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
EnumSet<MixerChannel> channels = selected.getChannels();
|
||||
|
||||
mComboChannels.setModel( new DefaultComboBoxModel<MixerChannel>(
|
||||
channels.toArray( new MixerChannel[ channels.size() ] ) ) );
|
||||
}
|
||||
|
||||
mComboChannels.setEnabled( false );
|
||||
mComboChannels.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Channel:" ) );
|
||||
add( mComboChannels );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( Channel item )
|
||||
{
|
||||
super.setItem( item );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
mComboMixers.setEnabled( true );
|
||||
mComboChannels.setEnabled( true );
|
||||
|
||||
SourceConfiguration config = item.getSourceConfiguration();
|
||||
|
||||
if( config instanceof SourceConfigMixer)
|
||||
{
|
||||
SourceConfigMixer mixer = (SourceConfigMixer)config;
|
||||
|
||||
for( MixerManager.InputMixerConfiguration inputMixer: mSourceManager.getMixerManager().getInputMixers() )
|
||||
{
|
||||
if( inputMixer.getMixerName().equalsIgnoreCase( mixer.getMixer() ) )
|
||||
{
|
||||
mComboMixers.setSelectedItem( inputMixer );
|
||||
|
||||
for( MixerChannel channel: inputMixer.getChannels() )
|
||||
{
|
||||
if( channel.getLabel() != null && mixer.getChannel() != null )
|
||||
{
|
||||
if( channel.getLabel().equalsIgnoreCase( mixer.getChannel().getLabel() ) )
|
||||
{
|
||||
mComboChannels.setSelectedItem( channel );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mComboMixers.setEnabled( false );
|
||||
mComboChannels.setEnabled( false );
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
|
||||
public void save()
|
||||
{
|
||||
if( hasItem() && isModified() )
|
||||
{
|
||||
SourceConfigMixer config = new SourceConfigMixer();
|
||||
|
||||
MixerManager.InputMixerConfiguration selected =
|
||||
(MixerManager.InputMixerConfiguration)mComboMixers.getSelectedItem();
|
||||
config.setMixer( selected.getMixerName() );
|
||||
|
||||
MixerChannel channel = (MixerChannel)mComboChannels.getSelectedItem();
|
||||
config.setChannel( channel );
|
||||
|
||||
getItem().setSourceConfiguration( config );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/*******************************************************************************
|
||||
* 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 io.github.dsheirer.source.mixer;
|
||||
|
||||
import io.github.dsheirer.channel.heartbeat.Heartbeat;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.adapter.ISampleAdapter;
|
||||
import io.github.dsheirer.sample.real.RealBuffer;
|
||||
import io.github.dsheirer.source.RealSource;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import io.github.dsheirer.dsp.gain.AutomaticGainControl;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class RealMixerSource extends RealSource
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RealMixerSource.class);
|
||||
|
||||
private long mFrequency = 0;
|
||||
private int mBufferSize = 16384;
|
||||
|
||||
private BufferReader mBufferReader = new BufferReader();
|
||||
private TargetDataLine mTargetDataLine;
|
||||
private AudioFormat mAudioFormat;
|
||||
private int mBytesPerFrame = 0;
|
||||
private ISampleAdapter mSampleAdapter;
|
||||
private AutomaticGainControl mAGC = new AutomaticGainControl();
|
||||
private Listener<Heartbeat> mHeartbeatListener;
|
||||
private static final Heartbeat HEARTBEAT = new Heartbeat();
|
||||
|
||||
private CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
|
||||
|
||||
/**
|
||||
* Real Mixer Source - constructs a reader on the mixer/sound card target
|
||||
* data line using the specified audio format (sample size, sample rate )
|
||||
* and broadcasts real buffers (float arrays) to all registered listeners.
|
||||
* Reads 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 sampleAdapter - adapter to convert byte array data read from the
|
||||
* mixer into float array data. Can optionally invert the channel data if
|
||||
* the left/right stereo channels are inverted.
|
||||
*/
|
||||
public RealMixerSource(TargetDataLine targetDataLine,
|
||||
AudioFormat format,
|
||||
ISampleAdapter sampleAdapter)
|
||||
{
|
||||
mTargetDataLine = targetDataLine;
|
||||
mAudioFormat = format;
|
||||
mSampleAdapter = sampleAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
//Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive heartbeats from this source.
|
||||
*
|
||||
* @param listener to receive heartbeats
|
||||
*/
|
||||
@Override
|
||||
public void setHeartbeatListener(Listener<Heartbeat> listener)
|
||||
{
|
||||
mHeartbeatListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the currently registered heartbeat listener
|
||||
*/
|
||||
@Override
|
||||
public void removeHeartbeatListener()
|
||||
{
|
||||
mHeartbeatListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
|
||||
public void setListener(Listener<RealBuffer> listener)
|
||||
{
|
||||
mSampleListeners.add(listener);
|
||||
|
||||
/* If this is the first listener, start the reader thread */
|
||||
if(!mBufferReader.isRunning())
|
||||
{
|
||||
Thread thread = new Thread(mBufferReader);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("Sample Reader");
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(Listener<RealBuffer> listener)
|
||||
{
|
||||
mSampleListeners.remove(listener);
|
||||
|
||||
/* If this is the laster listener, stop the reader thread */
|
||||
if(mSampleListeners.isEmpty())
|
||||
{
|
||||
mBufferReader.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcast(RealBuffer samples)
|
||||
{
|
||||
Iterator<Listener<RealBuffer>> it = mSampleListeners.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
Listener<RealBuffer> next = it.next();
|
||||
|
||||
/* if this is the last (or only) listener, send him the original
|
||||
* buffer, otherwise send him a copy of the buffer */
|
||||
if(it.hasNext())
|
||||
{
|
||||
next.receive(samples.copyOf());
|
||||
}
|
||||
else
|
||||
{
|
||||
next.receive(samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
//Send a heartbeat every time we run
|
||||
if(mHeartbeatListener != null)
|
||||
{
|
||||
mHeartbeatListener.receive(HEARTBEAT);
|
||||
}
|
||||
|
||||
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 */
|
||||
broadcast(new RealBuffer(samples));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("ComplexMixerSource - 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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
if(mBufferReader != null)
|
||||
{
|
||||
mBufferReader.stop();
|
||||
mSampleListeners.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*******************************************************************************
|
||||
* 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 io.github.dsheirer.source.mixer;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* THIS CLASS IS SCHEDULED FOR DELETION *****************************
|
||||
*/
|
||||
|
||||
public class SystemMixerDevices {
|
||||
|
||||
/**
|
||||
* Returns a hashmap of mixer names and target data lines that support the
|
||||
* audio format argument
|
||||
*
|
||||
* @param format - requested audio format
|
||||
* @return - mixer target data lines that support the requested audio format
|
||||
*/
|
||||
public static HashMap<String,TargetDataLine> getMixers( AudioFormat format )
|
||||
{
|
||||
HashMap<String,TargetDataLine> retVal = new HashMap<String,TargetDataLine>();
|
||||
|
||||
DataLine.Info datalineInfo=new DataLine.Info(TargetDataLine.class, format );
|
||||
|
||||
for( Mixer.Info mixerInfo: AudioSystem.getMixerInfo() )
|
||||
{
|
||||
Mixer mixer = AudioSystem.getMixer( mixerInfo );
|
||||
|
||||
if( mixer != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
TargetDataLine tdl = (TargetDataLine) mixer.getLine( datalineInfo );
|
||||
|
||||
if( tdl != null )
|
||||
{
|
||||
retVal.put( mixerInfo.getName(), tdl );
|
||||
}
|
||||
}
|
||||
catch( IllegalArgumentException iae )
|
||||
{
|
||||
//Do nothing ... mixer doesn't support the audio format
|
||||
}
|
||||
catch( LineUnavailableException e )
|
||||
{
|
||||
//Do nothing ... mixer doesn't support the audio format
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static String getSoundDevices()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for( Mixer.Info mixerInfo: AudioSystem.getMixerInfo() )
|
||||
{
|
||||
sb.append( "\n--------------------------------------------------" );
|
||||
sb.append( "\nMIXER name:" + mixerInfo.getName() +
|
||||
"\n desc:" + mixerInfo.getDescription() +
|
||||
"\n vendor:" + mixerInfo.getVendor() +
|
||||
"\n version:" + mixerInfo.getVersion() +
|
||||
"\n" );
|
||||
|
||||
Mixer mixer = AudioSystem.getMixer( mixerInfo );
|
||||
|
||||
Line.Info[] sourceLines = mixer.getSourceLineInfo();
|
||||
|
||||
for( Line.Info lineInfo: sourceLines )
|
||||
{
|
||||
sb.append( " SOURCE LINE desc:" + lineInfo.toString() +
|
||||
"\n class:" + lineInfo.getClass() +
|
||||
"\n lineclass:" + lineInfo.getLineClass() +
|
||||
"\n" );
|
||||
}
|
||||
|
||||
Line.Info[] targetLines = mixer.getTargetLineInfo();
|
||||
|
||||
for( Line.Info lineInfo: targetLines )
|
||||
{
|
||||
sb.append( " TARGET LINE desc:" + lineInfo.toString() +
|
||||
"\n class:" + lineInfo.getClass() +
|
||||
"\n lineclass:" + lineInfo.getLineClass() +
|
||||
"\n" );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user