mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 10:07:26 -05:00
Resolves issue #23.
Adds audio output display and management options supporting mono and stereo output options. Provides audio output supporting prioritization by talkgroup and/or channel selection. Supports muting all channels, or muting individual channels and individual audio gain/volume controls.
This commit is contained in:
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
<!-- version properties -->
|
<!-- version properties -->
|
||||||
<property name="major" value="0"/>
|
<property name="major" value="0"/>
|
||||||
<property name="minor" value="1"/>
|
<property name="minor" value="2"/>
|
||||||
<property name="build" value="5"/>
|
<property name="build" value="0"/>
|
||||||
|
|
||||||
<!-- Build properties -->
|
<!-- Build properties -->
|
||||||
<property name="classes" location="../classes"/>
|
<property name="classes" location="../classes"/>
|
||||||
|
|||||||
BIN
images/audio_muted.png
Normal file
BIN
images/audio_muted.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
BIN
images/audio_unmuted.png
Normal file
BIN
images/audio_unmuted.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
@@ -3,17 +3,10 @@ package audio;
|
|||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.Font;
|
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.JLabel;
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JSeparator;
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
@@ -22,15 +15,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import settings.ColorSetting;
|
import settings.ColorSetting;
|
||||||
|
import settings.ColorSetting.ColorSettingName;
|
||||||
import settings.Setting;
|
import settings.Setting;
|
||||||
import settings.SettingChangeListener;
|
import settings.SettingChangeListener;
|
||||||
import settings.SettingsManager;
|
import settings.SettingsManager;
|
||||||
import settings.ColorSetting.ColorSettingName;
|
|
||||||
import alias.Alias;
|
import alias.Alias;
|
||||||
import audio.AudioEvent.Type;
|
import audio.AudioEvent.Type;
|
||||||
import audio.metadata.AudioMetadata;
|
import audio.metadata.AudioMetadata;
|
||||||
import audio.metadata.Metadata;
|
import audio.metadata.Metadata;
|
||||||
import audio.metadata.MetadataType;
|
import audio.metadata.MetadataType;
|
||||||
|
import audio.output.AudioOutput;
|
||||||
|
|
||||||
public class AudioChannelPanel extends JPanel
|
public class AudioChannelPanel extends JPanel
|
||||||
implements Listener<AudioEvent>, SettingChangeListener
|
implements Listener<AudioEvent>, SettingChangeListener
|
||||||
@@ -44,85 +38,60 @@ public class AudioChannelPanel extends JPanel
|
|||||||
private Color mDetailsColor;
|
private Color mDetailsColor;
|
||||||
|
|
||||||
private SettingsManager mSettingsManager;
|
private SettingsManager mSettingsManager;
|
||||||
private IAudioController mController;
|
private AudioOutput mAudioOutput;
|
||||||
private String mChannel;
|
|
||||||
|
|
||||||
|
private JLabel mChannelName = new JLabel( " " );
|
||||||
private JLabel mToLabel = new JLabel( "TO:" );
|
private JLabel mToLabel = new JLabel( "TO:" );
|
||||||
private JLabel mTo = new JLabel( "" );
|
private JLabel mTo = new JLabel( "" );
|
||||||
private JLabel mToAlias = new JLabel( "" );
|
private JLabel mToAlias = new JLabel( "" );
|
||||||
|
|
||||||
|
private JLabel mMutedLabel = new JLabel( " " );
|
||||||
private JLabel mFromLabel = new JLabel( "FROM:" );
|
private JLabel mFromLabel = new JLabel( "FROM:" );
|
||||||
private JLabel mFrom = new JLabel( "" );
|
private JLabel mFrom = new JLabel( "" );
|
||||||
private JLabel mFromAlias = new JLabel( "" );
|
private JLabel mFromAlias = new JLabel( "" );
|
||||||
|
|
||||||
private JLabel mChannelLabel;
|
|
||||||
private JLabel mChannelName;
|
|
||||||
private JLabel mMutedLabel;
|
|
||||||
|
|
||||||
private boolean mConfigured = false;
|
private boolean mConfigured = false;
|
||||||
|
|
||||||
private BooleanControl mMuteControl;
|
public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput )
|
||||||
private FloatControl mGainControl;
|
|
||||||
|
|
||||||
public AudioChannelPanel( SettingsManager settingsManager,
|
|
||||||
IAudioController controller,
|
|
||||||
String channel )
|
|
||||||
{
|
{
|
||||||
mSettingsManager = settingsManager;
|
mSettingsManager = settingsManager;
|
||||||
mSettingsManager.addListener( this );
|
mSettingsManager.addListener( this );
|
||||||
|
|
||||||
|
mAudioOutput = audioOutput;
|
||||||
|
|
||||||
|
if( mAudioOutput != null )
|
||||||
|
{
|
||||||
|
mAudioOutput.addAudioEventListener( this );
|
||||||
|
mAudioOutput.setAudioMetadataListener( new AudioMetadataProcessor() );
|
||||||
|
}
|
||||||
|
|
||||||
mLabelColor = mSettingsManager.getColorSetting(
|
mLabelColor = mSettingsManager.getColorSetting(
|
||||||
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
|
ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
|
||||||
mDetailsColor = mSettingsManager.getColorSetting(
|
mDetailsColor = mSettingsManager.getColorSetting(
|
||||||
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
|
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();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void 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 );
|
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.setFont( mFont );
|
||||||
mToLabel.setForeground( mLabelColor );
|
if( mAudioOutput != null )
|
||||||
|
{
|
||||||
|
mToLabel.setForeground( mLabelColor );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mToLabel.setForeground( getBackground() );
|
||||||
|
}
|
||||||
add( mToLabel );
|
add( mToLabel );
|
||||||
|
|
||||||
mTo.setFont( mFont );
|
mTo.setFont( mFont );
|
||||||
@@ -133,8 +102,19 @@ public class AudioChannelPanel extends JPanel
|
|||||||
mToAlias.setForeground( mLabelColor );
|
mToAlias.setForeground( mLabelColor );
|
||||||
add( mToAlias,"wrap" );
|
add( mToAlias,"wrap" );
|
||||||
|
|
||||||
|
mMutedLabel.setFont( mFont );
|
||||||
|
mMutedLabel.setForeground( Color.RED );
|
||||||
|
add( mMutedLabel );
|
||||||
|
|
||||||
mFromLabel.setFont( mFont );
|
mFromLabel.setFont( mFont );
|
||||||
mFromLabel.setForeground( mLabelColor );
|
if( mAudioOutput != null )
|
||||||
|
{
|
||||||
|
mFromLabel.setForeground( mLabelColor );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFromLabel.setForeground( getBackground() );
|
||||||
|
}
|
||||||
add( mFromLabel );
|
add( mFromLabel );
|
||||||
|
|
||||||
mFrom.setFont( mFont );
|
mFrom.setFont( mFont );
|
||||||
@@ -144,54 +124,40 @@ public class AudioChannelPanel extends JPanel
|
|||||||
mFromAlias.setFont( mFont );
|
mFromAlias.setFont( mFont );
|
||||||
mFromAlias.setForeground( mLabelColor );
|
mFromAlias.setForeground( mLabelColor );
|
||||||
add( mFromAlias,"wrap" );
|
add( mFromAlias,"wrap" );
|
||||||
|
|
||||||
mChannelLabel = new JLabel( "Channel:" );
|
|
||||||
mChannelLabel.setFont( mFont );
|
|
||||||
mChannelLabel.setForeground( mDetailsColor );
|
|
||||||
|
|
||||||
add( mChannelLabel );
|
JSeparator separator = new JSeparator( JSeparator.HORIZONTAL );
|
||||||
|
separator.setBackground( Color.DARK_GRAY );
|
||||||
mChannelName = new JLabel( mChannel );
|
add( separator, "span,growx" );
|
||||||
mChannelName.setFont( mFont );
|
|
||||||
mChannelName.setForeground( mDetailsColor );
|
|
||||||
|
|
||||||
add( mChannelName );
|
|
||||||
|
|
||||||
mMutedLabel = new JLabel( "" );
|
|
||||||
mMutedLabel.setFont( mFont );
|
|
||||||
mMutedLabel.setForeground( Color.RED );
|
|
||||||
|
|
||||||
add( mMutedLabel, "wrap" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMuteState()
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
EventQueue.invokeLater( new Runnable()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
mMutedLabel.setText( mMuteControl.getValue() ? "Muted" : "" );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive( final AudioEvent audioEvent )
|
public void receive( final AudioEvent audioEvent )
|
||||||
{
|
{
|
||||||
if( audioEvent.getType() == Type.AUDIO_STOPPED )
|
switch( audioEvent.getType() )
|
||||||
{
|
{
|
||||||
EventQueue.invokeLater( new Runnable()
|
case AUDIO_STOPPED:
|
||||||
{
|
EventQueue.invokeLater( new Runnable()
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
{
|
||||||
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
|
* Processes audio metadata to update this panel's display values
|
||||||
|
|||||||
37
src/audio/AudioChannelsPanel.java
Normal file
37
src/audio/AudioChannelsPanel.java
Normal file
@@ -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<AudioOutput> 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" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,13 @@ public class AudioEvent
|
|||||||
|
|
||||||
public enum Type
|
public enum Type
|
||||||
{
|
{
|
||||||
|
AUDIO_MUTED,
|
||||||
|
AUDIO_UNMUTED,
|
||||||
|
AUDIO_SQUELCHED,
|
||||||
|
AUDIO_UNSQUELCHED,
|
||||||
AUDIO_STARTED,
|
AUDIO_STARTED,
|
||||||
AUDIO_STOPPED,
|
AUDIO_STOPPED,
|
||||||
AUDIO_CONFIGURATION_CHANGED;
|
AUDIO_CONFIGURATION_CHANGE_STARTED,
|
||||||
|
AUDIO_CONFIGURATION_CHANGE_COMPLETE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package audio;
|
package audio;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
import javax.sound.sampled.DataLine;
|
||||||
|
import javax.sound.sampled.Line;
|
||||||
|
import javax.sound.sampled.SourceDataLine;
|
||||||
|
|
||||||
public class AudioFormats
|
public class AudioFormats
|
||||||
{
|
{
|
||||||
@@ -18,8 +21,10 @@ public class AudioFormats
|
|||||||
public static final int IMBE_FRAME_SIZE_BYTES = 18;
|
public static final int IMBE_FRAME_SIZE_BYTES = 18;
|
||||||
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
|
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
|
||||||
public static final int ONE_CHANNEL = 1;
|
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_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 =
|
public static AudioFormat IMBE_AUDIO_FORMAT =
|
||||||
new AudioFormat( IMBE_ENCODING,
|
new AudioFormat( IMBE_ENCODING,
|
||||||
@@ -35,16 +40,37 @@ public class AudioFormats
|
|||||||
PCM_8KHZ_RATE,
|
PCM_8KHZ_RATE,
|
||||||
PCM_SAMPLE_SIZE_BITS,
|
PCM_SAMPLE_SIZE_BITS,
|
||||||
ONE_CHANNEL,
|
ONE_CHANNEL,
|
||||||
PCM_FRAME_SIZE_BYTES,
|
PCM_FRAME_SIZE_BYTES_MONO,
|
||||||
PCM_8KHZ_RATE,
|
PCM_8KHZ_RATE,
|
||||||
LITTLE_ENDIAN );
|
LITTLE_ENDIAN );
|
||||||
|
|
||||||
public static AudioFormat PCM_SIGNED_48KHZ_16BITS =
|
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
|
||||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||||
PCM_48KHZ_RATE,
|
PCM_48KHZ_RATE,
|
||||||
PCM_SAMPLE_SIZE_BITS,
|
PCM_SAMPLE_SIZE_BITS,
|
||||||
ONE_CHANNEL,
|
ONE_CHANNEL,
|
||||||
PCM_FRAME_SIZE_BYTES,
|
PCM_FRAME_SIZE_BYTES_MONO,
|
||||||
PCM_48KHZ_RATE,
|
PCM_48KHZ_RATE,
|
||||||
LITTLE_ENDIAN );
|
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 );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package audio;
|
package audio;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -10,16 +12,20 @@ import java.util.concurrent.LinkedTransferQueue;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
import javax.sound.sampled.AudioSystem;
|
||||||
import javax.sound.sampled.FloatControl;
|
import javax.sound.sampled.Mixer;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import sample.Broadcaster;
|
||||||
import sample.Listener;
|
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.AudioOutput;
|
||||||
import audio.output.MonoAudioOutput;
|
import audio.output.MonoAudioOutput;
|
||||||
|
import audio.output.StereoAudioOutput;
|
||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
import controller.ThreadPoolManager.ThreadType;
|
import controller.ThreadPoolManager.ThreadType;
|
||||||
|
|
||||||
@@ -29,23 +35,30 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
|
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
|
||||||
public static final int NO_SOURCE = 0;
|
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<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
|
||||||
private Map<Integer,AudioChannelAssignment> mChannelAssignments = new HashMap<>();
|
private Map<Integer,AudioChannelAssignment> mChannelAssignments = new HashMap<>();
|
||||||
private int mLowestPrioritySource;
|
private int mLowestPrioritySource;
|
||||||
|
|
||||||
private List<AudioOutput> mAvailableAudioOutputs = new ArrayList<>();
|
private List<AudioOutput> mAvailableAudioOutputs = new ArrayList<>();
|
||||||
private Map<String,AudioOutput> mChannelMap = new HashMap<>();
|
private Map<String,AudioOutput> mAudioOutputMap = new HashMap<>();
|
||||||
private List<String> mChannelNames = new ArrayList<>();
|
private List<String> mChannelNames = new ArrayList<>();
|
||||||
private AudioOutput mAudioOutput;
|
|
||||||
|
|
||||||
private Listener<AudioEvent> mConfigurationChangeListener;
|
private Broadcaster<AudioEvent> mControllerBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
private ScheduledFuture<?> mProcessingTask;
|
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
|
* packets to any combination of outputs based on any alias audio routing
|
||||||
* options specified by the user.
|
* options specified by the user.
|
||||||
*/
|
*/
|
||||||
@@ -53,14 +66,24 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
{
|
{
|
||||||
mThreadPoolManager = manager;
|
mThreadPoolManager = manager;
|
||||||
|
|
||||||
mAudioOutput = new MonoAudioOutput();
|
//TODO: save audio system preferences to the settings manager
|
||||||
mAvailableAudioOutputs.add( mAudioOutput );
|
|
||||||
mChannelNames.add( mAudioOutput.getChannelName() );
|
|
||||||
mChannelMap.put( mAudioOutput.getChannelName(), mAudioOutput );
|
|
||||||
|
|
||||||
mProcessingTask = mThreadPoolManager.scheduleFixedRate(
|
/* Use the system default mixer and mono channel as default startup */
|
||||||
ThreadType.AUDIO_PROCESSING, new AudioPacketProcessor(), 15,
|
Mixer mixer = AudioSystem.getMixer( null );
|
||||||
TimeUnit.MILLISECONDS );
|
|
||||||
|
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()
|
public void dispose()
|
||||||
@@ -74,8 +97,6 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
mThreadPoolManager = null;
|
mThreadPoolManager = null;
|
||||||
mProcessingTask = null;
|
mProcessingTask = null;
|
||||||
|
|
||||||
mAudioOutput.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -221,6 +242,14 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void endAllAudioAssignments()
|
||||||
|
{
|
||||||
|
for( AudioOutput output: mAudioOutputMap.values() )
|
||||||
|
{
|
||||||
|
output.receive( new AudioPacket( AudioPacket.Type.END, null ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class AudioChannelAssignment implements Listener<AudioEvent>
|
public class AudioChannelAssignment implements Listener<AudioEvent>
|
||||||
{
|
{
|
||||||
private static final long CALL_END_DELAY = 2000; //2 seconds
|
private static final long CALL_END_DELAY = 2000; //2 seconds
|
||||||
@@ -280,102 +309,91 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getAudioChannels()
|
public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException
|
||||||
{
|
{
|
||||||
return mChannelNames;
|
if( entry != null &&
|
||||||
}
|
( entry.getMixerChannel() == MixerChannel.MONO ||
|
||||||
|
entry.getMixerChannel() == MixerChannel.STEREO ) )
|
||||||
@Override
|
|
||||||
public BooleanControl getMuteControl( String channel )
|
|
||||||
throws AudioException
|
|
||||||
{
|
|
||||||
if( mChannelMap.containsKey( channel ) )
|
|
||||||
{
|
{
|
||||||
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
|
switch( entry.getMixerChannel() )
|
||||||
public boolean hasMuteControl( String channel )
|
{
|
||||||
{
|
case MONO:
|
||||||
return mChannelMap.containsKey( channel ) &&
|
AudioOutput mono = new MonoAudioOutput( entry.getMixer() );
|
||||||
mChannelMap.get( channel ) != null &&
|
mAvailableAudioOutputs.add( mono );
|
||||||
mChannelMap.get( channel ).getMuteControl() != null;
|
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
|
mProcessingTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
public FloatControl getGainControl( String channel ) throws AudioException
|
ThreadType.AUDIO_PROCESSING, new AudioPacketProcessor(), 15,
|
||||||
{
|
TimeUnit.MILLISECONDS );
|
||||||
if( mChannelMap.containsKey( channel ) )
|
|
||||||
{
|
|
||||||
return mChannelMap.get( channel ).getGainControl();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AudioException( "Channel [" + channel + "] is not a valid channel" );
|
mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_COMPLETE );
|
||||||
}
|
|
||||||
|
|
||||||
@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<AudioEvent> listener )
|
|
||||||
{
|
|
||||||
mConfigurationChangeListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAudioEventListener( String channel, Listener<AudioEvent> 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" );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAudioEventListener( String channel, Listener<AudioEvent> listener )
|
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
||||||
{
|
{
|
||||||
if( mChannelMap.containsKey( channel ) )
|
return mMixerChannel;
|
||||||
{
|
|
||||||
mChannelMap.get( channel ).removeAudioEventListener( listener );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAudioMetadataListener( String channel,
|
public List<AudioOutput> getAudioOutputs()
|
||||||
Listener<AudioMetadata> listener )
|
|
||||||
{
|
{
|
||||||
if( mChannelMap.containsKey( channel ) )
|
List<AudioOutput> outputs = new ArrayList<>( mAudioOutputMap.values() );
|
||||||
|
|
||||||
|
Collections.sort( outputs, new Comparator<AudioOutput>()
|
||||||
{
|
{
|
||||||
mChannelMap.get( channel ).setAudioMetadataListener( listener );
|
@Override
|
||||||
}
|
public int compare( AudioOutput first, AudioOutput second )
|
||||||
|
{
|
||||||
|
return first.getChannelName().compareTo( second.getChannelName() );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
return outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAudioMetadataListener( String channel )
|
public void addControllerListener( Listener<AudioEvent> listener )
|
||||||
{
|
{
|
||||||
if( mChannelMap.containsKey( channel ) )
|
mControllerBroadcaster.addListener( listener );
|
||||||
{
|
}
|
||||||
mChannelMap.get( channel ).removeAudioMetadataListener();
|
|
||||||
}
|
@Override
|
||||||
|
public void removeControllerListener( Listener<AudioEvent> listener )
|
||||||
|
{
|
||||||
|
mControllerBroadcaster.removeListener( listener );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,21 @@ import java.awt.Color;
|
|||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
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.JButton;
|
||||||
|
import javax.swing.JMenu;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
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;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
@@ -16,68 +27,329 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import settings.SettingsManager;
|
import settings.SettingsManager;
|
||||||
|
import source.mixer.MixerChannelConfiguration;
|
||||||
|
import source.mixer.MixerManager;
|
||||||
|
import audio.output.AudioOutput;
|
||||||
|
|
||||||
public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private static final Logger mLog = LoggerFactory.getLogger( AudioPanel.class );
|
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 SettingsManager mSettingsManager;
|
||||||
private IAudioController mController;
|
private IAudioController mController;
|
||||||
private MuteButton mMuteButton;
|
|
||||||
|
private JButton mMuteButton;
|
||||||
|
private AudioChannelsPanel mAudioChannelsPanel;
|
||||||
|
|
||||||
public AudioPanel( SettingsManager settingsManager, IAudioController controller )
|
public AudioPanel( SettingsManager settingsManager, IAudioController controller )
|
||||||
{
|
{
|
||||||
mSettingsManager = settingsManager;
|
mSettingsManager = settingsManager;
|
||||||
mController = controller;
|
mController = controller;
|
||||||
|
|
||||||
mMuteButton = new MuteButton( mController );
|
mController.addControllerListener( this );
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void 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 );
|
setBackground( Color.BLACK );
|
||||||
|
|
||||||
add( mMuteButton, "span" );
|
mMuteButton = new MuteButton();
|
||||||
|
mMuteButton.setBackground( getBackground() );
|
||||||
|
add( mMuteButton );
|
||||||
|
|
||||||
for( String channel: mController.getAudioChannels() )
|
mAudioChannelsPanel = new AudioChannelsPanel( mSettingsManager, mController );
|
||||||
{
|
|
||||||
add( new AudioChannelPanel( mSettingsManager, mController, channel ), "span" );
|
add( mAudioChannelsPanel );
|
||||||
}
|
|
||||||
|
addMouseListener( new MouseSelectionListener() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPreferredHeight()
|
|
||||||
{
|
|
||||||
return mMuteButton.getHeight() +
|
|
||||||
( mController.getAudioChannels().size() * 20 );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive( AudioEvent event )
|
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
|
public class MuteButton extends JButton
|
||||||
{
|
{
|
||||||
private IAudioController mController;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private boolean mMuted = false;
|
private boolean mMuted = false;
|
||||||
|
|
||||||
public MuteButton( IAudioController controller )
|
public MuteButton()
|
||||||
{
|
{
|
||||||
super( "Mute Audio" );
|
setIcon( UNMUTED_ICON );
|
||||||
|
setBorderPainted( false );
|
||||||
setBackground( Color.GREEN );
|
|
||||||
|
|
||||||
mController = controller;
|
|
||||||
|
|
||||||
addActionListener( new ActionListener()
|
addActionListener( new ActionListener()
|
||||||
{
|
{
|
||||||
@@ -86,31 +358,17 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
|||||||
{
|
{
|
||||||
mMuted = !mMuted;
|
mMuted = !mMuted;
|
||||||
|
|
||||||
for( String channel: mController.getAudioChannels() )
|
for( AudioOutput output: mController.getAudioOutputs() )
|
||||||
{
|
{
|
||||||
try
|
output.setMuted( mMuted );
|
||||||
{
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EventQueue.invokeLater( new Runnable()
|
EventQueue.invokeLater( new Runnable()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
MuteButton.this.setText( ( mMuted ? "Unmute Audio" : "Mute Audio" ) );
|
setIcon( mMuted ? MUTED_ICON : UNMUTED_ICON );
|
||||||
MuteButton.this.setBackground( ( mMuted ? Color.RED : Color.GREEN ) );
|
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,35 +2,23 @@ package audio;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
|
||||||
import javax.sound.sampled.FloatControl;
|
|
||||||
|
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import audio.metadata.AudioMetadata;
|
import source.mixer.MixerChannelConfiguration;
|
||||||
|
import audio.output.AudioOutput;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for controlling one or more audio channels
|
* Interface for controlling one or more audio channels
|
||||||
*/
|
*/
|
||||||
public interface IAudioController
|
public interface IAudioController
|
||||||
{
|
{
|
||||||
public List<String> 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;
|
/* Audio Output(s) */
|
||||||
|
public List<AudioOutput> getAudioOutputs();
|
||||||
public boolean hasMuteControl( String channel );
|
|
||||||
|
/* Controller Audio Event Listener */
|
||||||
public FloatControl getGainControl( String channel ) throws AudioException;
|
public void addControllerListener( Listener<AudioEvent> listener );
|
||||||
|
public void removeControllerListener( Listener<AudioEvent> listener );
|
||||||
public boolean hasGainControl( String channel );
|
|
||||||
|
|
||||||
public void addAudioEventListener( String channel, Listener<AudioEvent> listener )
|
|
||||||
throws AudioException;
|
|
||||||
|
|
||||||
public void removeAudioEventListener( String channel, Listener<AudioEvent> listener );
|
|
||||||
|
|
||||||
public void setAudioMetadataListener( String channel, Listener<AudioMetadata> listener );
|
|
||||||
|
|
||||||
public void removeAudioMetadataListener( String channel );
|
|
||||||
|
|
||||||
public void setConfigurationChangeListener( Listener<AudioEvent> listener );
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package audio.output;
|
package audio.output;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
import javax.sound.sampled.BooleanControl;
|
||||||
@@ -11,6 +13,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import sample.Broadcaster;
|
import sample.Broadcaster;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import audio.AudioEvent;
|
import audio.AudioEvent;
|
||||||
|
import audio.AudioEvent.Type;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.metadata.AudioMetadata;
|
import audio.metadata.AudioMetadata;
|
||||||
|
|
||||||
@@ -20,10 +23,24 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
|
|
||||||
private AtomicBoolean mSquelched = new AtomicBoolean( false );
|
private AtomicBoolean mSquelched = new AtomicBoolean( false );
|
||||||
private Listener<AudioMetadata> mAudioMetadataListener;
|
private Listener<AudioMetadata> mAudioMetadataListener;
|
||||||
|
|
||||||
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||||
|
protected LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
||||||
|
protected ScheduledExecutorService mExecutorService;
|
||||||
|
protected boolean mCanProcessAudio;
|
||||||
|
protected long mLastBufferReceived;
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
|
mCanProcessAudio = false;
|
||||||
|
|
||||||
|
mBuffer.clear();
|
||||||
|
|
||||||
|
if( mExecutorService != null )
|
||||||
|
{
|
||||||
|
mExecutorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
mAudioEventBroadcaster.dispose();
|
mAudioEventBroadcaster.dispose();
|
||||||
mAudioEventBroadcaster = null;
|
mAudioEventBroadcaster = null;
|
||||||
mAudioMetadataListener = null;
|
mAudioMetadataListener = null;
|
||||||
@@ -31,8 +48,8 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
|
|
||||||
public abstract String getChannelName();
|
public abstract String getChannelName();
|
||||||
|
|
||||||
public abstract BooleanControl getMuteControl();
|
public abstract void setMuted( boolean muted );
|
||||||
public abstract boolean hasMuteControl();
|
public abstract boolean isMuted();
|
||||||
|
|
||||||
public abstract FloatControl getGainControl();
|
public abstract FloatControl getGainControl();
|
||||||
public abstract boolean hasGainControl();
|
public abstract boolean hasGainControl();
|
||||||
@@ -45,9 +62,17 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void broadcast( AudioEvent audioEvent )
|
||||||
|
{
|
||||||
|
mAudioEventBroadcaster.broadcast( audioEvent );
|
||||||
|
}
|
||||||
|
|
||||||
public void setSquelched( boolean squelched )
|
public void setSquelched( boolean squelched )
|
||||||
{
|
{
|
||||||
mSquelched.set( squelched );
|
mSquelched.set( squelched );
|
||||||
|
|
||||||
|
broadcast( new AudioEvent( squelched ? Type.AUDIO_SQUELCHED :
|
||||||
|
Type.AUDIO_UNSQUELCHED, getChannelName() ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSquelched()
|
public boolean isSquelched()
|
||||||
@@ -81,4 +106,23 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
{
|
{
|
||||||
mAudioMetadataListener = null;
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,19 @@ import java.nio.ShortBuffer;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioSystem;
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
import javax.sound.sampled.BooleanControl;
|
||||||
import javax.sound.sampled.FloatControl;
|
import javax.sound.sampled.FloatControl;
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.sound.sampled.SourceDataLine;
|
import javax.sound.sampled.SourceDataLine;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import source.mixer.MixerChannel;
|
||||||
import audio.AudioEvent;
|
import audio.AudioEvent;
|
||||||
import audio.AudioFormats;
|
import audio.AudioFormats;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
@@ -27,14 +26,14 @@ import audio.AudioPacket.Type;
|
|||||||
import controller.NamingThreadFactory;
|
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.
|
* audio data packets.
|
||||||
*/
|
*/
|
||||||
public class MonoAudioOutput extends AudioOutput
|
public class MonoAudioOutput extends AudioOutput
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( MonoAudioOutput.class );
|
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 */
|
* two bytes per sample */
|
||||||
private final static int BUFFER_SIZE = 48000;
|
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
|
* 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
|
* 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 */
|
* 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 START_THRESHOLD = SAMPLE_SIZE_40_MS;
|
||||||
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
|
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
|
||||||
|
|
||||||
private LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
|
||||||
|
|
||||||
private ScheduledExecutorService mExecutorService;
|
|
||||||
|
|
||||||
private SourceDataLine mOutput;
|
private SourceDataLine mOutput;
|
||||||
private FloatControl mGainControl;
|
private FloatControl mGainControl;
|
||||||
private BooleanControl mMuteControl;
|
private BooleanControl mMuteControl;
|
||||||
|
|
||||||
private boolean mCanProcessAudio;
|
|
||||||
|
|
||||||
private long mLastBufferReceived;
|
|
||||||
|
|
||||||
private AudioEvent mAudioStartEvent;
|
private AudioEvent mAudioStartEvent;
|
||||||
private AudioEvent mAudioStopEvent;
|
private AudioEvent mAudioStopEvent;
|
||||||
|
|
||||||
public MonoAudioOutput()
|
public MonoAudioOutput( Mixer mixer )
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED, getChannelName() );
|
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
||||||
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, getChannelName() );
|
getChannelName() );
|
||||||
|
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
||||||
|
getChannelName() );
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mOutput = AudioSystem.getSourceDataLine(
|
mOutput = (SourceDataLine)mixer.getLine(
|
||||||
AudioFormats.PCM_SIGNED_48KHZ_16BITS );
|
AudioFormats.MONO_SOURCE_DATALINE_INFO );
|
||||||
|
|
||||||
if( mOutput != null )
|
if( mOutput != null )
|
||||||
{
|
{
|
||||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS, BUFFER_SIZE );
|
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
|
||||||
|
|
||||||
mCanProcessAudio = true;
|
mCanProcessAudio = true;
|
||||||
|
|
||||||
@@ -103,47 +96,18 @@ public class MonoAudioOutput extends AudioOutput
|
|||||||
@Override
|
@Override
|
||||||
public String getChannelName()
|
public String getChannelName()
|
||||||
{
|
{
|
||||||
return "Mono";
|
return MixerChannel.MONO.getLabel();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timestamp when most recent buffer was received
|
|
||||||
*/
|
|
||||||
public long getLastBufferReceived()
|
|
||||||
{
|
|
||||||
return mLastBufferReceived;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
||||||
mCanProcessAudio = false;
|
|
||||||
|
|
||||||
mBuffer.clear();
|
|
||||||
|
|
||||||
if( mExecutorService != null )
|
|
||||||
{
|
|
||||||
mExecutorService.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mOutput != null )
|
if( mOutput != null )
|
||||||
{
|
{
|
||||||
mOutput.close();
|
mOutput.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive( AudioPacket packet )
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio )
|
|
||||||
{
|
|
||||||
mLastBufferReceived = System.currentTimeMillis();
|
|
||||||
|
|
||||||
mBuffer.add( packet );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class QueueProcessor implements Runnable
|
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
|
@Override
|
||||||
public BooleanControl getMuteControl()
|
public boolean isMuted()
|
||||||
{
|
{
|
||||||
return mMuteControl;
|
if( mMuteControl != null )
|
||||||
}
|
{
|
||||||
|
return mMuteControl.getValue();
|
||||||
@Override
|
}
|
||||||
public boolean hasMuteControl()
|
|
||||||
{
|
return false;
|
||||||
return mMuteControl != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
259
src/audio/output/StereoAudioOutput.java
Normal file
259
src/audio/output/StereoAudioOutput.java
Normal file
@@ -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<AudioPacket> packets = new ArrayList<AudioPacket>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -154,7 +154,6 @@ public class ControllerPanel extends JPanel
|
|||||||
|
|
||||||
AudioPanel audioPanel = new AudioPanel( mResourceManager.getSettingsManager(),
|
AudioPanel audioPanel = new AudioPanel( mResourceManager.getSettingsManager(),
|
||||||
mResourceManager.getAudioManager() );
|
mResourceManager.getAudioManager() );
|
||||||
audioPanel.setPreferredSize( new Dimension( 200, audioPanel.getPreferredHeight() ) );
|
|
||||||
|
|
||||||
JideSplitPane audioChannelListSplit = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT );
|
JideSplitPane audioChannelListSplit = new JideSplitPane( JideSplitPane.VERTICAL_SPLIT );
|
||||||
audioChannelListSplit.setDividerSize( 5 );
|
audioChannelListSplit.setDividerSize( 5 );
|
||||||
|
|||||||
@@ -824,7 +824,7 @@ public class Channel extends Configuration
|
|||||||
List<Module> modules = DecoderFactory.getModules( mChannelType,
|
List<Module> modules = DecoderFactory.getModules( mChannelType,
|
||||||
mResourceManager, mDecodeConfiguration, mRecordConfiguration,
|
mResourceManager, mDecodeConfiguration, mRecordConfiguration,
|
||||||
mAuxDecodeConfiguration, aliasList, mSystem,
|
mAuxDecodeConfiguration, aliasList, mSystem,
|
||||||
mSite );
|
mSite, mName );
|
||||||
|
|
||||||
mProcessingChain.addModules( modules );
|
mProcessingChain.addModules( modules );
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.awt.event.ActionListener;
|
|||||||
|
|
||||||
import javax.swing.JMenu;
|
import javax.swing.JMenu;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JSeparator;
|
import javax.swing.JSeparator;
|
||||||
|
|
||||||
import module.decode.event.ActivitySummaryFrame;
|
import module.decode.event.ActivitySummaryFrame;
|
||||||
@@ -101,15 +102,22 @@ public class ChannelUtils
|
|||||||
@Override
|
@Override
|
||||||
public void actionPerformed( ActionEvent e )
|
public void actionPerformed( ActionEvent e )
|
||||||
{
|
{
|
||||||
/* Disable the channel */
|
int response = JOptionPane.showConfirmDialog( anchor,
|
||||||
channel.setEnabled( DISABLED, BROADCAST_CHANGE );
|
"Do you want to delete channel " + channel.getName() + "?",
|
||||||
|
"Are you sure?", JOptionPane.YES_NO_CANCEL_OPTION );
|
||||||
/* Broadcast channel deleted event */
|
|
||||||
channel.fireChannelEvent( Event.CHANNEL_DELETED );
|
if( response == JOptionPane.YES_OPTION )
|
||||||
|
|
||||||
if( playlistManager != null )
|
|
||||||
{
|
{
|
||||||
playlistManager.save();
|
/* Disable the channel */
|
||||||
|
channel.setEnabled( DISABLED, BROADCAST_CHANGE );
|
||||||
|
|
||||||
|
/* Broadcast channel deleted event */
|
||||||
|
channel.fireChannelEvent( Event.CHANNEL_DELETED );
|
||||||
|
|
||||||
|
if( playlistManager != null )
|
||||||
|
{
|
||||||
|
playlistManager.save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ public class DecoderViewFrame extends JInternalFrame
|
|||||||
auxConfig,
|
auxConfig,
|
||||||
aliasList,
|
aliasList,
|
||||||
new System( "Instrumented" ),
|
new System( "Instrumented" ),
|
||||||
new Site( "Instrumented" ) );
|
new Site( "Instrumented" ),
|
||||||
|
"Channel Name" );
|
||||||
|
|
||||||
mProcessingChain.addModules( modules );
|
mProcessingChain.addModules( modules );
|
||||||
|
|
||||||
|
|||||||
@@ -135,10 +135,11 @@ public class DecoderFactory
|
|||||||
AuxDecodeConfiguration auxConfig,
|
AuxDecodeConfiguration auxConfig,
|
||||||
AliasList aliasList,
|
AliasList aliasList,
|
||||||
System system,
|
System system,
|
||||||
Site site )
|
Site site,
|
||||||
|
String channelName )
|
||||||
{
|
{
|
||||||
List<Module> modules = getPrimaryModules( channelType, decodeConfig,
|
List<Module> modules = getPrimaryModules( channelType, decodeConfig,
|
||||||
recordConfig, aliasList, system, site, resourceManager );
|
recordConfig, aliasList, system, site, channelName, resourceManager );
|
||||||
|
|
||||||
modules.addAll( getAuxiliaryDecoders( auxConfig, aliasList ) );
|
modules.addAll( getAuxiliaryDecoders( auxConfig, aliasList ) );
|
||||||
|
|
||||||
@@ -176,6 +177,7 @@ public class DecoderFactory
|
|||||||
AliasList aliasList,
|
AliasList aliasList,
|
||||||
System system,
|
System system,
|
||||||
Site site,
|
Site site,
|
||||||
|
String channelName,
|
||||||
ResourceManager resourceManager )
|
ResourceManager resourceManager )
|
||||||
{
|
{
|
||||||
List<Module> modules = new ArrayList<Module>();
|
List<Module> modules = new ArrayList<Module>();
|
||||||
@@ -191,12 +193,12 @@ public class DecoderFactory
|
|||||||
{
|
{
|
||||||
case AM:
|
case AM:
|
||||||
modules.add( new AMDecoder( decodeConfig ) );
|
modules.add( new AMDecoder( decodeConfig ) );
|
||||||
modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.AM ) );
|
modules.add( new AlwaysUnsquelchedDecoderState( DecoderType.AM, channelName ) );
|
||||||
break;
|
break;
|
||||||
case NBFM:
|
case NBFM:
|
||||||
modules.add( new AudioModule( recordAudio, NO_REMOVE_DC ) );
|
modules.add( new AudioModule( recordAudio, NO_REMOVE_DC ) );
|
||||||
modules.add( new NBFMDecoder( decodeConfig ) );
|
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 ) );
|
modules.add( getFMDemodulator( decodeConfig, pass, stop, REMOVE_DC ) );
|
||||||
break;
|
break;
|
||||||
case LTR_STANDARD:
|
case LTR_STANDARD:
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ public class MPT1327DecoderState extends DecoderState
|
|||||||
}
|
}
|
||||||
mSite = mpt.getSiteID();
|
mSite = mpt.getSiteID();
|
||||||
|
|
||||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
broadcast( new DecoderStateEvent( this, Event.START, State.CONTROL ) );
|
||||||
break;
|
break;
|
||||||
case GTC:
|
case GTC:
|
||||||
String from = mpt.getFromID();
|
String from = mpt.getFromID();
|
||||||
|
|||||||
@@ -133,29 +133,37 @@ public class P25AudioModule extends Module implements Listener<Message>,
|
|||||||
Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
|
Class temp = Class.forName( "jmbe.JMBEAudioLibrary" );
|
||||||
|
|
||||||
library = (AudioConversionLibrary)temp.newInstance();
|
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"
|
mCanConvertAudio = true;
|
||||||
+ " - P25 audio will be available" );
|
|
||||||
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
if( !mLibraryLoadStatusLogged )
|
mLog.debug( "JMBE library version 0.3.x or higher is required" );
|
||||||
{
|
|
||||||
mLog.info( "JMBE audio conversion library NOT FOUND" );
|
|
||||||
mLibraryLoadStatusLogged = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( ClassNotFoundException e1 )
|
catch ( ClassNotFoundException e1 )
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import module.decode.state.DecoderStateEvent.Event;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import audio.metadata.Metadata;
|
||||||
|
import audio.metadata.MetadataType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic decoder channel state - provides the minimum channel state functionality
|
* Basic decoder channel state - provides the minimum channel state functionality
|
||||||
* to support an always un-squelched audio decoder.
|
* to support an always un-squelched audio decoder.
|
||||||
@@ -34,12 +37,14 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
|
|||||||
LoggerFactory.getLogger( AlwaysUnsquelchedDecoderState.class );
|
LoggerFactory.getLogger( AlwaysUnsquelchedDecoderState.class );
|
||||||
|
|
||||||
private DecoderType mDecoderType;
|
private DecoderType mDecoderType;
|
||||||
|
private String mChannelName;
|
||||||
|
|
||||||
public AlwaysUnsquelchedDecoderState( DecoderType decoderType )
|
public AlwaysUnsquelchedDecoderState( DecoderType decoderType, String channelName )
|
||||||
{
|
{
|
||||||
super( null );
|
super( null );
|
||||||
|
|
||||||
mDecoderType = decoderType;
|
mDecoderType = decoderType;
|
||||||
|
mChannelName = channelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -87,6 +92,7 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
|
|||||||
@Override
|
@Override
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
|
broadcast( new Metadata( MetadataType.TO, mChannelName, false ) );
|
||||||
broadcast( new DecoderStateEvent( this, Event.ALWAYS_UNSQUELCH, State.IDLE ) );
|
broadcast( new DecoderStateEvent( this, Event.ALWAYS_UNSQUELCH, State.IDLE ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ public class SettingsManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageIcon getScaledIcon( ImageIcon original, int height )
|
public static ImageIcon getScaledIcon( ImageIcon original, int height )
|
||||||
{
|
{
|
||||||
double scale = (double)original.getIconHeight() / (double)height;
|
double scale = (double)original.getIconHeight() / (double)height;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,40 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package source.mixer;
|
package source.mixer;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
public enum MixerChannel
|
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<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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
src/source/mixer/MixerChannelConfiguration.java
Normal file
68
src/source/mixer/MixerChannelConfiguration.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package source.mixer;
|
package source.mixer;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.swing.DefaultComboBoxModel;
|
import javax.swing.DefaultComboBoxModel;
|
||||||
import javax.swing.JComboBox;
|
import javax.swing.JComboBox;
|
||||||
@@ -25,13 +27,13 @@ import javax.swing.JLabel;
|
|||||||
import source.SourceEditor;
|
import source.SourceEditor;
|
||||||
import source.config.SourceConfigMixer;
|
import source.config.SourceConfigMixer;
|
||||||
import source.config.SourceConfiguration;
|
import source.config.SourceConfiguration;
|
||||||
import source.mixer.MixerManager.DiscoveredMixer;
|
import source.mixer.MixerManager.InputMixerConfiguration;
|
||||||
import controller.ResourceManager;
|
import controller.ResourceManager;
|
||||||
|
|
||||||
public class MixerEditor extends SourceEditor
|
public class MixerEditor extends SourceEditor
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private JComboBox<DiscoveredMixer> mComboMixers;
|
private JComboBox<InputMixerConfiguration> mComboMixers;
|
||||||
private JComboBox<MixerChannel>mComboChannels;
|
private JComboBox<MixerChannel>mComboChannels;
|
||||||
protected Mixer.Info mSelectedMixer = null;
|
protected Mixer.Info mSelectedMixer = null;
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ public class MixerEditor extends SourceEditor
|
|||||||
JLabel mixerLabel = new JLabel( "Mixer:" );
|
JLabel mixerLabel = new JLabel( "Mixer:" );
|
||||||
add( mixerLabel, "align right" );
|
add( mixerLabel, "align right" );
|
||||||
|
|
||||||
mComboMixers = new JComboBox<DiscoveredMixer>();
|
mComboMixers = new JComboBox<InputMixerConfiguration>();
|
||||||
|
|
||||||
add( mComboMixers, "wrap" );
|
add( mComboMixers, "wrap" );
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ public class MixerEditor extends SourceEditor
|
|||||||
{
|
{
|
||||||
SourceConfigMixer config = (SourceConfigMixer)mConfig;
|
SourceConfigMixer config = (SourceConfigMixer)mConfig;
|
||||||
|
|
||||||
DiscoveredMixer selectedMixer =
|
InputMixerConfiguration selectedMixer =
|
||||||
mComboMixers.getItemAt( mComboMixers.getSelectedIndex() );
|
mComboMixers.getItemAt( mComboMixers.getSelectedIndex() );
|
||||||
|
|
||||||
if( selectedMixer != null )
|
if( selectedMixer != null )
|
||||||
@@ -103,10 +105,10 @@ public class MixerEditor extends SourceEditor
|
|||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
DiscoveredMixer[] mixers = MixerManager.getInstance().getMixers();
|
InputMixerConfiguration[] mixers = MixerManager.getInstance().getInputMixers();
|
||||||
|
|
||||||
mComboMixers.setModel(
|
mComboMixers.setModel(
|
||||||
new DefaultComboBoxModel<DiscoveredMixer>( mixers ) );
|
new DefaultComboBoxModel<InputMixerConfiguration>( mixers ) );
|
||||||
|
|
||||||
SourceConfigMixer config = (SourceConfigMixer)mConfig;
|
SourceConfigMixer config = (SourceConfigMixer)mConfig;
|
||||||
|
|
||||||
@@ -125,8 +127,8 @@ public class MixerEditor extends SourceEditor
|
|||||||
{
|
{
|
||||||
mComboMixers.setSelectedIndex( 0 );
|
mComboMixers.setSelectedIndex( 0 );
|
||||||
|
|
||||||
DiscoveredMixer selectedMixer =
|
InputMixerConfiguration selectedMixer =
|
||||||
(DiscoveredMixer)mComboMixers.getSelectedItem();
|
(InputMixerConfiguration)mComboMixers.getSelectedItem();
|
||||||
|
|
||||||
config.setMixer( selectedMixer.getMixerName() );
|
config.setMixer( selectedMixer.getMixerName() );
|
||||||
}
|
}
|
||||||
@@ -142,8 +144,10 @@ public class MixerEditor extends SourceEditor
|
|||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
mComboChannels.setModel(
|
EnumSet<MixerChannel> channels = MixerChannel.getTargetChannels();
|
||||||
new DefaultComboBoxModel<MixerChannel>( MixerChannel.values() ) );
|
|
||||||
|
mComboChannels.setModel( new DefaultComboBoxModel<MixerChannel>(
|
||||||
|
channels.toArray( new MixerChannel[ channels.size() ] ) ) );
|
||||||
|
|
||||||
MixerChannel channel = ((SourceConfigMixer)mConfig).getChannel();
|
MixerChannel channel = ((SourceConfigMixer)mConfig).getChannel();
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
import javax.sound.sampled.AudioSystem;
|
import javax.sound.sampled.AudioSystem;
|
||||||
@@ -28,6 +29,8 @@ import javax.sound.sampled.DataLine;
|
|||||||
import javax.sound.sampled.Line;
|
import javax.sound.sampled.Line;
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
|
import javax.sound.sampled.Port;
|
||||||
|
import javax.sound.sampled.SourceDataLine;
|
||||||
import javax.sound.sampled.TargetDataLine;
|
import javax.sound.sampled.TargetDataLine;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -39,6 +42,7 @@ import source.config.SourceConfigMixer;
|
|||||||
import source.config.SourceConfiguration;
|
import source.config.SourceConfiguration;
|
||||||
import source.tuner.MixerTunerDataLine;
|
import source.tuner.MixerTunerDataLine;
|
||||||
import source.tuner.MixerTunerType;
|
import source.tuner.MixerTunerType;
|
||||||
|
import audio.AudioFormats;
|
||||||
|
|
||||||
public class MixerManager
|
public class MixerManager
|
||||||
{
|
{
|
||||||
@@ -47,23 +51,9 @@ public class MixerManager
|
|||||||
|
|
||||||
private static MixerManager sInstance = null;
|
private static MixerManager sInstance = null;
|
||||||
|
|
||||||
private ArrayList<DiscoveredMixer> mDiscoveredMixers =
|
private List<InputMixerConfiguration> mInputMixers = new ArrayList<>();
|
||||||
new ArrayList<DiscoveredMixer>();
|
private List<MixerChannelConfiguration> mOutputMixers = new ArrayList<>();
|
||||||
|
private HashMap<String,MixerTunerDataLine> mMixerTuners = new HashMap<>();
|
||||||
private HashMap<String,MixerTunerDataLine> mMixerTuners =
|
|
||||||
new HashMap<String,MixerTunerDataLine>();
|
|
||||||
|
|
||||||
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 MixerManager()
|
private MixerManager()
|
||||||
{
|
{
|
||||||
@@ -92,7 +82,7 @@ public class MixerManager
|
|||||||
|
|
||||||
if( mixerName != null )
|
if( mixerName != null )
|
||||||
{
|
{
|
||||||
DiscoveredMixer mixer = getDiscoveredMixer( mixerName );
|
InputMixerConfiguration mixer = getInputMixer( mixerName );
|
||||||
|
|
||||||
if( mixer != null )
|
if( mixer != null )
|
||||||
{
|
{
|
||||||
@@ -104,7 +94,8 @@ public class MixerManager
|
|||||||
if( channel == MixerChannel.MONO )
|
if( channel == MixerChannel.MONO )
|
||||||
{
|
{
|
||||||
DataLine.Info info =
|
DataLine.Info info =
|
||||||
new DataLine.Info(TargetDataLine.class, MONO );
|
new DataLine.Info(TargetDataLine.class,
|
||||||
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO );
|
||||||
|
|
||||||
TargetDataLine dataLine;
|
TargetDataLine dataLine;
|
||||||
|
|
||||||
@@ -116,7 +107,7 @@ public class MixerManager
|
|||||||
if( dataLine != null )
|
if( dataLine != null )
|
||||||
{
|
{
|
||||||
return new RealMixerSource( dataLine,
|
return new RealMixerSource( dataLine,
|
||||||
MONO,
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO,
|
||||||
mixerName,
|
mixerName,
|
||||||
new ShortAdapter() );
|
new ShortAdapter() );
|
||||||
}
|
}
|
||||||
@@ -132,7 +123,8 @@ public class MixerManager
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
DataLine.Info info =
|
DataLine.Info info =
|
||||||
new DataLine.Info(TargetDataLine.class, STEREO );
|
new DataLine.Info(TargetDataLine.class,
|
||||||
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO );
|
||||||
|
|
||||||
TargetDataLine dataLine;
|
TargetDataLine dataLine;
|
||||||
|
|
||||||
@@ -143,7 +135,8 @@ public class MixerManager
|
|||||||
|
|
||||||
if( dataLine != null )
|
if( dataLine != null )
|
||||||
{
|
{
|
||||||
return new RealMixerSource( dataLine, STEREO,
|
return new RealMixerSource( dataLine,
|
||||||
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO,
|
||||||
mixerName, new ChannelShortAdapter(
|
mixerName, new ChannelShortAdapter(
|
||||||
mixerConfig.getChannel() ) );
|
mixerConfig.getChannel() ) );
|
||||||
}
|
}
|
||||||
@@ -163,15 +156,14 @@ public class MixerManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DiscoveredMixer[] getMixers()
|
public InputMixerConfiguration[] getInputMixers()
|
||||||
{
|
{
|
||||||
return mDiscoveredMixers.toArray(
|
return mInputMixers.toArray( new InputMixerConfiguration[ mInputMixers.size() ] );
|
||||||
new DiscoveredMixer[ mDiscoveredMixers.size() ] );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DiscoveredMixer getDiscoveredMixer( String name )
|
public InputMixerConfiguration getInputMixer( String name )
|
||||||
{
|
{
|
||||||
for( DiscoveredMixer mixer: mDiscoveredMixers )
|
for( InputMixerConfiguration mixer: mInputMixers )
|
||||||
{
|
{
|
||||||
if( mixer.getMixerName().contentEquals( name ) )
|
if( mixer.getMixerName().contentEquals( name ) )
|
||||||
{
|
{
|
||||||
@@ -181,6 +173,11 @@ public class MixerManager
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MixerChannelConfiguration[] getOutputMixers()
|
||||||
|
{
|
||||||
|
return mOutputMixers.toArray( new MixerChannelConfiguration[ mOutputMixers.size() ] );
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<MixerTunerDataLine> getMixerTunerDataLines()
|
public Collection<MixerTunerDataLine> getMixerTunerDataLines()
|
||||||
{
|
{
|
||||||
@@ -204,21 +201,28 @@ public class MixerManager
|
|||||||
|
|
||||||
if( mixer != null )
|
if( mixer != null )
|
||||||
{
|
{
|
||||||
EnumSet<MixerChannel> channels = getSupportedChannels( mixer );
|
EnumSet<MixerChannel> inputChannels = getSupportedTargetChannels( mixer );
|
||||||
|
|
||||||
if( channels != null )
|
if( inputChannels != null )
|
||||||
{
|
{
|
||||||
mDiscoveredMixers.add(
|
mInputMixers.add(
|
||||||
new DiscoveredMixer( mixer, channels ) );
|
new InputMixerConfiguration( mixer, inputChannels ) );
|
||||||
|
|
||||||
sb.append( "\t[LOADED] Mixer:" + mixerInfo.getName() +
|
sb.append( "\t[LOADED] Input: " + mixerInfo.getName() +
|
||||||
" CHANNELS: " + channels + "\n" );
|
" CHANNELS: " + inputChannels + "\n" );
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
EnumSet<MixerChannel> outputChannels = getSupportedSourceChannels( mixer );
|
||||||
|
|
||||||
|
if( outputChannels != null )
|
||||||
{
|
{
|
||||||
sb.append( "\t[NOT LOADED] Mixer:" +
|
for( MixerChannel channel: outputChannels )
|
||||||
mixerInfo.getName() +
|
{
|
||||||
" - audio format not supported\n" );
|
mOutputMixers.add( new MixerChannelConfiguration( mixer, channel ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append( "\t[LOADED] Output: " + mixerInfo.getName() +
|
||||||
|
" CHANNELS: " + outputChannels + "\n" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -249,10 +253,6 @@ public class MixerManager
|
|||||||
break;
|
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;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EnumSet<MixerChannel> getSupportedChannels( Mixer mixer )
|
private EnumSet<MixerChannel> getSupportedTargetChannels( Mixer mixer )
|
||||||
{
|
{
|
||||||
DataLine.Info stereoInfo=
|
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 );
|
boolean stereoSupported = mixer.isLineSupported( stereoInfo );
|
||||||
|
|
||||||
DataLine.Info monoInfo=
|
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 );
|
boolean monoSupported = mixer.isLineSupported( monoInfo );
|
||||||
|
|
||||||
@@ -317,6 +319,42 @@ public class MixerManager
|
|||||||
return EnumSet.of( MixerChannel.MONO );
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,17 +392,24 @@ public class MixerManager
|
|||||||
"\n lineclass:" + lineInfo.getLineClass() +
|
"\n lineclass:" + lineInfo.getLineClass() +
|
||||||
"\n" );
|
"\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();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DiscoveredMixer
|
public class InputMixerConfiguration
|
||||||
{
|
{
|
||||||
private Mixer mMixer;
|
private Mixer mMixer;
|
||||||
private EnumSet<MixerChannel> mChannels;
|
private EnumSet<MixerChannel> mChannels;
|
||||||
|
|
||||||
public DiscoveredMixer( Mixer mixer, EnumSet<MixerChannel> channels )
|
public InputMixerConfiguration( Mixer mixer, EnumSet<MixerChannel> channels )
|
||||||
{
|
{
|
||||||
mMixer = mixer;
|
mMixer = mixer;
|
||||||
mChannels = channels;
|
mChannels = channels;
|
||||||
@@ -389,10 +434,14 @@ public class MixerManager
|
|||||||
{
|
{
|
||||||
return mChannels.contains( channel );
|
return mChannels.contains( channel );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main( String[] args )
|
||||||
|
{
|
||||||
|
mLog.debug( "Starting ..." );
|
||||||
|
|
||||||
public String toString()
|
MixerManager.getInstance();
|
||||||
{
|
|
||||||
return getMixerName();
|
mLog.debug( "Finished!" );
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class TimeStamp
|
|||||||
*/
|
*/
|
||||||
public static String getFormattedTimeWithMilliseconds( long timestamp )
|
public static String getFormattedTimeWithMilliseconds( long timestamp )
|
||||||
{
|
{
|
||||||
return mSDFTime.format( new Date( timestamp ) );
|
return mSDFTimeWithMilliseconds.format( new Date( timestamp ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user