Compare commits

..

25 Commits

Author SHA1 Message Date
Denny
0e0af932eb Incrementing build to 0.3.0-beta3 2017-02-10 17:09:54 -05:00
Denny Sheirer
6ba5e91fcb Merge pull request #182 from DSheirer/176-streaming-handle-additional-io-exceptions
Resolves #176 - resolves 2 streaming related issues
2017-02-10 22:06:32 +00:00
Denny
34dd2161ef Resolves #176 - 2 issues:
A) streaming connection errors - detects errors and if currently connected, resets the connection.

B)duplicate calls being streamed - issue with comparison of broadcast channels allowed the same broadcast channel to be queued two or more times for the list of channels to stream a recording.
2017-02-10 17:05:28 -05:00
Denny Sheirer
2543072e08 Merge pull request #180 from DSheirer/166-traffic_channel_manager_cleanup
Resolves #166 - removes traffic channel timeout from traffic channel manager
2017-02-10 15:31:24 +00:00
Denny
281be147f8 Resolves #166 - removes traffic channel timeout from traffic channel manager 2017-02-10 10:30:35 -05:00
Denny Sheirer
12ecca4555 Merge pull request #179 from DSheirer/169-aarch64-usb-support-for-ARM64
Resolves #165 - adds ARM64 (aarch64) linux libusb support
2017-02-10 15:26:16 +00:00
Denny
4c27d04120 Resolves #165 - adds ARM64 (aarch64) linux libusb support 2017-02-10 10:08:52 -05:00
Denny Sheirer
ece202315f Merge pull request #178 from DSheirer/165-overflowable-buffer-issue-causes-channels-decoding-stop
165 overflowable buffer issue causes channels decoding stop
2017-02-10 14:50:30 +00:00
Denny Sheirer
6afe5dcec9 Merge pull request #177 from DSheirer/master
165 - P25 Channel stops decoding
2017-02-10 14:47:33 +00:00
Denny
8db47c25bc #165 - cleanup of logging and comments in TunerChannelSource 2017-02-10 09:45:32 -05:00
Denny
6a340050c6 #165 - cleanup of logging and comments in TunerChannelSource 2017-02-10 06:41:45 -05:00
Denny
183eb2f7c7 Resolves #165 - P25 decoding channel stops arbitrarily and never recovers.
Issue identified with Patch Group management and circular dependencies that can develop because of stale patch groups.  Refactored all patch group management into a new PatchGroupManager.  Patch groups are now automatically deleted if we don't receive any update messages within 5 seconds.  The PatchGroupAlias was also updated to not roll-up attributes from subordinate patched group aliases when those aliases are also patch group aliases -- this eliminates the circular dependency chaining.

Added IOverflowListener interface and updated Sources to support overflow listeners.  Updated ChannelState to register as an overflow listener so that it can visually depict to the user when the channel enters/exits source overflow state.  Added new color pattern for OVERFLOW with yellow text on red.
2017-02-10 06:36:57 -05:00
Denny Sheirer
6bc92e8b94 Merge pull request #174 from RachelOlivero/gui_accessibility_enhancements
Add a Text Label to the Mute Image Button
2017-02-04 20:21:44 +00:00
Denny
767b302ed7 Adds additional unresolved address exception handler to metadata updater 2017-02-04 07:32:22 -05:00
Rachel Olivero
952d930b5b Adds a screen reader accessible label to the mute button. Label toggles between mute and unmute to match the action that will be taken when the button is next pressed. 2017-02-03 10:19:52 -05:00
Denny
26087c465f Adds additional unresolved address exception handler to metadata updater 2017-01-29 12:37:09 -05:00
Denny
74c15d530b Resolves #164 - USB time out while processing buffer transfers. Decreases periodicity of processing the LibUsb timeout handler from 9 milliseconds to 5 milliseconds to reduce chance that buffers are not being serviced in time. 2017-01-29 11:02:12 -05:00
Denny
8621d1c8e2 Bumping build to 0.3.0-beta2 2017-01-29 09:11:11 -05:00
Denny Sheirer
1cd45a1b79 Merge pull request #162 from DSheirer/161-e4000-tuner-error
Resolves #161 - E4000 tuner error.
2017-01-29 09:08:41 -05:00
Denny
8215b78a43 Resolves #161 - E4000 tuner error. Tweaks settings in the USB transfer processor to improve latency in IQ buffer handling. 2017-01-29 09:07:48 -05:00
Denny
f53519ad59 Updated CHANGELOG 2017-01-29 06:56:21 -05:00
Denny
e6ab615931 Updates the overflowable queue to reduce thread contention on the queue size monitor 2017-01-29 06:26:12 -05:00
Denny Sheirer
0c84984fcc Merge pull request #160 from DSheirer/159-icecast-unresolved-host
159 icecast unresolved host
2017-01-29 06:17:44 -05:00
Denny
b3e493989e Updates javadoc 2017-01-29 06:16:46 -05:00
Denny
50e838749d Resovles #159 - adds UnresolvedHostException error in Icecast metadata updater. 2017-01-29 04:31:31 -05:00
33 changed files with 786 additions and 492 deletions

View File

@@ -7,6 +7,7 @@
<w>fleetsync</w>
<w>icecast</w>
<w>ident</w>
<w>overflowable</w>
<w>polyphase</w>
<w>remez</w>
<w>samplerate</w>

View File

