mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 17:17:29 -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>fleetsync</w>
|
||||||
<w>icecast</w>
|
<w>icecast</w>
|
||||||
<w>ident</w>
|
<w>ident</w>
|
||||||
|
<w>overflowable</w>
|
||||||
<w>polyphase</w>
|
<w>polyphase</w>
|
||||||
<w>remez</w>
|
<w>remez</w>
|
||||||
<w>samplerate</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-codec-1.9.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/imports/commons-logging-1.2.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/slf4j-api-1.7.21.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-aarch64.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC>
|
<JAVADOC />
|
||||||
<root url="https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/index.html" />
|
<SOURCES />
|
||||||
</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>
|
|
||||||
</library>
|
</library>
|
||||||
</component>
|
</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
|
- 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
|
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
|
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.
|
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. Includes new
|
||||||
- Extensive GUI updates for tuner, channel and alias management.
|
'Now Playing' channel view and 'Detail' view.
|
||||||
- Updated alias list management of alias id values and wildcards. Weighting is now
|
- 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
|
applied to each wildcard so that they can be ordered from most specific to least
|
||||||
specific for lookup value matching purposes.
|
specific for lookup value matching purposes.
|
||||||
@@ -13,6 +14,10 @@
|
|||||||
patch group aliases that roll-up aliases from each patched talkgroup to support
|
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
|
audio recording and streaming of patch calls according to the identifiers and
|
||||||
actions defined for each of the patched talkgroups.
|
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.
|
- Updated airspy controller for airspy-mini specific sample rate handling.
|
||||||
- New developer remez filter designer tool and internal usage
|
- New developer remez filter designer tool and internal usage
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<property name="major" value="0"/>
|
<property name="major" value="0"/>
|
||||||
<property name="minor" value="3"/>
|
<property name="minor" value="3"/>
|
||||||
<property name="patch" value="0"/>
|
<property name="patch" value="0"/>
|
||||||
<property name="release" value="-beta1"/>
|
<property name="release" value="-beta3"/>
|
||||||
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
|
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
|
||||||
|
|
||||||
<!-- Build properties -->
|
<!-- 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(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)
|
for(Alias alias: mPatchedAliases)
|
||||||
{
|
{
|
||||||
if(alias.hasActions())
|
if(!(alias instanceof PatchGroupAlias) && alias.hasActions())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -201,7 +204,9 @@ public class PatchGroupAlias extends Alias
|
|||||||
|
|
||||||
for(Alias alias: mPatchedAliases)
|
for(Alias alias: mPatchedAliases)
|
||||||
{
|
{
|
||||||
if(alias.hasCallPriority() && alias.getCallPriority() < highestPriority)
|
if(!(alias instanceof PatchGroupAlias) &&
|
||||||
|
alias.hasCallPriority() &&
|
||||||
|
alias.getCallPriority() < highestPriority)
|
||||||
{
|
{
|
||||||
highestPriority = alias.getCallPriority();
|
highestPriority = alias.getCallPriority();
|
||||||
}
|
}
|
||||||
@@ -220,7 +225,7 @@ public class PatchGroupAlias extends Alias
|
|||||||
|
|
||||||
for(Alias alias: mPatchedAliases)
|
for(Alias alias: mPatchedAliases)
|
||||||
{
|
{
|
||||||
if(alias.hasCallPriority())
|
if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -239,7 +244,7 @@ public class PatchGroupAlias extends Alias
|
|||||||
|
|
||||||
for(Alias alias: mPatchedAliases)
|
for(Alias alias: mPatchedAliases)
|
||||||
{
|
{
|
||||||
if(alias.isRecordable())
|
if(!(alias instanceof PatchGroupAlias) && alias.isRecordable())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -258,7 +263,7 @@ public class PatchGroupAlias extends Alias
|
|||||||
|
|
||||||
for(Alias alias: mPatchedAliases)
|
for(Alias alias: mPatchedAliases)
|
||||||
{
|
{
|
||||||
if(alias.isStreamable())
|
if(!(alias instanceof PatchGroupAlias) && alias.isStreamable())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -279,11 +284,14 @@ public class PatchGroupAlias extends Alias
|
|||||||
|
|
||||||
for(Alias alias: mPatchedAliases)
|
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)
|
for(Alias alias: mPatchedAliases)
|
||||||
{
|
{
|
||||||
if(alias.hasBroadcastChannel(channel))
|
if(!(alias instanceof PatchGroupAlias) && alias.hasBroadcastChannel(channel))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,76 +25,99 @@ import javax.xml.bind.annotation.XmlAttribute;
|
|||||||
|
|
||||||
public class BroadcastChannel extends AliasID implements Comparable<BroadcastChannel>
|
public class BroadcastChannel extends AliasID implements Comparable<BroadcastChannel>
|
||||||
{
|
{
|
||||||
private String mChannelName;
|
private String mChannelName;
|
||||||
|
|
||||||
public BroadcastChannel()
|
public BroadcastChannel()
|
||||||
{
|
{
|
||||||
//JAXB Constructor
|
//JAXB Constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(BroadcastChannel other)
|
public int compareTo(BroadcastChannel other)
|
||||||
{
|
{
|
||||||
if(mChannelName != null && other.getChannelName() != null)
|
if(mChannelName != null && other.getChannelName() != null)
|
||||||
{
|
{
|
||||||
return mChannelName.compareTo(other.getChannelName());
|
return mChannelName.compareTo(other.getChannelName());
|
||||||
}
|
}
|
||||||
else if(mChannelName != null)
|
else if(mChannelName != null)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Creates a named broadcast channel
|
public boolean equals(Object o)
|
||||||
*/
|
{
|
||||||
public BroadcastChannel(String channelName)
|
if(this == o)
|
||||||
{
|
{
|
||||||
mChannelName = channelName;
|
return true;
|
||||||
}
|
}
|
||||||
|
if(!(o instanceof BroadcastChannel))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
BroadcastChannel that = (BroadcastChannel)o;
|
||||||
* Name of the broadcastAudio channel configuration
|
|
||||||
*/
|
|
||||||
@XmlAttribute(name="channel")
|
|
||||||
public String getChannelName()
|
|
||||||
{
|
|
||||||
return mChannelName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return getChannelName() != null ? getChannelName().equals(that.getChannelName()) : that.getChannelName() == null;
|
||||||
* Sets the name of the broadcastAudio channel configuration
|
}
|
||||||
*/
|
|
||||||
public void setChannelName(String channel)
|
|
||||||
{
|
|
||||||
mChannelName = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AliasIDType getType()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
return AliasIDType.BROADCAST_CHANNEL;
|
return getChannelName() != null ? getChannelName().hashCode() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean isValid()
|
* Creates a named broadcast channel
|
||||||
{
|
*/
|
||||||
return mChannelName != null;
|
public BroadcastChannel(String channelName)
|
||||||
}
|
{
|
||||||
|
mChannelName = channelName;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean matches( AliasID id )
|
* Name of the broadcastAudio channel configuration
|
||||||
{
|
*/
|
||||||
return false;
|
@XmlAttribute(name = "channel")
|
||||||
}
|
public String getChannelName()
|
||||||
|
{
|
||||||
@Override
|
return mChannelName;
|
||||||
public String toString()
|
}
|
||||||
{
|
|
||||||
|
/**
|
||||||
|
* 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())
|
if(isValid())
|
||||||
{
|
{
|
||||||
return "Broadcast Channel: " + mChannelName;
|
return "Broadcast Channel: " + mChannelName;
|
||||||
@@ -103,5 +126,5 @@ public class BroadcastChannel extends AliasID implements Comparable<BroadcastCha
|
|||||||
{
|
{
|
||||||
return "Broadcast Channel: None Selected";
|
return "Broadcast Channel: None Selected";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
|||||||
{
|
{
|
||||||
setIcon(UNMUTED_ICON);
|
setIcon(UNMUTED_ICON);
|
||||||
setBorderPainted(false);
|
setBorderPainted(false);
|
||||||
|
getAccessibleContext().setAccessibleName("Mute");
|
||||||
|
|
||||||
addActionListener(new ActionListener()
|
addActionListener(new ActionListener()
|
||||||
{
|
{
|
||||||
@@ -402,6 +403,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
|||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
setIcon(mMuted ? MUTED_ICON : UNMUTED_ICON);
|
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())
|
if(metadata != null && metadata.isStreamable())
|
||||||
{
|
{
|
||||||
for(BroadcastChannel broadcastChannel : metadata.getBroadcastChannels())
|
List<BroadcastChannel> broadcastChannels = metadata.getBroadcastChannels();
|
||||||
|
|
||||||
|
for(BroadcastChannel broadcastChannel : broadcastChannels)
|
||||||
{
|
{
|
||||||
String channelName = broadcastChannel.getChannelName();
|
String channelName = broadcastChannel.getChannelName();
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
@@ -144,6 +145,10 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
|||||||
session.write(updateRequest);
|
session.write(updateRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(UnresolvedAddressException uae)
|
||||||
|
{
|
||||||
|
//Do nothing - the server is temporarily unavailable
|
||||||
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
Throwable throwableCause = e.getCause();
|
Throwable throwableCause = e.getCause();
|
||||||
@@ -152,6 +157,10 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
|||||||
{
|
{
|
||||||
//Do nothing, the server is unavailable
|
//Do nothing, the server is unavailable
|
||||||
}
|
}
|
||||||
|
else if(throwableCause instanceof UnresolvedAddressException)
|
||||||
|
{
|
||||||
|
//Do nothing - the server is temporarily unavailable
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(!mStackTraceLoggingSuppressed)
|
if(!mStackTraceLoggingSuppressed)
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
private AtomicBoolean mConnecting = new AtomicBoolean();
|
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
|
* Creates an Icecast 2.4.x compatible broadcaster using HTTP 1.1 protocol. This broadcaster is
|
||||||
* compatible with Icecast version 2.3.2 and older versions of the server software.
|
* 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
|
* 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.
|
* 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();
|
String reason = ioe.getMessage();
|
||||||
|
|
||||||
|
if(connected())
|
||||||
|
{
|
||||||
|
mLog.info("Streaming connection error detected - resetting connection - " + reason);
|
||||||
|
disconnect();
|
||||||
|
connect();
|
||||||
|
}
|
||||||
if(reason.startsWith("Connection reset"))
|
if(reason.startsWith("Connection reset"))
|
||||||
{
|
{
|
||||||
mLog.info("Streaming connection reset by remote server - reestablishing connection");
|
mLog.info("Streaming connection reset by remote server - reestablishing connection");
|
||||||
@@ -264,14 +270,14 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
disconnect();
|
disconnect();
|
||||||
mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.");
|
mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.", cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.ERROR);
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
disconnect();
|
disconnect();
|
||||||
mLog.error("Unspecified IO error - streaming halted.");
|
mLog.error("Unspecified IO error - streaming halted.", cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -210,6 +210,10 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
|||||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "idle", Color.WHITE);
|
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "idle", Color.WHITE);
|
||||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "idle", Color.BLUE);
|
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "idle", Color.BLUE);
|
||||||
break;
|
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:
|
case RESET:
|
||||||
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "reset", Color.PINK);
|
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "reset", Color.PINK);
|
||||||
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "reset", Color.YELLOW);
|
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "reset", Color.YELLOW);
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class ChannelMetadataViewer extends JPanel
|
|||||||
mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager);
|
mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager);
|
||||||
mCallEventPanel = new CallEventPanel(iconManager);
|
mCallEventPanel = new CallEventPanel(iconManager);
|
||||||
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
|
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
|
||||||
mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager, channelProcessingManager);
|
// mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager, channelProcessingManager);
|
||||||
mChannelMetadataPanel = new ChannelMetadataPanel(channelProcessingManager, iconManager);
|
mChannelMetadataPanel = new ChannelMetadataPanel(channelProcessingManager, iconManager);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import module.decode.event.ICallEventProvider;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
|
import sample.real.IOverflowListener;
|
||||||
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@@ -42,7 +43,7 @@ import java.util.concurrent.ScheduledFuture;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
|
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
|
||||||
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener
|
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
|
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 mSquelchLocked = false;
|
||||||
private boolean mSelected = false;
|
private boolean mSelected = false;
|
||||||
|
private boolean mSourceOverflow = false;
|
||||||
|
|
||||||
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
|
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
|
||||||
private long mTrafficChannelFadeTimeout = DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
|
private long mTrafficChannelFadeTimeout = DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
|
||||||
@@ -123,6 +125,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
|||||||
|
|
||||||
mState = State.IDLE;
|
mState = State.IDLE;
|
||||||
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
|
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
|
||||||
|
mSourceOverflow = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -350,15 +353,26 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
|
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
|
||||||
}
|
mSourceOverflow ? State.OVERFLOW : mState));
|
||||||
else
|
|
||||||
{
|
|
||||||
// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Sets the state and processes related actions
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
|
|||||||
return mAliasList != null;
|
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
|
* Reset the decoder state to prepare for processing a different sample
|
||||||
* source
|
* source
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* 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
|
* Details the set of states for a channel and the allowable transition states
|
||||||
*/
|
*/
|
||||||
public enum State
|
public enum State
|
||||||
{
|
{
|
||||||
IDLE( "IDLE" )
|
IDLE("IDLE")
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean canChangeTo( State state )
|
|
||||||
{
|
{
|
||||||
return state != TEARDOWN &&
|
@Override
|
||||||
state != RESET;
|
public boolean canChangeTo(State state)
|
||||||
}
|
{
|
||||||
},
|
return state != TEARDOWN && state != RESET && state != OVERFLOW;
|
||||||
CALL( "CALL" )
|
}
|
||||||
{
|
},
|
||||||
@Override
|
CALL("CALL")
|
||||||
public boolean canChangeTo( State state )
|
|
||||||
{
|
{
|
||||||
return state == CONTROL ||
|
@Override
|
||||||
state == DATA ||
|
public boolean canChangeTo(State state)
|
||||||
state == ENCRYPTED ||
|
{
|
||||||
state == FADE ||
|
return state == CONTROL || state == DATA || state == ENCRYPTED || state == FADE || state == TEARDOWN;
|
||||||
state == TEARDOWN;
|
}
|
||||||
}
|
},
|
||||||
},
|
DATA("DATA")
|
||||||
DATA( "DATA" )
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean canChangeTo( State state )
|
|
||||||
{
|
{
|
||||||
return state == CALL ||
|
@Override
|
||||||
state == CONTROL ||
|
public boolean canChangeTo(State state)
|
||||||
state == ENCRYPTED ||
|
{
|
||||||
state == FADE ||
|
return state == CALL || state == CONTROL || state == ENCRYPTED || state == FADE || state == TEARDOWN;
|
||||||
state == TEARDOWN;
|
}
|
||||||
}
|
},
|
||||||
},
|
ENCRYPTED("ENCRYPTED")
|
||||||
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
|
mDisplayValue = displayValue;
|
||||||
public boolean canChangeTo( State state )
|
}
|
||||||
{
|
|
||||||
return state == FADE || state == TEARDOWN;
|
public abstract boolean canChangeTo(State state);
|
||||||
}
|
|
||||||
},
|
public String getDisplayValue()
|
||||||
CONTROL( "CONTROL" )
|
|
||||||
{
|
{
|
||||||
@Override
|
return mDisplayValue;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -84,8 +84,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
|||||||
* @param system label to use to describe the system
|
* @param system label to use to describe the system
|
||||||
* @param site label to use to describe the site
|
* @param site label to use to describe the site
|
||||||
* @param aliasListName designated for the channel
|
* @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
|
* @param trafficChannelPoolSize - maximum number of allocated traffic channels
|
||||||
* in the pool
|
* in the pool
|
||||||
*/
|
*/
|
||||||
@@ -96,7 +94,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
|||||||
String system,
|
String system,
|
||||||
String site,
|
String site,
|
||||||
String aliasListName,
|
String aliasListName,
|
||||||
long trafficChannelTimeout,
|
|
||||||
int trafficChannelPoolSize)
|
int trafficChannelPoolSize)
|
||||||
{
|
{
|
||||||
mChannelModel = channelModel;
|
mChannelModel = channelModel;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package controller;
|
package controller;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import util.ThreadPool;
|
||||||
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@@ -8,24 +12,23 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
*/
|
*/
|
||||||
public class NamingThreadFactory implements ThreadFactory
|
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 )
|
public NamingThreadFactory( String prefix )
|
||||||
{
|
{
|
||||||
SecurityManager s = System.getSecurityManager();
|
SecurityManager s = System.getSecurityManager();
|
||||||
|
|
||||||
mThreadGroup = (s != null)? s.getThreadGroup() :
|
mThreadGroup = (s != null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
|
||||||
Thread.currentThread().getThreadGroup();
|
|
||||||
|
|
||||||
mNamePrefix = prefix + " pool-" +
|
mNamePrefix = prefix + " pool-" + mPoolNumber.getAndIncrement() + "-thread-";
|
||||||
poolNumber.getAndIncrement() +
|
|
||||||
"-thread-";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Thread newThread( Runnable runnable )
|
public Thread newThread( Runnable runnable )
|
||||||
@@ -42,7 +45,16 @@ public class NamingThreadFactory implements ThreadFactory
|
|||||||
{
|
{
|
||||||
thread.setPriority( Thread.NORM_PRIORITY );
|
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;
|
return thread;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -538,6 +538,9 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
module.reset();
|
module.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Setup the channel state to monitor source overflow conditions
|
||||||
|
mSource.setOverflowListener(mChannelState);
|
||||||
|
|
||||||
/* Register with the source to receive sample data. Setup a
|
/* Register with the source to receive sample data. Setup a
|
||||||
* timer task to process the buffer queues 50 times a second
|
* timer task to process the buffer queues 50 times a second
|
||||||
* (every 20 ms) */
|
* (every 20 ms) */
|
||||||
@@ -606,6 +609,7 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
if(mSource != null)
|
if(mSource != null)
|
||||||
{
|
{
|
||||||
mSource.stop();
|
mSource.stop();
|
||||||
|
mSource.setOverflowListener(null);
|
||||||
|
|
||||||
switch(mSource.getSampleType())
|
switch(mSource.getSampleType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -222,11 +222,9 @@ public class DecoderFactory
|
|||||||
|
|
||||||
if(channelType == ChannelType.STANDARD)
|
if(channelType == ChannelType.STANDARD)
|
||||||
{
|
{
|
||||||
long timeout = mptConfig.getCallTimeout() * 1000; //convert to milliseconds
|
|
||||||
|
|
||||||
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
|
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
|
||||||
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
|
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));
|
modules.add(new FMDemodulatorModule(iqPass, iqStop));
|
||||||
@@ -264,12 +262,9 @@ public class DecoderFactory
|
|||||||
|
|
||||||
if(channelType == ChannelType.STANDARD)
|
if(channelType == ChannelType.STANDARD)
|
||||||
{
|
{
|
||||||
//Set call timeout to 3 seconds for P25 traffic channels
|
|
||||||
long timeout = 3000;
|
|
||||||
|
|
||||||
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
|
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
|
||||||
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
|
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));
|
modules.add(new P25AudioModule(metadata));
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ package module.decode.p25;
|
|||||||
|
|
||||||
import alias.Alias;
|
import alias.Alias;
|
||||||
import alias.AliasList;
|
import alias.AliasList;
|
||||||
import alias.PatchGroupAlias;
|
|
||||||
import alias.id.AliasIDType;
|
import alias.id.AliasIDType;
|
||||||
import alias.id.talkgroup.TalkgroupID;
|
|
||||||
import channel.metadata.AliasedStringAttributeMonitor;
|
import channel.metadata.AliasedStringAttributeMonitor;
|
||||||
import channel.metadata.Attribute;
|
import channel.metadata.Attribute;
|
||||||
import channel.metadata.AttributeChangeRequest;
|
import channel.metadata.AttributeChangeRequest;
|
||||||
@@ -163,7 +161,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
private Map<Integer,IdentifierUpdate> mBands = new HashMap<>();
|
private Map<Integer,IdentifierUpdate> mBands = new HashMap<>();
|
||||||
private Map<String,Long> mRegistrations = new HashMap<>();
|
private Map<String,Long> mRegistrations = new HashMap<>();
|
||||||
private Map<String,IAdjacentSite> mNeighborMap = new HashMap<>();
|
private Map<String,IAdjacentSite> mNeighborMap = new HashMap<>();
|
||||||
private Map<String,List<String>> mPatchGroupMap = new HashMap<>();
|
|
||||||
|
|
||||||
private String mLastCommandEventID;
|
private String mLastCommandEventID;
|
||||||
private String mLastPageEventID;
|
private String mLastPageEventID;
|
||||||
@@ -188,6 +185,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
private P25CallEvent mCurrentCallEvent;
|
private P25CallEvent mCurrentCallEvent;
|
||||||
private List<String> mCallDetectTalkgroups = new ArrayList<>();
|
private List<String> mCallDetectTalkgroups = new ArrayList<>();
|
||||||
private Map<String,P25CallEvent> mChannelCallMap = new HashMap<>();
|
private Map<String,P25CallEvent> mChannelCallMap = new HashMap<>();
|
||||||
|
private PatchGroupManager mPatchGroupManager;
|
||||||
|
|
||||||
public P25DecoderState(AliasList aliasList,
|
public P25DecoderState(AliasList aliasList,
|
||||||
ChannelType channelType,
|
ChannelType channelType,
|
||||||
@@ -199,6 +197,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
mModulation = modulation;
|
mModulation = modulation;
|
||||||
mIgnoreDataCalls = ignoreDataCalls;
|
mIgnoreDataCalls = ignoreDataCalls;
|
||||||
|
|
||||||
|
mPatchGroupManager = new PatchGroupManager(aliasList, getCallEventBroadcaster());
|
||||||
mSiteAttributeMonitor = new AliasedStringAttributeMonitor(Attribute.NETWORK_ID_2,
|
mSiteAttributeMonitor = new AliasedStringAttributeMonitor(Attribute.NETWORK_ID_2,
|
||||||
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
|
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
|
||||||
mFromTalkgroupMonitor = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM,
|
mFromTalkgroupMonitor = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM,
|
||||||
@@ -2719,6 +2718,9 @@ public class P25DecoderState extends DecoderState
|
|||||||
switch(((MotorolaTSBKMessage)tsbk).getMotorolaOpcode())
|
switch(((MotorolaTSBKMessage)tsbk).getMotorolaOpcode())
|
||||||
{
|
{
|
||||||
case PATCH_GROUP_CHANNEL_GRANT:
|
case PATCH_GROUP_CHANNEL_GRANT:
|
||||||
|
//Cleanup patch groups - auto-expire any patch groups before we allocate a channel
|
||||||
|
mPatchGroupManager.cleanupPatchGroups();
|
||||||
|
|
||||||
PatchGroupVoiceChannelGrant pgvcg = (PatchGroupVoiceChannelGrant)tsbk;
|
PatchGroupVoiceChannelGrant pgvcg = (PatchGroupVoiceChannelGrant)tsbk;
|
||||||
|
|
||||||
channel = pgvcg.getChannel();
|
channel = pgvcg.getChannel();
|
||||||
@@ -2754,6 +2756,9 @@ public class P25DecoderState extends DecoderState
|
|||||||
mChannelCallMap.get(channel)));
|
mChannelCallMap.get(channel)));
|
||||||
break;
|
break;
|
||||||
case PATCH_GROUP_CHANNEL_GRANT_UPDATE:
|
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;
|
PatchGroupVoiceChannelGrantUpdate gvcgu = (PatchGroupVoiceChannelGrantUpdate)tsbk;
|
||||||
|
|
||||||
channel = gvcgu.getChannel1();
|
channel = gvcgu.getChannel1();
|
||||||
@@ -2784,35 +2789,11 @@ public class P25DecoderState extends DecoderState
|
|||||||
break;
|
break;
|
||||||
case PATCH_GROUP_ADD:
|
case PATCH_GROUP_ADD:
|
||||||
PatchGroupAdd pga = (PatchGroupAdd)tsbk;
|
PatchGroupAdd pga = (PatchGroupAdd)tsbk;
|
||||||
|
mPatchGroupManager.updatePatchGroup(pga.getPatchGroupAddress(), pga.getPatchedTalkgroups());
|
||||||
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());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case PATCH_GROUP_DELETE:
|
case PATCH_GROUP_DELETE:
|
||||||
PatchGroupDelete pgd = (PatchGroupDelete)tsbk;
|
PatchGroupDelete pgd = (PatchGroupDelete)tsbk;
|
||||||
|
mPatchGroupManager.removePatchGroup(pgd.getPatchGroupAddress());
|
||||||
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());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case CCH_PLANNED_SHUTDOWN:
|
case CCH_PLANNED_SHUTDOWN:
|
||||||
if(!mControlChannelShutdownLogged)
|
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
|
* Process a traffic channel allocation message
|
||||||
*/
|
*/
|
||||||
private void processTSBKChannelGrant(TSBKMessage 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 channel = null;
|
||||||
String from = null;
|
String from = null;
|
||||||
String to = 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
|
* 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)
|
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(!mRunning.compareAndSet(true, false))
|
||||||
{
|
{
|
||||||
//If this recorder has already been stopped/closed, immediately inform the listener
|
synchronized(mRecordingClosedListener)
|
||||||
mRecordingClosedListener.receive(this);
|
{
|
||||||
|
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)
|
if(mProcessorHandle != null)
|
||||||
|
|||||||
@@ -18,20 +18,28 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package sample;
|
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.Collection;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class OverflowableTransferQueue<E>
|
public class OverflowableTransferQueue<E>
|
||||||
{
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(OverflowableTransferQueue.class);
|
||||||
|
|
||||||
public enum State {NORMAL, OVERFLOW};
|
public enum State {NORMAL, OVERFLOW};
|
||||||
private Listener<State> mStateListener;
|
private IOverflowListener mOverflowListener;
|
||||||
|
|
||||||
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
|
private LinkedTransferQueue<E> mQueue = new LinkedTransferQueue<E>();
|
||||||
private AtomicInteger mCounter = new AtomicInteger();
|
private AtomicInteger mCounter = new AtomicInteger();
|
||||||
|
private AtomicBoolean mOverflow = new AtomicBoolean();
|
||||||
private int mMaximumSize;
|
private int mMaximumSize;
|
||||||
private int mResetThreshold;
|
private int mResetThreshold;
|
||||||
private boolean mOverflow = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concurrent transfer queue that couples a higher-throughput linked transfer queue with an atomic integer for
|
* 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)
|
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);
|
setOverflow(true);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
mQueue.offer(e);
|
|
||||||
mCounter.incrementAndGet();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,41 +81,36 @@ public class OverflowableTransferQueue<E>
|
|||||||
{
|
{
|
||||||
int drainCount = mQueue.drainTo(collection, maxElements);
|
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;
|
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
|
* Toggles the overflow state and broadcast state change to listener
|
||||||
*/
|
|
||||||
public State getState()
|
|
||||||
{
|
|
||||||
return mOverflow ? State.OVERFLOW : State.NORMAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the overflow state
|
|
||||||
*/
|
*/
|
||||||
private void setOverflow(boolean overflow)
|
private void setOverflow(boolean overflow)
|
||||||
{
|
{
|
||||||
mOverflow = overflow;
|
if(mOverflow.compareAndSet(!overflow, overflow))
|
||||||
|
|
||||||
if(mStateListener != null)
|
|
||||||
{
|
{
|
||||||
mStateListener.receive(getState());
|
if(mOverflowListener != null)
|
||||||
|
{
|
||||||
|
mOverflowListener.sourceOverflow(overflow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +123,11 @@ public class OverflowableTransferQueue<E>
|
|||||||
{
|
{
|
||||||
mQueue.clear();
|
mQueue.clear();
|
||||||
mCounter.set(0);
|
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 module.Module;
|
||||||
import sample.SampleType;
|
import sample.SampleType;
|
||||||
|
import sample.real.IOverflowListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class to define the minimum functionality of a sample data provider.
|
* 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
|
public abstract class Source extends Module
|
||||||
{
|
{
|
||||||
protected SampleType mSampleType;
|
protected SampleType mSampleType;
|
||||||
|
protected IOverflowListener mOverflowListener;
|
||||||
|
|
||||||
public Source( SampleType sampleType )
|
public Source( SampleType sampleType )
|
||||||
{
|
{
|
||||||
@@ -47,4 +49,25 @@ public abstract class Source extends Module
|
|||||||
{
|
{
|
||||||
mSampleType = sampleType;
|
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))
|
if(mRunning.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
//Set periodicity to an odd multiple to avoid contention with transfer buffer receivers
|
//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.OverflowableTransferQueue;
|
||||||
import sample.complex.Complex;
|
import sample.complex.Complex;
|
||||||
import sample.complex.ComplexBuffer;
|
import sample.complex.ComplexBuffer;
|
||||||
|
import sample.real.IOverflowListener;
|
||||||
import source.ComplexSource;
|
import source.ComplexSource;
|
||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
@@ -51,7 +52,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
private static final int BUFFER_MAX_CAPACITY = 300;
|
private static final int BUFFER_MAX_CAPACITY = 300;
|
||||||
|
|
||||||
//Threshold for resetting buffer overflow condition
|
//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_RATE = 48000;
|
||||||
private static int CHANNEL_PASS_FREQUENCY = 12000;
|
private static int CHANNEL_PASS_FREQUENCY = 12000;
|
||||||
@@ -101,21 +102,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
mTunerFrequency = mTuner.getTunerController().getFrequency();
|
mTunerFrequency = mTuner.getTunerController().getFrequency();
|
||||||
|
|
||||||
mBuffer = new OverflowableTransferQueue<>(BUFFER_MAX_CAPACITY, BUFFER_OVERFLOW_RESET_THRESHOLD);
|
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 */
|
/* Setup the frequency translator to the current source frequency */
|
||||||
long frequencyOffset = mTunerFrequency - mTunerChannel.getFrequency();
|
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()));
|
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)
|
public void start(ScheduledExecutorService executor)
|
||||||
{
|
{
|
||||||
@@ -175,7 +169,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
}
|
}
|
||||||
else
|
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);
|
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 */
|
/* Check to see if we've been shutdown */
|
||||||
if(!mProcessing)
|
if(!mProcessing)
|
||||||
|
|||||||
@@ -78,14 +78,9 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
|
|
||||||
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
|
private SampleRate mSampleRate = DEFAULT_SAMPLE_RATE;
|
||||||
|
|
||||||
private ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
|
protected ByteSampleAdapter mSampleAdapter = new ByteSampleAdapter();
|
||||||
|
|
||||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
|
||||||
|
|
||||||
protected int mOscillatorFrequency = 28800000; //28.8 MHz
|
protected int mOscillatorFrequency = 28800000; //28.8 MHz
|
||||||
|
protected USBTransferProcessor mUSBTransferProcessor;
|
||||||
private USBTransferProcessor mUSBTransferProcessor;
|
|
||||||
|
|
||||||
protected Descriptor mDescriptor;
|
protected Descriptor mDescriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -249,6 +249,11 @@ public class E4KTunerController extends RTL2832TunerController
|
|||||||
throw new SourceException("E4K Tuner Controller - error during "
|
throw new SourceException("E4K Tuner Controller - error during "
|
||||||
+ "init()", e);
|
+ "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.OverflowableTransferQueue;
|
||||||
import sample.adapter.ISampleAdapter;
|
import sample.adapter.ISampleAdapter;
|
||||||
import sample.complex.ComplexBuffer;
|
import sample.complex.ComplexBuffer;
|
||||||
|
import sample.real.IOverflowListener;
|
||||||
import source.tuner.TunerManager;
|
import source.tuner.TunerManager;
|
||||||
import util.ThreadPool;
|
import util.ThreadPool;
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ public class USBTransferProcessor implements TransferCallback
|
|||||||
private static final int FILLED_BUFFER_MAX_CAPACITY = 300;
|
private static final int FILLED_BUFFER_MAX_CAPACITY = 300;
|
||||||
|
|
||||||
//Threshold for resetting buffer overflow condition
|
//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;
|
private String mDeviceName;
|
||||||
|
|
||||||
@@ -101,12 +102,12 @@ public class USBTransferProcessor implements TransferCallback
|
|||||||
mBufferSize = bufferSize;
|
mBufferSize = bufferSize;
|
||||||
|
|
||||||
mFilledBuffers = new OverflowableTransferQueue<>(FILLED_BUFFER_MAX_CAPACITY, FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD);
|
mFilledBuffers = new OverflowableTransferQueue<>(FILLED_BUFFER_MAX_CAPACITY, FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD);
|
||||||
mFilledBuffers.setStateListener(new Listener<OverflowableTransferQueue.State>()
|
mFilledBuffers.setOverflowListener(new IOverflowListener()
|
||||||
{
|
{
|
||||||
@Override
|
@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");
|
mLog.debug(mDeviceName + " - buffer overflow - temporary pause until processing catches up");
|
||||||
}
|
}
|
||||||
@@ -173,7 +174,7 @@ public class USBTransferProcessor implements TransferCallback
|
|||||||
|
|
||||||
//Start transferred buffer dispatcher
|
//Start transferred buffer dispatcher
|
||||||
mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferDispatcher,
|
mBufferDispatcherFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mBufferDispatcher,
|
||||||
20, 20, TimeUnit.MILLISECONDS);
|
0, 11, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
//Register with LibUSB processor so that it auto-starts LibUSB processing
|
//Register with LibUSB processor so that it auto-starts LibUSB processing
|
||||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
|
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
|
||||||
@@ -300,6 +301,7 @@ public class USBTransferProcessor implements TransferCallback
|
|||||||
{
|
{
|
||||||
case LibUsb.TRANSFER_COMPLETED:
|
case LibUsb.TRANSFER_COMPLETED:
|
||||||
case LibUsb.TRANSFER_STALL:
|
case LibUsb.TRANSFER_STALL:
|
||||||
|
case LibUsb.TRANSFER_TIMED_OUT:
|
||||||
if(transfer.actualLength() > 0)
|
if(transfer.actualLength() > 0)
|
||||||
{
|
{
|
||||||
ByteBuffer buffer = transfer.buffer();
|
ByteBuffer buffer = transfer.buffer();
|
||||||
|
|||||||
Reference in New Issue
Block a user