diff --git a/build/build.xml b/build/build.xml
index 3667c7ff..27ab0551 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -5,9 +5,9 @@
-
+
-
+
diff --git a/src/alias/Alias.java b/src/alias/Alias.java
index 1c6ddb87..7def2d64 100644
--- a/src/alias/Alias.java
+++ b/src/alias/Alias.java
@@ -34,7 +34,7 @@ public class Alias
private String mName;
private int mColor;
private String mIconName;
- private ArrayList mAliasIDs = new ArrayList();
+ private List mAliasIDs = new ArrayList();
private List mAliasActions = new ArrayList();
public Alias()
@@ -121,7 +121,7 @@ public class Alias
mIconName = iconName;
}
- public ArrayList getId()
+ public List getId()
{
return mAliasIDs;
}
diff --git a/src/alias/id/talkgroup/TalkgroupIDEditor.java b/src/alias/id/talkgroup/TalkgroupIDEditor.java
index feec6d2d..1feda866 100644
--- a/src/alias/id/talkgroup/TalkgroupIDEditor.java
+++ b/src/alias/id/talkgroup/TalkgroupIDEditor.java
@@ -36,10 +36,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
private TalkgroupIDNode mTalkgroupIDNode;
private JTextField mTextTalkgroupID;
-<<<<<<< Upstream, based on origin/master
-=======
- private JLabel mLabelAudio;
->>>>>>> 92e63f7 Adds AliasModel and AliasEvent and support for conversion from legacy playlist format.
private String mHelpText = "Enter a formatted talkgroup identifier.\n\n"
+ "P25 Talkgroup Format: hexadecimal value, 4 characters for "
diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java
index 6fa1a9f7..a8aae418 100644
--- a/src/audio/AudioManager.java
+++ b/src/audio/AudioManager.java
@@ -19,14 +19,11 @@ package audio;
******************************************************************************/
import java.util.ArrayList;
-
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -37,10 +34,12 @@ import javax.sound.sampled.Mixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import properties.SystemProperties;
import sample.Broadcaster;
import sample.Listener;
import source.mixer.MixerChannel;
import source.mixer.MixerChannelConfiguration;
+import source.mixer.MixerManager;
import audio.AudioEvent.Type;
import audio.output.AudioOutput;
import audio.output.MonoAudioOutput;
@@ -53,7 +52,9 @@ public class AudioManager implements Listener, IAudioController
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
- public static final int NO_SOURCE = 0;
+
+ public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
+ public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null );
@@ -62,49 +63,90 @@ public class AudioManager implements Listener, IAudioController
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null );
private LinkedTransferQueue mAudioPacketQueue = new LinkedTransferQueue<>();
- private Map mChannelAssignments = new HashMap<>();
- private int mLowestPrioritySource;
+ private Map mChannelConnectionMap = new HashMap<>();
+ private List mAudioOutputConnections = new ArrayList<>();
+ private AudioOutputConnection mLowestPriorityConnection;
+ private int mAvailableConnectionCount;
- private List mAvailableAudioOutputs = new ArrayList<>();
private Map mAudioOutputMap = new HashMap<>();
- private List mChannelNames = new ArrayList<>();
private Broadcaster mControllerBroadcaster = new Broadcaster<>();
private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture> mProcessingTask;
+ private MixerManager mMixerManager;
+ private MixerChannelConfiguration mMixerChannelConfiguration;
- private MixerChannelConfiguration mMixerChannel;
/**
* Processes all audio produced by the decoding channels and routes audio
* packets to any combination of outputs based on any alias audio routing
* options specified by the user.
*/
- public AudioManager( ThreadPoolManager manager )
+ public AudioManager( ThreadPoolManager manager, MixerManager mixerManager )
{
mThreadPoolManager = manager;
+ mMixerManager = mixerManager;
- //TODO: save audio system preferences to the settings manager
+ loadSettings();
+ }
+
+ private void loadSettings()
+ {
+ MixerChannelConfiguration configuration = null;
- /* Use the system default mixer and mono channel as default startup */
- Mixer mixer = AudioSystem.getMixer( null );
+ SystemProperties properties = SystemProperties.getInstance();
- if( mixer != null )
+ Mixer defaultMixer = AudioSystem.getMixer( null );
+
+ String mixer = properties.get( AUDIO_MIXER_PROPERTY, defaultMixer.getMixerInfo().getName() );
+
+ String channels = properties.get( AUDIO_CHANNELS_PROPERTY, MixerChannel.MONO.name() );
+
+ MixerChannelConfiguration[] mixerConfigurations = mMixerManager.getOutputMixers();
+
+ for( MixerChannelConfiguration mixerConfig: mixerConfigurations )
{
- mMixerChannel = new MixerChannelConfiguration( mixer, MixerChannel.MONO );
+ if( mixerConfig.matches( mixer, channels ) )
+ {
+ configuration = mixerConfig;
+ }
+ }
+
+ if( configuration == null )
+ {
+ configuration = getDefaultConfiguration();
+ }
+
+ try
+ {
+ setMixerChannelConfiguration( configuration );
+ }
+ catch( Exception e )
+ {
+ mLog.error( "Couldn't set stored audio mixer/channel configuration"
+ + " - using default", e );
try
{
- setMixerChannelConfiguration( mMixerChannel );
- }
- catch ( AudioException e )
+ setMixerChannelConfiguration( getDefaultConfiguration() );
+ }
+ catch( Exception e2 )
{
- mLog.error( "Couldn't create an audio output device", e );
+ mLog.error( "Couldn't set default audio mixer/channel "
+ + "configuration - no audio will be available", e2 );
}
}
}
+ private MixerChannelConfiguration getDefaultConfiguration()
+ {
+ /* Use the system default mixer and mono channel as default startup */
+ Mixer defaultMixer = AudioSystem.getMixer( null );
+
+ return new MixerChannelConfiguration( defaultMixer, MixerChannel.MONO );
+ }
+
public void dispose()
{
if( mThreadPoolManager != null && mProcessingTask != null )
@@ -116,6 +158,15 @@ public class AudioManager implements Listener, IAudioController
mThreadPoolManager = null;
mProcessingTask = null;
+
+ mChannelConnectionMap.clear();
+
+ for( AudioOutputConnection connection: mAudioOutputConnections )
+ {
+ connection.dispose();
+ }
+
+ mAudioOutputConnections.clear();
}
@Override
@@ -124,207 +175,44 @@ public class AudioManager implements Listener, IAudioController
mAudioPacketQueue.add( packet );
}
- public class AudioPacketProcessor implements Runnable
- {
- @Override
- public void run()
- {
- removeCompletedChannelAssignments();
-
- if( mAudioPacketQueue != null )
- {
- List packets = new ArrayList();
-
- mAudioPacketQueue.drainTo( packets );
-
- for( AudioPacket packet: packets )
- {
- /* Don't process any packet's marked as do not monitor */
- if( !packet.getAudioMetadata().isDoNotMonitor() )
- {
- int source = packet.getAudioMetadata().getSource();
-
- if( packet.getType() != AudioPacket.Type.END )
- {
- /* Existing channel assignment */
- if( mChannelAssignments.containsKey( source ) )
- {
- mChannelAssignments.get( source ).getAudioOutput()
- .receive( packet );
- }
- /* Start a new channel assignment */
- else if( !mAvailableAudioOutputs.isEmpty() )
- {
- AudioOutput channel = mAvailableAudioOutputs.remove( 0 );
-
- mChannelAssignments.put( packet.getAudioMetadata().getSource(),
- new AudioChannelAssignment( channel, packet.getAudioMetadata().getPriority() ) );
-
- updateLowestPriorityAssignment();
-
- channel.receive( packet );
- }
- /* Override the lowest priority assignment */
- else
- {
- if( mLowestPrioritySource > -1 )
- {
- int priority = packet.getAudioMetadata().getPriority();
-
- if( packet.getAudioMetadata().isSelected() ||
- priority < mChannelAssignments
- .get( mLowestPrioritySource ).getPriority() )
- {
- AudioChannelAssignment assignment =
- mChannelAssignments.remove( mLowestPrioritySource );
-
- if( assignment != null )
- {
- assignment.setPriority( priority );
-
- int overrideSource = packet.getAudioMetadata().getSource();
-
- mChannelAssignments.put( overrideSource, assignment );
-
- mLowestPrioritySource = overrideSource;
-
- assignment.getAudioOutput().receive( packet );
- }
- else
- {
- mLowestPrioritySource = -1;
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
/**
- * Checks each audio channel assignment and removes any completed channels.
+ * Checks each audio channel assignment and disconnects any inactive connections
*/
- private void removeCompletedChannelAssignments()
+ private void disconnectInactiveChannelAssignments()
{
- if( !mChannelAssignments.isEmpty() )
+ boolean changed = false;
+
+ for( AudioOutputConnection connection: mAudioOutputConnections )
{
- Iterator> it =
- mChannelAssignments.entrySet().iterator();
-
- Entry entry;
-
- boolean removed = false;
-
- while( it.hasNext() )
+ if( connection.isInactive() &&
+ mChannelConnectionMap.containsKey( connection.getSource() ) )
{
- entry = it.next();
-
- if( entry.getValue().isComplete() )
- {
- mAvailableAudioOutputs.add( entry.getValue().getAudioOutput() );
- entry.getValue().dispose();
- it.remove();
- removed = true;
- }
- }
-
- if( removed )
- {
- updateLowestPriorityAssignment();
+ mChannelConnectionMap.remove( connection.getSource() );
+ connection.disconnect();
+ changed = true;
}
}
+
+ if( changed )
+ {
+ updateLowestPriorityAssignment();
+ }
}
/**
- * Identifies the lowest priority channel assignment ... the one that can
- * be overridden with a higher priority call
+ * Identifies the lowest priority channel connection
*/
private void updateLowestPriorityAssignment()
{
- int lowestPriority = -1;
+ mLowestPriorityConnection = null;
- mLowestPrioritySource = -1;
-
- for( int source: mChannelAssignments.keySet() )
+ for( AudioOutputConnection connection: mAudioOutputConnections )
{
- AudioChannelAssignment assignment = mChannelAssignments.get( source );
-
- if( assignment.getPriority() > lowestPriority )
+ if( connection.isConnected() &&
+ ( mLowestPriorityConnection == null ||
+ mLowestPriorityConnection.getPriority() > connection.getPriority() ) )
{
- lowestPriority = assignment.getPriority();
-
- mLowestPrioritySource = source;
- }
- }
- }
-
- public void endAllAudioAssignments()
- {
- for( AudioOutput output: mAudioOutputMap.values() )
- {
- output.receive( new AudioPacket( AudioPacket.Type.END, null ) );
- }
- }
-
- public class AudioChannelAssignment implements Listener
- {
- private static final long CALL_END_DELAY = 2000; //2 seconds
-
- private AudioOutput mAudioOutput;
- private int mPriority;
- private long mAudioStoppedTime = System.currentTimeMillis();
-
- public AudioChannelAssignment( AudioOutput audioOutput, int priority )
- {
- mAudioOutput = audioOutput;
- mPriority = priority;
-
- mAudioOutput.addAudioEventListener( this );
- }
-
- public boolean isComplete()
- {
- return mAudioStoppedTime != Long.MAX_VALUE &&
- mAudioStoppedTime + CALL_END_DELAY < System.currentTimeMillis();
- }
-
- public void dispose()
- {
- mAudioOutput.removeAudioEventListener( this );
- mAudioOutput.reset();
- mAudioOutput = null;
- }
-
- public AudioOutput getAudioOutput()
- {
- return mAudioOutput;
- }
-
- public int getPriority()
- {
- return mPriority;
- }
-
- public void setPriority( int priority )
- {
- mPriority = priority;
- }
-
- @Override
- public void receive( AudioEvent event )
- {
- switch( event.getType() )
- {
- case AUDIO_STARTED:
- case AUDIO_STOPPED:
- case AUDIO_CONTINUATION:
- mAudioStoppedTime = System.currentTimeMillis();
- break;
- default:
- break;
+ mLowestPriorityConnection = connection;
}
}
}
@@ -342,41 +230,29 @@ public class AudioManager implements Listener, IAudioController
{
mThreadPoolManager.cancel( mProcessingTask );
}
-
- endAllAudioAssignments();
- for( Entry e: mChannelAssignments.entrySet() )
- {
- e.getValue().getAudioOutput().dispose();
- }
- mChannelAssignments.clear();
+ disposeCurrentConfiguration();
- for( AudioOutput output: mAvailableAudioOutputs )
- {
- output.dispose();
- }
- mAvailableAudioOutputs.clear();
-
- mChannelNames.clear();
- mAudioOutputMap.clear();
-
switch( entry.getMixerChannel() )
{
case MONO:
- AudioOutput mono = new MonoAudioOutput( entry.getMixer() );
- mAvailableAudioOutputs.add( mono );
- mChannelNames.add( mono.getChannelName() );
+ AudioOutput mono = new MonoAudioOutput( mThreadPoolManager,
+ entry.getMixer() );
+ mAudioOutputConnections.add( new AudioOutputConnection( mono ) );
+ mAvailableConnectionCount++;
mAudioOutputMap.put( mono.getChannelName(), mono );
break;
case STEREO:
- AudioOutput left = new StereoAudioOutput( entry.getMixer(), MixerChannel.LEFT );
- mAvailableAudioOutputs.add( left );
- mChannelNames.add( left.getChannelName() );
+ AudioOutput left = new StereoAudioOutput( mThreadPoolManager,
+ entry.getMixer(), MixerChannel.LEFT );
+ mAudioOutputConnections.add( new AudioOutputConnection( left ) );
+ mAvailableConnectionCount++;
mAudioOutputMap.put( left.getChannelName(), left );
- AudioOutput right = new StereoAudioOutput( entry.getMixer(), MixerChannel.RIGHT );
- mAvailableAudioOutputs.add( right );
- mChannelNames.add( right.getChannelName() );
+ AudioOutput right = new StereoAudioOutput( mThreadPoolManager,
+ entry.getMixer(), MixerChannel.RIGHT );
+ mAudioOutputConnections.add( new AudioOutputConnection( right ) );
+ mAvailableConnectionCount++;
mAudioOutputMap.put( right.getChannelName(), right );
break;
default:
@@ -389,6 +265,12 @@ public class AudioManager implements Listener, IAudioController
TimeUnit.MILLISECONDS );
mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_COMPLETE );
+
+ SystemProperties properties = SystemProperties.getInstance();
+
+ properties.set( AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName() );
+ properties.set( AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name() );
+ properties.save();
}
}
@@ -418,7 +300,7 @@ public class AudioManager implements Listener, IAudioController
@Override
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
{
- return mMixerChannel;
+ return mMixerChannelConfiguration;
}
@Override
@@ -449,4 +331,244 @@ public class AudioManager implements Listener, IAudioController
{
mControllerBroadcaster.removeListener( listener );
}
-}
+
+ /**
+ * Returns an audio output connection for the packet if one is
+ * available, or overrides an existing lower priority connection.
+ * Returns null if no connection is available for the audio packet.
+ *
+ * @param packet from a source
+ * @return an audio output connection or null
+ */
+ private AudioOutputConnection getConnection( AudioPacket packet )
+ {
+ int source = packet.getAudioMetadata().getSource();
+
+ //Use an existing connection
+ if( mChannelConnectionMap.containsKey( source ) )
+ {
+ return mChannelConnectionMap.get( source );
+ }
+
+ //Connect to and use an available connection
+ if( mAvailableConnectionCount > 0 )
+ {
+ for( AudioOutputConnection connection: mAudioOutputConnections )
+ {
+ if( connection.isDisconnected() )
+ {
+ connection.connect( source );
+ mChannelConnectionMap.put( source, connection );
+ return connection;
+ }
+ }
+ }
+ //Preempt an existing connection and connect it to this higher priority source
+ else //Check for a channel priority override
+ {
+ int priority = packet.getAudioMetadata().getPriority();
+
+ if( mLowestPriorityConnection != null &&
+ priority < mLowestPriorityConnection.getPriority() )
+ {
+ AudioOutputConnection connection = mLowestPriorityConnection;
+
+ mChannelConnectionMap.remove( connection.getSource() );
+
+ connection.connect( source );
+
+ mChannelConnectionMap.put( source, connection );
+
+ updateLowestPriorityAssignment();
+
+ return connection;
+ }
+ }
+
+ return null;
+ }
+
+ public class AudioPacketProcessor implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ disconnectInactiveChannelAssignments();
+
+ if( mAudioPacketQueue != null )
+ {
+ List packets = new ArrayList();
+
+ mAudioPacketQueue.drainTo( packets );
+
+ for( AudioPacket packet: packets )
+ {
+ /* Don't process any packet's marked as do not monitor */
+ if( !packet.getAudioMetadata().isDoNotMonitor() &&
+ packet.getType() == AudioPacket.Type.AUDIO )
+ {
+ AudioOutputConnection connection = getConnection( packet );
+
+ if( connection != null )
+ {
+ connection.receive( packet );
+ }
+ }
+ }
+ }
+ }
+ catch( Exception e )
+ {
+ mLog.error( "Encountered error while processing audio packets", e );
+ }
+ }
+ }
+
+ /**
+ * Audio output connection manages a connection between a source and an audio
+ * output and maintains current state information about the audio activity
+ * received form the source.
+ */
+ public class AudioOutputConnection implements Listener
+ {
+ private static final long CALL_END_DELAY = 2000; //2 seconds
+ private static final int DISCONNECTED = -1;
+
+ private AudioOutput mAudioOutput;
+ private int mPriority = 0;
+ private long mLastActivity = System.currentTimeMillis();
+ private int mSource = DISCONNECTED;
+
+ public AudioOutputConnection( AudioOutput audioOutput )
+ {
+ mAudioOutput = audioOutput;
+ mAudioOutput.addAudioEventListener( this );
+ }
+
+ public void receive( AudioPacket packet )
+ {
+ if( packet.hasAudioMetadata() &&
+ packet.getAudioMetadata().getSource() == mSource )
+ {
+ int priority = packet.getAudioMetadata().getPriority();
+
+ if( mPriority != priority )
+ {
+ mPriority = priority;
+ updateLowestPriorityAssignment();
+ }
+
+ updateTimestamp();
+
+ if( mAudioOutput != null )
+ {
+ mAudioOutput.receive( packet );
+ }
+ }
+ else
+ {
+ if( packet.hasAudioMetadata() )
+ {
+ mLog.error( "Received audio packet from channel source [" +
+ packet.getAudioMetadata().getSource() +
+ "] however this assignment is currently connected "
+ + "to channel source [" + mSource + "]" );
+ }
+ else
+ {
+ mLog.error( "Received audio packet with no metadata - "
+ + "cannot route audio packet" );
+ }
+ }
+ }
+
+ private void updateTimestamp()
+ {
+ mLastActivity = System.currentTimeMillis();
+ }
+
+ /**
+ * Terminates the audio output and prepares this connection for disposal
+ */
+ public void dispose()
+ {
+ mAudioOutput.dispose();
+ mAudioOutput = null;
+ }
+
+ /**
+ * Indicates if this assignment is currently disconnected from a channel source
+ */
+ public boolean isDisconnected()
+ {
+ return mSource == DISCONNECTED;
+ }
+
+ /**
+ * Indicates if this assignment is currently connected to a channel source
+ */
+ public boolean isConnected()
+ {
+ return !isDisconnected();
+ }
+
+ /**
+ * Connects this assignment to the indicated source so that audio
+ * packets from this source can be sent to the audio output
+ */
+ public void connect( int source )
+ {
+ mSource = source;
+ updateTimestamp();
+ }
+
+ /**
+ * Currently connected source or -1 if disconnected
+ */
+ public int getSource()
+ {
+ return mSource;
+ }
+
+ /**
+ * Disconnects this assignment from the source and prevents any audio
+ * from being routed to the audio output until another source is assigned
+ */
+ public void disconnect()
+ {
+ mSource = DISCONNECTED;
+ }
+
+ /**
+ * Indicates if audio output is current inactive, meaning that the
+ * audio output hasn't recently processed any audio packets.
+ */
+ public boolean isInactive()
+ {
+ return mLastActivity != Long.MAX_VALUE &&
+ mLastActivity + CALL_END_DELAY < System.currentTimeMillis();
+ }
+
+ public int getPriority()
+ {
+ return mPriority;
+ }
+
+ @Override
+ public void receive( AudioEvent event )
+ {
+ switch( event.getType() )
+ {
+ case AUDIO_STARTED:
+ case AUDIO_STOPPED:
+ case AUDIO_CONTINUATION:
+ updateTimestamp();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/audio/output/AudioOutput.java b/src/audio/output/AudioOutput.java
index d9506472..31f4300f 100644
--- a/src/audio/output/AudioOutput.java
+++ b/src/audio/output/AudioOutput.java
@@ -1,10 +1,9 @@
package audio.output;
import java.util.concurrent.LinkedTransferQueue;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.FloatControl;
+import javax.sound.sampled.LineListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,23 +11,26 @@ import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import audio.AudioEvent;
-import audio.AudioEvent.Type;
import audio.AudioPacket;
import audio.metadata.AudioMetadata;
+import controller.ThreadPoolManager;
-public abstract class AudioOutput implements Listener
+public abstract class AudioOutput implements Listener, LineListener
{
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
- private AtomicBoolean mSquelched = new AtomicBoolean( false );
private Listener mAudioMetadataListener;
-
protected Broadcaster mAudioEventBroadcaster = new Broadcaster<>();
protected LinkedTransferQueue mBuffer = new LinkedTransferQueue<>();
- protected ScheduledExecutorService mExecutorService;
+ protected ThreadPoolManager mThreadPoolManager;
protected boolean mCanProcessAudio;
protected long mLastActivity;
+ public AudioOutput( ThreadPoolManager threadPoolManager )
+ {
+ mThreadPoolManager = threadPoolManager;
+ }
+
public void reset()
{
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
@@ -41,14 +43,11 @@ public abstract class AudioOutput implements Listener
mBuffer.clear();
- if( mExecutorService != null )
- {
- mExecutorService.shutdown();
- }
-
mAudioEventBroadcaster.dispose();
mAudioEventBroadcaster = null;
mAudioMetadataListener = null;
+
+ mThreadPoolManager = null;
}
public abstract String getChannelName();
@@ -72,19 +71,6 @@ public abstract class AudioOutput implements Listener
mAudioEventBroadcaster.broadcast( audioEvent );
}
- public void setSquelched( boolean squelched )
- {
- mSquelched.set( squelched );
-
- broadcast( new AudioEvent( squelched ? Type.AUDIO_SQUELCHED :
- Type.AUDIO_UNSQUELCHED, getChannelName() ) );
- }
-
- public boolean isSquelched()
- {
- return mSquelched.get();
- }
-
/**
* Registers a single listener to receive audio start and audio stop events
*/
@@ -130,4 +116,4 @@ public abstract class AudioOutput implements Listener
mBuffer.add( packet );
}
}
-}
+}
\ No newline at end of file
diff --git a/src/audio/output/MonoAudioOutput.java b/src/audio/output/MonoAudioOutput.java
index 76bf2324..f1aad5cd 100644
--- a/src/audio/output/MonoAudioOutput.java
+++ b/src/audio/output/MonoAudioOutput.java
@@ -5,13 +5,14 @@ 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.ScheduledFuture;
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.LineEvent;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
@@ -24,8 +25,8 @@ import audio.AudioEvent;
import audio.AudioFormats;
import audio.AudioPacket;
import audio.AudioPacket.Type;
-import audio.output.StereoAudioOutput.QueueProcessor;
-import controller.NamingThreadFactory;
+import controller.ThreadPoolManager;
+import controller.ThreadPoolManager.ThreadType;
/**
* Mono Audio output with automatic flow control based on the availability of
@@ -50,14 +51,14 @@ public class MonoAudioOutput extends AudioOutput
private SourceDataLine mOutput;
private FloatControl mGainControl;
private BooleanControl mMuteControl;
-
private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent;
private AudioEvent mAudioContinuationEvent;
+ private ScheduledFuture> mProcessorTask;
- public MonoAudioOutput( Mixer mixer )
+ public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
{
- super();
+ super( threadPoolManager );
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
getChannelName() );
@@ -74,15 +75,8 @@ public class MonoAudioOutput extends AudioOutput
if( mOutput != null )
{
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
-
+ mOutput.addLineListener( this );
mCanProcessAudio = true;
-
- mExecutorService = Executors.newSingleThreadScheduledExecutor(
- new NamingThreadFactory( "audio (mono) output" ) );
-
- /* Run the queue processor task every 40 milliseconds */
- mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
- 0, 40, TimeUnit.MILLISECONDS );
}
}
catch ( LineUnavailableException e )
@@ -115,12 +109,10 @@ public class MonoAudioOutput extends AudioOutput
mixer.getMixerInfo().getName() + "]" );
}
- mExecutorService = Executors.newSingleThreadScheduledExecutor(
- new NamingThreadFactory( "audio (mono) output" ) );
-
/* Run the queue processor task every 40 milliseconds */
- mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
- 0, 40, TimeUnit.MILLISECONDS );
+ mProcessorTask = mThreadPoolManager.scheduleFixedRate(
+ ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
+ 40, TimeUnit.MILLISECONDS );
}
}
@@ -132,102 +124,108 @@ public class MonoAudioOutput extends AudioOutput
public void dispose()
{
+ if( mProcessorTask != null && mThreadPoolManager != null )
+ {
+ mThreadPoolManager.cancel( mProcessorTask );
+ }
+
+ mProcessorTask = null;
+
super.dispose();
if( mOutput != null )
{
mOutput.close();
}
+
+ mOutput = null;
+ mGainControl = null;
+ mMuteControl = null;
}
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 ) )
+ try
{
- List packets = new ArrayList();
-
- mBuffer.drainTo( packets );
-
- if( packets.size() > 0 )
+ /* The mProcessing flag ensures that only one instance of the
+ * processor can run at any given time */
+ if( mProcessing.compareAndSet( false, true ) )
{
- broadcast( mAudioContinuationEvent );
+ List packets = new ArrayList();
- for( AudioPacket packet: packets )
+ mBuffer.drainTo( packets );
+
+ if( packets.size() > 0 )
{
- if( mCanProcessAudio )
+ for( AudioPacket packet: packets )
{
- if( isSquelched() )
+ broadcast( mAudioContinuationEvent );
+
+ if( mCanProcessAudio )
{
- checkStop();
- }
- else if( packet.getType() == Type.AUDIO )
- {
- float[] samples = packet.getAudioBuffer().getSamples();
-
- /* Little-endian byte buffer */
- ByteBuffer buffer =
- ByteBuffer.allocate( samples.length * 2 )
- .order( ByteOrder.LITTLE_ENDIAN );
-
- ShortBuffer shortBuffer = buffer.asShortBuffer();
-
- for( float sample: samples )
+ if( packet.getType() == Type.AUDIO )
{
- shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
- }
-
- mOutput.write( buffer.array(), 0,
- buffer.array().length );
-
- checkStart();
-
- broadcast( packet.getAudioMetadata() );
+ float[] samples = packet.getAudioBuffer().getSamples();
+
+ /* Little-endian byte buffer */
+ ByteBuffer buffer =
+ ByteBuffer.allocate( samples.length * 2 )
+ .order( ByteOrder.LITTLE_ENDIAN );
+
+ ShortBuffer shortBuffer = buffer.asShortBuffer();
- mLastActivity = System.currentTimeMillis();
+ for( float sample: samples )
+ {
+ shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
+ }
+
+ //This is a blocking write
+ mOutput.write( buffer.array(), 0, buffer.array().length );
+
+ checkStart();
+
+ broadcast( packet.getAudioMetadata() );
+
+ mLastActivity = System.currentTimeMillis();
+ }
}
}
}
+ else
+ {
+ checkStop();
+ }
+
+ mProcessing.set( false );
}
- else
- {
- checkStop();
- }
-
- mProcessing.set( false );
+ }
+ catch( Exception e )
+ {
+ mLog.error( "Error while processing audio buffers", e );
}
}
private void checkStart()
{
- if( mCanProcessAudio && !isSquelched() &&
+ if( mCanProcessAudio &&
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
{
mOutput.start();
-
- mAudioEventBroadcaster.broadcast( mAudioStartEvent );
}
}
private void checkStop()
{
if( mCanProcessAudio && mOutput.isRunning() &&
- ( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
+ mOutput.available() >= STOP_THRESHOLD )
{
mOutput.drain();
mOutput.stop();
-
- mAudioEventBroadcaster.broadcast( mAudioStopEvent );
}
}
}
@@ -266,4 +264,19 @@ public class MonoAudioOutput extends AudioOutput
{
return mGainControl != null;
}
-}
+
+ @Override
+ public void update( LineEvent event )
+ {
+ LineEvent.Type type = event.getType();
+
+ if( type == LineEvent.Type.START )
+ {
+ mAudioEventBroadcaster.broadcast( mAudioStartEvent );
+ }
+ else if( type == LineEvent.Type.STOP )
+ {
+ mAudioEventBroadcaster.broadcast( mAudioStopEvent );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/audio/output/StereoAudioOutput.java b/src/audio/output/StereoAudioOutput.java
index 81892ca5..8a624d18 100644
--- a/src/audio/output/StereoAudioOutput.java
+++ b/src/audio/output/StereoAudioOutput.java
@@ -5,13 +5,14 @@ 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.ScheduledFuture;
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.LineEvent;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
@@ -24,10 +25,11 @@ import audio.AudioEvent;
import audio.AudioFormats;
import audio.AudioPacket;
import audio.AudioPacket.Type;
-import controller.NamingThreadFactory;
+import controller.ThreadPoolManager;
+import controller.ThreadPoolManager.ThreadType;
/**
- * Mono Audio output in stereo format with automatic flow control based on the
+ * Stereo Audio output in stereo format with automatic flow control based on the
* availability of audio data packets.
*/
public class StereoAudioOutput extends AudioOutput
@@ -39,9 +41,9 @@ public class StereoAudioOutput extends AudioOutput
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
+ * audio and automatically start or stop 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 */
+ * 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;
@@ -49,16 +51,16 @@ public class StereoAudioOutput extends AudioOutput
private SourceDataLine mOutput;
private FloatControl mGainControl;
private BooleanControl mMuteControl;
-
private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent;
private AudioEvent mAudioContinuationEvent;
-
private MixerChannel mMixerChannel;
+ private ScheduledFuture> mProcessorTask;
- public StereoAudioOutput( Mixer mixer, MixerChannel channel )
+ public StereoAudioOutput( ThreadPoolManager threadPoolManager,
+ Mixer mixer, MixerChannel channel )
{
- super();
+ super( threadPoolManager );
assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
@@ -79,6 +81,7 @@ public class StereoAudioOutput extends AudioOutput
if( mOutput != null )
{
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO, BUFFER_SIZE );
+ mOutput.addLineListener( this );
mCanProcessAudio = true;
}
}
@@ -113,12 +116,10 @@ public class StereoAudioOutput extends AudioOutput
mixer.getMixerInfo().getName() + " | " + channel.name() + "]" );
}
- mExecutorService = Executors.newSingleThreadScheduledExecutor(
- new NamingThreadFactory( "audio (stereo) output" ) );
-
/* Run the queue processor task every 40 milliseconds */
- mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
- 0, 40, TimeUnit.MILLISECONDS );
+ mProcessorTask = mThreadPoolManager.scheduleFixedRate(
+ ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
+ 40, TimeUnit.MILLISECONDS );
}
}
@@ -130,20 +131,30 @@ public class StereoAudioOutput extends AudioOutput
public void dispose()
{
+ if( mProcessorTask != null && mThreadPoolManager != null )
+ {
+ mThreadPoolManager.cancel( mProcessorTask );
+ }
+
+ mProcessorTask = null;
+
super.dispose();
if( mOutput != null )
{
mOutput.close();
}
+
+ mOutput = null;
+ mGainControl = null;
+ mMuteControl = null;
}
private ByteBuffer convert( float[] samples )
{
/* Little-endian byte buffer */
- ByteBuffer buffer =
- ByteBuffer.allocate( samples.length * 4 )
- .order( ByteOrder.LITTLE_ENDIAN );
+ ByteBuffer buffer = ByteBuffer.allocate( samples.length * 4 )
+ .order( ByteOrder.LITTLE_ENDIAN );
ShortBuffer shortBuffer = buffer.asShortBuffer();
@@ -178,72 +189,72 @@ public class StereoAudioOutput extends AudioOutput
@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 ) )
+ try
{
- List packets = new ArrayList();
-
- mBuffer.drainTo( packets );
-
- if( packets.size() > 0 )
+ /* The mProcessing flag ensures that only one instance of the
+ * processor can run at any given time */
+ if( mProcessing.compareAndSet( false, true ) )
{
- broadcast( mAudioContinuationEvent );
+ List packets = new ArrayList();
- for( AudioPacket packet: packets )
+ mBuffer.drainTo( packets );
+
+ if( packets.size() > 0 )
{
- if( mCanProcessAudio )
+
+ for( AudioPacket packet: packets )
{
- if( isSquelched() )
+ broadcast( mAudioContinuationEvent );
+
+ if( mCanProcessAudio )
{
- 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() );
-
- mLastActivity = System.currentTimeMillis();
+ if( packet.getType() == Type.AUDIO )
+ {
+ ByteBuffer buffer = convert(
+ packet.getAudioBuffer().getSamples() );
+
+ mOutput.write( buffer.array(), 0,
+ buffer.array().length );
+
+ checkStart();
+
+ broadcast( packet.getAudioMetadata() );
+
+ mLastActivity = System.currentTimeMillis();
+ }
}
}
}
+ else
+ {
+ checkStop();
+ }
+
+ mProcessing.set( false );
}
- else
- {
- checkStop();
- }
-
- mProcessing.set( false );
+ }
+ catch( Exception e )
+ {
+ mLog.error( "Error while processing audio buffers", e );
}
}
private void checkStart()
{
- if( mCanProcessAudio && !isSquelched() &&
+ if( mCanProcessAudio &&
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
{
mOutput.start();
-
- mAudioEventBroadcaster.broadcast( mAudioStartEvent );
}
}
private void checkStop()
{
if( mCanProcessAudio && mOutput.isRunning() &&
- ( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
+ mOutput.available() >= STOP_THRESHOLD )
{
mOutput.drain();
mOutput.stop();
-
- mAudioEventBroadcaster.broadcast( mAudioStopEvent );
}
}
}
@@ -282,4 +293,20 @@ public class StereoAudioOutput extends AudioOutput
{
return mGainControl != null;
}
-}
+
+
+ @Override
+ public void update( LineEvent event )
+ {
+ LineEvent.Type type = event.getType();
+
+ if( type == LineEvent.Type.START )
+ {
+ mAudioEventBroadcaster.broadcast( mAudioStartEvent );
+ }
+ else if( type == LineEvent.Type.STOP )
+ {
+ mAudioEventBroadcaster.broadcast( mAudioStopEvent );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/controller/ConfigurationControllerModel.java b/src/controller/ConfigurationControllerModel.java
index 692ee7ee..22d3232c 100644
--- a/src/controller/ConfigurationControllerModel.java
+++ b/src/controller/ConfigurationControllerModel.java
@@ -64,7 +64,6 @@ public class ConfigurationControllerModel extends DefaultTreeModel
private PlaylistManager mPlaylistManager;
private SettingsManager mSettingsManager;
private SourceManager mSourceManager;
- private ChannelModel mChannelModel;
/**
* Implements a merged system controller and (tree) model. The user will
@@ -80,8 +79,6 @@ public class ConfigurationControllerModel extends DefaultTreeModel
{
super( new BaseNode( null ) );
- mChannelModel = channelModel;
-
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mPlaylistManager = playlistManager;
diff --git a/src/controller/ThreadPoolManager.java b/src/controller/ThreadPoolManager.java
index c5527970..c076422a 100644
--- a/src/controller/ThreadPoolManager.java
+++ b/src/controller/ThreadPoolManager.java
@@ -74,11 +74,13 @@ public class ThreadPoolManager
mExecutor.schedule( command, delay, unit );
}
- public void cancel( ScheduledFuture> task )
+ public boolean cancel( ScheduledFuture> task )
{
- task.cancel( true );
+ boolean success = task.cancel( true );
mTasks.remove( task );
+
+ return success;
}
public int getTaskCount( ThreadType type )
diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java
index a86d4aae..be2bf97f 100644
--- a/src/controller/channel/Channel.java
+++ b/src/controller/channel/Channel.java
@@ -78,14 +78,13 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
private boolean mEnabled;
private boolean mSelected;
-
private TunerChannel mTunerChannel = null;
private ChannelType mChannelType = ChannelType.STANDARD;
private int mChannelID;
private int mChannelFrequencyCorrection = 0;
-
+
/**
* Channel represents a complete set of configurations needed to setup and
* manage a processing chain and/or manage as a set of business objects that
@@ -112,8 +111,6 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
/**
* Constructs a new standard channel with a default name of "Channel"
*/
-
- //TODO: remove this
public Channel()
{
mChannelID = UNIQUE_ID++;
@@ -163,7 +160,6 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
return channel;
}
-//TODO: remove this
/**
* Unique identifier for this channel. Value is transient (not persisted).
*/
@@ -442,4 +438,25 @@ public class Channel extends Configuration implements IFrequencyChangeProcessor
return tunerChannel != null &&
tunerChannel.overlaps( minimum, maximum );
}
+
+ /**
+ * Convenience method to make the current channel frequency correction value
+ * available for use outside of a constructed processing chain while maintaining
+ * linkage to the source channel. This hack allows the correction value to
+ * be used to visually show in the spectral display.
+ */
+ @Override
+ public void frequencyChanged( FrequencyChangeEvent event )
+ {
+ if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE )
+ {
+ mChannelFrequencyCorrection = event.getValue().intValue();
+ }
+ }
+
+ @XmlTransient
+ public int getChannelFrequencyCorrection()
+ {
+ return mChannelFrequencyCorrection;
+ }
}
diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java
index db22429c..ef21d641 100644
--- a/src/gui/SDRTrunk.java
+++ b/src/gui/SDRTrunk.java
@@ -140,7 +140,7 @@ public class SDRTrunk
AliasActionManager aliasActionManager = new AliasActionManager( threadPoolManager );
channelProcessingManager.addMessageListener( aliasActionManager );
- AudioManager audioManager = new AudioManager( threadPoolManager );
+ AudioManager audioManager = new AudioManager( threadPoolManager, sourceManager.getMixerManager() );
channelProcessingManager.addAudioPacketListener( audioManager );
MapService mapService = new MapService( mSettingsManager );
diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java
index 68c94256..e48e644e 100644
--- a/src/module/ProcessingChain.java
+++ b/src/module/ProcessingChain.java
@@ -108,7 +108,8 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>();
-// private ThreadPoolManager mThreadPoolManager;
+ private ThreadPoolManager mThreadPoolManager;
+ private ScheduledFuture> mBufferProcessingTask;
private AtomicBoolean mRunning = new AtomicBoolean();
protected Source mSource;
@@ -135,11 +136,6 @@ public class ProcessingChain implements IChannelEventListener
addCallEventListener( mCallEventModel );
}
- public MessageActivityModel getMessageActivityModel()
- {
- return mMessageActivityModel;
- }
-
public CallEventModel getCallEventModel()
{
return mCallEventModel;
@@ -150,6 +146,11 @@ public class ProcessingChain implements IChannelEventListener
return mChannelState;
}
+ public MessageActivityModel getMessageActivityModel()
+ {
+ return mMessageActivityModel;
+ }
+
public void setMessageActivityModel( MessageActivityModel model )
{
mMessageActivityModel = model;
@@ -526,6 +527,12 @@ public class ProcessingChain implements IChannelEventListener
mSource = null;
}
+ if( mBufferProcessingTask != null )
+ {
+ mThreadPoolManager.cancel( mBufferProcessingTask );
+ mBufferProcessingTask = null;
+ }
+
/* Stop each of the modules */
for( Module module: mModules )
{
diff --git a/src/module/decode/DecoderFactory.java b/src/module/decode/DecoderFactory.java
index 02a41441..447e7865 100644
--- a/src/module/decode/DecoderFactory.java
+++ b/src/module/decode/DecoderFactory.java
@@ -211,11 +211,12 @@ public class DecoderFactory
if( channelType == ChannelType.STANDARD )
{
+ long timeout = mptConfig.getCallTimeout() * 1000; //convert to milliseconds
modules.add( new TrafficChannelManager( channelModel, channelProcessingManager,
- decodeConfig, recordConfig, channel.getSystem(),
- channel.getSite(), ( aliasList != null ? aliasList.getName() : null ), timeout,
- mptConfig.getTrafficChannelPoolSize() ) );
+ decodeConfig, recordConfig, channel.getSystem(),
+ channel.getSite(), ( aliasList != null ? aliasList.getName() : null ), timeout,
+ mptConfig.getTrafficChannelPoolSize() ) );
}
modules.add( new FMDemodulatorModule( iqPass, iqStop ) );
@@ -256,7 +257,7 @@ public class DecoderFactory
if( channelType == ChannelType.STANDARD )
{
//Set call timeout to 3 seconds for P25 traffic channels
- timeout = 3000;
+ long timeout = 3000;
modules.add( new TrafficChannelManager( channelModel,
channelProcessingManager, decodeConfig,
diff --git a/src/module/decode/mpt1327/MPT1327DecoderState.java b/src/module/decode/mpt1327/MPT1327DecoderState.java
index ad40aa52..a5b94f27 100644
--- a/src/module/decode/mpt1327/MPT1327DecoderState.java
+++ b/src/module/decode/mpt1327/MPT1327DecoderState.java
@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014 Dennis Sheirer
+ * Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,9 +26,11 @@ import java.util.TreeSet;
import message.Message;
import module.decode.DecoderType;
+import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.CallEvent.CallEventType;
import module.decode.mpt1327.MPT1327Message.IdentType;
+import module.decode.state.ChangeChannelTimeoutEvent;
import module.decode.state.ChangedAttribute;
import module.decode.state.DecoderState;
import module.decode.state.DecoderStateEvent;
@@ -41,6 +43,8 @@ import org.slf4j.LoggerFactory;
import alias.Alias;
import alias.AliasList;
+import audio.metadata.Metadata;
+import audio.metadata.MetadataType;
import controller.channel.Channel.ChannelType;
import controller.channel.map.ChannelMap;
@@ -60,15 +64,18 @@ public class MPT1327DecoderState extends DecoderState
private ChannelMap mChannelMap;
private long mFrequency = 0;
+ private long mCallTimeout;
- public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap,
- ChannelType channelType )
+ public MPT1327DecoderState( AliasList aliasList,
+ ChannelMap channelMap,
+ ChannelType channelType,
+ long callTimeout )
{
super( aliasList );
mChannelMap = channelMap;
-
mChannelType = channelType;
+ mCallTimeout = callTimeout;
}
@Override
@@ -252,8 +259,42 @@ public class MPT1327DecoderState extends DecoderState
/* Traffic Channel Events */
case CLEAR:
+ mChannelNumber = mpt.getChannel();
+
+ broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
+ break;
case MAINT:
- broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
+ if( mChannelType == ChannelType.STANDARD )
+ {
+ /**
+ * When we receive a MAINT message and we're configured
+ * as a standard channel, we need to apply the call
+ * timeout specified by the user. Otherwise we'll
+ * be using the shorter default call timeout
+ */
+ broadcast( new ChangeChannelTimeoutEvent( this,
+ mChannelType, mCallTimeout ) );
+
+ if( mCurrentCallEvent == null )
+ {
+ mCurrentCallEvent = new MPT1327CallEvent.Builder( CallEventType.CALL )
+ .aliasList( getAliasList() )
+ .channel( String.valueOf( mChannelNumber ) )
+ .details( "MONITORED TRAFFIC CHANNEL" )
+ .frequency( mFrequency )
+ .to( mpt.getToID() )
+ .build();
+
+ broadcast( mCurrentCallEvent );
+ }
+
+ broadcast(new Metadata(MetadataType.TO,
+ mpt.getToID(), mpt.getToIDAlias(), true));
+
+ broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
+
+ setToTalkgroup( mpt.getToID() );
+ }
break;
default:
break;
@@ -276,6 +317,25 @@ public class MPT1327DecoderState extends DecoderState
mToTalkgroup = null;
broadcast( ChangedAttribute.TO_TALKGROUP );
+
+ /**
+ * If this is a standard channel, reset the fade timeout to the default
+ * timeout. Once processing is underway, if we get a MAINT message,
+ * this indicates we're processing a traffic channel as a standard
+ * channel, so we'll issue a different call timeout then.
+ */
+ if( mChannelType == ChannelType.STANDARD )
+ {
+ broadcast( new ChangeChannelTimeoutEvent( this, mChannelType,
+ DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000 ) );
+
+ if( mCurrentCallEvent != null )
+ {
+ mCurrentCallEvent.end();
+ broadcast( mCurrentCallEvent );
+ mCurrentCallEvent = null;
+ }
+ }
}
public String getSite()
@@ -552,4 +612,4 @@ public class MPT1327DecoderState extends DecoderState
break;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java
index 0ffe62ce..55340dba 100644
--- a/src/module/decode/p25/P25DecoderState.java
+++ b/src/module/decode/p25/P25DecoderState.java
@@ -437,7 +437,7 @@ public class P25DecoderState extends DecoderState
{
if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION )
{
- broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
+ broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
if( mCurrentCallEvent != null )
{
@@ -1057,7 +1057,7 @@ public class P25DecoderState extends DecoderState
}
break;
case CALL_TERMINATION_OR_CANCELLATION:
- broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
+ broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
mCurrentCallEvent = null;
if( !( ldu instanceof CallTermination ) )
diff --git a/src/module/decode/state/ChannelState.java b/src/module/decode/state/ChannelState.java
index 3b538349..450ed92d 100644
--- a/src/module/decode/state/ChannelState.java
+++ b/src/module/decode/state/ChannelState.java
@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014,2015 Dennis Sheirer
+ * Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,20 +17,14 @@
******************************************************************************/
package module.decode.state;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-import module.Module;
-import module.decode.config.DecodeConfiguration;
-import module.decode.event.CallEvent;
-import module.decode.event.ICallEventProvider;
-import module.decode.state.DecoderStateEvent.Event;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import sample.Listener;
import audio.metadata.IMetadataProvider;
import audio.metadata.Metadata;
import audio.metadata.MetadataReset;
@@ -40,38 +34,13 @@ import audio.squelch.SquelchState;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import controller.channel.Channel.ChannelType;
+import module.Module;
+import module.decode.config.DecodeConfiguration;
+import module.decode.event.CallEvent;
+import module.decode.event.ICallEventProvider;
+import module.decode.state.DecoderStateEvent.Event;
+import sample.Listener;
-/**
- * Channel state tracks the overall state of all processing modules and decoders
- * configured for the channel and provides squelch control events.
- *
- * Primary channel state provides a state machine for tracking decode events
- * during a voice or data call through it's life cycle of CALL/DATA, FADE, END,
- * and then back to an IDLE state. This class also provides squelch control
- * to support any audio processing.
- *
- * State Descriptions:
- * IDLE No voice or data call activity
- * CALL/DATA A voice or data call has started, or is continuing
- * CONTROL Control channel
- * FADE The phase after a voice or data call when either an explicit
- * call end has been received, or when no new signalling updates
- * have been received, and the fade timer has expired. This phase
- * allows for gui updates to signal to the user that the call is
- * ended, while continuing to display the call details for the user
- * END The phase after the voice or data call FADE, when the call is
- * now over, and the gui should be reset to IDLE.
- *
- * When a call is started, invoke mTimerService.startFadeTimer() to start the
- * call fade monitoring timer process that will automatically signal a call end
- * once no more call signalling has been detected after the fade timeout has
- * occurred, by invoking the fade() method.
- *
- * When a call ends, invoke the mTimerService.stopFadeTimer() and then invoke
- * the mTimerService.startResetTimer(). Once the reset timeout occurs,
- * the reset() method will be called, allowing the channel state to perform
- * any reset actions, like clearing status and id labels on any view panels.
- */
public class ChannelState extends Module implements ICallEventProvider,
IChangedAttributeProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider
@@ -83,8 +52,6 @@ public class ChannelState extends Module implements ICallEventProvider,
public static final long RESET_TIMEOUT_DELAY = 2000;
private State mState = State.IDLE;
- private long mTrafficChannelTimeout =
- DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS;
private Listener mCallEventListener;
private Listener mChangedAttributeListener;
@@ -97,12 +64,15 @@ public class ChannelState extends Module implements ICallEventProvider,
private boolean mSquelchLocked = false;
private boolean mSelected = false;
+ private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
+ private long mTrafficChannelFadeTimeout =
+ DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
private long mFadeTimeout;
- private long mResetTimeout;
+ private long mEndTimeout;
private StateMonitor mStateMonitor = new StateMonitor();
private AtomicBoolean mMonitoring = new AtomicBoolean();
- private ScheduledFuture> mRunningMonitor;
+ private ScheduledFuture> mStateMonitorFuture;
private ThreadPoolManager mThreadPoolManager;
private ChannelType mChannelType;
@@ -110,32 +80,97 @@ public class ChannelState extends Module implements ICallEventProvider,
private TrafficChannelStatusListener mTrafficChannelStatusListener;
private CallEvent mTrafficChannelCallEvent;
+ /**
+ * Channel state tracks the overall state of all processing modules and decoders
+ * configured for the channel and provides squelch control and decoder state
+ * reset events.
+ *
+ * Uses a state enumeration that defines allowable channel state transitions in
+ * order to track a call or data decode event from start to finish. Uses a
+ * timer to monitor for inactivity and to provide a FADE period that indicates
+ * to the user that the activity has stopped while continuing to provide details
+ * about the call, before the state is reset to IDLE.
+ *
+ * Since this class is multi-threaded between decoder events and the internal
+ * timer that monitors for inactivity, this class uses the mState object for
+ * thread synchronization.
+ *
+ * State Descriptions:
+ * IDLE Normal state. No voice or data call activity
+ * CALL/DATA/ENCRYPTED/CONTROL
+ * Decoding states.
+ * FADE The phase after a voice or data call when either an explicit
+ * call end has been received, or when no new signalling updates
+ * have been received, and the fade timer has expired. This phase
+ * allows for gui updates to signal to the user that the call is
+ * ended, while continuing to display the call details for the user
+ * TEARDOWN Indicates a traffic channel that will be torn down for reuse.
+ */
public ChannelState( ThreadPoolManager manager, ChannelType channelType )
{
mThreadPoolManager = manager;
mChannelType = channelType;
}
+ /**
+ * Resets this channel state and prepares it for reuse.
+ */
@Override
public void reset()
{
+ broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
+ broadcast( new MetadataReset() );
+
+ mState = State.IDLE;
+ broadcast( ChangedAttribute.CHANNEL_STATE );
}
@Override
public void start()
{
+ //Start a monitoring task to check for FADE and END event timeouts
+ if( mMonitoring.compareAndSet( false, true ) )
+ {
+ if( mStateMonitorFuture == null && mStateMonitor != null )
+ {
+ try
+ {
+ mStateMonitorFuture = mThreadPoolManager.scheduleFixedRate(
+ ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
+ }
+ catch( RejectedExecutionException ree )
+ {
+ mLog.error( "state monitor scheduled task rejected", ree );
+ mMonitoring.set( false );
+ }
+ }
+ }
}
@Override
public void stop()
{
mSquelchLocked = false;
+
+ //Shutdown the state montoring task
+ if( mMonitoring.compareAndSet( true, false ) )
+ {
+ if( mStateMonitorFuture != null )
+ {
+ boolean success = mThreadPoolManager.cancel( mStateMonitorFuture );
+
+ if( !success )
+ {
+ mLog.error("Couldn't stop monitoring scheduled future" );
+ }
+ }
+
+ mStateMonitorFuture = null;
+ }
}
public void dispose()
{
- stopMonitor();
-
mCallEventListener = null;
mChangedAttributeListener = null;
mDecoderStateListener = null;
@@ -144,9 +179,34 @@ public class ChannelState extends Module implements ICallEventProvider,
mStateMonitor = null;
}
+ private boolean isStandardChannel()
+ {
+ return mChannelType == ChannelType.STANDARD;
+ }
+
+ private boolean isTrafficChannel()
+ {
+ return mChannelType == ChannelType.TRAFFIC;
+ }
+
+ public void setStandardChannelTimeout( long milliseconds )
+ {
+ mStandardChannelFadeTimeout = milliseconds;
+
+ if( mChannelType == ChannelType.STANDARD )
+ {
+ mFadeTimeout = mStandardChannelFadeTimeout;
+ }
+ }
+
public void setTrafficChannelTimeout( long milliseconds )
{
- mTrafficChannelTimeout = milliseconds;
+ mTrafficChannelFadeTimeout = milliseconds;
+
+ if( mChannelType == ChannelType.TRAFFIC )
+ {
+ mFadeTimeout = mTrafficChannelFadeTimeout;
+ }
}
public void setSelected( boolean selected )
@@ -164,71 +224,18 @@ public class ChannelState extends Module implements ICallEventProvider,
return mState;
}
- private boolean isIdleOrEndingState()
- {
- return mState == State.FADE ||
- mState == State.END ||
- mState == State.IDLE;
- }
-
-
- /**
- * Starts the state monitor running when we're not in IDLE state. Calls to
- * start an already started monitor are ignored.
- */
- private void startMonitor()
- {
- if( mMonitoring.compareAndSet( false, true ) )
- {
- if( mRunningMonitor == null && mStateMonitor != null )
- {
- mRunningMonitor = mThreadPoolManager.scheduleFixedRate(
- ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
- }
- else
- {
- throw new RuntimeException( "Channel state monitor's scheduled "
- + "future pointer was not null" );
- }
- }
- }
-
- /**
- * Stops the state monitor when we're in an IDLE state. Calls to stop an
- * already stopped monitor are ignored.
- */
- private void stopMonitor()
- {
- if( mMonitoring.compareAndSet( true, false ) )
- {
- if( mRunningMonitor != null )
- {
- mThreadPoolManager.cancel( mRunningMonitor );
- }
-
- mRunningMonitor = null;
- }
- }
-
/**
* Updates the fade timeout threshold to the current time plus delay
*/
private void updateFadeTimeout()
{
- /* Standard channel uses the FADE_TIMEOUT_DELAY and traffic channels
- * use the TRAFFIC_FADE_TIMEOUT_DELAY. Some traffic channels (e.g. MPT)
- * don't have signaling on the traffic channel that can be used to
- * update the fade timeout, so we set the timeout value larger to ensure
- * the channel doesn't fade before the end of the call. */
- long current = System.currentTimeMillis();
-
- long timeout = current +
- ( mChannelType == ChannelType.STANDARD ? FADE_TIMEOUT_DELAY :
- mTrafficChannelTimeout );
-
- if( timeout > mFadeTimeout )
+ if( isTrafficChannel() )
{
- mFadeTimeout = timeout;
+ mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
+ }
+ else
+ {
+ mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
}
}
@@ -237,13 +244,13 @@ public class ChannelState extends Module implements ICallEventProvider,
*/
private void updateResetTimeout()
{
- if( mChannelType == ChannelType.TRAFFIC )
+ if( isTrafficChannel() )
{
- mResetTimeout = System.currentTimeMillis();
+ mEndTimeout = System.currentTimeMillis();
}
else
{
- mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
+ mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
}
}
@@ -285,120 +292,120 @@ public class ChannelState extends Module implements ICallEventProvider,
*/
protected void setState( State state )
{
- if( mState == state )
+ synchronized( mState )
{
- switch( state )
+ if( state == mState )
{
- case CALL:
- case CONTROL:
- case DATA:
- case ENCRYPTED:
- updateFadeTimeout();
- break;
- default:
- break;
- }
- }
- else if( mState.canChangeTo( state ) )
- {
- switch( state )
- {
- case CONTROL:
- //Don't allow traffic channels to be control channels,
- //otherwise they won't get call tear down
- if( mChannelType != ChannelType.TRAFFIC )
- {
- broadcast( SquelchState.SQUELCH );
- updateFadeTimeout();
- startMonitor();
- mState = state;
- broadcast( ChangedAttribute.CHANNEL_STATE );
- }
- break;
- case DATA:
- case ENCRYPTED:
- broadcast( SquelchState.SQUELCH );
- updateFadeTimeout();
- startMonitor();
- mState = state;
- broadcast( ChangedAttribute.CHANNEL_STATE );
- break;
- case CALL:
- broadcast( SquelchState.UNSQUELCH );
- updateFadeTimeout();
- startMonitor();
- mState = state;
- broadcast( ChangedAttribute.CHANNEL_STATE );
- break;
- case FADE:
- broadcast( SquelchState.SQUELCH );
- updateResetTimeout();
- startMonitor();
- mState = state;
- broadcast( ChangedAttribute.CHANNEL_STATE );
- break;
- case END:
- broadcast( SquelchState.SQUELCH );
- broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
- broadcast( new MetadataReset() );
- mState = state;
- broadcast( ChangedAttribute.CHANNEL_STATE );
- break;
- case IDLE:
- broadcast( SquelchState.SQUELCH );
- stopMonitor();
-
- if( mChannelType == ChannelType.TRAFFIC )
- {
- if( mTrafficChannelStatusListener != null )
- {
- mTrafficChannelStatusListener.callEnd();
- mTrafficChannelStatusListener = null;
- }
-
- if( mTrafficChannelCallEvent != null )
- {
- mTrafficChannelCallEvent.end();
-
- broadcast( mTrafficChannelCallEvent );
- mTrafficChannelCallEvent = null;
- }
- }
-
- mState = state;
- broadcast( ChangedAttribute.CHANNEL_STATE );
- break;
- default:
- break;
- }
- }
- }
-
- public class StateMonitor implements Runnable
- {
- @Override
- public void run()
- {
- if( getState() == State.FADE )
- {
- if( mResetTimeout <= System.currentTimeMillis() )
+ if( State.CALL_STATES.contains( state ) )
{
- setState( State.END );
+ updateFadeTimeout();
}
}
- else if( getState() == State.END )
+ else if( state != mState && mState.canChangeTo( state ) )
{
- setState( State.IDLE );
+ switch( state )
+ {
+ case CONTROL:
+ //Don't allow traffic channels to be control channels,
+ //otherwise they can't transition to call tear down
+ if( isStandardChannel() )
+ {
+ broadcast( SquelchState.SQUELCH );
+ updateFadeTimeout();
+ mState = state;
+ broadcast( ChangedAttribute.CHANNEL_STATE );
+ }
+ break;
+ case DATA:
+ case ENCRYPTED:
+ broadcast( SquelchState.SQUELCH );
+ updateFadeTimeout();
+ mState = state;
+ broadcast( ChangedAttribute.CHANNEL_STATE );
+ break;
+ case CALL:
+ broadcast( SquelchState.UNSQUELCH );
+ updateFadeTimeout();
+ mState = state;
+ broadcast( ChangedAttribute.CHANNEL_STATE );
+ break;
+ case FADE:
+ processFadeState();
+ break;
+ case IDLE:
+ processIdleState();
+ break;
+ case TEARDOWN:
+ processTeardownState();
+ break;
+ case RESET:
+ mState = State.IDLE;
+ break;
+ default:
+ break;
+ }
}
else
{
- if( mFadeTimeout <= System.currentTimeMillis() )
- {
- setState( State.FADE );
- }
+// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
}
}
}
+
+ /**
+ * Sets the state and processes related actions
+ */
+ private void processFadeState()
+ {
+ updateResetTimeout();
+ mState = State.FADE;
+
+ broadcast( SquelchState.SQUELCH );
+ broadcast( ChangedAttribute.CHANNEL_STATE );
+ }
+
+ private void processIdleState()
+ {
+ broadcast( SquelchState.SQUELCH );
+
+ if( mState == State.FADE )
+ {
+ broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
+ broadcast( new MetadataReset() );
+ }
+
+ mState = State.IDLE;
+
+ broadcast( ChangedAttribute.CHANNEL_STATE );
+ }
+
+ private void processTeardownState()
+ {
+ broadcast( SquelchState.SQUELCH );
+
+ mState = State.TEARDOWN;
+
+ broadcast( ChangedAttribute.CHANNEL_STATE );
+
+ if( mTrafficChannelCallEvent != null )
+ {
+ mTrafficChannelCallEvent.end();
+
+ broadcast( mTrafficChannelCallEvent );
+ mTrafficChannelCallEvent = null;
+ }
+
+ if( mTrafficChannelStatusListener != null )
+ {
+ mTrafficChannelStatusListener.callEnd();
+ mTrafficChannelStatusListener = null;
+ }
+ else
+ {
+ mLog.error( "Traffic channel status listener was null - couldn't "
+ + "teardown the channel");
+ }
+ }
/**
* Broadcasts the call event to the registered listener
@@ -590,35 +597,95 @@ public class ChannelState extends Module implements ICallEventProvider,
broadcast( SquelchState.UNSQUELCH );
mSquelchLocked = true;
break;
+ case CHANGE_CALL_TIMEOUT:
+ if( event instanceof ChangeChannelTimeoutEvent )
+ {
+ ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
+
+ if( timeout.getChannelType() == ChannelType.STANDARD )
+ {
+ setStandardChannelTimeout( timeout.getCallTimeout() );
+ }
+ else
+ {
+ setTrafficChannelTimeout( timeout.getCallTimeout() );
+ }
+ }
case CONTINUATION:
- if( isIdleOrEndingState() )
- {
- setState( event.getState() );
- }
- else
- {
- updateFadeTimeout();
- }
- break;
case DECODE:
- if( getState() == State.IDLE )
+ case START:
+ if( State.CALL_STATES.contains( event.getState() ) )
{
setState( event.getState() );
}
break;
case END:
- setState( State.FADE );
+ if( isTrafficChannel() )
+ {
+ setState( State.TEARDOWN );
+ }
+ else
+ {
+ setState( State.FADE );
+ }
break;
case RESET:
/* Channel State does not respond to reset events */
break;
- case START:
- setState( event.getState() );
- break;
default:
break;
}
}
}
}
-}
+
+ /**
+ * Monitors decoder state events to automatically transition the channel
+ * state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
+ * decoding stops or the monitored channel returns to a no signal state.
+ *
+ * Provides a FADE transition state to allow for momentary decoding dropouts
+ * and to allow the user access to call details for a fade period upon
+ * call end.
+ */
+ public class StateMonitor implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ synchronized( mState )
+ {
+ if( State.CALL_STATES.contains( mState ) &&
+ mFadeTimeout <= System.currentTimeMillis() )
+ {
+ mState = State.FADE;
+ processFadeState();
+ }
+ else if( mState == State.FADE &&
+ mEndTimeout <= System.currentTimeMillis() )
+ {
+ if( isTrafficChannel() )
+ {
+ processTeardownState();
+ }
+ else
+ {
+ processIdleState();
+ }
+ }
+ }
+ }
+ catch( Exception e )
+ {
+ mLog.error( "Exception thrown while state monitor is running " +
+ "- state [" + getState() +
+ "] current [" + System.currentTimeMillis() +
+ "] mResetTimeout [" + mEndTimeout +
+ "] mFadeTimeout [" + mFadeTimeout +
+ "]", e );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/module/decode/state/ChannelStatePanel.java b/src/module/decode/state/ChannelStatePanel.java
index e133989f..9b9dd7cb 100644
--- a/src/module/decode/state/ChannelStatePanel.java
+++ b/src/module/decode/state/ChannelStatePanel.java
@@ -254,38 +254,8 @@ public class ChannelStatePanel extends JPanel
Paint p = null;
- final Channel channel = mChannel;
-
- final ChannelState channelState = ( channel == null ? null : channel.getChannelState() );
-
- if( channelState != null )
+ switch( mChannelState.getState() )
{
-<<<<<<< Upstream, based on origin/master
- switch( channelState.getState() )
- {
- case CALL:
- p = getGradient( mColorTopCall, mColorMiddleCall );
- break;
- case CONTROL:
- p = getGradient( mColorTopControl, mColorMiddleControl );
- break;
- case DATA:
- p = getGradient( mColorTopData, mColorMiddleData );
- break;
- case FADE:
- p = getGradient( mColorTopFade, mColorMiddleFade );
- break;
- case IDLE:
- default:
- p = getGradient( mColorTopIdle, mColorMiddleIdle );
- break;
- }
- }
-
- if( p == null )
- {
- p = getGradient( mColorTopIdle, mColorMiddleIdle );
-=======
case CALL:
p = getGradient( mColorTopCall, mColorMiddleCall );
break;
@@ -298,13 +268,11 @@ public class ChannelStatePanel extends JPanel
case FADE:
p = getGradient( mColorTopFade, mColorMiddleFade );
break;
- case END:
case IDLE:
default:
p = getGradient( mColorTopIdle, mColorMiddleIdle );
break;
->>>>>>> 0e01381 Updates.
- }
+ }
g2.setPaint( p );
g2.fillRect( 0, 0, getWidth(), getHeight() );
diff --git a/src/module/decode/state/DecoderStateEvent.java b/src/module/decode/state/DecoderStateEvent.java
index 19623bbc..a4ebd68f 100644
--- a/src/module/decode/state/DecoderStateEvent.java
+++ b/src/module/decode/state/DecoderStateEvent.java
@@ -56,6 +56,7 @@ public class DecoderStateEvent
public enum Event
{
ALWAYS_UNSQUELCH,
+ CHANGE_CALL_TIMEOUT,
CONTINUATION,
DECODE,
END,
@@ -64,4 +65,4 @@ public class DecoderStateEvent
START,
TRAFFIC_CHANNEL_ALLOCATION;
}
-}
+}
\ No newline at end of file
diff --git a/src/module/decode/state/State.java b/src/module/decode/state/State.java
index 2820429d..1f1bc79b 100644
--- a/src/module/decode/state/State.java
+++ b/src/module/decode/state/State.java
@@ -1,5 +1,27 @@
+/*******************************************************************************
+ * SDR Trunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ ******************************************************************************/
package module.decode.state;
+import java.util.EnumSet;
+
+/**
+ * Details the set of states for a channel and the allowable transition states
+ */
public enum State
{
IDLE( "IDLE" )
@@ -7,7 +29,8 @@ public enum State
@Override
public boolean canChangeTo( State state )
{
- return true;
+ return state != TEARDOWN &&
+ state != RESET;
}
},
CALL( "CALL" )
@@ -15,11 +38,11 @@ public enum State
@Override
public boolean canChangeTo( State state )
{
- return state == CALL ||
- state == CONTROL ||
+ return state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
- state == FADE;
+ state == FADE ||
+ state == TEARDOWN;
}
},
DATA( "DATA" )
@@ -29,9 +52,9 @@ public enum State
{
return state == CALL ||
state == CONTROL ||
- state == DATA ||
- state == State.ENCRYPTED ||
- state == FADE;
+ state == ENCRYPTED ||
+ state == FADE ||
+ state == TEARDOWN;
}
},
ENCRYPTED( "ENCRYPTED" )
@@ -39,7 +62,7 @@ public enum State
@Override
public boolean canChangeTo( State state )
{
- return state == FADE;
+ return state == FADE || state == TEARDOWN;
}
},
CONTROL( "CONTROL" )
@@ -47,7 +70,8 @@ public enum State
@Override
public boolean canChangeTo( State state )
{
- return true;
+ return state == IDLE ||
+ state == FADE;
}
},
FADE( "FADE" )
@@ -55,20 +79,36 @@ public enum State
@Override
public boolean canChangeTo( State state )
{
- return state != FADE; //All states except fade allowed
- }
+ return state != FADE &&
+ state != RESET;
+ }
},
- END( "END" )
+ RESET( "RESET" )
{
- @Override
- public boolean canChangeTo( State state )
- {
- return state != FADE; //Fade is only disallowed state
- }
- };
+ @Override
+ public boolean canChangeTo(State state)
+ {
+ return state == IDLE;
+ }
+ },
+
+ TEARDOWN( "TEARDOWN" )
+ {
+ @Override
+ public boolean canChangeTo(State state)
+ {
+ return state == RESET;
+ }
+ };
private String mDisplayValue;
+ public static final EnumSet CALL_STATES =
+ EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED );
+
+ public static final EnumSet IDLE_STATES =
+ EnumSet.of( IDLE, FADE );
+
private State( String displayValue )
{
mDisplayValue = displayValue;
@@ -80,4 +120,4 @@ public enum State
{
return mDisplayValue;
}
-}
+}
\ No newline at end of file
diff --git a/src/playlist/PlaylistManager.java b/src/playlist/PlaylistManager.java
index ae97cfbb..167f5180 100644
--- a/src/playlist/PlaylistManager.java
+++ b/src/playlist/PlaylistManager.java
@@ -66,7 +66,7 @@ public class PlaylistManager implements ChannelEventListener
private boolean mPlaylistLoading = false;
/**
- * Playlist manager - manages all channel configurationc, channel maps, and
+ * Playlist manager - manages all channel configurations, channel maps, and
* alias lists and handles loading or persisting to a playlist.xml file
*
* Monitors playlist changes to automatically save configuration changes
@@ -103,8 +103,11 @@ public class PlaylistManager implements ChannelEventListener
mPlaylistLoading = true;
+ mLog.debug( "Transferring aliases to model" );
mAliasModel.addAliases( mPlaylist.getAliases() );
+ mLog.debug( "Transferring channels to model" );
mChannelModel.addChannels( mPlaylist.getChannels() );
+ mLog.debug( "Transfer to models is complete" );
mPlaylistLoading = false;
}
@@ -126,48 +129,6 @@ public class PlaylistManager implements ChannelEventListener
props.get( "playlist.currentfilename", defaultPlaylistFile );
load( playlistFolder.resolve( playlistFile ) );
-
- transferPlaylistToModels();
- }
-
- /**
- * Channel event listener method. Monitors channel events for events that
- * indicate that the playlist has changed and queues automatic playlist
- * saving.
- */
- @Override
- public void channelChanged( ChannelEvent event )
- {
- //Only save playlist for changes to standard channels (not traffic)
- if( event.getChannel().getChannelType() == ChannelType.STANDARD )
- {
- switch( event.getEvent() )
- {
- case NOTIFICATION_ADD:
- case NOTIFICATION_CONFIGURATION_CHANGE:
- case NOTIFICATION_DELETE:
- case NOTIFICATION_PROCESSING_START:
- case NOTIFICATION_PROCESSING_STOP:
- schedulePlaylistSave();
- break;
- case NOTIFICATION_ENABLE_REJECTED:
- case NOTIFICATION_SELECTION_CHANGE:
- case NOTIFICATION_STATE_RESET:
- case REQUEST_DELETE:
- case REQUEST_DESELECT:
- case REQUEST_DISABLE:
- case REQUEST_ENABLE:
- case REQUEST_SELECT:
- //Do nothing for these event types
- break;
- default:
- //When a new event enum entry is added and received, throw an
- //exception here to ensure developer adds support for the
- //use case
- throw new IllegalArgumentException( "Unrecognized Channel "
- + "Event [" + event.getEvent().name() + "]" );
- }
- }
}
/**
@@ -426,7 +387,6 @@ public class PlaylistManager implements ChannelEventListener
saveRequired = true;
}
-
//Check for and convert from legacy play list format
if( mPlaylist.hasSystemList() || mPlaylist.hasAliasDirectory() )
{
diff --git a/src/source/mixer/MixerChannelConfiguration.java b/src/source/mixer/MixerChannelConfiguration.java
index 0be3b358..b8520bcc 100644
--- a/src/source/mixer/MixerChannelConfiguration.java
+++ b/src/source/mixer/MixerChannelConfiguration.java
@@ -23,6 +23,14 @@ public class MixerChannelConfiguration
return mMixerChannel;
}
+ public boolean matches( String mixer, String channels )
+ {
+ return mixer != null &&
+ channels != null &&
+ mMixer.getMixerInfo().getName().contentEquals( mixer ) &&
+ mMixerChannel.name().contentEquals( channels );
+ }
+
public String toString()
{
StringBuilder sb = new StringBuilder();
diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java
index 44682e7a..faf36961 100644
--- a/src/source/tuner/TunerController.java
+++ b/src/source/tuner/TunerController.java
@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
- * Copyright (C) 2014,2015 Dennis Sheirer
+ * Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,7 +28,6 @@ import source.SourceException;
import source.tuner.frequency.FrequencyController;
import source.tuner.frequency.FrequencyController.Tunable;
import source.tuner.frequency.IFrequencyChangeProcessor;
-import source.tuner.frequency.IFrequencyChangeListener;
import controller.ThreadPoolManager;
public abstract class TunerController implements Tunable
@@ -37,11 +36,7 @@ public abstract class TunerController implements Tunable
LoggerFactory.getLogger( TunerController.class );
/* List of currently tuned channels being served to demod channels */
-<<<<<<< Upstream, based on origin/master
private SortedSet mTunedChannels = new ConcurrentSkipListSet<>();
-=======
- protected ArrayList mTunedChannels = new ArrayList<>();
->>>>>>> 20e3bee Work in progress. -Started work on new channel editor channel viewer/controller -Removed a number of unused buffer package classes -Moved automatic frequency control to be a module and removed special handling -Added IUnfilteredRealBufferListener & Provider interfaces -Completely removed the ResourceManager class
protected FrequencyController mFrequencyController;
private int mMiddleUnusable;
private double mUsableBandwidthPercentage;