@@ -27,39 +27,9 @@
<root url="jar://$PROJECT_DIR$/imports/commons-codec-1.9.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-logging-1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/slf4j-api-1.7.21.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-aarch64.jar!/" />
</CLASSES>
<JAVADOC>
<root url="https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/index.html" />
</JAVADOC>
<SOURCES>
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
</SOURCES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,11 +1,12 @@
0.3.0 2017xxxx (work in progress)
0.3.0-beta1 20170129
- REQUIRES: Java 8 (or newer) for compatibility and eventual changeover to JavaFX
- FEATURE: audio streaming - adds capability to stream to multiple remote broadcast
servers (Broadcastify, Icecast v2.3 and v2.4+, and Shoutcast V1 and V2). Each
stream can be independently configured with a delay. Audio calls are directed
to one or more streams by attaching one or more stream identifiers to each alias.
- Requires Java 8 (or newer) for compatibility with eventual changeover to JavaFX
- Extensive GUI updates for tuner, channel and alias management.
- Extensive GUI updates for tuner, channel and alias management. Includes new
'Now Playing' channel view and 'Detail' view.
- Updated alias list management of alias id values and wildcards. Weighting is now
applied to each wildcard so that they can be ordered from most specific to least
specific for lookup value matching purposes.
@@ -13,6 +14,10 @@
patch group aliases that roll-up aliases from each patched talkgroup to support
audio recording and streaming of patch calls according to the identifiers and
actions defined for each of the patched talkgroups.
- Implemented bounded queues for both tuner and individual channel IQ sample
buffers. Bounded queues have ability to enter/exit overflow state when queue
size reaches overflow threshold and reset when queue size falls below the reset
threshold. Channel state will show OVERFLOW in red/yellow.
- Updated airspy controller for airspy-mini specific sample rate handling.
- New developer remez filter designer tool and internal usage

View File

@@ -7,7 +7,7 @@
<property name="major" value="0"/>
<property name="minor" value="3"/>
<property name="patch" value="0"/>
<property name="release" value="-beta1"/>
<property name="release" value="-beta3"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties -->

Binary file not shown.

View File

@@ -168,11 +168,14 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
for(AliasAction action: alias.getAction())
if(!(alias instanceof PatchGroupAlias))
{
if(!aliasActions.contains(action))
for(AliasAction action: alias.getAction())
{
aliasActions.add(action);
if(!aliasActions.contains(action))
{
aliasActions.add(action);
}
}
}
}
@@ -185,7 +188,7 @@ public class PatchGroupAlias extends Alias
{
for(Alias alias: mPatchedAliases)
{
if(alias.hasActions())
if(!(alias instanceof PatchGroupAlias) && alias.hasActions())
{
return true;
}
@@ -201,7 +204,9 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.hasCallPriority() && alias.getCallPriority() < highestPriority)
if(!(alias instanceof PatchGroupAlias) &&
alias.hasCallPriority() &&
alias.getCallPriority() < highestPriority)
{
highestPriority = alias.getCallPriority();
}
@@ -220,7 +225,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.hasCallPriority())
if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority())
{
return true;
}
@@ -239,7 +244,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.isRecordable())
if(!(alias instanceof PatchGroupAlias) && alias.isRecordable())
{
return true;
}
@@ -258,7 +263,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.isStreamable())
if(!(alias instanceof PatchGroupAlias) && alias.isStreamable())
{
return true;
}
@@ -279,11 +284,14 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
if(!(alias instanceof PatchGroupAlias))
{
if(!broadcastChannels.contains(broadcastChannel))
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
{
broadcastChannels.add(broadcastChannel);
if(!broadcastChannels.contains(broadcastChannel))
{
broadcastChannels.add(broadcastChannel);
}
}
}
}
@@ -301,7 +309,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.hasBroadcastChannel(channel))
if(!(alias instanceof PatchGroupAlias) && alias.hasBroadcastChannel(channel))
{
return true;
}

View File

@@ -25,76 +25,99 @@ import javax.xml.bind.annotation.XmlAttribute;
public class BroadcastChannel extends AliasID implements Comparable<BroadcastChannel>
{
private String mChannelName;
private String mChannelName;
public BroadcastChannel()
{
//JAXB Constructor
}
public BroadcastChannel()
{
//JAXB Constructor
}
@Override
public int compareTo(BroadcastChannel other)
{
if(mChannelName != null && other.getChannelName() != null)
{
return mChannelName.compareTo(other.getChannelName());
}
else if(mChannelName != null)
{
return -1;
}
else
{
return 1;
}
}
@Override
public int compareTo(BroadcastChannel other)
{
if(mChannelName != null && other.getChannelName() != null)
{
return mChannelName.compareTo(other.getChannelName());
}
else if(mChannelName != null)
{
return -1;
}
else
{
return 1;
}
}
/**
* Creates a named broadcast channel
*/
public BroadcastChannel(String channelName)
{
mChannelName = channelName;
}
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof BroadcastChannel))
{
return false;
}
/**
* Name of the broadcastAudio channel configuration
*/
@XmlAttribute(name="channel")
public String getChannelName()
{
return mChannelName;
}
BroadcastChannel that = (BroadcastChannel)o;
/**
* Sets the name of the broadcastAudio channel configuration
*/
public void setChannelName(String channel)
{
mChannelName = channel;
}
return getChannelName() != null ? getChannelName().equals(that.getChannelName()) : that.getChannelName() == null;
}
@Override
public AliasIDType getType()
{
return AliasIDType.BROADCAST_CHANNEL;
}
@Override
public int hashCode()
{
return getChannelName() != null ? getChannelName().hashCode() : 0;
}
@Override
public boolean isValid()
{
return mChannelName != null;
}
/**
* Creates a named broadcast channel
*/
public BroadcastChannel(String channelName)
{
mChannelName = channelName;
}
@Override
public boolean matches( AliasID id )
{
return false;
}
@Override
public String toString()
{
/**
* Name of the broadcastAudio channel configuration
*/
@XmlAttribute(name = "channel")
public String getChannelName()
{
return mChannelName;
}
/**
* Sets the name of the broadcastAudio channel configuration
*/
public void setChannelName(String channel)
{
mChannelName = channel;
}
@Override
public AliasIDType getType()
{
return AliasIDType.BROADCAST_CHANNEL;
}
@Override
public boolean isValid()
{
return mChannelName != null;
}
@Override
public boolean matches(AliasID id)
{
return false;
}
@Override
public String toString()
{
if(isValid())
{
return "Broadcast Channel: " + mChannelName;
@@ -103,5 +126,5 @@ public class BroadcastChannel extends AliasID implements Comparable<BroadcastCha
{
return "Broadcast Channel: None Selected";
}
}
}
}

View File

@@ -383,6 +383,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
{
setIcon(UNMUTED_ICON);
setBorderPainted(false);
getAccessibleContext().setAccessibleName("Mute");
addActionListener(new ActionListener()
{
@@ -402,6 +403,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
public void run()
{
setIcon(mMuted ? MUTED_ICON : UNMUTED_ICON);
getAccessibleContext().setAccessibleName(mMuted ? "Unmute" : "Mute");
}
});
}

