Initial commit of redesigned internal processing and audio management.

Note: Several of the decoders are currently non-functioning in this
commit.  If you want the latest functioning codebase, please checkout
the commit prior to this one.  All decoders will eventually be updated
with this new architecture.

Currently broken:
- Logging and recording
- Aux decoders (Fleetsync/MDC/LoJack/Tait)
- LTR/Passport decoders

Changes in this commit:
- Module architecture for decoders and components with central
provisioning by the DecoderFactory
- Transition from Complex sample stream processing to float buffer
processing for improved memory management
- Redesigned filter structures - to support buffers and streams
- Resolved several memory leak issues
- Universal sharing of the thread pool manager
- Added centralized audio management with call/user prioritization and
new gui with mute/volume controls
- Embedded audio metadata for eventual streaming support
- Redesigned ProcessingChain to support independent re-use
- Added new TrafficChannelManager to handle traffic channel allocations
- Traffic Channel pool size and Call timeout available in P25 and
MPT1327 configs
- Redesigned channel and decoder gui components
This commit is contained in:
denny
2015-09-01 07:10:26 -04:00
parent 538a299246
commit f4d8ea281d
570 changed files with 21578 additions and 17012 deletions

View File

@@ -17,17 +17,13 @@
******************************************************************************/
package controller.channel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@@ -35,12 +31,23 @@ import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import message.Message;
import module.Module;
import module.ProcessingChain;
import module.decode.DecoderFactory;
import module.decode.config.AuxDecodeConfiguration;
import module.decode.config.DecodeConfigFactory;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEventModel;
import module.decode.event.MessageActivityModel;
import module.decode.state.ChannelState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.config.RecordConfiguration;
import sample.Listener;
import source.Source;
import source.SourceException;
import source.SourceType;
import source.config.SourceConfigFactory;
import source.config.SourceConfigRecording;
@@ -48,19 +55,15 @@ import source.config.SourceConfigTuner;
import source.config.SourceConfiguration;
import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type;
import source.tuner.frequency.AutomaticFrequencyControl;
import alias.AliasList;
import controller.ResourceManager;
import controller.activity.ActivitySummaryFrame;
import controller.channel.ChannelEvent.Event;
import controller.config.Configuration;
import controller.site.Site;
import controller.state.ChannelState;
import controller.state.ChannelState.State;
import controller.system.System;
import decode.config.AuxDecodeConfiguration;
import decode.config.DecodeConfigFactory;
import decode.config.DecodeConfiguration;
import eventlog.MessageEventLogger;
import eventlog.config.EventLogConfiguration;
import filter.FilterSet;
@XmlSeeAlso( { Configuration.class } )
@XmlRootElement( name = "channel" )
@@ -68,15 +71,16 @@ public class Channel extends Configuration
{
private final static Logger mLog = LoggerFactory.getLogger( Channel.class );
private static final boolean ENABLED = true;
private static final boolean DISABLED = false;
private static final boolean BROADCAST_CHANGE = true;
public enum ChannelType
{
STANDARD,
TRAFFIC
};
private static final boolean ENABLED = true;
private static final boolean DISABLED = false;
private static final boolean BROADCAST_CHANGE = true;
private CopyOnWriteArrayList<ChannelEventListener> mChannelListeners =
new CopyOnWriteArrayList<ChannelEventListener>();
@@ -84,8 +88,7 @@ public class Channel extends Configuration
private CopyOnWriteArrayList<Listener<Message>> mMessageListeners =
new CopyOnWriteArrayList<Listener<Message>>();
private HashMap<Integer,Channel> mTrafficChannels =
new HashMap<Integer,Channel>();
private Map<String,Channel> mTrafficChannels = new HashMap<>();
private DecodeConfiguration mDecodeConfiguration =
DecodeConfigFactory.getDefaultDecodeConfiguration();
@@ -105,7 +108,15 @@ public class Channel extends Configuration
private boolean mEnabled;
private boolean mSelected;
private ChannelType mChannelType;
private CallEventModel mCallEventModel;
private MessageActivityModel mMessageActivityModel;
private MessageEventLogger mEventLogger;
private ProcessingChain mProcessingChain;
private ChannelState mChannelState;
private long mTrafficChannelTimeout =
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS;
/**
* Constructs a new standard channel with a default name of "New Channel"
@@ -135,34 +146,46 @@ public class Channel extends Configuration
mChannelType = channelType;
}
public ChannelType getChannelType()
{
return mChannelType;
}
/**
* Indicates if the channel has Automatic or Direct Frequency Control
*/
public boolean hasFrequencyControl()
{
return mProcessingChain != null &&
mProcessingChain.getDecoder().hasFrequencyCorrectionControl();
mProcessingChain.hasFrequencyCorrectionControl();
}
public long getFrequencyCorrection()
{
if( mProcessingChain != null )
if( hasFrequencyControl() )
{
return mProcessingChain.getDecoder().getFrequencyCorrection();
return mProcessingChain.getFrequencyCorrectionControl().getErrorCorrection();
}
return 0;
}
/**
* Sets the channel state traffic channel call end timer to the specified
* value.
*/
public void setTrafficChannelTimeout( long milliseconds )
{
mTrafficChannelTimeout = milliseconds;
if( mChannelState != null )
{
mChannelState.setTrafficChannelTimeout( milliseconds );
}
}
/**
* Sets the resource manager responsible for this channel and registers
* the channel manager as a listener to this channel. Fires channel add
* channel event to the newly added channel manager channel listener.
*
* Note: resource manager instance is applied after object construction
* since we construct the channel objects from the xml playlist
*/
public void setResourceManager( ResourceManager resourceManager )
{
@@ -170,7 +193,7 @@ public class Channel extends Configuration
/* Add system-wide channel listeners onto this channel */
addListeners( mResourceManager.getChannelManager().getChannelListeners() );
/* If we're enabled, fire enable changed event to get processing started */
if( mEnabled )
{
@@ -200,7 +223,7 @@ public class Channel extends Configuration
*/
public boolean isProcessing()
{
return mProcessingChain != null && mProcessingChain.isProcessing();
return mProcessingChain != null && mProcessingChain.processing();
}
/**
@@ -216,100 +239,51 @@ public class Channel extends Configuration
{
mSelected = selected;
if( mProcessingChain != null &&
mProcessingChain.isProcessing() &&
mProcessingChain.getAudioOutput() != null )
{
mProcessingChain.getAudioOutput().setAudioPlaybackEnabled( selected );
}
fireChannelEvent( Event.CHANGE_SELECTED );
}
public JMenu getContextMenu()
@XmlTransient
public CallEventModel getCallEventModel()
{
JMenu menu = new JMenu( mSite + "-" + mName );
if( mEnabled )
{
JMenuItem disable = new JMenuItem( "Disable" );
disable.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setEnabled( DISABLED, BROADCAST_CHANGE );
getResourceManager().getPlaylistManager().save();
}
} );
menu.add( disable );
menu.add( new JSeparator() );
JMenuItem actySummaryItem =
new JMenuItem( "Activity Summary" );
actySummaryItem.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
if( mResourceManager != null )
{
ProcessingChain chain = getProcessingChain();
if( chain != null )
{
ChannelState state = chain.getChannelState();
if( state != null )
{
String summary = state.getActivitySummary();
new ActivitySummaryFrame( summary,
mResourceManager.getController().getTree() );
}
}
}
}
} );
menu.add( actySummaryItem );
}
else
{
JMenuItem enable = new JMenuItem( "Enable" );
enable.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setEnabled( ENABLED, BROADCAST_CHANGE );
getResourceManager().getPlaylistManager().save(); }
} );
menu.add( enable );
}
menu.add( new JSeparator() );
JMenuItem deleteItem = new JMenuItem( "Delete" );
deleteItem.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
dispose();
getResourceManager().getPlaylistManager().save(); }
} );
menu.add( deleteItem );
return menu;
return mCallEventModel;
}
/**
* Returns the MessageEventLogger for this processing chain
*/
@XmlTransient
public MessageEventLogger getEventLogger()
{
return mEventLogger;
}
/**
* Sets the MessageEventLogger for this processing chain
*/
public void setEventLogger( MessageEventLogger eventLogger )
{
mEventLogger = eventLogger;
}
@XmlTransient
public MessageActivityModel getMessageActivityModel()
{
return mMessageActivityModel;
}
@XmlTransient
public ChannelState getChannelState()
{
return mChannelState;
}
@XmlTransient
public ChannelType getChannelType()
{
return mChannelType;
}
/**
* Orderly shutdown method when this channel is going to be deleted.
*/
@@ -393,6 +367,11 @@ public class Channel extends Configuration
return mSystem;
}
public boolean hasSystem()
{
return mSystem != null;
}
/**
* Sets the owning system for this channel and optionally broadcasts a
* system change channel event.
@@ -415,6 +394,11 @@ public class Channel extends Configuration
return mSite;
}
public boolean hasSite()
{
return mSite != null;
}
/**
* Sets the owning site for this channel and optionally broadcasts a site
* change channel event.
@@ -468,7 +452,14 @@ public class Channel extends Configuration
if( fireChannelEvent )
{
fireChannelEvent( Event.CHANGE_ENABLED );
if( enabled )
{
fireChannelEvent( Event.CHANNEL_ENABLED );
}
else
{
fireChannelEvent( Event.CHANNEL_DISABLED );
}
}
}
@@ -522,11 +513,6 @@ public class Channel extends Configuration
public void setDecodeConfiguration( DecodeConfiguration config )
{
mDecodeConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateDecoder();
}
}
/**
@@ -544,11 +530,6 @@ public class Channel extends Configuration
public void setAuxDecodeConfiguration( AuxDecodeConfiguration config )
{
mAuxDecodeConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateDecoder();
}
}
/**
@@ -581,11 +562,6 @@ public class Channel extends Configuration
public void setSourceConfiguration( SourceConfiguration config )
{
mSourceConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateSource();
}
}
/**
@@ -618,11 +594,6 @@ public class Channel extends Configuration
public void setEventLogConfiguration( EventLogConfiguration config )
{
mEventLogConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateEventLogging();
}
}
/**
@@ -655,11 +626,6 @@ public class Channel extends Configuration
public void setRecordConfiguration( RecordConfiguration config )
{
mRecordConfiguration = config;
if( mProcessingChain != null && mProcessingChain.isRunning() )
{
mProcessingChain.updateRecording();
}
}
/**
@@ -730,38 +696,124 @@ public class Channel extends Configuration
*/
private void start()
{
if( mEnabled && mProcessingChain == null && mResourceManager != null )
if( mEnabled && mResourceManager != null )
{
mProcessingChain = new ProcessingChain( this, mResourceManager );
setupChannelState();
try
{
setup();
mProcessingChain.start();
fireChannelEvent( Event.CHANNEL_PROCESSING_STARTED );
}
catch( SourceException se )
{
mLog.error( "Error obtaining source for channel" );
}
}
}
private void setupChannelState()
{
if( mChannelState == null )
{
mChannelState = new ChannelState( mResourceManager
.getThreadPoolManager(), mChannelType );
mChannelState.setTrafficChannelTimeout( mTrafficChannelTimeout );
}
}
/**
* Sets up the channel and prepares to commense processing. Sets up the
* processing chain and requests a source.
*
* @throws SourceException - if no source is available or if there is an
* error obtaining a source
*/
public void setup() throws SourceException
{
setupProcessingChain();
setupSource();
//TODO: Add logging
//TODO: Add recorder(s)
}
private void setupProcessingChain()
{
if( mProcessingChain == null )
{
mProcessingChain = new ProcessingChain(
mResourceManager.getThreadPoolManager() );
/* Get the optional alias list for the decode modules to use */
AliasList aliasList = mResourceManager.getPlaylistManager()
.getPlayist().getAliasDirectory().getAliasList( mAliasListName );
/* Add the channel state as a module */
mProcessingChain.addModule( mChannelState );
/* Processing Modules */
List<Module> modules = DecoderFactory.getModules( mChannelType,
mResourceManager, mDecodeConfiguration, mAuxDecodeConfiguration,
aliasList );
mProcessingChain.addModules( modules );
/* Get message filters for the set of processing modules */
FilterSet messageFilter = DecoderFactory.getMessageFilters( modules );
/* Setup the message activity model and add message listeners */
mMessageActivityModel = new MessageActivityModel( messageFilter );
mProcessingChain.addMessageListener( mMessageActivityModel );
/* Add audio manager as listener for audio packets */
mProcessingChain.addAudioPacketListener(
mResourceManager.getAudioManager() );
/* Add system-wide message listeners */
mProcessingChain.addListeners(
mProcessingChain.addMessageListeners(
mResourceManager.getChannelManager().getMessageListeners() );
/* Add individual message listeners */
mProcessingChain.addListeners( mMessageListeners );
mProcessingChain.start();
fireChannelEvent( Event.PROCESSING_STARTED );
}
/* Add individual message listeners */
mProcessingChain.addMessageListeners( mMessageListeners );
/* If this is a traffic channel, override the call fade timeout before
* we set the state to call, so that it will auto-expire after the
* fade timeout and be auto deleted */
if( mChannelType == ChannelType.TRAFFIC )
/* Create the call event model to receive call events */
mCallEventModel = new CallEventModel();
mProcessingChain.addCallEventListener( mCallEventModel );
}
}
/**
* Attempts to obtain a source and apply it to the processing chain.
*
* @throws SourceException - if the processing chain is not setup or if
* there is an error produced while obtaining the source
*/
private void setupSource() throws SourceException
{
if( mProcessingChain != null )
{
if( mProcessingChain.isProcessing() )
Source source = mResourceManager.getSourceManager()
.getSource( getSourceConfiguration(), getDecodeConfiguration()
.getDecoderType().getChannelBandwidth() );
if( source != null )
{
mProcessingChain.getChannelState().setCallFadeTimeout( 40000 );
mProcessingChain.getChannelState().setState( State.CALL );
mProcessingChain.setSource( source );
}
else
{
mProcessingChain.getChannelState().setCallFadeTimeout( 5000 );
/* Channel state should already be no_tuner */
throw new SourceException( "Couldn't obtain tuner channel source" );
}
}
else
{
throw new SourceException( "Cannot obtain source for a null "
+ "processing chain - setup the processing chain first" );
}
}
@@ -770,27 +822,14 @@ public class Channel extends Configuration
*/
private void stop()
{
/* Stop and remove any traffic channels */
for( Channel traffic: mTrafficChannels.values() )
{
traffic.stop();
traffic.dispose();
}
mTrafficChannels.clear();
if( !mEnabled && mProcessingChain != null )
{
mProcessingChain.stop();
mProcessingChain.dispose();
mProcessingChain = null;
mSelected = false;
}
fireChannelEvent( Event.PROCESSING_STOPPED );
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
}
/**
@@ -798,9 +837,18 @@ public class Channel extends Configuration
*/
public void fireChannelEvent( Event event )
{
ChannelEvent channelEvent = new ChannelEvent( this, event );
for( ChannelEventListener listener: mChannelListeners )
{
listener.channelChanged( new ChannelEvent( this, event ) );
listener.channelChanged( channelEvent );
}
/* Send event to processing chain so modules can respond to channel
* selection events */
if( mProcessingChain != null )
{
mProcessingChain.getChannelEventListener().receive( channelEvent );
}
}
@@ -813,7 +861,7 @@ public class Channel extends Configuration
if( !mChannelListeners.contains( listener ) )
{
mChannelListeners.add( listener );
listener.channelChanged(
new ChannelEvent( this, Event.CHANNEL_ADDED ) );
}
@@ -850,7 +898,7 @@ public class Channel extends Configuration
if( isProcessing() )
{
mProcessingChain.addListener( listener );
mProcessingChain.addMessageListener( listener );
}
}
@@ -858,9 +906,9 @@ public class Channel extends Configuration
{
mMessageListeners.remove( listener );
if( isProcessing() )
if( mProcessingChain != null )
{
mProcessingChain.removeListener( listener );
mProcessingChain.removeMessageListener( listener );
}
}
@@ -870,13 +918,15 @@ public class Channel extends Configuration
*/
public boolean isWithin( long minimum, long maximum )
{
return getTunerChannel() != null &&
getTunerChannel().isWithin( minimum, maximum );
TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null &&
tunerChannel.isWithin( minimum, maximum );
}
public void addTrafficChannel( int channelNumber, Channel channel )
public void addTrafficChannel( String channelID, Channel channel )
{
mTrafficChannels.put( channelNumber, channel );
mTrafficChannels.put( channelID, channel );
}
public boolean hasTrafficChannel( Channel channel )
@@ -886,22 +936,11 @@ public class Channel extends Configuration
public void removeTrafficChannel( Channel channel )
{
int keyToRemove = -1;
String name = channel.getChannelDisplayName();
if( mTrafficChannels.values().contains( channel ) )
if( mTrafficChannels.containsKey( name ) )
{
for( Integer channelNumber: mTrafficChannels.keySet() )
{
if( mTrafficChannels.get( channelNumber ).equals( channel ) )
{
keyToRemove = channelNumber;
}
}
}
if( keyToRemove != -1 )
{
mTrafficChannels.remove( keyToRemove );
mTrafficChannels.remove( name );
}
}
@@ -910,8 +949,9 @@ public class Channel extends Configuration
return Collections.unmodifiableCollection( mTrafficChannels.values() );
}
public boolean hasTrafficChannel( int channelNumber )
public boolean hasTrafficChannel( String name )
{
return mTrafficChannels.containsKey( channelNumber );
return mTrafficChannels.containsKey( name );
}
}

