mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
Compare commits
25 Commits
v0.3.0-bet
...
v0.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e0af932eb | ||
|
|
6ba5e91fcb | ||
|
|
34dd2161ef | ||
|
|
2543072e08 | ||
|
|
281be147f8 | ||
|
|
12ecca4555 | ||
|
|
4c27d04120 | ||
|
|
ece202315f | ||
|
|
6afe5dcec9 | ||
|
|
8db47c25bc | ||
|
|
6a340050c6 | ||
|
|
183eb2f7c7 | ||
|
|
6bc92e8b94 | ||
|
|
767b302ed7 | ||
|
|
952d930b5b | ||
|
|
26087c465f | ||
|
|
74c15d530b | ||
|
|
8621d1c8e2 | ||
|
|
1cd45a1b79 | ||
|
|
8215b78a43 | ||
|
|
f53519ad59 | ||
|
|
e6ab615931 | ||
|
|
0c84984fcc | ||
|
|
b3e493989e | ||
|
|
50e838749d |
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -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>
|
||||
|
||||
36
.idea/libraries/imports.xml
generated
36
.idea/libraries/imports.xml
generated
@@ -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>
|
||||
11
CHANGELOG
11
CHANGELOG
@@ -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
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
BIN
imports/libusb4java-1.2.0-linux-aarch64.jar
Normal file
BIN
imports/libusb4java-1.2.0-linux-aarch64.jar
Normal file
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
102
src/module/decode/p25/PatchGroup.java
Normal file
102
src/module/decode/p25/PatchGroup.java
Normal 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;
|
||||
}
|
||||
}
|
||||
245
src/module/decode/p25/PatchGroupManager.java
Normal file
245
src/module/decode/p25/PatchGroupManager.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
src/sample/real/IOverflowListener.java
Normal file
28
src/sample/real/IOverflowListener.java
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user