View File

@@ -707,7 +707,9 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
if(metadata != null && metadata.isStreamable())
{
for(BroadcastChannel broadcastChannel : metadata.getBroadcastChannels())
List<BroadcastChannel> broadcastChannels = metadata.getBroadcastChannels();
for(BroadcastChannel broadcastChannel : broadcastChannels)
{
String channelName = broadcastChannel.getChannelName();

View File

@@ -38,6 +38,7 @@ import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.nio.channels.UnresolvedAddressException;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
@@ -144,6 +145,10 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
session.write(updateRequest);
}
}
catch(UnresolvedAddressException uae)
{
//Do nothing - the server is temporarily unavailable
}
catch(Exception e)
{
Throwable throwableCause = e.getCause();
@@ -152,6 +157,10 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
{
//Do nothing, the server is unavailable
}
else if(throwableCause instanceof UnresolvedAddressException)
{
//Do nothing - the server is temporarily unavailable
}
else
{
if(!mStackTraceLoggingSuppressed)

View File

@@ -59,10 +59,10 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
private AtomicBoolean mConnecting = new AtomicBoolean();
/**
* Creates an Icecast 2.3.2 compatible broadcaster using TCP and a pseudo HTTP 1.0 protocol. This broadcaster is
* compatible with Icecast version 2.3.2 and older versions of the server software.
* Creates an Icecast 2.4.x compatible broadcaster using HTTP 1.1 protocol. This broadcaster is
* compatible with Icecast version 2.4.x and newer versions of the server software.
*
* Note: use @see IcecastHTTPAudioBroadcaster for Icecast version 2.4.x and newer.
* Note: use @see IcecastTCPAudioBroadcaster for Icecast version 2.3.x and older.
*
* This broadcaster uses the Apache Mina library for the streaming socket connection and for metadata updates. The
* IcecastHTTPIOHandler manages all interaction with the Icecast server and manages the overall broadcast state.

View File

@@ -248,6 +248,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
String reason = ioe.getMessage();
if(connected())
{
mLog.info("Streaming connection error detected - resetting connection - " + reason);
disconnect();
connect();
}
if(reason.startsWith("Connection reset"))
{
mLog.info("Streaming connection reset by remote server - reestablishing connection");
@@ -264,14 +270,14 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
setBroadcastState(BroadcastState.ERROR);
disconnect();
mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.");
mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.", cause);
}
}
else
{
setBroadcastState(BroadcastState.ERROR);
disconnect();
mLog.error("Unspecified IO error - streaming halted.");
mLog.error("Unspecified IO error - streaming halted.", cause);
}
}
else

View File

@@ -210,6 +210,10 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "idle", Color.WHITE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "idle", Color.BLUE);
break;
case OVERFLOW:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "overflow", Color.RED);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "overflow", Color.YELLOW);
break;
case RESET:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "reset", Color.PINK);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "reset", Color.YELLOW);

View File

@@ -51,7 +51,7 @@ public class ChannelMetadataViewer extends JPanel
mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager);
mCallEventPanel = new CallEventPanel(iconManager);
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager, channelProcessingManager);
// mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager, channelProcessingManager);
mChannelMetadataPanel = new ChannelMetadataPanel(channelProcessingManager, iconManager);
init();

View File

@@ -35,6 +35,7 @@ import module.decode.event.ICallEventProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.IOverflowListener;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
@@ -42,7 +43,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -63,6 +64,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private boolean mSquelchLocked = false;
private boolean mSelected = false;
private boolean mSourceOverflow = false;
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
private long mTrafficChannelFadeTimeout = DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
@@ -123,6 +125,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mState = State.IDLE;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
mSourceOverflow = false;
}
@Override
@@ -350,15 +353,26 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
}
else
{
// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
}
}
/**
* This method is invoked if the source buffer provider goes into overflow state. Since this is an external state,
* we use the mSourceOverflow variable to override the internal state reported to external listeners.
* @param overflow true to indicate an overflow state
*/
@Override
public void sourceOverflow(boolean overflow)
{
mSourceOverflow = overflow;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
/**
* Sets the state and processes related actions
*/

View File

@@ -51,6 +51,14 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
return mAliasList != null;
}
/**
* Provides subclass reference to the call event broadcaster
*/
protected Broadcaster<CallEvent> getCallEventBroadcaster()
{
return mCallEventBroadcaster;
}
/**
* Reset the decoder state to prepare for processing a different sample
* source

View File

@@ -1,17 +1,17 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
@@ -23,98 +23,94 @@ import java.util.EnumSet;
* Details the set of states for a channel and the allowable transition states
*/
public enum State
{
IDLE( "IDLE" )
{
@Override
public boolean canChangeTo( State state )
{
IDLE("IDLE")
{
return state != TEARDOWN &&
state != RESET;
}
},
CALL( "CALL" )
{
@Override
public boolean canChangeTo( State state )
@Override
public boolean canChangeTo(State state)
{
return state != TEARDOWN && state != RESET && state != OVERFLOW;
}
},
CALL("CALL")
{
return state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
DATA( "DATA" )
{
@Override
public boolean canChangeTo( State state )
@Override
public boolean canChangeTo(State state)
{
return state == CONTROL || state == DATA || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
DATA("DATA")
{
return state == CALL ||
state == CONTROL ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
ENCRYPTED( "ENCRYPTED" )
@Override
public boolean canChangeTo(State state)
{
return state == CALL || state == CONTROL || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
ENCRYPTED("ENCRYPTED")
{
@Override
public boolean canChangeTo(State state)
{
return state == FADE || state == TEARDOWN;
}
},
CONTROL("CONTROL")
{
@Override
public boolean canChangeTo(State state)
{
return state == IDLE || state == FADE;
}
},
FADE("FADE")
{
@Override
public boolean canChangeTo(State state)
{
return state != FADE && state != RESET && state != OVERFLOW;
}
},
OVERFLOW("OVERFLOW")
{
@Override
public boolean canChangeTo(State state)
{
return false;
}
},
RESET("RESET")
{
@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<State> CALL_STATES = EnumSet.of(CALL, CONTROL, DATA, ENCRYPTED);
public static final EnumSet<State> IDLE_STATES = EnumSet.of(IDLE, FADE);
private State(String displayValue)
{
@Override
public boolean canChangeTo( State state )
{
return state == FADE || state == TEARDOWN;
}
},
CONTROL( "CONTROL" )
mDisplayValue = displayValue;
}
public abstract boolean canChangeTo(State state);
public String getDisplayValue()
{
@Override
public boolean canChangeTo( State state )
{
return state == IDLE ||
state == FADE;
}
},
FADE( "FADE" )
{
@Override
public boolean canChangeTo( State state )
{
return state != FADE &&
state != RESET;
}
},
RESET( "RESET" )
{
@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<State> CALL_STATES = EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED );
public static final EnumSet<State> IDLE_STATES = EnumSet.of( IDLE, FADE );
private State( String displayValue )
{
mDisplayValue = displayValue;
}
public abstract boolean canChangeTo( State state );
public String getDisplayValue()
{
return mDisplayValue;
}
return mDisplayValue;
}
}

View File

@@ -84,8 +84,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
* @param system label to use to describe the system
* @param site label to use to describe the site
* @param aliasListName designated for the channel
* @param trafficChannelTimeout - millisecond call timer limit used when an
* end of call signalling event is not decoded.
* @param trafficChannelPoolSize - maximum number of allocated traffic channels
* in the pool
*/
@@ -96,7 +94,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
String system,
String site,
String aliasListName,
long trafficChannelTimeout,
int trafficChannelPoolSize)
{
mChannelModel = channelModel;

View File

@@ -1,5 +1,9 @@
package controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.ThreadPool;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@@ -8,24 +12,23 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class NamingThreadFactory implements ThreadFactory
{
static final AtomicInteger poolNumber = new AtomicInteger(1);
private final static Logger mLog = LoggerFactory.getLogger(ThreadPool.class);
private static final AtomicInteger mPoolNumber = new AtomicInteger(1);
final ThreadGroup mThreadGroup;
private final ThreadGroup mThreadGroup;
final AtomicInteger mThreadNumber = new AtomicInteger(1);
private final AtomicInteger mThreadNumber = new AtomicInteger(1);
final String mNamePrefix;
private final String mNamePrefix;
public NamingThreadFactory( String prefix )
{
SecurityManager s = System.getSecurityManager();
mThreadGroup = (s != null)? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
mThreadGroup = (s != null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
mNamePrefix = prefix + " pool-" +
poolNumber.getAndIncrement() +
"-thread-";
mNamePrefix = prefix + " pool-" + mPoolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread( Runnable runnable )
@@ -42,7 +45,16 @@ public class NamingThreadFactory implements ThreadFactory
{
thread.setPriority( Thread.NORM_PRIORITY );
}
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
mLog.error("Error while executing runnable in scheduled thread pool [" + t.getName() + "]", e);
}
});
return thread;
}
}

View File

@@ -538,6 +538,9 @@ public class ProcessingChain implements IChannelEventListener
module.reset();
}
//Setup the channel state to monitor source overflow conditions
mSource.setOverflowListener(mChannelState);
/* Register with the source to receive sample data. Setup a
* timer task to process the buffer queues 50 times a second
* (every 20 ms) */
@@ -606,6 +609,7 @@ public class ProcessingChain implements IChannelEventListener
if(mSource != null)
{
mSource.stop();
mSource.setOverflowListener(null);
switch(mSource.getSampleType())
{

View File

@@ -222,11 +222,9 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
long timeout = mptConfig.getCallTimeout() * 1000; //convert to milliseconds
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), timeout, mptConfig.getTrafficChannelPoolSize()));
(aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize()));
}
modules.add(new FMDemodulatorModule(iqPass, iqStop));
@@ -264,12 +262,9 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
//Set call timeout to 3 seconds for P25 traffic channels
long timeout = 3000;
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), timeout, p25Config.getTrafficChannelPoolSize()));
(aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize()));
}
modules.add(new P25AudioModule(metadata));