View File

@@ -35,6 +35,8 @@ import javax.swing.JTextField;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import module.decode.AuxDecodeComponentEditor;
import module.decode.DecodeComponentEditor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
@@ -47,8 +49,6 @@ import alias.AliasList;
import com.jidesoft.swing.JideTabbedPane;
import controller.Editor;
import decode.AuxDecodeComponentEditor;
import decode.DecodeComponentEditor;
import eventlog.EventLogComponentEditor;
public class ChannelEditor extends Editor implements ActionListener
@@ -99,7 +99,7 @@ public class ChannelEditor extends Editor implements ActionListener
/**
* ComboBox: Alias Lists
*/
add( new JLabel( "Aliases:" ) );
add( new JLabel( "Alias List:" ) );
mComboAliasLists = new JComboBox<AliasList>();

View File

@@ -34,19 +34,26 @@ public class ChannelEvent
{
CHANGE_ALIAS_LIST,
CHANGE_DECODER,
CHANGE_ENABLED,
CHANGE_EVENT_LOGGER,
CHANGE_NAME,
CHANGE_SELECTED,
CHANGE_SITE,
CHANGE_RECORDER,
CHANGE_SELECTED,
CHANGE_SOURCE,
CHANGE_SYSTEM,
CHANNEL_ADDED,
CHANNEL_ADDED,
CHANNEL_DELETED,
CHANNEL_ENABLED,
CHANNEL_DISABLED,
PROCESSING_STARTED,
PROCESSING_STOPPED,
CHANNEL_PROCESSING_STARTED,
CHANNEL_PROCESSING_STOPPED,
CHANNEL_STATE_RESET,
TRAFFIC_CHANNEL_ADDED,
TRAFFIC_CHANNEL_DELETED;
}
}

