diff --git a/build/build.xml b/build/build.xml
index 2a9ff1dc..90dd15ac 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -5,8 +5,8 @@
-
-
+
+
diff --git a/images/audio_muted.png b/images/audio_muted.png
new file mode 100644
index 00000000..a52b40ad
Binary files /dev/null and b/images/audio_muted.png differ
diff --git a/images/audio_unmuted.png b/images/audio_unmuted.png
new file mode 100644
index 00000000..3a5d8cc5
Binary files /dev/null and b/images/audio_unmuted.png differ
diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java
index 0f217211..ca793384 100644
--- a/src/audio/AudioChannelPanel.java
+++ b/src/audio/AudioChannelPanel.java
@@ -3,17 +3,10 @@ package audio;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import javax.sound.sampled.BooleanControl;
-import javax.sound.sampled.FloatControl;
import javax.swing.JLabel;
-import javax.swing.JMenuItem;
import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
import net.miginfocom.swing.MigLayout;
@@ -22,15 +15,16 @@ import org.slf4j.LoggerFactory;
import sample.Listener;
import settings.ColorSetting;
+import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
-import settings.ColorSetting.ColorSettingName;
import alias.Alias;
import audio.AudioEvent.Type;
import audio.metadata.AudioMetadata;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
+import audio.output.AudioOutput;
public class AudioChannelPanel extends JPanel
implements Listener, SettingChangeListener
@@ -44,85 +38,60 @@ public class AudioChannelPanel extends JPanel
private Color mDetailsColor;
private SettingsManager mSettingsManager;
- private IAudioController mController;
- private String mChannel;
+ private AudioOutput mAudioOutput;
+ private JLabel mChannelName = new JLabel( " " );
private JLabel mToLabel = new JLabel( "TO:" );
private JLabel mTo = new JLabel( "" );
private JLabel mToAlias = new JLabel( "" );
+ private JLabel mMutedLabel = new JLabel( " " );
private JLabel mFromLabel = new JLabel( "FROM:" );
private JLabel mFrom = new JLabel( "" );
private JLabel mFromAlias = new JLabel( "" );
- private JLabel mChannelLabel;
- private JLabel mChannelName;
- private JLabel mMutedLabel;
-
private boolean mConfigured = false;
- private BooleanControl mMuteControl;
- private FloatControl mGainControl;
-
- public AudioChannelPanel( SettingsManager settingsManager,
- IAudioController controller,
- String channel )
+ public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput )
{
mSettingsManager = settingsManager;
mSettingsManager.addListener( this );
+ mAudioOutput = audioOutput;
+
+ if( mAudioOutput != null )
+ {
+ mAudioOutput.addAudioEventListener( this );
+ mAudioOutput.setAudioMetadataListener( new AudioMetadataProcessor() );
+ }
+
mLabelColor = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
mDetailsColor = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
- mController = controller;
- mChannel = channel;
-
- try
- {
- mMuteControl = mController.getMuteControl( mChannel );
- }
- catch ( AudioException e )
- {
- mLog.error( "Couldn't obtain mute control from audio channel [" +
- mChannel + "]", e );
- }
-
- try
- {
- mGainControl = mController.getGainControl( mChannel );
- }
- catch ( AudioException e )
- {
- mLog.error( "Couldn't obtain gain control from audio channel [" +
- mChannel + "]", e );
- }
-
- try
- {
- mController.addAudioEventListener( mChannel, this );
- }
- catch( AudioException e )
- {
- mLog.error( "Couldn't register as audio event listener on channel [" +
- mChannel + "]", e );
- }
-
- mController.setAudioMetadataListener( mChannel, new AudioMetadataProcessor() );
-
init();
}
private void init()
{
- setLayout( new MigLayout( "insets 0 0 0 0", "[right][][grow,fill]", "[grow,fill]0[]0[]" ) );
+ setLayout( new MigLayout( "insets 0 0 0 0", "[][right][][grow,fill]", "[]0[]0[grow,fill]" ) );
setBackground( Color.BLACK );
- addMouseListener( new MouseSelectionListener() );
+ mChannelName = new JLabel( mAudioOutput != null ? mAudioOutput.getChannelName() : " " );
+ mChannelName.setFont( mFont );
+ mChannelName.setForeground( mDetailsColor );
+ add( mChannelName );
mToLabel.setFont( mFont );
- mToLabel.setForeground( mLabelColor );
+ if( mAudioOutput != null )
+ {
+ mToLabel.setForeground( mLabelColor );
+ }
+ else
+ {
+ mToLabel.setForeground( getBackground() );
+ }
add( mToLabel );
mTo.setFont( mFont );
@@ -133,8 +102,19 @@ public class AudioChannelPanel extends JPanel
mToAlias.setForeground( mLabelColor );
add( mToAlias,"wrap" );
+ mMutedLabel.setFont( mFont );
+ mMutedLabel.setForeground( Color.RED );
+ add( mMutedLabel );
+
mFromLabel.setFont( mFont );
- mFromLabel.setForeground( mLabelColor );
+ if( mAudioOutput != null )
+ {
+ mFromLabel.setForeground( mLabelColor );
+ }
+ else
+ {
+ mFromLabel.setForeground( getBackground() );
+ }
add( mFromLabel );
mFrom.setFont( mFont );
@@ -144,54 +124,40 @@ public class AudioChannelPanel extends JPanel
mFromAlias.setFont( mFont );
mFromAlias.setForeground( mLabelColor );
add( mFromAlias,"wrap" );
-
- mChannelLabel = new JLabel( "Channel:" );
- mChannelLabel.setFont( mFont );
- mChannelLabel.setForeground( mDetailsColor );
- add( mChannelLabel );
-
- mChannelName = new JLabel( mChannel );
- mChannelName.setFont( mFont );
- mChannelName.setForeground( mDetailsColor );
-
- add( mChannelName );
-
- mMutedLabel = new JLabel( "" );
- mMutedLabel.setFont( mFont );
- mMutedLabel.setForeground( Color.RED );
-
- add( mMutedLabel, "wrap" );
+ JSeparator separator = new JSeparator( JSeparator.HORIZONTAL );
+ separator.setBackground( Color.DARK_GRAY );
+ add( separator, "span,growx" );
}
- private void updateMuteState()
- {
- if( mMuteControl != null )
- {
- EventQueue.invokeLater( new Runnable()
- {
- @Override
- public void run()
- {
- mMutedLabel.setText( mMuteControl.getValue() ? "Muted" : "" );
- }
- } );
- }
- }
-
@Override
public void receive( final AudioEvent audioEvent )
{
- if( audioEvent.getType() == Type.AUDIO_STOPPED )
+ switch( audioEvent.getType() )
{
- EventQueue.invokeLater( new Runnable()
- {
- @Override
- public void run()
+ case AUDIO_STOPPED:
+ EventQueue.invokeLater( new Runnable()
{
- resetLabels();
- }
- } );
+ @Override
+ public void run()
+ {
+ resetLabels();
+ }
+ } );
+ break;
+ case AUDIO_MUTED:
+ case AUDIO_UNMUTED:
+ EventQueue.invokeLater( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ mMutedLabel.setText( mAudioOutput.isMuted() ? "M" : " " );
+ }
+ } );
+ break;
+ default:
+ break;
}
}
@@ -240,43 +206,6 @@ public class AudioChannelPanel extends JPanel
}
}
- public class MouseSelectionListener implements MouseListener
- {
- @Override
- public void mouseClicked( MouseEvent event )
- {
- JPopupMenu popup = new JPopupMenu();
-
- if( mMuteControl != null )
- {
- JMenuItem mute = new JMenuItem( mMuteControl.getValue() ?
- "Unmute Channel: " + mChannel : "Mute Channel: " + mChannel );
-
- mute.addActionListener( new ActionListener()
- {
- @Override
- public void actionPerformed( ActionEvent e )
- {
- mMuteControl.setValue( !mMuteControl.getValue() );
- updateMuteState();
- }
- } );
- popup.add( mute );
- }
-
- popup.show( event.getComponent(), event.getX(), event.getY() );
- }
-
- @Override
- public void mousePressed( MouseEvent e ) {}
- @Override
- public void mouseReleased( MouseEvent e ) {}
- @Override
- public void mouseEntered( MouseEvent e ) {}
- @Override
- public void mouseExited( MouseEvent e ) {}
- }
-
/**
* Processes audio metadata to update this panel's display values
diff --git a/src/audio/AudioChannelsPanel.java b/src/audio/AudioChannelsPanel.java
new file mode 100644
index 00000000..07c7604c
--- /dev/null
+++ b/src/audio/AudioChannelsPanel.java
@@ -0,0 +1,37 @@
+package audio;
+
+import java.awt.Color;
+import java.util.List;
+
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import settings.SettingsManager;
+import audio.output.AudioOutput;
+
+public class AudioChannelsPanel extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+
+ public AudioChannelsPanel( SettingsManager settingsManager,
+ IAudioController controller )
+ {
+ setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]") );
+
+ setBackground( Color.BLACK );
+
+ List outputs = controller.getAudioOutputs();
+
+ for( AudioOutput audioOutput: controller.getAudioOutputs() )
+ {
+ add( new AudioChannelPanel( settingsManager, audioOutput ), "wrap" );
+ }
+
+ /* Add an empty channel panel so that the panel is sized appropriately
+ * for either a single channel or two channels */
+ if( outputs.size() == 1 )
+ {
+ add( new AudioChannelPanel( settingsManager, null ), "wrap" );
+ }
+ }
+}
diff --git a/src/audio/AudioEvent.java b/src/audio/AudioEvent.java
index e82bbf14..146d31d2 100644
--- a/src/audio/AudioEvent.java
+++ b/src/audio/AudioEvent.java
@@ -23,8 +23,13 @@ public class AudioEvent
public enum Type
{
+ AUDIO_MUTED,
+ AUDIO_UNMUTED,
+ AUDIO_SQUELCHED,
+ AUDIO_UNSQUELCHED,
AUDIO_STARTED,
AUDIO_STOPPED,
- AUDIO_CONFIGURATION_CHANGED;
+ AUDIO_CONFIGURATION_CHANGE_STARTED,
+ AUDIO_CONFIGURATION_CHANGE_COMPLETE;
}
}
diff --git a/src/audio/AudioFormats.java b/src/audio/AudioFormats.java
index e9ab4add..f7c58198 100644
--- a/src/audio/AudioFormats.java
+++ b/src/audio/AudioFormats.java
@@ -1,6 +1,9 @@
package audio;
import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.Line;
+import javax.sound.sampled.SourceDataLine;
public class AudioFormats
{
@@ -18,8 +21,10 @@ public class AudioFormats
public static final int IMBE_FRAME_SIZE_BYTES = 18;
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
public static final int ONE_CHANNEL = 1;
+ public static final int TWO_CHANNELS = 2;
public static final int PCM_SAMPLE_SIZE_BITS = 16;
- public static final int PCM_FRAME_SIZE_BYTES = 2;
+ public static final int PCM_FRAME_SIZE_BYTES_MONO = 2;
+ public static final int PCM_FRAME_SIZE_BYTES_STEREO = 4;
public static AudioFormat IMBE_AUDIO_FORMAT =
new AudioFormat( IMBE_ENCODING,
@@ -35,16 +40,37 @@ public class AudioFormats
PCM_8KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
- PCM_FRAME_SIZE_BYTES,
+ PCM_FRAME_SIZE_BYTES_MONO,
PCM_8KHZ_RATE,
LITTLE_ENDIAN );
- public static AudioFormat PCM_SIGNED_48KHZ_16BITS =
+ public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_48KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
- PCM_FRAME_SIZE_BYTES,
+ PCM_FRAME_SIZE_BYTES_MONO,
PCM_48KHZ_RATE,
LITTLE_ENDIAN );
+
+ public static AudioFormat PCM_SIGNED_48KHZ_16BITS_STEREO =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_48KHZ_RATE,
+ PCM_SAMPLE_SIZE_BITS,
+ TWO_CHANNELS,
+ PCM_FRAME_SIZE_BYTES_STEREO,
+ PCM_48KHZ_RATE,
+ LITTLE_ENDIAN );
+
+ /**
+ * Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
+ */
+ public static final Line.Info MONO_SOURCE_DATALINE_INFO =
+ new DataLine.Info( SourceDataLine.class, PCM_SIGNED_48KHZ_16BITS_MONO );
+
+ /**
+ * Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
+ */
+ public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
+ new DataLine.Info( SourceDataLine.class, PCM_SIGNED_48KHZ_16BITS_STEREO );
}
diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java
index 344d8a1a..4a26cecc 100644
--- a/src/audio/AudioManager.java
+++ b/src/audio/AudioManager.java
@@ -1,6 +1,8 @@
package audio;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -10,16 +12,20 @@ import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
-import javax.sound.sampled.BooleanControl;
-import javax.sound.sampled.FloatControl;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Mixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import sample.Broadcaster;
import sample.Listener;
-import audio.metadata.AudioMetadata;
+import source.mixer.MixerChannel;
+import source.mixer.MixerChannelConfiguration;
+import audio.AudioEvent.Type;
import audio.output.AudioOutput;
import audio.output.MonoAudioOutput;
+import audio.output.StereoAudioOutput;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
@@ -29,23 +35,30 @@ public class AudioManager implements Listener, IAudioController
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
public static final int NO_SOURCE = 0;
+
+ public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
+ new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null );
+
+ public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
+ new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null );
private LinkedTransferQueue mAudioPacketQueue = new LinkedTransferQueue<>();
private Map mChannelAssignments = new HashMap<>();
private int mLowestPrioritySource;
private List mAvailableAudioOutputs = new ArrayList<>();
- private Map mChannelMap = new HashMap<>();
+ private Map mAudioOutputMap = new HashMap<>();
private List mChannelNames = new ArrayList<>();
- private AudioOutput mAudioOutput;
- private Listener mConfigurationChangeListener;
+ private Broadcaster mControllerBroadcaster = new Broadcaster<>();
private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture> mProcessingTask;
+
+ private MixerChannelConfiguration mMixerChannel;
/**
- * Handles all audio produced by the decoding channels and routes audio
+ * Processes all audio produced by the decoding channels and routes audio
* packets to any combination of outputs based on any alias audio routing
* options specified by the user.
*/
@@ -53,14 +66,24 @@ public class AudioManager implements Listener, IAudioController
{
mThreadPoolManager = manager;
- mAudioOutput = new MonoAudioOutput();
- mAvailableAudioOutputs.add( mAudioOutput );
- mChannelNames.add( mAudioOutput.getChannelName() );
- mChannelMap.put( mAudioOutput.getChannelName(), mAudioOutput );
+ //TODO: save audio system preferences to the settings manager
- mProcessingTask = mThreadPoolManager.scheduleFixedRate(
- ThreadType.AUDIO_PROCESSING, new AudioPacketProcessor(), 15,
- TimeUnit.MILLISECONDS );
+ /* Use the system default mixer and mono channel as default startup */
+ Mixer mixer = AudioSystem.getMixer( null );
+
+ if( mixer != null )
+ {
+ mMixerChannel = new MixerChannelConfiguration( mixer, MixerChannel.MONO );
+
+ try
+ {
+ setMixerChannelConfiguration( mMixerChannel );
+ }
+ catch ( AudioException e )
+ {
+ mLog.error( "Couldn't create an audio output device", e );
+ }
+ }
}
public void dispose()
@@ -74,8 +97,6 @@ public class AudioManager implements Listener, IAudioController
mThreadPoolManager = null;
mProcessingTask = null;
-
- mAudioOutput.dispose();
}
@Override
@@ -221,6 +242,14 @@ public class AudioManager implements Listener, IAudioController
}
}
+ public void endAllAudioAssignments()
+ {
+ for( AudioOutput output: mAudioOutputMap.values() )
+ {
+ output.receive( new AudioPacket( AudioPacket.Type.END, null ) );
+ }
+ }
+
public class AudioChannelAssignment implements Listener
{
private static final long CALL_END_DELAY = 2000; //2 seconds
@@ -280,102 +309,91 @@ public class AudioManager implements Listener, IAudioController
}
}
}
-
+
@Override
- public List getAudioChannels()
+ public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException
{
- return mChannelNames;
- }
-
- @Override
- public BooleanControl getMuteControl( String channel )
- throws AudioException
- {
- if( mChannelMap.containsKey( channel ) )
+ if( entry != null &&
+ ( entry.getMixerChannel() == MixerChannel.MONO ||
+ entry.getMixerChannel() == MixerChannel.STEREO ) )
{
- return mChannelMap.get( channel ).getMuteControl();
- }
+ mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_STARTED );
+
+ if( mThreadPoolManager != null && mProcessingTask != null )
+ {
+ mThreadPoolManager.cancel( mProcessingTask );
+ }
- throw new AudioException( "Channel [" + channel + "] is not a valid channel" );
- }
+ endAllAudioAssignments();
+
+ mAvailableAudioOutputs.clear();
+ mChannelNames.clear();
+ mAudioOutputMap.clear();
- @Override
- public boolean hasMuteControl( String channel )
- {
- return mChannelMap.containsKey( channel ) &&
- mChannelMap.get( channel ) != null &&
- mChannelMap.get( channel ).getMuteControl() != null;
- }
+ switch( entry.getMixerChannel() )
+ {
+ case MONO:
+ AudioOutput mono = new MonoAudioOutput( entry.getMixer() );
+ mAvailableAudioOutputs.add( mono );
+ mChannelNames.add( mono.getChannelName() );
+ mAudioOutputMap.put( mono.getChannelName(), mono );
+ break;
+ case STEREO:
+ AudioOutput left = new StereoAudioOutput( entry.getMixer(), MixerChannel.LEFT );
+ mAvailableAudioOutputs.add( left );
+ mChannelNames.add( left.getChannelName() );
+ mAudioOutputMap.put( left.getChannelName(), left );
+
+ AudioOutput right = new StereoAudioOutput( entry.getMixer(), MixerChannel.RIGHT );
+ mAvailableAudioOutputs.add( right );
+ mChannelNames.add( right.getChannelName() );
+ mAudioOutputMap.put( right.getChannelName(), right );
+ break;
+ default:
+ throw new AudioException( "Unsupported mixer channel "
+ + "configuration: " + entry.getMixerChannel() );
+ }
- @Override
- public FloatControl getGainControl( String channel ) throws AudioException
- {
- if( mChannelMap.containsKey( channel ) )
- {
- return mChannelMap.get( channel ).getGainControl();
- }
+ mProcessingTask = mThreadPoolManager.scheduleFixedRate(
+ ThreadType.AUDIO_PROCESSING, new AudioPacketProcessor(), 15,
+ TimeUnit.MILLISECONDS );
- throw new AudioException( "Channel [" + channel + "] is not a valid channel" );
- }
-
- @Override
- public boolean hasGainControl( String channel )
- {
- return mChannelMap.containsKey( channel ) &&
- mChannelMap.get( channel ) != null &&
- mChannelMap.get( channel ).getGainControl() != null;
- }
-
- /**
- * Sets the listener to receive audio events, specifically, audio
- * configuration change events
- */
- @Override
- public void setConfigurationChangeListener( Listener listener )
- {
- mConfigurationChangeListener = listener;
- }
-
- @Override
- public void addAudioEventListener( String channel, Listener listener )
- throws AudioException
- {
- if( mChannelMap.containsKey( channel ) )
- {
- mChannelMap.get( channel ).addAudioEventListener( listener );
- }
- else
- {
- throw new AudioException( "Channel [" + channel + "] is not a valid "
- + "channel. Audio event listener was not registered" );
+ mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_COMPLETE );
}
}
@Override
- public void removeAudioEventListener( String channel, Listener listener )
+ public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
{
- if( mChannelMap.containsKey( channel ) )
- {
- mChannelMap.get( channel ).removeAudioEventListener( listener );
- }
+ return mMixerChannel;
}
@Override
- public void setAudioMetadataListener( String channel,
- Listener listener )
+ public List getAudioOutputs()
{
- if( mChannelMap.containsKey( channel ) )
+ List outputs = new ArrayList<>( mAudioOutputMap.values() );
+
+ Collections.sort( outputs, new Comparator()
{
- mChannelMap.get( channel ).setAudioMetadataListener( listener );
- }
+ @Override
+ public int compare( AudioOutput first, AudioOutput second )
+ {
+ return first.getChannelName().compareTo( second.getChannelName() );
+ }
+ } );
+
+ return outputs;
}
@Override
- public void removeAudioMetadataListener( String channel )
+ public void addControllerListener( Listener listener )
{
- if( mChannelMap.containsKey( channel ) )
- {
- mChannelMap.get( channel ).removeAudioMetadataListener();
- }
+ mControllerBroadcaster.addListener( listener );
+ }
+
+ @Override
+ public void removeControllerListener( Listener listener )
+ {
+ mControllerBroadcaster.removeListener( listener );
}
}
diff --git a/src/audio/AudioPanel.java b/src/audio/AudioPanel.java
index f62b733b..844c3486 100644
--- a/src/audio/AudioPanel.java
+++ b/src/audio/AudioPanel.java
@@ -4,10 +4,21 @@ import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
-import javax.sound.sampled.BooleanControl;
+import javax.sound.sampled.FloatControl;
+import javax.swing.ImageIcon;
import javax.swing.JButton;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JSlider;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
@@ -16,68 +27,329 @@ import org.slf4j.LoggerFactory;
import sample.Listener;
import settings.SettingsManager;
+import source.mixer.MixerChannelConfiguration;
+import source.mixer.MixerManager;
+import audio.output.AudioOutput;
public class AudioPanel extends JPanel implements Listener
{
private static final long serialVersionUID = 1L;
private static final Logger mLog = LoggerFactory.getLogger( AudioPanel.class );
+
+ private static ImageIcon MUTED_ICON = SettingsManager.getScaledIcon(
+ new ImageIcon( "images/audio_muted.png" ), 38 );
+ private static ImageIcon UNMUTED_ICON = SettingsManager.getScaledIcon(
+ new ImageIcon( "images/audio_unmuted.png" ), 38 );
private SettingsManager mSettingsManager;
private IAudioController mController;
- private MuteButton mMuteButton;
+
+ private JButton mMuteButton;
+ private AudioChannelsPanel mAudioChannelsPanel;
public AudioPanel( SettingsManager settingsManager, IAudioController controller )
{
mSettingsManager = settingsManager;
mController = controller;
- mMuteButton = new MuteButton( mController );
+ mController.addControllerListener( this );
init();
}
private void init()
{
- setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]") );
+ setLayout( new MigLayout( "insets 0 0 0 0", "[]0[grow,fill]", "[fill]0[]") );
setBackground( Color.BLACK );
- add( mMuteButton, "span" );
+ mMuteButton = new MuteButton();
+ mMuteButton.setBackground( getBackground() );
+ add( mMuteButton );
- for( String channel: mController.getAudioChannels() )
- {
- add( new AudioChannelPanel( mSettingsManager, mController, channel ), "span" );
- }
+ mAudioChannelsPanel = new AudioChannelsPanel( mSettingsManager, mController );
+
+ add( mAudioChannelsPanel );
+
+ addMouseListener( new MouseSelectionListener() );
}
- public int getPreferredHeight()
- {
- return mMuteButton.getHeight() +
- ( mController.getAudioChannels().size() * 20 );
- }
-
@Override
public void receive( AudioEvent event )
{
- if( event.getType() == AudioEvent.Type.AUDIO_CONFIGURATION_CHANGED )
+ switch( event.getType() )
{
- mLog.debug( "Audio configuration change detected" );
+ case AUDIO_CONFIGURATION_CHANGE_STARTED:
+ break;
+ case AUDIO_CONFIGURATION_CHANGE_COMPLETE:
+ EventQueue.invokeLater( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ remove( mAudioChannelsPanel );
+
+ mAudioChannelsPanel = new AudioChannelsPanel(
+ mSettingsManager, mController );
+
+ add( mAudioChannelsPanel );
+
+ mAudioChannelsPanel.repaint();
+
+ revalidate();
+ repaint();
+ }
+ } );
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Audio output mute control menu item.
+ */
+ public class AudioOutputMuteItem extends JMenuItem
+ {
+ private static final long serialVersionUID = 1L;
+
+ private AudioOutput mAudioOutput;
+
+ public AudioOutputMuteItem( AudioOutput audioOutput )
+ {
+ super( audioOutput.isMuted() ? "Unmute" : "Mute" );
+
+ mAudioOutput = audioOutput;
+
+ addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ mAudioOutput.setMuted( !mAudioOutput.isMuted() );
+ }
+ } );
}
}
+ /**
+ * Mouse listener
+ */
+ public class MouseSelectionListener implements MouseListener
+ {
+ @Override
+ public void mouseClicked( MouseEvent event )
+ {
+ if( SwingUtilities.isRightMouseButton( event ) )
+ {
+ JPopupMenu popup = new JPopupMenu();
+
+ /* Audio mixer/output selection menus */
+ JMenu outputMenu = new JMenu( "Audio Output" );
+
+ MixerChannelConfiguration[] mixerConfigurations =
+ MixerManager.getInstance().getOutputMixers();
+
+ for( MixerChannelConfiguration mixerConfig: mixerConfigurations )
+ {
+ MixerSelectionItem mixerItem = new MixerSelectionItem( mixerConfig );
+
+ try
+ {
+ MixerChannelConfiguration current = mController.getMixerChannelConfiguration();
+
+ if( current != null && current.equals( mixerConfig ) )
+ {
+ mixerItem.setSelected( true );
+ }
+ }
+ catch( AudioException e )
+ {
+ mLog.debug( "Error while detecting current mixer "
+ + "channel configuration", e );
+ }
+
+ outputMenu.add( mixerItem );
+ }
+
+ popup.add( outputMenu );
+
+ /* Audio output mute and volume control */
+ for( AudioOutput output: mController.getAudioOutputs() )
+ {
+ JMenu menu = new JMenu( "Channel: " + output.getChannelName() );
+
+ menu.add( new AudioOutputMuteItem( output ) );
+
+ if( output.hasGainControl() )
+ {
+ JMenu volume = new JMenu( "Volume" );
+ volume.add( new VolumeSlider( output.getGainControl() ) );
+ menu.add( volume );
+ }
+
+ popup.add( menu );
+ }
+
+ popup.show( event.getComponent(), event.getX(), event.getY() );
+ }
+ }
+
+ @Override
+ public void mousePressed( MouseEvent e ) {}
+ @Override
+ public void mouseReleased( MouseEvent e ) {}
+ @Override
+ public void mouseEntered( MouseEvent e ) {}
+ @Override
+ public void mouseExited( MouseEvent e ) {}
+ }
+
+ /**
+ * Audio volume (gain) adjustment slider control
+ */
+ public class VolumeSlider extends JSlider
+ {
+ private static final long serialVersionUID = 1L;
+
+ private FloatControl mFloatControl;
+
+ public VolumeSlider( FloatControl control )
+ {
+ super( 0, 100, 0 );
+
+ setMajorTickSpacing( 25 );
+ setMinorTickSpacing( 5 );
+ setPaintTicks( true );
+ setPaintLabels( true );
+
+ mFloatControl = control;
+
+ setValue( getIntegerValue( mFloatControl.getValue() ) );
+
+ addChangeListener( new ChangeListener()
+ {
+ @Override
+ public void stateChanged( ChangeEvent event )
+ {
+ mFloatControl.shift( mFloatControl.getValue(),
+ getFloatValue( VolumeSlider.this.getValue() ),
+ 1000 );
+ }
+ } );
+
+ addMouseListener( new MouseListener()
+ {
+ @Override
+ public void mouseClicked( MouseEvent event )
+ {
+ if( event.getClickCount() == 2 )
+ {
+ VolumeSlider.this.setValue( 50 );
+ }
+ }
+
+ public void mouseReleased( MouseEvent arg0 ) {}
+ public void mousePressed( MouseEvent arg0 ) {}
+ public void mouseExited( MouseEvent arg0 ) {}
+ public void mouseEntered( MouseEvent arg0 ) {}
+ } );
+ }
+
+ /**
+ * Converts the integer value to a floating point value to use in the
+ * float control. Assumes an integer value of 50 is the 0.0 dB mid
+ * point (ie no gain ) value.
+ */
+ private int getIntegerValue( float value )
+ {
+ if( value == 0.0f )
+ {
+ return 50;
+ }
+ else if( value < 0.0f )
+ {
+ float ratio = value / mFloatControl.getMinimum();
+
+ return 50 - (int)( ratio * 50.0f );
+ }
+ else
+ {
+ float ratio = value / mFloatControl.getMaximum();
+
+ return 50 + (int)( ratio * 50.0f );
+ }
+ }
+
+ private float getFloatValue( int value )
+ {
+ if( value == 50 )
+ {
+ return 0.0f;
+ }
+ else if( value < 50 )
+ {
+ return (float)( 50 - value ) / 50.0f * mFloatControl.getMinimum();
+ }
+ else
+ {
+ return (float)( value - 50 ) / 50.0f * mFloatControl.getMaximum();
+ }
+ }
+ }
+
+ /**
+ * Mixer/Channel configuration selection item
+ */
+ public class MixerSelectionItem extends JMenuItem
+ {
+ private static final long serialVersionUID = 1L;
+
+ private MixerChannelConfiguration mConfiguration;
+
+ public MixerSelectionItem( MixerChannelConfiguration config )
+ {
+ super( config.toString() );
+
+ mConfiguration = config;
+
+ addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ try
+ {
+ mController.setMixerChannelConfiguration( mConfiguration );
+ }
+ catch ( AudioException e1 )
+ {
+ mLog.error( "Couldn't set mixer channel configuration "
+ + "to: " + mConfiguration.toString() );
+
+ JOptionPane.showMessageDialog( MixerSelectionItem.this,
+ "Couldn't set [" + mConfiguration.toString() +
+ "] as the audio output device" );
+ }
+ }
+ } );
+ }
+ }
+
+ /**
+ * Mute button to mute all audio output channels exposed by the audio
+ * controller
+ */
public class MuteButton extends JButton
{
- private IAudioController mController;
-
+ private static final long serialVersionUID = 1L;
+
private boolean mMuted = false;
- public MuteButton( IAudioController controller )
+ public MuteButton()
{
- super( "Mute Audio" );
-
- setBackground( Color.GREEN );
-
- mController = controller;
+ setIcon( UNMUTED_ICON );
+ setBorderPainted( false );
addActionListener( new ActionListener()
{
@@ -86,31 +358,17 @@ public class AudioPanel extends JPanel implements Listener
{
mMuted = !mMuted;
- for( String channel: mController.getAudioChannels() )
+ for( AudioOutput output: mController.getAudioOutputs() )
{
- try
- {
- BooleanControl control = mController.getMuteControl( channel );
-
- if( control != null )
- {
- control.setValue( mMuted );
- }
- }
- catch ( AudioException e1 )
- {
- mLog.error( "Couldn't obtain mute control for "
- + "channel [" + channel + "] from audio manager", e1 );
- }
+ output.setMuted( mMuted );
}
-
+
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
- MuteButton.this.setText( ( mMuted ? "Unmute Audio" : "Mute Audio" ) );
- MuteButton.this.setBackground( ( mMuted ? Color.RED : Color.GREEN ) );
+ setIcon( mMuted ? MUTED_ICON : UNMUTED_ICON );
}
} );
}
diff --git a/src/audio/IAudioController.java b/src/audio/IAudioController.java
index eeb324e6..4b12b6d0 100644
--- a/src/audio/IAudioController.java
+++ b/src/audio/IAudioController.java
@@ -2,35 +2,23 @@ package audio;
import java.util.List;
-import javax.sound.sampled.BooleanControl;
-import javax.sound.sampled.FloatControl;
-
import sample.Listener;
-import audio.metadata.AudioMetadata;
+import source.mixer.MixerChannelConfiguration;
+import audio.output.AudioOutput;
/**
* Interface for controlling one or more audio channels
*/
public interface IAudioController
{
- public List getAudioChannels();
+ /* Current Mixer and MixerChannel configuration */
+ public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException;
+ public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException;
- public BooleanControl getMuteControl( String channel ) throws AudioException;
-
- public boolean hasMuteControl( String channel );
-
- public FloatControl getGainControl( String channel ) throws AudioException;
-
- public boolean hasGainControl( String channel );
-
- public void addAudioEventListener( String channel, Listener listener )
- throws AudioException;
-
- public void removeAudioEventListener( String channel, Listener listener );
-
- public void setAudioMetadataListener( String channel, Listener listener );
-
- public void removeAudioMetadataListener( String channel );
-
- public void setConfigurationChangeListener( Listener listener );
+ /* Audio Output(s) */
+ public List getAudioOutputs();
+
+ /* Controller Audio Event Listener */
+ public void addControllerListener( Listener listener );
+ public void removeControllerListener( Listener listener );
}
diff --git a/src/audio/output/AudioOutput.java b/src/audio/output/AudioOutput.java
index 9b81abf3..6c831b24 100644
--- a/src/audio/output/AudioOutput.java
+++ b/src/audio/output/AudioOutput.java
@@ -1,5 +1,7 @@
package audio.output;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.BooleanControl;
@@ -11,6 +13,7 @@ import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import audio.AudioEvent;
+import audio.AudioEvent.Type;
import audio.AudioPacket;
import audio.metadata.AudioMetadata;
@@ -20,10 +23,24 @@ public abstract class AudioOutput implements Listener
private AtomicBoolean mSquelched = new AtomicBoolean( false );
private Listener mAudioMetadataListener;
+
protected Broadcaster mAudioEventBroadcaster = new Broadcaster<>();
+ protected LinkedTransferQueue mBuffer = new LinkedTransferQueue<>();
+ protected ScheduledExecutorService mExecutorService;
+ protected boolean mCanProcessAudio;
+ protected long mLastBufferReceived;
public void dispose()
{
+ mCanProcessAudio = false;
+
+ mBuffer.clear();
+
+ if( mExecutorService != null )
+ {
+ mExecutorService.shutdown();
+ }
+
mAudioEventBroadcaster.dispose();
mAudioEventBroadcaster = null;
mAudioMetadataListener = null;
@@ -31,8 +48,8 @@ public abstract class AudioOutput implements Listener
public abstract String getChannelName();
- public abstract BooleanControl getMuteControl();
- public abstract boolean hasMuteControl();
+ public abstract void setMuted( boolean muted );
+ public abstract boolean isMuted();
public abstract FloatControl getGainControl();
public abstract boolean hasGainControl();
@@ -45,9 +62,17 @@ public abstract class AudioOutput implements Listener
}
}
+ protected void broadcast( AudioEvent audioEvent )
+ {
+ mAudioEventBroadcaster.broadcast( audioEvent );
+ }
+
public void setSquelched( boolean squelched )
{
mSquelched.set( squelched );
+
+ broadcast( new AudioEvent( squelched ? Type.AUDIO_SQUELCHED :
+ Type.AUDIO_UNSQUELCHED, getChannelName() ) );
}
public boolean isSquelched()
@@ -81,4 +106,23 @@ public abstract class AudioOutput implements Listener
{
mAudioMetadataListener = null;
}
+
+ /**
+ * Timestamp when most recent buffer was received
+ */
+ public long getLastBufferReceived()
+ {
+ return mLastBufferReceived;
+ }
+
+ @Override
+ public void receive( AudioPacket packet )
+ {
+ if( mCanProcessAudio )
+ {
+ mLastBufferReceived = System.currentTimeMillis();
+
+ mBuffer.add( packet );
+ }
+ }
}
diff --git a/src/audio/output/MonoAudioOutput.java b/src/audio/output/MonoAudioOutput.java
index 532d4385..5715fa78 100644
--- a/src/audio/output/MonoAudioOutput.java
+++ b/src/audio/output/MonoAudioOutput.java
@@ -6,20 +6,19 @@ import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedTransferQueue;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import source.mixer.MixerChannel;
import audio.AudioEvent;
import audio.AudioFormats;
import audio.AudioPacket;
@@ -27,14 +26,14 @@ import audio.AudioPacket.Type;
import controller.NamingThreadFactory;
/**
- * Audio output with automatic flow control based on the availability of
+ * Mono Audio output with automatic flow control based on the availability of
* audio data packets.
*/
public class MonoAudioOutput extends AudioOutput
{
private final static Logger mLog = LoggerFactory.getLogger( MonoAudioOutput.class );
- /* Output line buffer size is set at one second of audio at 48 kHz and
+ /* Output line buffer size is set at one half second of audio at 48 kHz and
* two bytes per sample */
private final static int BUFFER_SIZE = 48000;
@@ -42,40 +41,34 @@ public class MonoAudioOutput extends AudioOutput
* audio and automatically starting or stopping audio playback. We set the
* data line available space threshold to start when we're 20 ms away from
* being full, and the stop threshold when 20 ms away from being empty */
- private final static int SAMPLE_SIZE_40_MS = 48000 / 25;
+ private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25;
private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
-
- private LinkedTransferQueue mBuffer = new LinkedTransferQueue<>();
- private ScheduledExecutorService mExecutorService;
-
private SourceDataLine mOutput;
private FloatControl mGainControl;
private BooleanControl mMuteControl;
- private boolean mCanProcessAudio;
-
- private long mLastBufferReceived;
-
private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent;
- public MonoAudioOutput()
+ public MonoAudioOutput( Mixer mixer )
{
super();
-
- mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED, getChannelName() );
- mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, getChannelName() );
+
+ mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
+ getChannelName() );
+ mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
+ getChannelName() );
try
{
- mOutput = AudioSystem.getSourceDataLine(
- AudioFormats.PCM_SIGNED_48KHZ_16BITS );
-
+ mOutput = (SourceDataLine)mixer.getLine(
+ AudioFormats.MONO_SOURCE_DATALINE_INFO );
+
if( mOutput != null )
{
- mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS, BUFFER_SIZE );
+ mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
mCanProcessAudio = true;
@@ -103,47 +96,18 @@ public class MonoAudioOutput extends AudioOutput
@Override
public String getChannelName()
{
- return "Mono";
- }
-
- /**
- * Timestamp when most recent buffer was received
- */
- public long getLastBufferReceived()
- {
- return mLastBufferReceived;
+ return MixerChannel.MONO.getLabel();
}
public void dispose()
{
super.dispose();
- mCanProcessAudio = false;
-
- mBuffer.clear();
-
- if( mExecutorService != null )
- {
- mExecutorService.shutdown();
- }
-
if( mOutput != null )
{
mOutput.close();
}
}
-
- @Override
- public void receive( AudioPacket packet )
- {
- if( mCanProcessAudio )
- {
- mLastBufferReceived = System.currentTimeMillis();
-
- mBuffer.add( packet );
- }
- }
-
public class QueueProcessor implements Runnable
{
@@ -233,17 +197,27 @@ public class MonoAudioOutput extends AudioOutput
}
}
+ @Override
+ public void setMuted( boolean muted )
+ {
+ if( mMuteControl != null )
+ {
+ mMuteControl.setValue( muted );
+
+ broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
+ AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
+ }
+ }
@Override
- public BooleanControl getMuteControl()
+ public boolean isMuted()
{
- return mMuteControl;
- }
-
- @Override
- public boolean hasMuteControl()
- {
- return mMuteControl != null;
+ if( mMuteControl != null )
+ {
+ return mMuteControl.getValue();
+ }
+
+ return false;
}
@Override
diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java
new file mode 100644
index 00000000..bd1d8338
--- /dev/null
+++ b/src/audio/output/StereoAudioOutput.java
@@ -0,0 +1,259 @@
+package audio.output;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.sound.sampled.BooleanControl;
+import javax.sound.sampled.Control;
+import javax.sound.sampled.FloatControl;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.Mixer;
+import javax.sound.sampled.SourceDataLine;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import source.mixer.MixerChannel;
+import audio.AudioEvent;
+import audio.AudioFormats;
+import audio.AudioPacket;
+import audio.AudioPacket.Type;
+import controller.NamingThreadFactory;
+
+/**
+ * Mono Audio output in stereo format with automatic flow control based on the
+ * availability of audio data packets.
+ */
+public class StereoAudioOutput extends AudioOutput
+{
+ private final static Logger mLog = LoggerFactory.getLogger( StereoAudioOutput.class );
+
+ /* Output line buffer size is set at one half second of audio at 48 kHz and
+ * two bytes per sample for two channels */
+ private final static int BUFFER_SIZE = 96000;
+
+ /* The queue processor will run every 20 milliseconds checking for inbound
+ * audio and automatically starting or stopping audio playback. We set the
+ * data line available space threshold to start when we're 20 ms away from
+ * being full, and the stop threshold when 20 ms away from being empty */
+ private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25;
+ private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
+ private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
+
+ private SourceDataLine mOutput;
+ private FloatControl mGainControl;
+ private BooleanControl mMuteControl;
+
+ private AudioEvent mAudioStartEvent;
+ private AudioEvent mAudioStopEvent;
+
+ private MixerChannel mMixerChannel;
+
+ public StereoAudioOutput( Mixer mixer, MixerChannel channel )
+ {
+ super();
+
+ assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
+
+ mMixerChannel = channel;
+
+ mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
+ getChannelName() );
+ mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
+ getChannelName() );
+
+ try
+ {
+ mOutput = (SourceDataLine)mixer.getLine(
+ AudioFormats.STEREO_SOURCE_DATALINE_INFO );
+
+ if( mOutput != null )
+ {
+ mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO, BUFFER_SIZE );
+
+ mCanProcessAudio = true;
+
+ mGainControl = (FloatControl)mOutput.getControl(
+ FloatControl.Type.MASTER_GAIN );
+
+ mMuteControl = (BooleanControl)mOutput.getControl(
+ BooleanControl.Type.MUTE );
+
+ mExecutorService = Executors.newSingleThreadScheduledExecutor(
+ new NamingThreadFactory( "audio (stereo) output" ) );
+
+ /* Run the queue processor task every 40 milliseconds */
+ mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
+ 0, 40, TimeUnit.MILLISECONDS );
+ }
+ }
+ catch ( LineUnavailableException e )
+ {
+ mLog.error( "Couldn't obtain source data line for 48kHz PCM stereo "
+ + "audio output" );
+ }
+ }
+
+ @Override
+ public String getChannelName()
+ {
+ return mMixerChannel.getLabel();
+ }
+
+ public void dispose()
+ {
+ super.dispose();
+
+ if( mOutput != null )
+ {
+ mOutput.close();
+ }
+ }
+
+ private ByteBuffer convert( float[] samples )
+ {
+ /* Little-endian byte buffer */
+ ByteBuffer buffer =
+ ByteBuffer.allocate( samples.length * 4 )
+ .order( ByteOrder.LITTLE_ENDIAN );
+
+ ShortBuffer shortBuffer = buffer.asShortBuffer();
+
+ if( mMixerChannel == MixerChannel.LEFT )
+ {
+ for( float sample: samples )
+ {
+ shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
+ shortBuffer.put( (short)0 );
+ }
+ }
+ else
+ {
+ for( float sample: samples )
+ {
+ shortBuffer.put( (short)0 );
+ shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
+ }
+ }
+
+ return buffer;
+ }
+
+ public class QueueProcessor implements Runnable
+ {
+ private AtomicBoolean mProcessing = new AtomicBoolean();
+
+ public QueueProcessor()
+ {
+ }
+
+ @Override
+ public void run()
+ {
+ /* The mProcessing flag ensures that only one instance of the
+ * processor can run at any given time */
+ if( mProcessing.compareAndSet( false, true ) )
+ {
+ List packets = new ArrayList();
+
+ mBuffer.drainTo( packets );
+
+ if( packets.size() > 0 )
+ {
+ for( AudioPacket packet: packets )
+ {
+ if( mCanProcessAudio )
+ {
+ if( isSquelched() )
+ {
+ checkStop();
+ }
+ else if( packet.getType() == Type.AUDIO )
+ {
+ ByteBuffer buffer = convert(
+ packet.getAudioBuffer().getSamples() );
+
+ mOutput.write( buffer.array(), 0,
+ buffer.array().length );
+
+ checkStart();
+
+ broadcast( packet.getAudioMetadata() );
+ }
+ }
+ }
+ }
+ else
+ {
+ checkStop();
+ }
+
+ mProcessing.set( false );
+ }
+ }
+
+ private void checkStart()
+ {
+ if( mCanProcessAudio && !isSquelched() &&
+ !mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
+ {
+ mOutput.start();
+
+ mAudioEventBroadcaster.broadcast( mAudioStartEvent );
+ }
+ }
+
+ private void checkStop()
+ {
+ if( mCanProcessAudio && mOutput.isRunning() &&
+ ( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
+ {
+ mOutput.drain();
+ mOutput.stop();
+
+ mAudioEventBroadcaster.broadcast( mAudioStopEvent );
+ }
+ }
+ }
+
+ @Override
+ public void setMuted( boolean muted )
+ {
+ if( mMuteControl != null )
+ {
+ mMuteControl.setValue( muted );
+
+ broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
+ AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
+ }
+ }
+
+ @Override
+ public boolean isMuted()
+ {
+ if( mMuteControl != null )
+ {
+ return mMuteControl.getValue();
+ }
+
+ return false;
+ }
+
+ @Override
+ public FloatControl getGainControl()
+ {
+ return mGainControl;
+ }
+
+ @Override
+ public boolean hasGainControl()
+ {
+ return mGainControl != null;
+ }
+}
diff --git a/src/controller/ControllerPanel.java b/src/controller/ControllerPanel.java
index d5eb8de3..6b627649 100644
--- a/src/controller/ControllerPanel.java
+++ b/src/controller/ControllerPanel.java
@@ -154,7 +154,6 @@ public class ControllerPanel extends JPanel
AudioPanel audioPanel = new AudioPanel( mResourceManager.getSettingsManager(),
mResourceManager.getAudioManager() );
- audioPanel.setPreferredSize( new Dimension( 200, audioPanel.getPreferredHeight() ) );
JideSplitPane audioChannelListSplit = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT );
audioChannelListSplit.setDividerSize( 5 );
diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java
index c7d946cc..9d50da47 100644
--- a/src/controller/channel/Channel.java
+++ b/src/controller/channel/Channel.java
@@ -824,7 +824,7 @@ public class Channel extends Configuration
List modules = DecoderFactory.getModules( mChannelType,
mResourceManager, mDecodeConfiguration, mRecordConfiguration,
mAuxDecodeConfiguration, aliasList, mSystem,
- mSite );
+ mSite, mName );
mProcessingChain.addModules( modules );
diff --git a/src/controller/channel/ChannelUtils.java b/src/controller/channel/ChannelUtils.java
index 1d49ca9e..5f50f6e6 100644
--- a/src/controller/channel/ChannelUtils.java
+++ b/src/controller/channel/ChannelUtils.java
@@ -6,6 +6,7 @@ import java.awt.event.ActionListener;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
import javax.swing.JSeparator;
import module.decode.event.ActivitySummaryFrame;
@@ -101,15 +102,22 @@ public class ChannelUtils
@Override
public void actionPerformed( ActionEvent e )
{
- /* Disable the channel */
- channel.setEnabled( DISABLED, BROADCAST_CHANGE );
-
- /* Broadcast channel deleted event */
- channel.fireChannelEvent( Event.CHANNEL_DELETED );
-
- if( playlistManager != null )
+ int response = JOptionPane.showConfirmDialog( anchor,
+ "Do you want to delete channel " + channel.getName() + "?",
+ "Are you sure?", JOptionPane.YES_NO_CANCEL_OPTION );
+
+ if( response == JOptionPane.YES_OPTION )
{
- playlistManager.save();
+ /* Disable the channel */
+ channel.setEnabled( DISABLED, BROADCAST_CHANGE );
+
+ /* Broadcast channel deleted event */
+ channel.fireChannelEvent( Event.CHANNEL_DELETED );
+
+ if( playlistManager != null )
+ {
+ playlistManager.save();
+ }
}
}
} );
diff --git a/src/instrument/gui/DecoderViewFrame.java b/src/instrument/gui/DecoderViewFrame.java
index 3a11edc0..08738bdb 100644
--- a/src/instrument/gui/DecoderViewFrame.java
+++ b/src/instrument/gui/DecoderViewFrame.java
@@ -109,7 +109,8 @@ public class DecoderViewFrame extends JInternalFrame
auxConfig,
aliasList,
new System( "Instrumented" ),
- new Site( "Instrumented" ) );
+ new Site( "Instrumented" ),
+ "Channel Name" );
mProcessingChain.addModules( modules );
diff --git a/src/module/decode/DecoderFactory.java b/src/module/decode/DecoderFactory.java
index 9bf12ffb..4eed26cd 100644
--- a/src/module/decode/DecoderFactory.java
+++ b/src/module/decode/DecoderFactory.java
@@ -135,10 +135,11 @@ public class DecoderFactory
AuxDecodeConfiguration auxConfig,
AliasList aliasList,
System system,
- Site site )
+ Site site,
+ String channelName )
{
List modules = getPrimaryModules( channelType, decodeConfig,
- recordConfig, aliasList, system, site, resourceManager );
+ recordConfig, aliasList, system, site, channelName, resourceManager );
modules.addAll( getAuxiliaryDecoders( auxConfig, aliasList ) );
@@ -176,6 +177,7 @@ public class DecoderFactory
AliasList aliasList,
System system,
Site site,
+ String channelName,
ResourceManager resourceManager )
{
List modules = new ArrayList();
@@ -191,12 +193,12 @@ public class DecoderFactory
{
case AM:
modules.add( new AMDecoder( decodeConfig ) );
- modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.AM ) );
+ modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.AM, channelName ) );
break;
case NBFM:
modules.add( new AudioModule( recordAudio, NO_REMOVE_DC ) );
modules.add( new NBFMDecoder( decodeConfig ) );
- modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.NBFM ) );
+ modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.NBFM, channelName ) );
modules.add( getFMDemodulator( decodeConfig, pass, stop, REMOVE_DC ) );
break;
case LTR_STANDARD:
diff --git a/src/module/decode/mpt1327/MPT1327DecoderState.java b/src/module/decode/mpt1327/MPT1327DecoderState.java
index 1f4dac33..ad40aa52 100644
--- a/src/module/decode/mpt1327/MPT1327DecoderState.java
+++ b/src/module/decode/mpt1327/MPT1327DecoderState.java
@@ -180,7 +180,7 @@ public class MPT1327DecoderState extends DecoderState
}
mSite = mpt.getSiteID();
- broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
+ broadcast( new DecoderStateEvent( this, Event.START, State.CONTROL ) );
break;
case GTC:
String from = mpt.getFromID();
diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java
index a3389f7e..bd68ff3f 100644
--- a/src/module/decode/p25/audio/P25AudioModule.java
+++ b/src/module/decode/p25/audio/P25AudioModule.java
@@ -133,29 +133,37 @@ public class P25AudioModule extends Module implements Listener,
Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
library = (AudioConversionLibrary)temp.newInstance();
-
- mAudioConverter = library.getAudioConverter( IMBE_CODEC,
- AudioFormats.PCM_SIGNED_48KHZ_16BITS );
- if( mAudioConverter != null )
+ if( ( library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 ) ||
+ library.getMajorVersion() > 0 )
{
- mCanConvertAudio = true;
+ mAudioConverter = library.getAudioConverter( IMBE_CODEC,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
- if( !mLibraryLoadStatusLogged )
+ if( mAudioConverter != null )
{
- mLog.info( "JMBE audio conversion library successfully loaded"
- + " - P25 audio will be available" );
+ mCanConvertAudio = true;
- mLibraryLoadStatusLogged = true;
+ if( !mLibraryLoadStatusLogged )
+ {
+ mLog.info( "JMBE audio conversion library successfully loaded"
+ + " - P25 audio will be available" );
+
+ mLibraryLoadStatusLogged = true;
+ }
+ }
+ else
+ {
+ if( !mLibraryLoadStatusLogged )
+ {
+ mLog.info( "JMBE audio conversion library NOT FOUND" );
+ mLibraryLoadStatusLogged = true;
+ }
}
}
else
{
- if( !mLibraryLoadStatusLogged )
- {
- mLog.info( "JMBE audio conversion library NOT FOUND" );
- mLibraryLoadStatusLogged = true;
- }
+ mLog.debug( "JMBE library version 0.3.x or higher is required" );
}
}
catch ( ClassNotFoundException e1 )
diff --git a/src/module/decode/state/AlwaysUnsquelchedDecoderState.java b/src/module/decode/state/AlwaysUnsquelchedDecoderState.java
index a93e1090..ad74e2f8 100644
--- a/src/module/decode/state/AlwaysUnsquelchedDecoderState.java
+++ b/src/module/decode/state/AlwaysUnsquelchedDecoderState.java
@@ -24,6 +24,9 @@ import module.decode.state.DecoderStateEvent.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import audio.metadata.Metadata;
+import audio.metadata.MetadataType;
+
/**
* Basic decoder channel state - provides the minimum channel state functionality
* to support an always un-squelched audio decoder.
@@ -34,12 +37,14 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
LoggerFactory.getLogger( AlwaysUnsquelchedDecoderState.class );
private DecoderType mDecoderType;
+ private String mChannelName;
- public AlwaysUnsquelchedDecoderState( DecoderType decoderType )
+ public AlwaysUnsquelchedDecoderState( DecoderType decoderType, String channelName )
{
super( null );
mDecoderType = decoderType;
+ mChannelName = channelName;
}
@Override
@@ -87,6 +92,7 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
@Override
public void start()
{
+ broadcast( new Metadata( MetadataType.TO, mChannelName, false ) );
broadcast( new DecoderStateEvent( this, Event.ALWAYS_UNSQUELCH, State.IDLE ) );
}
diff --git a/src/settings/SettingsManager.java b/src/settings/SettingsManager.java
index 838f3df1..8d8b31a6 100644
--- a/src/settings/SettingsManager.java
+++ b/src/settings/SettingsManager.java
@@ -434,7 +434,7 @@ public class SettingsManager
return null;
}
- private ImageIcon getScaledIcon( ImageIcon original, int height )
+ public static ImageIcon getScaledIcon( ImageIcon original, int height )
{
double scale = (double)original.getIconHeight() / (double)height;
diff --git a/src/source/mixer/MixerChannel.java b/src/source/mixer/MixerChannel.java
index 7b45ae38..9c6ff309 100644
--- a/src/source/mixer/MixerChannel.java
+++ b/src/source/mixer/MixerChannel.java
@@ -17,7 +17,40 @@
******************************************************************************/
package source.mixer;
+import java.util.EnumSet;
+
public enum MixerChannel
{
- LEFT,RIGHT,MONO;
+ LEFT( "L" ),
+ RIGHT( "R" ),
+ MONO( "M" ),
+ STEREO( "S" );
+
+ 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 getTargetChannels()
+ {
+ return EnumSet.of( LEFT,RIGHT,MONO );
+ }
+
+ /**
+ * Mixer Source Data Line (ie sound card output) channel options
+ */
+ public static EnumSet getSourceChannels()
+ {
+ return EnumSet.of( MONO,STEREO );
+ }
}
diff --git a/src/source/mixer/MixerChannelConfiguration.java b/src/source/mixer/MixerChannelConfiguration.java
new file mode 100644
index 00000000..0be3b358
--- /dev/null
+++ b/src/source/mixer/MixerChannelConfiguration.java
@@ -0,0 +1,68 @@
+package 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 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;
+ }
+}
diff --git a/src/source/mixer/MixerEditor.java b/src/source/mixer/MixerEditor.java
index effa7f65..3782213b 100644
--- a/src/source/mixer/MixerEditor.java
+++ b/src/source/mixer/MixerEditor.java
@@ -17,6 +17,8 @@
******************************************************************************/
package source.mixer;
+import java.util.EnumSet;
+
import javax.sound.sampled.Mixer;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
@@ -25,13 +27,13 @@ import javax.swing.JLabel;
import source.SourceEditor;
import source.config.SourceConfigMixer;
import source.config.SourceConfiguration;
-import source.mixer.MixerManager.DiscoveredMixer;
+import source.mixer.MixerManager.InputMixerConfiguration;
import controller.ResourceManager;
public class MixerEditor extends SourceEditor
{
private static final long serialVersionUID = 1L;
- private JComboBox mComboMixers;
+ private JComboBox mComboMixers;
private JComboBoxmComboChannels;
protected Mixer.Info mSelectedMixer = null;
@@ -49,7 +51,7 @@ public class MixerEditor extends SourceEditor
JLabel mixerLabel = new JLabel( "Mixer:" );
add( mixerLabel, "align right" );
- mComboMixers = new JComboBox();
+ mComboMixers = new JComboBox();
add( mComboMixers, "wrap" );
@@ -75,7 +77,7 @@ public class MixerEditor extends SourceEditor
{
SourceConfigMixer config = (SourceConfigMixer)mConfig;
- DiscoveredMixer selectedMixer =
+ InputMixerConfiguration selectedMixer =
mComboMixers.getItemAt( mComboMixers.getSelectedIndex() );
if( selectedMixer != null )
@@ -103,10 +105,10 @@ public class MixerEditor extends SourceEditor
@Override
public void run()
{
- DiscoveredMixer[] mixers = MixerManager.getInstance().getMixers();
+ InputMixerConfiguration[] mixers = MixerManager.getInstance().getInputMixers();
mComboMixers.setModel(
- new DefaultComboBoxModel( mixers ) );
+ new DefaultComboBoxModel( mixers ) );
SourceConfigMixer config = (SourceConfigMixer)mConfig;
@@ -125,8 +127,8 @@ public class MixerEditor extends SourceEditor
{
mComboMixers.setSelectedIndex( 0 );
- DiscoveredMixer selectedMixer =
- (DiscoveredMixer)mComboMixers.getSelectedItem();
+ InputMixerConfiguration selectedMixer =
+ (InputMixerConfiguration)mComboMixers.getSelectedItem();
config.setMixer( selectedMixer.getMixerName() );
}
@@ -142,8 +144,10 @@ public class MixerEditor extends SourceEditor
@Override
public void run()
{
- mComboChannels.setModel(
- new DefaultComboBoxModel( MixerChannel.values() ) );
+ EnumSet channels = MixerChannel.getTargetChannels();
+
+ mComboChannels.setModel( new DefaultComboBoxModel(
+ channels.toArray( new MixerChannel[ channels.size() ] ) ) );
MixerChannel channel = ((SourceConfigMixer)mConfig).getChannel();
diff --git a/src/source/mixer/MixerManager.java b/src/source/mixer/MixerManager.java
index 7217f7d5..7e05e07a 100644
--- a/src/source/mixer/MixerManager.java
+++ b/src/source/mixer/MixerManager.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.List;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
@@ -28,6 +29,8 @@ 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 org.slf4j.Logger;
@@ -39,6 +42,7 @@ import source.config.SourceConfigMixer;
import source.config.SourceConfiguration;
import source.tuner.MixerTunerDataLine;
import source.tuner.MixerTunerType;
+import audio.AudioFormats;
public class MixerManager
{
@@ -47,23 +51,9 @@ public class MixerManager
private static MixerManager sInstance = null;
- private ArrayList mDiscoveredMixers =
- new ArrayList();
-
- private HashMap mMixerTuners =
- new HashMap();
-
- public static AudioFormat MONO = new AudioFormat( 48000, //SampleRate
- 16, //Sample Size
- 1, //Channels
- true, //Signed
- false ); //Little Endian
-
- public static AudioFormat STEREO = new AudioFormat( 48000, //SampleRate
- 16, //Sample Size
- 2, //Channels
- true, //Signed
- false ); //Little Endian
+ private List mInputMixers = new ArrayList<>();
+ private List mOutputMixers = new ArrayList<>();
+ private HashMap mMixerTuners = new HashMap<>();
private MixerManager()
{
@@ -92,7 +82,7 @@ public class MixerManager
if( mixerName != null )
{
- DiscoveredMixer mixer = getDiscoveredMixer( mixerName );
+ InputMixerConfiguration mixer = getInputMixer( mixerName );
if( mixer != null )
{
@@ -104,7 +94,8 @@ public class MixerManager
if( channel == MixerChannel.MONO )
{
DataLine.Info info =
- new DataLine.Info(TargetDataLine.class, MONO );
+ new DataLine.Info(TargetDataLine.class,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
TargetDataLine dataLine;
@@ -116,7 +107,7 @@ public class MixerManager
if( dataLine != null )
{
return new RealMixerSource( dataLine,
- MONO,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO,
mixerName,
new ShortAdapter() );
}
@@ -132,7 +123,8 @@ public class MixerManager
else
{
DataLine.Info info =
- new DataLine.Info(TargetDataLine.class, STEREO );
+ new DataLine.Info(TargetDataLine.class,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO );
TargetDataLine dataLine;
@@ -143,7 +135,8 @@ public class MixerManager
if( dataLine != null )
{
- return new RealMixerSource( dataLine, STEREO,
+ return new RealMixerSource( dataLine,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO,
mixerName, new ChannelShortAdapter(
mixerConfig.getChannel() ) );
}
@@ -163,15 +156,14 @@ public class MixerManager
return null;
}
- public DiscoveredMixer[] getMixers()
+ public InputMixerConfiguration[] getInputMixers()
{
- return mDiscoveredMixers.toArray(
- new DiscoveredMixer[ mDiscoveredMixers.size() ] );
+ return mInputMixers.toArray( new InputMixerConfiguration[ mInputMixers.size() ] );
}
- public DiscoveredMixer getDiscoveredMixer( String name )
+ public InputMixerConfiguration getInputMixer( String name )
{
- for( DiscoveredMixer mixer: mDiscoveredMixers )
+ for( InputMixerConfiguration mixer: mInputMixers )
{
if( mixer.getMixerName().contentEquals( name ) )
{
@@ -181,6 +173,11 @@ public class MixerManager
return null;
}
+
+ public MixerChannelConfiguration[] getOutputMixers()
+ {
+ return mOutputMixers.toArray( new MixerChannelConfiguration[ mOutputMixers.size() ] );
+ }
public Collection getMixerTunerDataLines()
{
@@ -204,21 +201,28 @@ public class MixerManager
if( mixer != null )
{
- EnumSet channels = getSupportedChannels( mixer );
+ EnumSet inputChannels = getSupportedTargetChannels( mixer );
- if( channels != null )
+ if( inputChannels != null )
{
- mDiscoveredMixers.add(
- new DiscoveredMixer( mixer, channels ) );
+ mInputMixers.add(
+ new InputMixerConfiguration( mixer, inputChannels ) );
- sb.append( "\t[LOADED] Mixer:" + mixerInfo.getName() +
- " CHANNELS: " + channels + "\n" );
+ sb.append( "\t[LOADED] Input: " + mixerInfo.getName() +
+ " CHANNELS: " + inputChannels + "\n" );
}
- else
+
+ EnumSet outputChannels = getSupportedSourceChannels( mixer );
+
+ if( outputChannels != null )
{
- sb.append( "\t[NOT LOADED] Mixer:" +
- mixerInfo.getName() +
- " - audio format not supported\n" );
+ for( MixerChannel channel: outputChannels )
+ {
+ mOutputMixers.add( new MixerChannelConfiguration( mixer, channel ) );
+ }
+
+ sb.append( "\t[LOADED] Output: " + mixerInfo.getName() +
+ " CHANNELS: " + outputChannels + "\n" );
}
}
else
@@ -249,10 +253,6 @@ public class MixerManager
break;
}
}
- else
- {
- sb.append( "\t[NOT LOADED] Tuner:" + mixerInfo.getName() + " - couldn't get target data line\n" );
- }
}
}
@@ -288,15 +288,17 @@ public class MixerManager
return retVal;
}
- private EnumSet getSupportedChannels( Mixer mixer )
+ private EnumSet getSupportedTargetChannels( Mixer mixer )
{
DataLine.Info stereoInfo=
- new DataLine.Info(TargetDataLine.class, STEREO );
+ new DataLine.Info(TargetDataLine.class,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO );
boolean stereoSupported = mixer.isLineSupported( stereoInfo );
DataLine.Info monoInfo=
- new DataLine.Info(TargetDataLine.class, MONO );
+ new DataLine.Info(TargetDataLine.class,
+ AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
boolean monoSupported = mixer.isLineSupported( monoInfo );
@@ -317,6 +319,42 @@ public class MixerManager
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 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;
}
@@ -354,17 +392,24 @@ public class MixerManager
"\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 DiscoveredMixer
+ public class InputMixerConfiguration
{
private Mixer mMixer;
private EnumSet mChannels;
- public DiscoveredMixer( Mixer mixer, EnumSet channels )
+ public InputMixerConfiguration( Mixer mixer, EnumSet channels )
{
mMixer = mixer;
mChannels = channels;
@@ -389,10 +434,14 @@ public class MixerManager
{
return mChannels.contains( channel );
}
+ }
+
+ public static void main( String[] args )
+ {
+ mLog.debug( "Starting ..." );
- public String toString()
- {
- return getMixerName();
- }
+ MixerManager.getInstance();
+
+ mLog.debug( "Finished!" );
}
}
diff --git a/src/util/TimeStamp.java b/src/util/TimeStamp.java
index 0985f6cd..94314c12 100644
--- a/src/util/TimeStamp.java
+++ b/src/util/TimeStamp.java
@@ -63,7 +63,7 @@ public class TimeStamp
*/
public static String getFormattedTimeWithMilliseconds( long timestamp )
{
- return mSDFTime.format( new Date( timestamp ) );
+ return mSDFTimeWithMilliseconds.format( new Date( timestamp ) );
}
/**