View File

@@ -19,9 +19,7 @@ package module.decode.p25;
import alias.Alias;
import alias.AliasList;
import alias.PatchGroupAlias;
import alias.id.AliasIDType;
import alias.id.talkgroup.TalkgroupID;
import channel.metadata.AliasedStringAttributeMonitor;
import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest;
@@ -163,7 +161,6 @@ public class P25DecoderState extends DecoderState
private Map<Integer,IdentifierUpdate> mBands = new HashMap<>();
private Map<String,Long> mRegistrations = new HashMap<>();
private Map<String,IAdjacentSite> mNeighborMap = new HashMap<>();
private Map<String,List<String>> mPatchGroupMap = new HashMap<>();
private String mLastCommandEventID;
private String mLastPageEventID;
@@ -188,6 +185,7 @@ public class P25DecoderState extends DecoderState
private P25CallEvent mCurrentCallEvent;
private List<String> mCallDetectTalkgroups = new ArrayList<>();
private Map<String,P25CallEvent> mChannelCallMap = new HashMap<>();
private PatchGroupManager mPatchGroupManager;
public P25DecoderState(AliasList aliasList,
ChannelType channelType,
@@ -199,6 +197,7 @@ public class P25DecoderState extends DecoderState
mModulation = modulation;
mIgnoreDataCalls = ignoreDataCalls;
mPatchGroupManager = new PatchGroupManager(aliasList, getCallEventBroadcaster());
mSiteAttributeMonitor = new AliasedStringAttributeMonitor(Attribute.NETWORK_ID_2,
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
mFromTalkgroupMonitor = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM,
@@ -2719,6 +2718,9 @@ public class P25DecoderState extends DecoderState
switch(((MotorolaTSBKMessage)tsbk).getMotorolaOpcode())
{
case PATCH_GROUP_CHANNEL_GRANT:
//Cleanup patch groups - auto-expire any patch groups before we allocate a channel
mPatchGroupManager.cleanupPatchGroups();
PatchGroupVoiceChannelGrant pgvcg = (PatchGroupVoiceChannelGrant)tsbk;
channel = pgvcg.getChannel();
@@ -2754,6 +2756,9 @@ public class P25DecoderState extends DecoderState
mChannelCallMap.get(channel)));
break;
case PATCH_GROUP_CHANNEL_GRANT_UPDATE:
//Cleanup patch groups - auto-expire any patch groups before we allocate a channel
mPatchGroupManager.cleanupPatchGroups();
PatchGroupVoiceChannelGrantUpdate gvcgu = (PatchGroupVoiceChannelGrantUpdate)tsbk;
channel = gvcgu.getChannel1();
@@ -2784,35 +2789,11 @@ public class P25DecoderState extends DecoderState
break;
case PATCH_GROUP_ADD:
PatchGroupAdd pga = (PatchGroupAdd)tsbk;
if(addPatchGroup(pga.getPatchGroupAddress(), pga.getPatchedTalkgroups()))
{
StringBuilder sb = new StringBuilder();
sb.append("PATCH GROUP:").append(pga.getPatchGroupAddress());
sb.append(" ").append(pga.getPatchedTalkgroups());
broadcast(new P25CallEvent.Builder(CallEventType.PATCH_GROUP_ADD)
.aliasList(getAliasList())
.details(sb.toString())
.build());
}
mPatchGroupManager.updatePatchGroup(pga.getPatchGroupAddress(), pga.getPatchedTalkgroups());
break;
case PATCH_GROUP_DELETE:
PatchGroupDelete pgd = (PatchGroupDelete)tsbk;
if(removePatchGroup(pgd.getPatchGroupAddress(), pgd.getPatchedTalkgroups()))
{
StringBuilder sbpgd = new StringBuilder();
sbpgd.append("PATCH GROUP:").append(pgd.getPatchGroupAddress());
sbpgd.append(" ").append(pgd.getPatchedTalkgroups());
broadcast(new P25CallEvent.Builder(CallEventType.PATCH_GROUP_DELETE)
.aliasList(getAliasList())
.details(sbpgd.toString())
.build());
}
mPatchGroupManager.removePatchGroup(pgd.getPatchGroupAddress());
break;
case CCH_PLANNED_SHUTDOWN:
if(!mControlChannelShutdownLogged)
@@ -2827,174 +2808,14 @@ public class P25DecoderState extends DecoderState
}
}
/**
* Creates a new patch group or updates an existing patch group with the patched talkgroup entries.
*
* @param patchGroupID for the patch group
* @param talkgroupsToAdd to include in the patch group
* @return true if the patch group or the patch group map was updated
*/
private boolean addPatchGroup(String patchGroupID, List<String> talkgroupsToAdd)
{
boolean updated = false;
if(patchGroupID != null && !talkgroupsToAdd.isEmpty())
{
List<String> patchedTalkgroups = mPatchGroupMap.get(patchGroupID);
if(patchedTalkgroups == null)
{
patchedTalkgroups = new ArrayList<>();
}
for(String talkgroupToAdd : talkgroupsToAdd)
{
//Exclude the patch group ID if it is included in the patched talkgroup list so that we don't have
//an infinite loop situation
if(talkgroupToAdd.equals(patchGroupID))
{
continue;
}
if(!patchedTalkgroups.contains(talkgroupToAdd))
{
patchedTalkgroups.add(talkgroupToAdd);
updated = true;
}
}
if(updated)
{
mPatchGroupMap.put(patchGroupID, patchedTalkgroups);
updatePatchAlias(patchGroupID, patchedTalkgroups);
}
}
return updated;
}
/**
* Removes the patched talkgroups from the patch group and removes the patch group from the patch group map if
* there are no more patched talkgroups in the patch group.
*
* @param patchGroupID for the patch group
* @param talkgroupsToRemove to remove from the patch group
* @return true if the patch group or the patch group map was updated
*/
private boolean removePatchGroup(String patchGroupID, List<String> talkgroupsToRemove)
{
boolean updated = false;
if(patchGroupID != null && !talkgroupsToRemove.isEmpty())
{
List<String> patchedTalkgroups = mPatchGroupMap.get(patchGroupID);
if(patchedTalkgroups != null)
{
for(String talkgroupToRemove : talkgroupsToRemove)
{
if(patchedTalkgroups.contains(talkgroupToRemove))
{
patchedTalkgroups.remove(talkgroupToRemove);
updated = true;
}
}
if(patchedTalkgroups.isEmpty())
{
mPatchGroupMap.remove(patchGroupID);
removePatchGroupAlias(patchGroupID);
updated = true;
}
else
{
updatePatchAlias(patchGroupID, patchedTalkgroups);
}
}
}
return updated;
}
/**
* Adds/updates a patch group alias to the alias list containing aliases for each of the patched talkgroups. If the
* patch group alias already exists, any patched talkgroups will be removed from the existing patch group alias and
* replaced with the aliases corresponding to the patched talkgroup aliases.
*
* @param patchGroupID for the patch group
* @param patchedTalkgroups containing the talkgroup IDs for each of the patched talkgroups
*/
private void updatePatchAlias(String patchGroupID, List<String> patchedTalkgroups)
{
PatchGroupAlias patchGroupAlias = null;
//Check for an existing alias for the patch talkgroup - do not include wildcard aliases
Alias existingAlias = getAliasList().getTalkgroupAlias(patchGroupID, false);
if(existingAlias instanceof PatchGroupAlias)
{
patchGroupAlias = (PatchGroupAlias)existingAlias;
}
else
{
patchGroupAlias = new PatchGroupAlias();
patchGroupAlias.addAliasID(new TalkgroupID(patchGroupID));
if(existingAlias != null)
{
getAliasList().removeAlias(existingAlias);
patchGroupAlias.setPatchGroupAlias(existingAlias);
}
getAliasList().addAlias(patchGroupAlias);
}
if(patchGroupAlias != null)
{
patchGroupAlias.setPatchedTalkgroupIDs(patchedTalkgroups);
patchGroupAlias.clearPatchedAliases();
for(String patchedTalkgroup : patchedTalkgroups)
{
Alias patchedAlias = getAliasList().getTalkgroupAlias(patchedTalkgroup);
if(patchedAlias != null)
{
patchGroupAlias.addPatchedAlias(patchedAlias);
}
}
}
}
/**
* Removes the temporary patch group alias from the alias list
*/
private void removePatchGroupAlias(String patchGroupID)
{
Alias alias = getAliasList().getTalkgroupAlias(patchGroupID);
if(alias instanceof PatchGroupAlias)
{
PatchGroupAlias patchGroupAlias = (PatchGroupAlias)alias;
getAliasList().removeAlias(patchGroupAlias);
//Replace our temporary patch group alias with the original alias for the patch group ID
if(patchGroupAlias.hasPatchGroupAlias())
{
getAliasList().addAlias(patchGroupAlias.getPatchGroupAlias());
}
}
}
/**
* Process a traffic channel allocation message
*/
private void processTSBKChannelGrant(TSBKMessage message)
{
//Cleanup patch groups - auto-expire any patch groups before we allocate a channel
mPatchGroupManager.cleanupPatchGroups();
String channel = null;
String from = null;
String to = null;

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 module.decode.p25;
import java.util.ArrayList;
import java.util.List;
public class PatchGroup
{
private long mExpireThreshold = 5000; //5 seconds
private long mLastUpdated;
private String mPatchGroupID;
private List<String> mPatchedGroups = new ArrayList<>();
/**
* Constructs a patch group with the specified patch group ID
* @param patchGroupID that identifies this patch group
*/
public PatchGroup(String patchGroupID)
{
mPatchGroupID = patchGroupID;
mLastUpdated = System.currentTimeMillis();
}
/**
* Patch group ID for this patch group
*/
public String getPatchGroupID()
{
return mPatchGroupID;
}
/**
* Adds the patched group to this patch group. Duplicate patched groups are ignored.
*
* Note: the patch group ID will not be added as a patched group, to avoid circular dependencies.
*
* @return true if the patched group was added to this patch group
*/
public boolean addPatchedGroup(String patchedGroup)
{
if(patchedGroup != null && !patchedGroup.equals(mPatchGroupID) && !mPatchedGroups.contains(patchedGroup))
{
mPatchedGroups.add(patchedGroup);
return true;
}
return false;
}
/**
* Patched talkgroups that are part of this patch group.
*/
public List<String> getPatchedGroups()
{
return mPatchedGroups;
}
/**
* Updates the timestamp for this patch group.
*/
public void updateTimestamp()
{
mLastUpdated = System.currentTimeMillis();
}
/**
* Indicates if this patch group is expired, meaning that it hasn't been updated in the past (expire
* threshold) seconds.
*/
public boolean isExpired()
{
return System.currentTimeMillis() - mLastUpdated > mExpireThreshold;
}
/**
* Sets the patch group expiration threshold (default = 5000 ms). A patch group is considered expired if
* no new updates have been received within the past (expire threshold) milliseconds
*
* @param expireThreshold in milliseconds
*/
public void setExpireThreshold(long expireThreshold)
{
mExpireThreshold = expireThreshold;
}
}

View File

@@ -0,0 +1,245 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 module.decode.p25;
import alias.Alias;
import alias.AliasList;
import alias.PatchGroupAlias;
import alias.id.talkgroup.TalkgroupID;
import module.decode.event.CallEvent;
import sample.Broadcaster;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class PatchGroupManager
{
private AliasList mAliasList;
private List<PatchGroup> mPatchGroups = new ArrayList<>();
private Broadcaster<CallEvent> mCallEventBroadcaster;
/**
* Manages active P25 patch groups and auto-expires patch groups if not updated every 5 seconds.
*
* @param aliasList to keep updated with patch group changes
* @param broadcaster for rebroadcasting patch group related call events
*/
public PatchGroupManager(AliasList aliasList, Broadcaster<CallEvent> broadcaster)
{
mAliasList = aliasList;
mCallEventBroadcaster = broadcaster;
}
/**
* Removes the patch group.
*
* @param patchGroupID of the patch group to remove
*/
public void removePatchGroup(String patchGroupID)
{
removePatchGroup(patchGroupID, false);
}
/**
* Removes the patch group.
*
* @param patchGroupID of the patch group to remove
* @param expired true if the patch group is being removed because it expired, false if we receive a
* message commanding commanding a patch group delete.
*/
private void removePatchGroup(String patchGroupID, boolean expired)
{
PatchGroup patchGroup = getPatchGroup(patchGroupID);
if(patchGroup != null)
{
//Cleanup the alias list
if(mAliasList != null)
{
Alias alias = mAliasList.getTalkgroupAlias(patchGroupID);
if(alias instanceof PatchGroupAlias)
{
PatchGroupAlias patchGroupAlias = (PatchGroupAlias)alias;
mAliasList.removeAlias(patchGroupAlias);
//Replace our temporary patch group alias with the original alias for the patch group ID
if(patchGroupAlias.hasPatchGroupAlias())
{
mAliasList.addAlias(patchGroupAlias.getPatchGroupAlias());
}
}
}
mPatchGroups.remove(patchGroup);
if(mCallEventBroadcaster != null)
{
StringBuilder sb = new StringBuilder();
sb.append((expired ? "EXPIRED " : "DELETED "));
sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID());
sb.append(" ").append(patchGroup.getPatchedGroups());
mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_DELETE)
.to(patchGroup.getPatchGroupID())
.details(sb.toString())
.build());
}
}
}
/**
* Adds/updates a patch group alias to the alias list containing aliases for each of the patched talkgroups. If the
* patch group alias already exists, any patched talkgroups will be removed from the existing patch group alias and
* replaced with the aliases corresponding to the patched talkgroup aliases.
*
* @param patchGroupID for the patch group
* @param patchedTalkgroups containing the talkgroup IDs for each of the patched talkgroups
*/
public void updatePatchGroup(String patchGroupID, List<String> patchedTalkgroups)
{
boolean updated = false;
PatchGroup patchGroup = getPatchGroup(patchGroupID);
if(patchGroup == null)
{
//Remove any expired patch groups before we add a new one
cleanupPatchGroups();
patchGroup = new PatchGroup(patchGroupID);
mPatchGroups.add(patchGroup);
updated = true;
}
else
{
patchGroup.updateTimestamp();
}
for(String patchedGroup : patchedTalkgroups)
{
if(patchGroup.addPatchedGroup(patchedGroup))
{
updated = true;
}
}
//If we make any changes to the patch group, update the patch group alias
if(updated)
{
PatchGroupAlias patchGroupAlias = null;
//Check for an existing alias for the patch talkgroup - do not include wildcard aliases
if(mAliasList != null)
{
Alias existingAlias = mAliasList.getTalkgroupAlias(patchGroupID, false);
if(existingAlias instanceof PatchGroupAlias)
{
patchGroupAlias = (PatchGroupAlias)existingAlias;
}
else
{
patchGroupAlias = new PatchGroupAlias();
patchGroupAlias.addAliasID(new TalkgroupID(patchGroupID));
if(existingAlias != null)
{
mAliasList.removeAlias(existingAlias);
patchGroupAlias.setPatchGroupAlias(existingAlias);
}
mAliasList.addAlias(patchGroupAlias);
}
if(patchGroupAlias != null)
{
patchGroupAlias.setPatchedTalkgroupIDs(patchGroup.getPatchedGroups());
patchGroupAlias.clearPatchedAliases();
for(String patchedTalkgroup : patchGroup.getPatchedGroups())
{
Alias patchedAlias = mAliasList.getTalkgroupAlias(patchedTalkgroup);
if(patchedAlias != null)
{
patchGroupAlias.addPatchedAlias(patchedAlias);
}
}
}
}
if(mCallEventBroadcaster != null)
{
StringBuilder sb = new StringBuilder();
sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID());
sb.append(" ").append(patchGroup.getPatchedGroups());
mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_ADD)
.aliasList(mAliasList)
.details(sb.toString())
.build());
}
}
}
/**
* Returns the patch group matching the patch group ID or null
*/
private PatchGroup getPatchGroup(String patchGroupID)
{
for(PatchGroup patchGroup : mPatchGroups)
{
if(patchGroup.getPatchGroupID().equals(patchGroupID))
{
return patchGroup;
}
}
return null;
}
/**
* Checks the last updated timestamp on all patch groups and removes any patch groups that have not been
* updated in the past 5 seconds
*/
public void cleanupPatchGroups()
{
Iterator<PatchGroup> it = mPatchGroups.iterator();
while(it.hasNext())
{
PatchGroup patchGroup = it.next();
if(patchGroup.isExpired())
{
it.remove();
removePatchGroup(patchGroup.getPatchGroupID(), true);
}
}
}
}