View File

@@ -17,8 +17,13 @@
******************************************************************************/
package controller.channel;
import gui.SDRTrunk;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.action.AliasActionManager;
import message.Message;
import sample.Listener;
@@ -26,6 +31,8 @@ import controller.ResourceManager;
public class ChannelManager implements ChannelEventListener
{
private final static Logger mLog = LoggerFactory.getLogger( ChannelManager.class );
private ArrayList<Channel> mChannels = new ArrayList<Channel>();
private ArrayList<ChannelEventListener> mChannelListeners =
@@ -56,13 +63,13 @@ public class ChannelManager implements ChannelEventListener
{
switch( event.getEvent() )
{
case CHANNEL_ADDED:
case CHANNEL_ENABLED:
if( !mChannels.contains( event.getChannel() ) )
{
mChannels.add( event.getChannel() );
}
break;
case CHANNEL_DELETED:
case CHANNEL_DISABLED:
mChannels.remove( event.getChannel() );
break;
}

View File

@@ -78,17 +78,17 @@ public class ChannelNode extends ConfigurableNode implements ChannelEventListene
{
/* Refresh the node for each of these events */
case CHANGE_DECODER:
case CHANGE_ENABLED:
case CHANGE_NAME:
case CHANGE_SELECTED:
case CHANGE_SITE:
case CHANGE_SYSTEM:
case PROCESSING_STARTED:
case PROCESSING_STOPPED:
case CHANNEL_ENABLED:
case CHANNEL_PROCESSING_STARTED:
case CHANNEL_PROCESSING_STOPPED:
this.refresh();
break;
/* We're being deleted, so cleanup */
case CHANNEL_DELETED:
case CHANNEL_DISABLED:
delete();
break;
default:
@@ -154,7 +154,7 @@ public class ChannelNode extends ConfigurableNode implements ChannelEventListene
public JPopupMenu getContextMenu()
{
JPopupMenu popupMenu = new JPopupMenu( "Channel Menu" );
popupMenu.add( getChannel().getContextMenu() );
// popupMenu.add( getChannel().getContextMenu() );
return popupMenu;
}

View File

@@ -17,7 +17,9 @@
******************************************************************************/
package controller.channel;
import module.ProcessingChainOld;
public interface ChannelSelectionListener
{
public void channelSelected( ProcessingChain channel );
public void channelSelected( ProcessingChainOld channel );
}

View File

@@ -0,0 +1,8 @@
package controller.channel;
import sample.Listener;
public interface IChannelEventListener
{
public Listener<ChannelEvent> getChannelEventListener();
}

View File

@@ -0,0 +1,9 @@
package controller.channel;
import sample.Listener;
public interface IChannelEventProvider
{
public void setChannelEventListener( Listener<ChannelEvent> listener );
public void removeChannelEventListener();
}

View File

@@ -1,744 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
import gui.SDRTrunk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.Recorder;
import record.wave.ComplexWaveRecorder;
import record.wave.FloatWaveRecorder;
import sample.Broadcaster;
import sample.Listener;
import sample.complex.ComplexSample;
import sample.real.RealBuffer;
import sample.real.RealSampleListener;
import source.ComplexSource;
import source.RealSource;
import source.Source;
import source.SourceException;
import source.config.SourceConfigMixer;
import source.tuner.Tuner;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.AutomaticFrequencyControl;
import source.tuner.frequency.FrequencyCorrectionControl;
import alias.AliasList;
import audio.IAudioOutput;
import audio.SquelchListener;
import audio.inverted.IAudioTypeListener;
import controller.ResourceManager;
import controller.ThreadPoolManager.ThreadType;
import controller.activity.CallEvent;
import controller.channel.Channel.ChannelType;
import controller.state.ChannelState;
import controller.state.ChannelState.State;
import decode.Decoder;
import decode.DecoderFactory;
import eventlog.EventLogType;
import eventlog.EventLogger;
/**
* A channel has all of the pieces needed to wire together a source, decoder,
* event logger and recorder and be started and stopped.
*/
public class ProcessingChain implements Listener<Message>
{
private final static Logger mLog = LoggerFactory.getLogger( SDRTrunk.class );
protected AtomicBoolean mRunning = new AtomicBoolean();
protected Channel mChannel;
protected Source mSource;
protected Decoder mDecoder;
protected List<Recorder> mRecorders = new ArrayList<Recorder>();
protected ArrayList<EventLogger> mEventLoggers = new ArrayList<EventLogger>();
protected AutomaticFrequencyControl mAFC;
protected ChannelState mChannelState;
protected ResourceManager mResourceManager;
private ScheduledFuture<?> mProcessorTask;
private AliasList mAliasList;
private ComplexSampleReceiver mComplexReceiver = new ComplexSampleReceiver();
private LinkedTransferQueue<List<ComplexSample>> mComplexQueue =
new LinkedTransferQueue<List<ComplexSample>>();
private RealSampleReceiver mRealReceiver = new RealSampleReceiver();
private LinkedTransferQueue<RealBuffer> mRealQueue =
new LinkedTransferQueue<RealBuffer>();
protected Broadcaster<Message> mBroadcaster = new Broadcaster<Message>();
public ProcessingChain( Channel channel, ResourceManager resourceManager )
{
mChannel = channel;
mResourceManager = resourceManager;
if( mResourceManager != null )
{
mAliasList = mResourceManager.getPlaylistManager()
.getPlayist().getAliasDirectory().getAliasList(
mChannel.getAliasListName() );
}
}
public IAudioOutput getAudioOutput()
{
if( mDecoder != null )
{
return mDecoder.getAudioOutput();
}
return null;
}
public Listener<List<ComplexSample>> getComplexReceiver()
{
return mComplexReceiver;
}
public Listener<RealBuffer> getRealReceiver()
{
return (Listener<RealBuffer>)mRealReceiver;
}
public String toString()
{
return mChannel.getName();
}
public AliasList getAliasList()
{
return mAliasList;
}
public void dispose()
{
if( mProcessorTask != null )
{
mResourceManager.getThreadPoolManager().cancel( mProcessorTask );
}
if( mChannelState != null )
{
mChannelState.dispose();
}
mChannelState = null;
if( mDecoder != null )
{
mDecoder.dispose();
}
mBroadcaster.dispose();
mEventLoggers.clear();
mRecorders.clear();
mResourceManager = null;
}
public ResourceManager getResourceManager()
{
return mResourceManager;
}
public void start()
{
if( !mRunning.get() && mChannel.getEnabled() )
{
updateSource();
updateDecoder();
updateEventLogging();
updateRecording();
mRunning.set( true );
}
else
{
mLog.info( getLogPrefix() + "start() invoked, but processing chain is "
+ "already started" );
}
}
/**
* Performs closeout operations to prepare for channel deletion
*/
public void stop()
{
if( mRunning.get() )
{
updateSource();
updateDecoder();
updateRecording();
/* Flush call events to the logger */
if( getChannel().getChannelType() == ChannelType.STANDARD )
{
getChannelState().getCallEventModel().flush();
}
updateEventLogging();
mRunning.set( false );
}
else
{
mLog.info( getLogPrefix() + "stop() invoked, but processing chain was "
+ "already stopped" );
}
}
private String getLogPrefix()
{
return "Channel [" + mChannel.getName() + "] - ";
}
public void updateSource()
{
/* Cleanup existing source */
if( mSource != null )
{
if( mSource instanceof ComplexSource )
{
((ComplexSource)mSource).removeListener( this.getComplexReceiver() );
}
else if( mSource instanceof RealSource )
{
((RealSource)mSource)
.removeListener( this.getRealReceiver() );
}
if( mProcessorTask != null )
{
mResourceManager.getThreadPoolManager().cancel( mProcessorTask );
}
mSource.dispose();
mSource = null;
}
/* Get a new source if needed and start the sample processor thread */
if( mChannel.getEnabled() && mResourceManager != null )
{
try
{
mSource = mResourceManager.getSourceManager()
.getSource( ProcessingChain.this );
}
catch ( SourceException e )
{
mLog.error( "Couldn't obtain source for processing chain [" +
toString() + "]", e );
mSource = null;
}
if( mSource != null )
{
if( mSource instanceof ComplexSource )
{
try
{
mProcessorTask =
mResourceManager.getThreadPoolManager()
.scheduleFixedRate( ThreadType.DECODER,
new ComplexProcessor(),
50, TimeUnit.MILLISECONDS );
((ComplexSource)mSource)
.setListener( this.getComplexReceiver() );
}
catch( RejectedExecutionException ree )
{
mLog.error( getLogPrefix() + "ProcessingChain - "
+ "error scheduling complex sample processing "
+ "thread - " + ree.getLocalizedMessage() );
mSource.dispose();
}
}
else if( mSource instanceof RealSource )
{
try
{
mProcessorTask =
mResourceManager.getThreadPoolManager()
.scheduleFixedRate( ThreadType.DECODER,
new RealProcessor(),
50, TimeUnit.MILLISECONDS );
((RealSource)mSource)
.setListener( this.getRealReceiver() );
}
catch( RejectedExecutionException ree )
{
mLog.error( getLogPrefix() + "ProcessingChain - "
+ "error scheduling float sample processing "
+ "thread", ree );
mSource.dispose();
}
}
}
}
}
public void updateDecoder()
{
if( mChannel.getEnabled() )
{
AliasList aliasList = null;
if( mResourceManager != null )
{
aliasList = mResourceManager.getPlaylistManager()
.getPlayist().getAliasDirectory().getAliasList(
mChannel.getAliasListName() );
}
/* Setup the channel state */
mChannelState = DecoderFactory.getChannelStateNew( this, aliasList );
if( mSource == null )
{
mChannelState.setState( State.NO_TUNER );
}
else
{
mDecoder = DecoderFactory.
getDecoder( this, mSource.getSampleType(), aliasList );
if( mDecoder != null )
{
/* Register to receive decoded messages and auxiliary messages */
mDecoder.addMessageListener( ProcessingChain.this );
/* Establish two-way communication between the frequency
* correction controller and the tuner channel source */
if( mSource instanceof TunerChannelSource &&
mDecoder.hasFrequencyCorrectionControl() )
{
TunerChannelSource tcs = (TunerChannelSource)mSource;
FrequencyCorrectionControl fcc =
mDecoder.getFrequencyCorrectionControl();
if( fcc != null )
{
tcs.addListener( fcc );
fcc.setListener( tcs );
}
}
/* Register audio output to be controlled by the channel state */
mChannelState.addListener( (SquelchListener)mDecoder.getAudioOutput() );
mChannelState.setListener( (IAudioTypeListener)mDecoder.getAudioOutput() );
}
}
}
}
/**
* Starts, stops or changes event logging
*/
public void updateEventLogging()
{
/* Shutdown and remove existing event loggers */
if( mEventLoggers != null )
{
for( EventLogger logger: mEventLoggers )
{
logger.stop();
}
mEventLoggers.clear();
}
/* Construct event loggers and add as message listeners */
if( mChannel.getEnabled() &&
mResourceManager != null )
{
//Get the event loggers
ArrayList<EventLogType> types = getChannel()
.getEventLogConfiguration().getLoggers();
for( EventLogType type: types )
{
EventLogger logger;
if( type == EventLogType.CALL_EVENT )
{
logger = mResourceManager.getEventLogManager()
.getLogger( this, type );
getChannelState().getCallEventModel()
.addListener( (Listener<CallEvent>)logger );
}
else
{
logger = mResourceManager.getEventLogManager()
.getLogger( this, type );
addListener( (Listener<Message>)logger );
}
logger.start();
mEventLoggers.add( logger );
}
}
}
/**
* Starts, stops or changes data recording
*/
public void updateRecording()
{
/* Remove any existing recorders */
if( mRecorders != null )
{
for( Recorder recorder: mRecorders )
{
if( recorder != null )
{
try
{
recorder.stop();
}
catch ( IOException ioe )
{
mLog.error( getLogPrefix() + "Couldn't stop recorder [" +
recorder.getFileName() + "]", ioe );
}
}
}
}
/* Start any new recorders */
if( mChannel.getEnabled() &&
mResourceManager != null &&
mDecoder != null )
{
mRecorders = mResourceManager.getRecorderManager()
.getRecorder( this );
/* Iterate any returned recorders and start/add them */
for( Recorder recorder: mRecorders )
{
try
{
recorder.start();
if( recorder instanceof FloatWaveRecorder )
{
mLog.info( getLogPrefix() + "- started audio recording [" +
recorder.getFileName() + "]" );
mDecoder.addRealSampleListener( (FloatWaveRecorder)recorder );
}
else if( recorder instanceof ComplexWaveRecorder )
{
mLog.info( getLogPrefix() + "- started baseband recording [" +
recorder.getFileName() + "]" );
mDecoder.addComplexListener( (ComplexWaveRecorder )recorder );
}
}
catch ( IOException e )
{
mLog.error( getLogPrefix() + "Unable to start recorder [" +
recorder.getFileName() + "]" );
}
}
}
}
public Channel getChannel()
{
return mChannel;
}
public Source getSource()
{
return mSource;
}
/**
* Returns the tuner that is sourcing this channel, or null if it's being
* sourced from some other source
*/
public Tuner getTuner()
{
Tuner retVal = null;
if( mSource instanceof TunerChannelSource )
{
retVal = ((TunerChannelSource)mSource).getTuner();
}
return retVal;
}
public Decoder getDecoder()
{
return mDecoder;
}
public ChannelState getChannelState()
{
return mChannelState;
}
public boolean isRunning()
{
return mRunning.get();
}
public boolean isProcessing()
{
return isRunning() && mSource != null;
}
private source.mixer.MixerChannel getMixerChannel()
{
source.mixer.MixerChannel retVal = null;
if( mChannel.getSourceConfiguration() instanceof SourceConfigMixer )
{
SourceConfigMixer mixer = (SourceConfigMixer)mChannel.getSourceConfiguration();
retVal = mixer.getChannel();
}
return retVal;
}
@Override
public void receive( Message message )
{
if( message.isValid() )
{
mBroadcaster.receive( message );
}
}
/**
* Adds the listener to receive demodulated samples from the decoder
*/
public void addRealListener( RealSampleListener listener )
{
if( mDecoder != null )
{
mDecoder.addRealSampleListener( listener );
}
}
/**
* Removes the listener from receiving demodulated samples from the decoder
*/
public void removeRealListener( RealSampleListener listener )
{
if( mDecoder != null )
{
mDecoder.removeRealListener( listener );
}
}
/**
* Adds a listener to receive a copy of complex (I/Q) baseband samples
* received by this processing chain
*/
public void addComplexListener( Listener<ComplexSample> listener )
{
if( mDecoder != null )
{
mDecoder.addComplexListener( listener );
}
else
{
mLog.error( getLogPrefix() + "attempt to add a complex sample "
+ "listener, but the decoder is null" );
}
}
/**
* Removes the listener from receiving a copy of complex (I/Q) baseband
* samples received by this processing chain
*/
public void removeComplexListener( Listener<ComplexSample> listener )
{
if( mDecoder != null )
{
mDecoder.removeComplexListener( listener );
}
else
{
mLog.error( getLogPrefix() + "attempt to remove a complex sample "
+ "listener, but the decoder is null" );
}
}
/**
* Registers the listener to receive messages
*/
public void addListener( Listener<Message> listener )
{
mBroadcaster.addListener( listener );
}
/**
* Registers all listeners in the list to receive messages
*/
public void addListeners( List<Listener<Message>> listeners )
{
for( Listener<Message> listener: listeners )
{
addListener( listener );
}
}
/**
* Removes the listener from receiving messages
*/
public void removeListener( Listener<Message> listener )
{
mBroadcaster.removeListener( listener );
}
/**
* Manages the real sample queue and distributes samples to the decoder.
*/
private class RealProcessor implements Runnable
{
@Override
public void run()
{
try
{
List<RealBuffer> sampleBuffers = new ArrayList<RealBuffer>();
mRealQueue.drainTo( sampleBuffers, 4 );
for( RealBuffer sampleBuffer: sampleBuffers )
{
if( mDecoder != null )
{
float[] samples = sampleBuffer.getSamples();
for( float sample: samples )
{
mDecoder.getRealReceiver().receive( sample );
}
}
sampleBuffer.dispose();
}
sampleBuffers.clear();
}
catch( Exception e )
{
mLog.error( "error during processing chain real processor run", e );
}
}
}
/**
* Manages the complex sample queue and distributes samples to the decoder.
*/
private class ComplexProcessor implements Runnable
{
@Override
public void run()
{
try
{
List<List<ComplexSample>> sampleSets =
new ArrayList<List<ComplexSample>>();
mComplexQueue.drainTo( sampleSets, 16 );
for( List<ComplexSample> samples: sampleSets )
{
for( ComplexSample sample: samples )
{
if( sample != null && mDecoder != null )
{
mDecoder.getComplexReceiver().receive( sample );
}
}
samples.clear();
}
sampleSets.clear();
}
catch( Exception e )
{
mLog.error( "error during processing chain complex processor run", e );
}
}
}
/**
* Internal listener to receive complex (I/Q) baseband samples. Places
* received samples into the queue managed by the ComplexProcessor.
*/
public class ComplexSampleReceiver implements Listener<List<ComplexSample>>
{
@Override
public void receive( List<ComplexSample> samples )
{
mComplexQueue.add( samples );
}
}
/**
* Internal listener to receive real(ie demodulated) samples. Places
* received samples into the queue managed by the RealProcessor
*/
public class RealSampleReceiver implements Listener<RealBuffer>
{
@Override
public void receive( RealBuffer samples )
{
mRealQueue.add( samples );
}
}
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
package controller.channel.map;
import java.util.ArrayList;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
package controller.channel.map;
import java.awt.Component;
import java.awt.Dimension;
@@ -80,7 +80,6 @@ public class ChannelMapEditor extends JPanel implements ActionListener
@Override
public void focusLost( FocusEvent arg0 )
{
mLog.info( "text field action event fired, new value:" + mNameText.getText() );
mChannelMapCopy.setName( mNameText.getText() );
}
} );
@@ -208,7 +207,6 @@ public class ChannelMapEditor extends JPanel implements ActionListener
@Override
public void actionPerformed( ActionEvent e )
{
mLog.info( "Action Performed:" + e.getActionCommand() );
}
@Override

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
package controller.channel.map;
import java.util.ArrayList;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
package controller.channel.map;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
package controller.channel.map;
import javax.swing.table.AbstractTableModel;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
package controller.channel.map;
import java.awt.event.ActionEvent;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package controller.channel;
package controller.channel.map;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;