View File

@@ -163,7 +163,10 @@ public abstract class AudioRecorder extends Module implements Listener<AudioPack
/**
* Closes the recording file. Upon successful closing of the recording file, the listener is notified that the
* audio recorder is closed.
* audio recorder is closed. There is potential for the calling thread (here) and the buffer processor thread to
* both inform the recording closed listener that the recording is ended. So, we synchronize on the listener and
* the first thread to get the lock informs the listener and then nullifies the listener pointer so that if the
* second thread attempts a duplicate notification the listener would be null at that point.
*/
public void close(Listener<AudioRecorder> listener)
{
@@ -171,8 +174,14 @@ public abstract class AudioRecorder extends Module implements Listener<AudioPack
if(!mRunning.compareAndSet(true, false))
{
//If this recorder has already been stopped/closed, immediately inform the listener
mRecordingClosedListener.receive(this);
synchronized(mRecordingClosedListener)
{
if(mRecordingClosedListener != null)
{
mRecordingClosedListener.receive(AudioRecorder.this);
mRecordingClosedListener = null;
}
}
}
}
@@ -303,9 +312,13 @@ public abstract class AudioRecorder extends Module implements Listener<AudioPack
}
}
if(mRecordingClosedListener != null)
synchronized(mRecordingClosedListener)
{
mRecordingClosedListener.receive(AudioRecorder.this);
if(mRecordingClosedListener != null)
{
mRecordingClosedListener.receive(AudioRecorder.this);
mRecordingClosedListener = null;
}
}
if(mProcessorHandle != null)

View File

@@ -18,20 +18,28 @@
******************************************************************************/
package sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.real.IOverflowListener;
import source.tuner.TunerChannelSource;
import java.util.Collection;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class OverflowableTransferQueue<E>
{
private final static Logger mLog = LoggerFactory.getLogger(OverflowableTransferQueue.class);
public enum State {NORMAL, OVERFLOW};
private Listener<State> mStateListener;
private IOverflowListener mOverflowListener;
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
private AtomicInteger mCounter = new AtomicInteger();
private AtomicBoolean mOverflow = new AtomicBoolean();
private int mMaximumSize;
private int mResetThreshold;
private boolean mOverflow = false;
/**
* Concurrent transfer queue that couples a higher-throughput linked transfer queue with an atomic integer for
@@ -53,17 +61,16 @@ public class OverflowableTransferQueue<E>
*/
public void offer(E e)
{
if(!mOverflow)
if(!mOverflow.get())
{
if(mCounter.get() > mMaximumSize)
mQueue.offer(e);
int size = mCounter.incrementAndGet();
if(size > mMaximumSize)
{
setOverflow(true);
}
else
{
mQueue.offer(e);
mCounter.incrementAndGet();
}
}
}
@@ -74,41 +81,36 @@ public class OverflowableTransferQueue<E>
{
int drainCount = mQueue.drainTo(collection, maxElements);
if(drainCount > 0)
int size = mCounter.addAndGet(-drainCount);
if(mOverflow.get() && size <= mResetThreshold)
{
mCounter.addAndGet(-drainCount);
setOverflow(false);
}
return drainCount;
}
/**
* Sets a listener to receive state change events
* Sets a listener to receive overflow state change events
*/
public void setStateListener(Listener<State> listener)
public void setOverflowListener(IOverflowListener listener)
{
mStateListener = listener;
mOverflowListener = listener;
}
/**
* Current state of the queue, normal or overflow
*/
public State getState()
{
return mOverflow ? State.OVERFLOW : State.NORMAL;
}
/**
* Toggles the overflow state
* Toggles the overflow state and broadcast state change to listener
*/
private void setOverflow(boolean overflow)
{
mOverflow = overflow;
if(mStateListener != null)
if(mOverflow.compareAndSet(!overflow, overflow))
{
mStateListener.receive(getState());
if(mOverflowListener != null)
{
mOverflowListener.sourceOverflow(overflow);
}
}
}
@@ -121,7 +123,11 @@ public class OverflowableTransferQueue<E>
{
mQueue.clear();
mCounter.set(0);
mOverflow = false;
mOverflow.set(false);
if(mOverflowListener != null)
{
mOverflowListener.sourceOverflow(false);
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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 sample.real;
public interface IOverflowListener
{
/**
* Indicates that the source is in overflow state (true) or out of overflow state (false)
* @param overflow true to indicate an overflow state
*/
void sourceOverflow(boolean overflow);
}

View File

@@ -19,6 +19,7 @@ package source;
import module.Module;
import sample.SampleType;
import sample.real.IOverflowListener;
/**
* Abstract class to define the minimum functionality of a sample data provider.
@@ -26,6 +27,7 @@ import sample.SampleType;
public abstract class Source extends Module
{
protected SampleType mSampleType;
protected IOverflowListener mOverflowListener;
public Source( SampleType sampleType )
{
@@ -47,4 +49,25 @@ public abstract class Source extends Module
{
mSampleType = sampleType;
}
/**
* Registers the listener to receive overflow state changes. Use null argument to clear the listener
*/
public void setOverflowListener(IOverflowListener listener)
{
mOverflowListener = listener;
}
/**
* Broadcasts an overflow state
*
* @param overflow true if overlow, false if normal
*/
protected void broadcastOverflowState(boolean overflow)
{
if(mOverflowListener != null)
{
mOverflowListener.sourceOverflow(overflow);
}
}
}

View File

@@ -84,7 +84,7 @@ public class LibUSBTransferProcessor
if(mRunning.compareAndSet(false, true))
{
//Set periodicity to an odd multiple to avoid contention with transfer buffer receivers
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 0L, 9L, TimeUnit.MILLISECONDS);
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 0L, 5L, TimeUnit.MILLISECONDS);
}
}

View File

@@ -29,6 +29,7 @@ import sample.Listener;
import sample.OverflowableTransferQueue;
import sample.complex.Complex;
import sample.complex.ComplexBuffer;
import sample.real.IOverflowListener;
import source.ComplexSource;
import source.SourceException;
import source.tuner.frequency.FrequencyChangeEvent;
@@ -51,7 +52,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private static final int BUFFER_MAX_CAPACITY = 300;
//Threshold for resetting buffer overflow condition
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 200;
private static final int BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
@@ -101,21 +102,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
mTunerFrequency = mTuner.getTunerController().getFrequency();
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
mBuffer.setStateListener(new Listener<OverflowableTransferQueue.State>()
{
@Override
public void receive(OverflowableTransferQueue.State state)
{
if(state == OverflowableTransferQueue.State.NORMAL)
{
mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow - temporary pause until processing catches up");
}
else
{
mLog.debug("Channel [" + mTunerFrequency + "] buffer overflow cleared - resuming normal processing");
}
}
});
/* Setup the frequency translator to the current source frequency */
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
@@ -126,6 +112,14 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
}
/**
* Overrides the default source overflow listener management to delegate responsibility to the overflow buffer
*/
@Override
public void setOverflowListener(IOverflowListener listener)
{
mBuffer.setOverflowListener(listener);
}
public void start(ScheduledExecutorService executor)
{
@@ -175,7 +169,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
}
else
{
mLog.warn("Attempt to stop() and already stopped tuner channel source was ignored");
mLog.warn("Attempt to stop() an already stopped tuner channel source was ignored");
}
}
@@ -379,6 +373,10 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
mLog.error("Error encountered during decimation process", e);
}
}
catch(Throwable throwable)
{
mLog.error("Code error encountered during decimation process - channel thread will probably die", throwable);
}
/* Check to see if we've been shutdown */
if(!mProcessing)

View File

@@ -78,14 +78,9 @@ public abstract class RTL2832TunerController extends TunerController
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
protected ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
protected int mOscillatorFrequency = 28800000; //28.8 MHz
private USBTransferProcessor mUSBTransferProcessor;
protected USBTransferProcessor mUSBTransferProcessor;
protected Descriptor mDescriptor;
/**

View File

@@ -249,6 +249,11 @@ public class E4KTunerController extends RTL2832TunerController
throw new SourceException("E4K Tuner Controller - error during "
+ "init()", e);
}
String deviceName = getTunerType().getLabel() + " " + getUniqueID();
mUSBTransferProcessor = new RTL2832USBTransferProcessor(deviceName, mDeviceHandle, mSampleAdapter,
USB_TRANSFER_BUFFER_SIZE);
}
/**

View File

@@ -30,6 +30,7 @@ import sample.Listener;
import sample.OverflowableTransferQueue;
import sample.adapter.ISampleAdapter;
import sample.complex.ComplexBuffer;
import sample.real.IOverflowListener;
import source.tuner.TunerManager;
import util.ThreadPool;
@@ -56,7 +57,7 @@ public class USBTransferProcessor implements TransferCallback
private static final int FILLED_BUFFER_MAX_CAPACITY = 300;
//Threshold for resetting buffer overflow condition
private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 200;
private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
private String mDeviceName;
@@ -101,12 +102,12 @@ public class USBTransferProcessor implements TransferCallback
mBufferSize = bufferSize;
mFilledBuffers = new OverflowableTransferQueue<>(FILLED_BUFFER_MAX_CAPACITY, FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD);
mFilledBuffers.setStateListener(new Listener<OverflowableTransferQueue.State>()
mFilledBuffers.setOverflowListener(new IOverflowListener()
{
@Override
public void receive(OverflowableTransferQueue.State state)
public void sourceOverflow(boolean overflow)
{
if(state == OverflowableTransferQueue.State.NORMAL)
if(overflow)
{
mLog.debug(mDeviceName + " - buffer overflow - temporary pause until processing catches up");
}
@@ -173,7 +174,7 @@ public class USBTransferProcessor implements TransferCallback
//Start transferred buffer dispatcher
mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferDispatcher,
20, 20, TimeUnit.MILLISECONDS);
0, 11, TimeUnit.MILLISECONDS);
//Register with LibUSB processor so that it auto-starts LibUSB processing
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
@@ -300,6 +301,7 @@ public class USBTransferProcessor implements TransferCallback
{
case LibUsb.TRANSFER_COMPLETED:
case LibUsb.TRANSFER_STALL:
case LibUsb.TRANSFER_TIMED_OUT:
if(transfer.actualLength() > 0)
{
ByteBuffer buffer = transfer.buffer();