mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 23:27:24 -05:00
Compare commits
67 Commits
v0.3.0-bet
...
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64ccacf04a | ||
|
|
f00b9555fe | ||
|
|
85442a9648 | ||
|
|
c67a0b329d | ||
|
|
8035d64fcd | ||
|
|
df2a061a08 | ||
|
|
e430319937 | ||
|
|
724f769771 | ||
|
|
c196cfebed | ||
|
|
e80b5866d4 | ||
|
|
c8994d5593 | ||
|
|
99ef57c90f | ||
|
|
be62ae0459 | ||
|
|
b9be6f43e0 | ||
|
|
d5ad3f807d | ||
|
|
d85ebd018d | ||
|
|
ab4e31ae00 | ||
|
|
5efd89632e | ||
|
|
1dc3863489 | ||
|
|
441286ba13 | ||
|
|
e4323df347 | ||
|
|
a872e0d664 | ||
|
|
fbc92cfe31 | ||
|
|
1a432197ac | ||
|
|
fbc6e508bd | ||
|
|
f9b6e67b00 | ||
|
|
a628800a71 | ||
|
|
25f71a76c4 | ||
|
|
5fe8ccd478 | ||
|
|
069f951a86 | ||
|
|
dab6df7836 | ||
|
|
89da46b63c | ||
|
|
5f2ce2a9f2 | ||
|
|
7a0b377971 | ||
|
|
5dbe0ecce3 | ||
|
|
6647c581a6 | ||
|
|
a2ab5bfa95 | ||
|
|
ed486440b8 | ||
|
|
13464e01fc | ||
|
|
ea46a8b0e8 | ||
|
|
d510c34356 | ||
|
|
ae8a44caed | ||
|
|
1aa85a86ca | ||
|
|
17539d4d55 | ||
|
|
1db1d28390 | ||
|
|
37a941e904 | ||
|
|
57f6655824 | ||
|
|
4cd8f8209f | ||
|
|
61bfef5798 | ||
|
|
ef172a04c6 | ||
|
|
a877afcceb | ||
|
|
980ad72d4d | ||
|
|
9b746cc032 | ||
|
|
f51ae0b5da | ||
|
|
561078e9bf | ||
|
|
e052b01f9b | ||
|
|
09cf4fcfd7 | ||
|
|
5e731d0e9f | ||
|
|
670a02b0ba | ||
|
|
9ef156b061 | ||
|
|
d9cf1b19f3 | ||
|
|
a4a339b505 | ||
|
|
119370c46e | ||
|
|
4eb66e6045 | ||
|
|
8827ecd0e9 | ||
|
|
8660002f93 | ||
|
|
2058fcbc1d |
@@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
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
|
||||
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
@@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
16
README.md
16
README.md
@@ -2,18 +2,10 @@
|
||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
|
||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
|
||||
* [User's Manual Version 0.2.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
|
||||
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0)
|
||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||
|
||||

|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-67489234-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||

|
||||
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
||||
@@ -7,7 +7,7 @@
|
||||
<property name="major" value="0"/>
|
||||
<property name="minor" value="3"/>
|
||||
<property name="patch" value="0"/>
|
||||
<property name="release" value="-beta5"/>
|
||||
<property name="release" value=""/>
|
||||
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
|
||||
|
||||
<!-- Build properties -->
|
||||
@@ -16,7 +16,6 @@
|
||||
<property name="source.configs" location="${repo}/config"/>
|
||||
<property name="source.images" location="${repo}/images"/>
|
||||
<property name="source.libs" location="${repo}/imports"/>
|
||||
<property name="source.license" location="${repo}/license"/>
|
||||
<property name="source.scripts" location="${repo}/scripts"/>
|
||||
|
||||
<property name="output" location="${repo}/product"/>
|
||||
@@ -57,8 +56,8 @@
|
||||
<exclude name="*.rules"/>
|
||||
</fileset>
|
||||
|
||||
<fileset dir="${source.license}">
|
||||
<include name="license.txt"/>
|
||||
<fileset dir="${repo}">
|
||||
<include name="LICENSE"/>
|
||||
</fileset>
|
||||
</jar>
|
||||
|
||||
@@ -85,8 +84,8 @@
|
||||
|
||||
<!-- Copy license file -->
|
||||
<copy todir="${output.folder}">
|
||||
<fileset dir="${source.license}">
|
||||
<include name="license.txt"/>
|
||||
<fileset dir="${repo}">
|
||||
<include name="LICENSE"/>
|
||||
</fileset>
|
||||
</copy>
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public class AliasModel extends AbstractTableModel
|
||||
{
|
||||
for(Alias alias : mAliases)
|
||||
{
|
||||
if(alias.getList().equalsIgnoreCase(name))
|
||||
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
|
||||
{
|
||||
aliasList.addAlias(alias);
|
||||
}
|
||||
|
||||
@@ -40,10 +40,11 @@ public class AliasNameEditor extends Editor<Alias>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>();
|
||||
private static ComboBoxModel<String> EMPTY_LIST_MODEL = new DefaultComboBoxModel<>();
|
||||
private static ComboBoxModel<String> EMPTY_GROUP_MODEL = new DefaultComboBoxModel<>();
|
||||
|
||||
private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_MODEL);
|
||||
private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_MODEL);
|
||||
private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_LIST_MODEL);
|
||||
private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_GROUP_MODEL);
|
||||
private JComboBox<Icon> mIconCombo;
|
||||
private JTextField mName;
|
||||
private JButton mButtonColor;
|
||||
@@ -110,7 +111,7 @@ public class AliasNameEditor extends Editor<Alias>
|
||||
|
||||
if(groups.isEmpty())
|
||||
{
|
||||
mGroupCombo.setModel(EMPTY_MODEL);
|
||||
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -238,7 +239,7 @@ public class AliasNameEditor extends Editor<Alias>
|
||||
|
||||
if(listNames.isEmpty())
|
||||
{
|
||||
mListCombo.setModel(EMPTY_MODEL);
|
||||
mListCombo.setModel(EMPTY_LIST_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -253,7 +254,7 @@ public class AliasNameEditor extends Editor<Alias>
|
||||
|
||||
if(groupNames.isEmpty())
|
||||
{
|
||||
mGroupCombo.setModel(EMPTY_MODEL);
|
||||
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -285,8 +286,8 @@ public class AliasNameEditor extends Editor<Alias>
|
||||
}
|
||||
else
|
||||
{
|
||||
mListCombo.setModel(EMPTY_MODEL);
|
||||
mGroupCombo.setModel(EMPTY_MODEL);
|
||||
mListCombo.setModel(EMPTY_LIST_MODEL);
|
||||
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
|
||||
mName.setText(null);
|
||||
|
||||
mButtonColor.setBackground(getBackground());
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
******************************************************************************/
|
||||
package alias.id;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class WildcardID implements Comparable<WildcardID>
|
||||
@@ -45,7 +47,7 @@ public class WildcardID implements Comparable<WildcardID>
|
||||
*/
|
||||
public WildcardID(String value)
|
||||
{
|
||||
assert(value != null && value.contains(WILDCARD) && value.length() < 10);
|
||||
Validate.isTrue(value != null && value.contains(WILDCARD) && value.length() < 10);
|
||||
|
||||
mValue = value;
|
||||
|
||||
|
||||
@@ -90,7 +90,8 @@ public class AudioChannelPanel extends JPanel
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("align center center, insets 0 0 0 0", "[][][align right]0[grow,fill]", ""));
|
||||
setLayout(new MigLayout("align center center, insets 0 0 0 0",
|
||||
"[][][align right]0[grow,fill]", ""));
|
||||
setBackground(mBackgroundColor);
|
||||
|
||||
mMutedLabel.setFont(mFont);
|
||||
@@ -109,7 +110,7 @@ public class AudioChannelPanel extends JPanel
|
||||
|
||||
mTo.setFont(mFont);
|
||||
mTo.setForeground(mValueColor);
|
||||
add(mTo);
|
||||
add(mTo,"wmin 10lp");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,30 +160,40 @@ public class AudioChannelPanel extends JPanel
|
||||
* does not occur on the Swing event thread -- wrap any calls to this
|
||||
* method with an event thread call.
|
||||
*/
|
||||
private void updateLabel(JLabel valueLabel, String value, JLabel aliasLabel, Alias alias)
|
||||
private void updateLabel(JLabel textLabel, String value, JLabel iconLabel, Alias alias)
|
||||
{
|
||||
if(value != null)
|
||||
{
|
||||
|
||||
if(alias != null)
|
||||
{
|
||||
valueLabel.setText(alias.getName());
|
||||
aliasLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
|
||||
iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
|
||||
|
||||
String aliasName = alias.getName();
|
||||
|
||||
//Truncate label if length exceeds 33 characters - hack!
|
||||
if(aliasName.length() > 33)
|
||||
{
|
||||
textLabel.setText(aliasName.substring(0,30) + "...");
|
||||
}
|
||||
else
|
||||
{
|
||||
textLabel.setText(aliasName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
valueLabel.setText(value);
|
||||
aliasLabel.setIcon(null);
|
||||
iconLabel.setIcon(null);
|
||||
textLabel.setText(value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
valueLabel.setText("-----");
|
||||
aliasLabel.setIcon(null);
|
||||
iconLabel.setIcon(null);
|
||||
textLabel.setText("-----");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes audio metadata to update this panel's display values
|
||||
*/
|
||||
|
||||
@@ -33,8 +33,7 @@ public class AudioChannelsPanel extends JPanel
|
||||
|
||||
public AudioChannelsPanel(IconManager iconManager, SettingsManager settingsManager, IAudioController controller)
|
||||
{
|
||||
// setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill][][grow,fill]", "[grow,fill]"));
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[][sg,grow,fill][][sg,grow,fill]", "[grow,fill]"));
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[][sg abc,grow,fill][][sg abc,grow,fill]", "[grow,fill]"));
|
||||
|
||||
setBackground(Color.BLACK);
|
||||
|
||||
@@ -44,7 +43,7 @@ public class AudioChannelsPanel extends JPanel
|
||||
|
||||
for(int x = 0; x < outputs.size(); x++)
|
||||
{
|
||||
add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x)), "growx");
|
||||
add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x)));
|
||||
|
||||
if(x < outputs.size() - 1)
|
||||
{
|
||||
|
||||
@@ -86,6 +86,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the saved mixer configuration or a default configuration for audio playback.
|
||||
*/
|
||||
private void loadSettings()
|
||||
{
|
||||
MixerChannelConfiguration configuration = null;
|
||||
@@ -132,6 +135,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default audio playback configuration with a mono audio playback channel.
|
||||
*/
|
||||
private MixerChannelConfiguration getDefaultConfiguration()
|
||||
{
|
||||
/* Use the system default mixer and mono channel as default startup */
|
||||
@@ -161,6 +167,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
mAudioOutputConnections.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void receive(AudioPacket packet)
|
||||
{
|
||||
@@ -176,11 +185,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
|
||||
for(AudioOutputConnection connection : mAudioOutputConnections)
|
||||
{
|
||||
if(connection.isInactive() &&
|
||||
mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
|
||||
if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
|
||||
{
|
||||
mChannelConnectionMap.remove(connection.getChannelMetadataID());
|
||||
connection.disconnect();
|
||||
mAvailableConnectionCount++;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -192,7 +201,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the lowest priority channel connection
|
||||
* Identifies the lowest priority channel connection where the a higher value indicates a lower priority.
|
||||
*/
|
||||
private void updateLowestPriorityAssignment()
|
||||
{
|
||||
@@ -201,19 +210,32 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
for(AudioOutputConnection connection : mAudioOutputConnections)
|
||||
{
|
||||
if(connection.isConnected() &&
|
||||
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority()))
|
||||
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority()))
|
||||
{
|
||||
mLowestPriorityConnection = connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures audio playback to use the configuration specified in the entry argument.
|
||||
*
|
||||
* @param entry to use in configuring the audio playback setup.
|
||||
* @throws AudioException if there is an error
|
||||
*/
|
||||
@Override
|
||||
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
|
||||
{
|
||||
setMixerChannelConfiguration(entry, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures audio playback to use the configuration specified in the entry argument.
|
||||
*
|
||||
* @param entry to use in configuring the audio playback setup.
|
||||
* @param saveSettings to save the audio playback configuration settings in the properties file.
|
||||
* @throws AudioException if there is an error
|
||||
*/
|
||||
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
|
||||
{
|
||||
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
|
||||
@@ -266,9 +288,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all channel assignments and terminates all audio outputs in
|
||||
* preparation for complete shutdown or change to another mixer/channel
|
||||
* configuration
|
||||
* Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change
|
||||
* to another mixer/channel configuration
|
||||
*/
|
||||
private void disposeCurrentConfiguration()
|
||||
{
|
||||
@@ -288,12 +309,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
mLowestPriorityConnection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current audio playback mixer channel configuration setting.
|
||||
*/
|
||||
@Override
|
||||
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
||||
{
|
||||
return mMixerChannelConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of audio outputs available for the current mixer channel configuration
|
||||
*/
|
||||
@Override
|
||||
public List<AudioOutput> getAudioOutputs()
|
||||
{
|
||||
@@ -311,12 +338,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an audio event listener to receive audio event notifications.
|
||||
*/
|
||||
@Override
|
||||
public void addControllerListener(Listener<AudioEvent> listener)
|
||||
{
|
||||
mControllerBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an audio event listener from receiving audio event notifications.
|
||||
*/
|
||||
@Override
|
||||
public void removeControllerListener(Listener<AudioEvent> listener)
|
||||
{
|
||||
@@ -324,16 +357,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an audio output connection for the packet if one is
|
||||
* available, or overrides an existing lower priority connection.
|
||||
* Returns null if no connection is available for the audio packet.
|
||||
* Returns an audio output connection for the packet if one is available, or overrides an existing lower priority
|
||||
* connection. Returns null if no connection is available for the audio packet.
|
||||
*
|
||||
* @param packet from a source
|
||||
* @param audioPacket from a decoding channel source
|
||||
* @return an audio output connection or null
|
||||
*/
|
||||
private AudioOutputConnection getConnection(AudioPacket packet)
|
||||
private AudioOutputConnection getConnection(AudioPacket audioPacket)
|
||||
{
|
||||
int channelMetadataID = packet.getMetadata().getMetadataID();
|
||||
int channelMetadataID = audioPacket.getMetadata().getMetadataID();
|
||||
|
||||
//Use an existing connection
|
||||
if(mChannelConnectionMap.containsKey(channelMetadataID))
|
||||
@@ -341,37 +373,35 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
return mChannelConnectionMap.get(channelMetadataID);
|
||||
}
|
||||
|
||||
//Connect to and use an available connection
|
||||
//Connect to an unused, available connection
|
||||
if(mAvailableConnectionCount > 0)
|
||||
{
|
||||
for(AudioOutputConnection connection : mAudioOutputConnections)
|
||||
{
|
||||
if(connection.isDisconnected())
|
||||
{
|
||||
connection.connect(channelMetadataID);
|
||||
connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority());
|
||||
mChannelConnectionMap.put(channelMetadataID, connection);
|
||||
mAvailableConnectionCount--;
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Preempt an existing connection and connect it to this higher priority source
|
||||
else //Check for a channel priority override
|
||||
//Preempt an existing lower priority connection and connect when this is a higher priority packet
|
||||
else
|
||||
{
|
||||
int priority = packet.getMetadata().getAudioPriority();
|
||||
int priority = audioPacket.getMetadata().getAudioPriority();
|
||||
|
||||
if(mLowestPriorityConnection != null &&
|
||||
priority < mLowestPriorityConnection.getPriority())
|
||||
AudioOutputConnection connection = mLowestPriorityConnection;
|
||||
|
||||
if(connection != null && priority < connection.getPriority())
|
||||
{
|
||||
AudioOutputConnection connection = mLowestPriorityConnection;
|
||||
|
||||
mChannelConnectionMap.remove(connection.getChannelMetadataID());
|
||||
|
||||
connection.connect(channelMetadataID);
|
||||
connection.connect(channelMetadataID, priority);
|
||||
|
||||
mChannelConnectionMap.put(channelMetadataID, connection);
|
||||
|
||||
updateLowestPriorityAssignment();
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -396,7 +426,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
|
||||
for(AudioPacket packet : packets)
|
||||
{
|
||||
/* Don't process any packet's marked as do not monitor */
|
||||
/* Don't process any packet's marked as do not monitor */
|
||||
if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO)
|
||||
{
|
||||
AudioOutputConnection connection = getConnection(packet);
|
||||
@@ -494,9 +524,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
||||
* Connects this assignment to the indicated source so that audio
|
||||
* packets from this source can be sent to the audio output
|
||||
*/
|
||||
public void connect(int source)
|
||||
public void connect(int source, int priority)
|
||||
{
|
||||
mChannelMetadataID = source;
|
||||
mPriority = priority;
|
||||
updateLowestPriorityAssignment();
|
||||
|
||||
mAudioOutput.updateTimestamp();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import util.ThreadPool;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -44,7 +45,8 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
||||
private final static String TERMINATOR = "\r\n";
|
||||
private final static String SEPARATOR = ":";
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
||||
private static final long CONNECTION_ATTEMPT_TIMEOUT = 5000; //5 seconds
|
||||
|
||||
private NioSocketConnector mSocketConnector;
|
||||
private IoSession mStreamingSession = null;
|
||||
@@ -99,10 +101,11 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
if(mSocketConnector == null)
|
||||
{
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||
mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT);
|
||||
|
||||
// mSocketConnector.getFilterChain().addLast("logger",
|
||||
// new LoggingFilter(IcecastTCPAudioBroadcaster.class));
|
||||
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
|
||||
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
|
||||
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
|
||||
|
||||
mSocketConnector.getFilterChain().addLast("codec",
|
||||
new ProtocolCodecFilter(new IcecastCodecFactory()));
|
||||
@@ -123,9 +126,20 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
ConnectFuture future = mSocketConnector
|
||||
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
|
||||
getBroadcastConfiguration().getPort()));
|
||||
future.awaitUninterruptibly();
|
||||
|
||||
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
|
||||
mStreamingSession = future.getSession();
|
||||
}
|
||||
catch(UnresolvedAddressException uae)
|
||||
{
|
||||
//What do you do with this? Nothing.
|
||||
}
|
||||
catch(InterruptedException ie)
|
||||
{
|
||||
//We couldn't connect within the timeout period -- disconnect so that we can try again later
|
||||
disconnect();
|
||||
}
|
||||
catch(RuntimeIoException rie)
|
||||
{
|
||||
Throwable throwableCause = rie.getCause();
|
||||
@@ -137,16 +151,20 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
else if(throwableCause != null)
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.debug("Failed to connect", rie);
|
||||
mLog.debug("[" + getStreamName() + "] failed to connect", rie);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.debug("Failed to connect - no exception is available");
|
||||
mLog.debug("[" + getStreamName() + "] failed to connect - no exception is available");
|
||||
}
|
||||
|
||||
disconnect();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
mConnecting.set(false);
|
||||
}
|
||||
@@ -164,12 +182,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if(connected())
|
||||
if(connected() && mStreamingSession != null)
|
||||
{
|
||||
if(mStreamingSession != null)
|
||||
{
|
||||
mStreamingSession.closeNow();
|
||||
}
|
||||
mStreamingSession.closeNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,17 +245,18 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
@Override
|
||||
public void sessionClosed(IoSession session) throws Exception
|
||||
{
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
//If there is already an error state, don't override it. Otherwise, set state to disconnected
|
||||
if(!getBroadcastState().isErrorState())
|
||||
{
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
}
|
||||
|
||||
mSocketConnector.dispose();
|
||||
mStreamingSession = null;
|
||||
mSocketConnector = null;
|
||||
|
||||
mConnecting.set(false);
|
||||
|
||||
super.sessionClosed(session);
|
||||
}
|
||||
|
||||
@@ -253,45 +273,36 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
|
||||
if(connected())
|
||||
{
|
||||
mLog.info("Streaming connection error detected - resetting connection - " + reason);
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else if(reason.startsWith("Connection reset"))
|
||||
{
|
||||
mLog.info("Streaming connection reset by remote server - reestablishing connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else if(reason.startsWith("Operation timed out"))
|
||||
{
|
||||
mLog.info("Streaming connection timed out - resetting connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
disconnect();
|
||||
mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.", cause);
|
||||
}
|
||||
}
|
||||
else if(ioe instanceof WriteTimeoutException)
|
||||
{
|
||||
mLog.info("Network write timeout error - resetting connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
disconnect();
|
||||
mLog.error("Unspecified IO error - streaming halted.", cause);
|
||||
mLog.error("[" + getStreamName() + "] Unspecified IO error - streaming halted.", cause);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Broadcast error", cause);
|
||||
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
disconnect();
|
||||
}
|
||||
@@ -319,6 +330,10 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
|
||||
}
|
||||
else if(message.contains("HTTP/1.1 501"))
|
||||
{
|
||||
disconnect(); //So that we can reconnect later
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Unrecognized server response:" + message);
|
||||
@@ -328,7 +343,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Icecast TCP broadcaster - unrecognized message [ " + object.getClass() +
|
||||
mLog.error("[" + getStreamName() + "]Icecast TCP broadcaster - unrecognized message [ " + object.getClass() +
|
||||
"] received:" + object.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
|
||||
|
||||
disconnect();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
mConnecting.set(false);
|
||||
}
|
||||
@@ -185,12 +189,13 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if(connected())
|
||||
if(connected() && mStreamingSession != null)
|
||||
{
|
||||
if(mStreamingSession != null)
|
||||
{
|
||||
mStreamingSession.closeNow();
|
||||
}
|
||||
mStreamingSession.closeNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,15 +231,15 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
|
||||
@Override
|
||||
public void sessionClosed(IoSession session) throws Exception
|
||||
{
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
//If there is already an error state, don't override it. Otherwise, set state to disconnected
|
||||
if(!getBroadcastState().isErrorState())
|
||||
{
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
}
|
||||
|
||||
mSocketConnector.dispose();
|
||||
mStreamingSession = null;
|
||||
mSocketConnector = null;
|
||||
|
||||
mConnecting.set(false);
|
||||
super.sessionClosed(session);
|
||||
@@ -255,13 +260,11 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
|
||||
{
|
||||
mLog.info("Streaming connection reset by remote server - reestablishing connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else if(reason.startsWith("Operation timed out"))
|
||||
{
|
||||
mLog.info("Streaming connection timed out - resetting connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -204,6 +204,10 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
|
||||
|
||||
disconnect();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
mConnecting.set(false);
|
||||
}
|
||||
@@ -221,13 +225,13 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if(connected())
|
||||
if(connected() && mStreamingSession != null)
|
||||
{
|
||||
if(mStreamingSession != null)
|
||||
{
|
||||
mStreamingSession.write(new TerminateBroadcast());
|
||||
mStreamingSession.closeNow();
|
||||
}
|
||||
mStreamingSession.closeNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,15 +353,15 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
|
||||
@Override
|
||||
public void sessionClosed(IoSession session) throws Exception
|
||||
{
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
//If there is already an error state, don't override it. Otherwise, set state to disconnected
|
||||
if(!getBroadcastState().isErrorState())
|
||||
{
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
}
|
||||
|
||||
mSocketConnector.dispose();
|
||||
mStreamingSession = null;
|
||||
mSocketConnector = null;
|
||||
|
||||
mConnecting.set(false);
|
||||
super.sessionClosed(session);
|
||||
@@ -378,13 +382,11 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
|
||||
{
|
||||
mLog.info("Streaming connection reset by remote server - reestablishing connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else if(reason.startsWith("Operation timed out"))
|
||||
{
|
||||
mLog.info("Streaming connection timed out - resetting connection");
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ package audio.broadcast.shoutcast.v2;
|
||||
import audio.broadcast.BroadcastConfiguration;
|
||||
import audio.broadcast.BroadcastFormat;
|
||||
import audio.broadcast.BroadcastServerType;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
|
||||
@@ -90,7 +91,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||
*/
|
||||
public void setStreamID(int streamID)
|
||||
{
|
||||
assert(1 <= streamID && streamID <= Integer.MAX_VALUE);
|
||||
Validate.isTrue(1 <= streamID && streamID <= Integer.MAX_VALUE);
|
||||
mStreamID = streamID;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
******************************************************************************/
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class NegotiateMaxPayloadSize extends UltravoxMessage
|
||||
{
|
||||
/**
|
||||
@@ -47,8 +49,8 @@ public class NegotiateMaxPayloadSize extends UltravoxMessage
|
||||
*/
|
||||
public void setMaximumPayloadSize(int desiredMaximum, int minimum)
|
||||
{
|
||||
assert(desiredMaximum <= 16377);
|
||||
assert(minimum <= 16377);
|
||||
Validate.isTrue(desiredMaximum <= 16377);
|
||||
Validate.isTrue(minimum <= 16377);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(desiredMaximum).append(":").append(minimum);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package audio.broadcast.shoutcast.v2.ultravox;
|
||||
|
||||
import bits.BinaryMessage;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -154,7 +155,7 @@ public abstract class UltravoxMessage
|
||||
*/
|
||||
public void setSendQueuePriority(int priority)
|
||||
{
|
||||
assert(0 <= priority && priority <= 7);
|
||||
Validate.isTrue(0 <= priority && priority <= 7);
|
||||
|
||||
mMessage.setInt(priority, SEND_QUEUE_PRIORITY);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.BitSet;
|
||||
import java.util.logging.LogManager;
|
||||
|
||||
import edac.CRC;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class BinaryMessage extends BitSet
|
||||
{
|
||||
@@ -135,6 +136,17 @@ public class BinaryMessage extends BitSet
|
||||
{
|
||||
mCRC = crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the number of bit errors that were corrected in this message.
|
||||
*
|
||||
* @see CorrectedBinaryMessage subclass that supports setting this value
|
||||
* @return number of corrected bits in this message
|
||||
*/
|
||||
public int getCorrectedBitCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current pointer index
|
||||
@@ -456,7 +468,7 @@ public class BinaryMessage extends BitSet
|
||||
*/
|
||||
public byte getByte(int index)
|
||||
{
|
||||
assert((index + 7) <= size());
|
||||
Validate.isTrue((index + 7) <= size());
|
||||
|
||||
int value = 0;
|
||||
|
||||
@@ -481,7 +493,7 @@ public class BinaryMessage extends BitSet
|
||||
*/
|
||||
public void setByte(int index, byte value)
|
||||
{
|
||||
assert((index + 8) <= size());
|
||||
Validate.isTrue((index + 8) <= size());
|
||||
|
||||
int mask = 0x80;
|
||||
|
||||
|
||||
65
src/bits/CorrectedBinaryMessage.java
Normal file
65
src/bits/CorrectedBinaryMessage.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*******************************************************************************
|
||||
* 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 bits;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public class CorrectedBinaryMessage extends BinaryMessage
|
||||
{
|
||||
private int mCorrectedBitCount;
|
||||
|
||||
/**
|
||||
* Subclass of binary message class to allow capturing a corrected bits metric.
|
||||
* @param size of message to create.
|
||||
*/
|
||||
public CorrectedBinaryMessage(int size)
|
||||
{
|
||||
super(size);
|
||||
}
|
||||
|
||||
public CorrectedBinaryMessage(int size, boolean[] bitsToPreload)
|
||||
{
|
||||
super(size, bitsToPreload);
|
||||
}
|
||||
|
||||
public CorrectedBinaryMessage(BitSet bitset, int size)
|
||||
{
|
||||
super(bitset, size);
|
||||
}
|
||||
|
||||
public CorrectedBinaryMessage(byte[] data)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectedBitCount()
|
||||
{
|
||||
return mCorrectedBitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of bits that were corrected in this message
|
||||
* @param count of corrected bits
|
||||
*/
|
||||
public void setCorrectedBitCount(int count)
|
||||
{
|
||||
mCorrectedBitCount = count;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
******************************************************************************/
|
||||
package bits;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -39,7 +41,7 @@ public class MultiSyncPatternMatcher
|
||||
|
||||
public MultiSyncPatternMatcher( int syncSize )
|
||||
{
|
||||
assert( syncSize < 64 );
|
||||
Validate.isTrue(syncSize < 64);
|
||||
|
||||
//Setup a bit mask of all ones the length of the sync pattern
|
||||
mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 );
|
||||
|
||||
31
src/channel/heartbeat/Heartbeat.java
Normal file
31
src/channel/heartbeat/Heartbeat.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* 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 channel.heartbeat;
|
||||
|
||||
public class Heartbeat
|
||||
{
|
||||
/**
|
||||
* Periodic pulse that is broadcast to subscribing modules within the demodulating chain so that
|
||||
* monitoring of state and other attributes can occur on the primary demodulation and decoding
|
||||
* thread.
|
||||
*/
|
||||
public Heartbeat()
|
||||
{
|
||||
}
|
||||
}
|
||||
26
src/channel/heartbeat/IHeartbeatListener.java
Normal file
26
src/channel/heartbeat/IHeartbeatListener.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* 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 channel.heartbeat;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
public interface IHeartbeatListener
|
||||
{
|
||||
public Listener<Heartbeat> getHeartbeatListener();
|
||||
}
|
||||
28
src/channel/heartbeat/IHeartbeatProvider.java
Normal file
28
src/channel/heartbeat/IHeartbeatProvider.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 channel.heartbeat;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
public interface IHeartbeatProvider
|
||||
{
|
||||
public void setHeartbeatListener(Listener<Heartbeat> listener);
|
||||
|
||||
public void removeHeartbeatListener();
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import java.util.Map;
|
||||
|
||||
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
|
||||
{
|
||||
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" );
|
||||
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
|
||||
|
||||
public static final int COLUMN_STATE = 0;
|
||||
public static final int COLUMN_DECODER = 1;
|
||||
|
||||
@@ -26,6 +26,7 @@ import controller.channel.ChannelUtils;
|
||||
import icon.IconManager;
|
||||
import module.ProcessingChain;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import properties.SystemProperties;
|
||||
@@ -140,7 +141,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
|
||||
public AliasedValueCellRenderer(Attribute attribute)
|
||||
{
|
||||
assert(attribute != null);
|
||||
Validate.isTrue(attribute != null);
|
||||
mAttribute = attribute;
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ package channel.state;
|
||||
|
||||
import audio.squelch.ISquelchStateProvider;
|
||||
import audio.squelch.SquelchState;
|
||||
import channel.heartbeat.Heartbeat;
|
||||
import channel.heartbeat.IHeartbeatListener;
|
||||
import channel.metadata.Attribute;
|
||||
import channel.metadata.AttributeChangeRequest;
|
||||
import channel.metadata.IAttributeChangeRequestListener;
|
||||
@@ -36,14 +38,16 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.real.IOverflowListener;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
|
||||
|
||||
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
|
||||
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener
|
||||
IDecoderStateEventProvider, IFrequencyChangeListener, IHeartbeatListener, ISquelchStateProvider,
|
||||
IAttributeChangeRequestListener, IOverflowListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
|
||||
|
||||
@@ -56,11 +60,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
private Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
private Listener<SquelchState> mSquelchStateListener;
|
||||
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
|
||||
private StateMonitor mStateMonitor = new StateMonitor();
|
||||
private ScheduledFuture<?> mStateMonitorFuture;
|
||||
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
|
||||
private ChannelType mChannelType;
|
||||
private TrafficChannelManager mTrafficChannelEndListener;
|
||||
private CallEvent mTrafficChannelCallEvent;
|
||||
private FrequencyChangeListener mFrequencyChangeListener;
|
||||
|
||||
private boolean mSquelchLocked = false;
|
||||
private boolean mSelected = false;
|
||||
@@ -73,30 +77,21 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
|
||||
|
||||
/**
|
||||
* Channel state tracks the overall state of all processing modules and decoders
|
||||
* configured for the channel and provides squelch control and decoder state
|
||||
* reset events.
|
||||
* Channel state tracks the overall state of all processing modules and decoders configured for the channel and
|
||||
* provides squelch control and decoder state reset events.
|
||||
*
|
||||
* Uses a state enumeration that defines allowable channel state transitions in
|
||||
* order to track a call or data decode event from start to finish. Uses a
|
||||
* timer to monitor for inactivity and to provide a FADE period that indicates
|
||||
* to the user that the activity has stopped while continuing to provide details
|
||||
* about the call, before the state is reset to IDLE.
|
||||
*
|
||||
* Since this class is multi-threaded between decoder events and the internal
|
||||
* timer that monitors for inactivity, this class uses the mState object for
|
||||
* thread synchronization.
|
||||
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
|
||||
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
|
||||
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
|
||||
* reset to IDLE.
|
||||
*
|
||||
* State Descriptions:
|
||||
* IDLE Normal state. No voice or data call activity
|
||||
* CALL/DATA/ENCRYPTED/CONTROL
|
||||
* Decoding states.
|
||||
* FADE The phase after a voice or data call when either an explicit
|
||||
* call end has been received, or when no new signalling updates
|
||||
* have been received, and the fade timer has expired. This phase
|
||||
* allows for gui updates to signal to the user that the call is
|
||||
* ended, while continuing to display the call details for the user
|
||||
* TEARDOWN Indicates a traffic channel that will be torn down for reuse.
|
||||
* IDLE: Normal state. No voice or data call activity
|
||||
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
|
||||
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
|
||||
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
|
||||
* signal to the user that the call is ended, while continuing to display the call details for the user
|
||||
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
|
||||
*/
|
||||
public ChannelState(ChannelType channelType)
|
||||
{
|
||||
@@ -131,23 +126,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
if(mStateMonitorFuture == null && mStateMonitor != null)
|
||||
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
|
||||
|
||||
if(mTrafficChannelEndListener != null)
|
||||
{
|
||||
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
|
||||
|
||||
if(mTrafficChannelEndListener != null)
|
||||
{
|
||||
setState(State.CALL);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mStateMonitorFuture = executor.scheduleAtFixedRate(mStateMonitor, 0, 20, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch(RejectedExecutionException ree)
|
||||
{
|
||||
mLog.error("state monitor scheduled task rejected", ree);
|
||||
}
|
||||
setState(State.CALL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,16 +139,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
{
|
||||
mMutableMetadata.resetAllAttributes();
|
||||
|
||||
if(mStateMonitorFuture != null)
|
||||
{
|
||||
boolean success = mStateMonitorFuture.cancel(true);
|
||||
|
||||
if(!success)
|
||||
{
|
||||
mLog.error("Couldn't stop monitoring scheduled future");
|
||||
}
|
||||
}
|
||||
|
||||
mTrafficChannelEndListener = null;
|
||||
|
||||
if(mTrafficChannelCallEvent != null)
|
||||
@@ -176,8 +149,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
|
||||
mTrafficChannelCallEvent = null;
|
||||
|
||||
mStateMonitorFuture = null;
|
||||
|
||||
mSquelchLocked = false;
|
||||
}
|
||||
|
||||
@@ -186,7 +157,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
mCallEventListener = null;
|
||||
mDecoderStateListener = null;
|
||||
mSquelchStateListener = null;
|
||||
mStateMonitor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
if(mFrequencyChangeListener == null)
|
||||
{
|
||||
mFrequencyChangeListener = new FrequencyChangeListener();
|
||||
}
|
||||
|
||||
return mFrequencyChangeListener;
|
||||
}
|
||||
|
||||
private boolean isStandardChannel()
|
||||
@@ -295,70 +276,74 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel state to the specified state, or updates the timeout
|
||||
* values so that the state monitor will not change state. Broadcasts a
|
||||
* squelch event when the state changes and the audio squelch state should
|
||||
* change. Also broadcasts changed attribute and decoder state events so
|
||||
* that external processes can maintain sync with this channel state.
|
||||
* Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not
|
||||
* change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also
|
||||
* broadcasts changed attribute and decoder state events so that external processes can maintain sync with this
|
||||
* channel state.
|
||||
*/
|
||||
protected void setState(State state)
|
||||
{
|
||||
synchronized(mState)
|
||||
if(state == mState)
|
||||
{
|
||||
if(state == mState)
|
||||
if(State.CALL_STATES.contains(state))
|
||||
{
|
||||
if(State.CALL_STATES.contains(state))
|
||||
{
|
||||
updateFadeTimeout();
|
||||
}
|
||||
updateFadeTimeout();
|
||||
}
|
||||
else if(state != mState && mState.canChangeTo(state))
|
||||
}
|
||||
else if(mState.canChangeTo(state))
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case CONTROL:
|
||||
//Don't allow traffic channels to be control channels,
|
||||
//otherwise they can't transition to call tear down
|
||||
if(isStandardChannel())
|
||||
{
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
case ENCRYPTED:
|
||||
case CONTROL:
|
||||
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
|
||||
if(isStandardChannel())
|
||||
{
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
break;
|
||||
case CALL:
|
||||
broadcast(SquelchState.UNSQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
break;
|
||||
case FADE:
|
||||
processFadeState();
|
||||
break;
|
||||
case IDLE:
|
||||
processIdleState();
|
||||
break;
|
||||
case TEARDOWN:
|
||||
processTeardownState();
|
||||
break;
|
||||
case RESET:
|
||||
mState = State.IDLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
|
||||
mSourceOverflow ? State.OVERFLOW : mState));
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
case ENCRYPTED:
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
break;
|
||||
case CALL:
|
||||
broadcast(SquelchState.UNSQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
break;
|
||||
case FADE:
|
||||
processFadeState();
|
||||
break;
|
||||
case IDLE:
|
||||
processIdleState();
|
||||
break;
|
||||
case TEARDOWN:
|
||||
processTeardownState();
|
||||
break;
|
||||
case RESET:
|
||||
mState = State.IDLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
|
||||
mSourceOverflow ? State.OVERFLOW : mState));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
|
||||
*/
|
||||
@Override
|
||||
public Listener<Heartbeat> getHeartbeatListener()
|
||||
{
|
||||
return mHeartbeatReceiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -532,6 +517,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
broadcast(mTrafficChannelCallEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive frequency change events and rebroadcast them to the decoder states
|
||||
*/
|
||||
public class FrequencyChangeListener implements Listener<FrequencyChangeEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||
{
|
||||
if(frequencyChangeEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
|
||||
{
|
||||
long frequency = frequencyChangeEvent.getValue().longValue();
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DecoderStateEvent receiver wrapper
|
||||
*/
|
||||
@@ -591,45 +593,41 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors decoder state events to automatically transition the channel
|
||||
* state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
|
||||
* decoding stops or the monitored channel returns to a no signal state.
|
||||
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
|
||||
* functions.
|
||||
*
|
||||
* Provides a FADE transition state to allow for momentary decoding dropouts
|
||||
* and to allow the user access to call details for a fade period upon
|
||||
* call end.
|
||||
* Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
|
||||
* TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
|
||||
*
|
||||
* Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call
|
||||
* details for a fade period upon call end.
|
||||
*/
|
||||
public class StateMonitor implements Runnable
|
||||
public class HeartbeatReceiver implements Listener<Heartbeat>
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
public void receive(Heartbeat heartbeat)
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized(mState)
|
||||
if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
|
||||
{
|
||||
if(State.CALL_STATES.contains(mState) &&
|
||||
mFadeTimeout <= System.currentTimeMillis())
|
||||
processFadeState();
|
||||
}
|
||||
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
|
||||
{
|
||||
if(isTrafficChannel())
|
||||
{
|
||||
processFadeState();
|
||||
processTeardownState();
|
||||
}
|
||||
else if(mState == State.FADE &&
|
||||
mEndTimeout <= System.currentTimeMillis())
|
||||
else
|
||||
{
|
||||
if(isTrafficChannel())
|
||||
{
|
||||
processTeardownState();
|
||||
}
|
||||
else
|
||||
{
|
||||
processIdleState();
|
||||
}
|
||||
processIdleState();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
catch(Throwable e)
|
||||
{
|
||||
mLog.error("Exception thrown while state monitor is running " +
|
||||
mLog.error("An error occurred while state monitor was running " +
|
||||
"- state [" + getState() +
|
||||
"] current [" + System.currentTimeMillis() +
|
||||
"] mResetTimeout [" + mEndTimeout +
|
||||
|
||||
@@ -63,13 +63,11 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
||||
private Listener<CallEvent> mCallEventListener;
|
||||
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
private DecodeConfiguration mDecodeConfiguration;
|
||||
private RecordConfiguration mRecordConfiguration;
|
||||
private String mSystem;
|
||||
private String mSite;
|
||||
private String mAliasListName;
|
||||
private CallEvent mPreviousDoNotMonitorCallEvent;
|
||||
|
||||
/**
|
||||
* Monitors call events and allocates traffic decoder channels in response
|
||||
@@ -77,7 +75,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
||||
* traffic channel allocations.
|
||||
*
|
||||
* @param channelModel containing channels currently in use
|
||||
* @param channelProcessingManager
|
||||
* @param decodeConfiguration - decoder configuration to use for each
|
||||
* traffic channel allocation.
|
||||
* @param recordConfiguration - recording options for each traffic channel
|
||||
@@ -88,7 +85,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
||||
* in the pool
|
||||
*/
|
||||
public TrafficChannelManager(ChannelModel channelModel,
|
||||
ChannelProcessingManager channelProcessingManager,
|
||||
DecodeConfiguration decodeConfiguration,
|
||||
RecordConfiguration recordConfiguration,
|
||||
String system,
|
||||
@@ -97,7 +93,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
||||
int trafficChannelPoolSize)
|
||||
{
|
||||
mChannelModel = channelModel;
|
||||
mChannelProcessingManager = channelProcessingManager;
|
||||
mDecodeConfiguration = decodeConfiguration;
|
||||
mRecordConfiguration = recordConfiguration;
|
||||
mSystem = system;
|
||||
@@ -120,7 +115,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
||||
|
||||
mCallEventListener = null;
|
||||
mDecodeConfiguration = null;
|
||||
mPreviousDoNotMonitorCallEvent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,21 +191,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
||||
|
||||
long frequency = callEvent.getFrequency();
|
||||
|
||||
/* Check the from/to aliases for do not monitor priority */
|
||||
if(isDoNotMonitor(callEvent))
|
||||
{
|
||||
callEvent.setCallEventType(CallEventType.CALL_DO_NOT_MONITOR);
|
||||
|
||||
if(isSameCallEvent(mPreviousDoNotMonitorCallEvent, callEvent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
mPreviousDoNotMonitorCallEvent = callEvent;
|
||||
}
|
||||
}
|
||||
else if(frequency > 0)
|
||||
if(frequency > 0)
|
||||
{
|
||||
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(Type.TRAFFIC,
|
||||
frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
|
||||
@@ -330,31 +310,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for aliases that contain a do not follow/process priority.
|
||||
*
|
||||
* @param event
|
||||
* @return - true if there is an alias that has a do not process alias id
|
||||
*/
|
||||
private boolean isDoNotMonitor(CallEvent event)
|
||||
{
|
||||
Alias to = event.getToIDAlias();
|
||||
|
||||
if(to != null && to.hasCallPriority() && to.getCallPriority() == Priority.DO_NOT_MONITOR)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Alias from = event.getFromIDAlias();
|
||||
|
||||
if(from != null && from.hasCallPriority() && from.getCallPriority() == Priority.DO_NOT_MONITOR)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
* 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 controller.channel;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
import controller.config.Configuration;
|
||||
import module.decode.DecoderFactory;
|
||||
import module.decode.DecoderType;
|
||||
import module.decode.config.AuxDecodeConfiguration;
|
||||
@@ -31,6 +27,7 @@ import module.log.EventLogType;
|
||||
import module.log.config.EventLogConfiguration;
|
||||
import record.RecorderType;
|
||||
import record.config.RecordConfiguration;
|
||||
import sample.Listener;
|
||||
import source.SourceType;
|
||||
import source.config.SourceConfigFactory;
|
||||
import source.config.SourceConfigRecording;
|
||||
@@ -40,424 +37,444 @@ import source.tuner.TunerChannel;
|
||||
import source.tuner.TunerChannel.Type;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||
import source.tuner.frequency.IFrequencyChangeProcessor;
|
||||
import controller.config.Configuration;
|
||||
|
||||
@XmlSeeAlso( { Configuration.class } )
|
||||
@XmlRootElement( name = "channel" )
|
||||
public class Channel extends Configuration implements IFrequencyChangeProcessor
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
@XmlSeeAlso({Configuration.class})
|
||||
@XmlRootElement(name = "channel")
|
||||
public class Channel extends Configuration implements Listener<FrequencyChangeEvent>
|
||||
{
|
||||
// Standard channels are persisted and traffic channels are temporary
|
||||
public enum ChannelType { STANDARD, TRAFFIC };
|
||||
|
||||
// Standard channels are persisted and traffic channels are temporary
|
||||
public enum ChannelType
|
||||
{
|
||||
STANDARD, TRAFFIC
|
||||
}
|
||||
|
||||
// Static unique channel identifier tracking
|
||||
private static int UNIQUE_ID = 0;
|
||||
;
|
||||
|
||||
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
|
||||
private AuxDecodeConfiguration mAuxDecodeConfiguration =
|
||||
new AuxDecodeConfiguration();
|
||||
private SourceConfiguration mSourceConfiguration =
|
||||
SourceConfigFactory.getDefaultSourceConfiguration();
|
||||
private EventLogConfiguration mEventLogConfiguration =
|
||||
new EventLogConfiguration();
|
||||
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
|
||||
|
||||
private String mAliasListName;
|
||||
private String mSystem = "System";
|
||||
// Static unique channel identifier tracking
|
||||
private static int UNIQUE_ID = 0;
|
||||
|
||||
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
|
||||
private AuxDecodeConfiguration mAuxDecodeConfiguration =
|
||||
new AuxDecodeConfiguration();
|
||||
private SourceConfiguration mSourceConfiguration =
|
||||
SourceConfigFactory.getDefaultSourceConfiguration();
|
||||
private EventLogConfiguration mEventLogConfiguration =
|
||||
new EventLogConfiguration();
|
||||
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
|
||||
|
||||
private String mAliasListName;
|
||||
private String mSystem = "System";
|
||||
private String mSite = "Site";
|
||||
private String mName = "Channel";
|
||||
|
||||
private boolean mEnabled;
|
||||
private boolean mSelected;
|
||||
private TunerChannel mTunerChannel = null;
|
||||
|
||||
private ChannelType mChannelType = ChannelType.STANDARD;
|
||||
|
||||
private int mChannelID;
|
||||
private int mChannelFrequencyCorrection = 0;
|
||||
|
||||
/**
|
||||
* Channel represents a complete set of configurations needed to setup and
|
||||
* manage a processing chain and/or manage as a set of business objects that
|
||||
* can be displayed in graphical components.
|
||||
*
|
||||
* @param channelName
|
||||
* @param channelType
|
||||
*/
|
||||
public Channel( String channelName, ChannelType channelType )
|
||||
{
|
||||
this( channelName );
|
||||
mChannelType = channelType;
|
||||
}
|
||||
private boolean mEnabled;
|
||||
private boolean mSelected;
|
||||
private TunerChannel mTunerChannel = null;
|
||||
|
||||
/**
|
||||
* Constructs a new standard channel with the specified channel name
|
||||
*/
|
||||
public Channel( String channelName )
|
||||
{
|
||||
this();
|
||||
mName = channelName;
|
||||
}
|
||||
private ChannelType mChannelType = ChannelType.STANDARD;
|
||||
|
||||
/**
|
||||
* Constructs a new standard channel with a default name of "Channel"
|
||||
*/
|
||||
public Channel()
|
||||
{
|
||||
mChannelID = UNIQUE_ID++;
|
||||
}
|
||||
private int mChannelID;
|
||||
private int mChannelFrequencyCorrection = 0;
|
||||
|
||||
/**
|
||||
* Creates a (new) deep copy of this channel
|
||||
*/
|
||||
public Channel copyOf()
|
||||
{
|
||||
Channel channel = new Channel( mName );
|
||||
channel.setSystem( mSystem );
|
||||
channel.setSite( mSite );
|
||||
channel.setAliasListName( mAliasListName );
|
||||
/**
|
||||
* Channel represents a complete set of configurations needed to setup and
|
||||
* manage a processing chain and/or manage as a set of business objects that
|
||||
* can be displayed in graphical components.
|
||||
*
|
||||
* @param channelName
|
||||
* @param channelType
|
||||
*/
|
||||
public Channel(String channelName, ChannelType channelType)
|
||||
{
|
||||
this(channelName);
|
||||
mChannelType = channelType;
|
||||
}
|
||||
|
||||
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
|
||||
|
||||
for( DecoderType auxType: aux.getAuxDecoders() )
|
||||
{
|
||||
aux.addAuxDecoder( auxType );
|
||||
}
|
||||
|
||||
channel.setAuxDecodeConfiguration( aux );
|
||||
|
||||
channel.setDecodeConfiguration( DecoderFactory.copy( mDecodeConfiguration ) );
|
||||
|
||||
EventLogConfiguration log = new EventLogConfiguration();
|
||||
|
||||
for( EventLogType logType: mEventLogConfiguration.getLoggers() )
|
||||
{
|
||||
log.addLogger( logType );
|
||||
}
|
||||
|
||||
channel.setEventLogConfiguration( log );
|
||||
/**
|
||||
* Constructs a new standard channel with the specified channel name
|
||||
*/
|
||||
public Channel(String channelName)
|
||||
{
|
||||
this();
|
||||
mName = channelName;
|
||||
}
|
||||
|
||||
RecordConfiguration record = new RecordConfiguration();
|
||||
|
||||
for( RecorderType recordType: mRecordConfiguration.getRecorders() )
|
||||
{
|
||||
record.addRecorder( recordType );
|
||||
}
|
||||
|
||||
channel.setRecordConfiguration( record );
|
||||
/**
|
||||
* Constructs a new standard channel with a default name of "Channel"
|
||||
*/
|
||||
public Channel()
|
||||
{
|
||||
mChannelID = UNIQUE_ID++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (new) deep copy of this channel
|
||||
*/
|
||||
public Channel copyOf()
|
||||
{
|
||||
Channel channel = new Channel(mName);
|
||||
channel.setSystem(mSystem);
|
||||
channel.setSite(mSite);
|
||||
channel.setAliasListName(mAliasListName);
|
||||
|
||||
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
|
||||
|
||||
for(DecoderType auxType : aux.getAuxDecoders())
|
||||
{
|
||||
aux.addAuxDecoder(auxType);
|
||||
}
|
||||
|
||||
channel.setAuxDecodeConfiguration(aux);
|
||||
|
||||
channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
|
||||
|
||||
EventLogConfiguration log = new EventLogConfiguration();
|
||||
|
||||
for(EventLogType logType : mEventLogConfiguration.getLoggers())
|
||||
{
|
||||
log.addLogger(logType);
|
||||
}
|
||||
|
||||
channel.setEventLogConfiguration(log);
|
||||
|
||||
RecordConfiguration record = new RecordConfiguration();
|
||||
|
||||
for(RecorderType recordType : mRecordConfiguration.getRecorders())
|
||||
{
|
||||
record.addRecorder(recordType);
|
||||
}
|
||||
|
||||
channel.setRecordConfiguration(record);
|
||||
|
||||
channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifier for this channel. Value is transient (not persisted).
|
||||
*/
|
||||
@XmlTransient
|
||||
public int getChannelID()
|
||||
{
|
||||
return mChannelID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel has been selected for prioritized audio output
|
||||
* and display of processing chain products
|
||||
*/
|
||||
@XmlTransient
|
||||
public boolean isSelected()
|
||||
{
|
||||
return mSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets selection status of the channel.
|
||||
*
|
||||
* NOTE: this method is package private so that only the ChannelSelectionManager
|
||||
* can toggle the selection state. Use the channel model to broadcast a
|
||||
* request select/deselect channel event.
|
||||
*
|
||||
* @param selected
|
||||
*/
|
||||
void setSelected(boolean selected)
|
||||
{
|
||||
mSelected = selected;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public ChannelType getChannelType()
|
||||
{
|
||||
return mChannelType;
|
||||
}
|
||||
|
||||
channel.setSourceConfiguration( SourceConfigFactory.copy( mSourceConfiguration ) );
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifier for this channel. Value is transient (not persisted).
|
||||
*/
|
||||
@XmlTransient
|
||||
public int getChannelID()
|
||||
{
|
||||
return mChannelID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel has been selected for prioritized audio output
|
||||
* and display of processing chain products
|
||||
*/
|
||||
@XmlTransient
|
||||
public boolean isSelected()
|
||||
{
|
||||
return mSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets selection status of the channel.
|
||||
*
|
||||
* NOTE: this method is package private so that only the ChannelSelectionManager
|
||||
* can toggle the selection state. Use the channel model to broadcast a
|
||||
* request select/deselect channel event.
|
||||
*
|
||||
* @param selected
|
||||
*/
|
||||
void setSelected( boolean selected )
|
||||
{
|
||||
mSelected = selected;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public ChannelType getChannelType()
|
||||
{
|
||||
return mChannelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owning system for this channel.
|
||||
*/
|
||||
@XmlAttribute( name = "system" )
|
||||
public String getSystem()
|
||||
{
|
||||
return mSystem;
|
||||
}
|
||||
|
||||
public void setSystem( String system )
|
||||
{
|
||||
mSystem = system;
|
||||
}
|
||||
@XmlAttribute(name = "system")
|
||||
public String getSystem()
|
||||
{
|
||||
return mSystem;
|
||||
}
|
||||
|
||||
public boolean hasSystem()
|
||||
{
|
||||
return mSystem != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owning site for this channel.
|
||||
*/
|
||||
@XmlAttribute( name = "site" )
|
||||
public String getSite()
|
||||
{
|
||||
return mSite;
|
||||
}
|
||||
|
||||
public void setSite( String site )
|
||||
{
|
||||
mSite = site;
|
||||
}
|
||||
|
||||
public boolean hasSite()
|
||||
{
|
||||
return mSite != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this channel.
|
||||
*/
|
||||
@XmlAttribute( name = "name" )
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this channel.
|
||||
*/
|
||||
public void setName( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
public void setSystem(String system)
|
||||
{
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
public boolean hasSystem()
|
||||
{
|
||||
return mSystem != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owning site for this channel.
|
||||
*/
|
||||
@XmlAttribute(name = "site")
|
||||
public String getSite()
|
||||
{
|
||||
return mSite;
|
||||
}
|
||||
|
||||
public void setSite(String site)
|
||||
{
|
||||
mSite = site;
|
||||
}
|
||||
|
||||
public boolean hasSite()
|
||||
{
|
||||
return mSite != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this channel.
|
||||
*/
|
||||
@XmlAttribute(name = "name")
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this channel.
|
||||
*/
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default display string for this channel: SYSTEM_SITE_NAME
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( hasSystem() ? getSystem() : "SYSTEM" );
|
||||
sb.append( "_" );
|
||||
sb.append( hasSite() ? getSite() : "SITE" );
|
||||
sb.append( "_" );
|
||||
sb.append( getName() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(hasSystem() ? getSystem() : "SYSTEM");
|
||||
sb.append("_");
|
||||
sb.append(hasSite() ? getSite() : "SITE");
|
||||
sb.append("_");
|
||||
sb.append(getName());
|
||||
|
||||
/**
|
||||
* Indicates if this channel is enabled for processing.
|
||||
*/
|
||||
@XmlAttribute( name = "enabled" )
|
||||
public boolean getEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel will automatically start processing on
|
||||
* application startup or if this channel is currently processing after
|
||||
* application startup.
|
||||
*
|
||||
* Note: this method is package private and is intended to be managed
|
||||
* by a co-package central processing manager
|
||||
*
|
||||
* @see controller.ChannelProcessingManager
|
||||
*/
|
||||
void setEnabled( boolean enabled )
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
/**
|
||||
* Indicates if this channel is enabled for processing.
|
||||
*/
|
||||
@XmlAttribute(name = "enabled")
|
||||
public boolean getEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alias list that is used by this channel for looking up alias
|
||||
* values for the various identifiers produced by the decoder(s).
|
||||
*/
|
||||
@XmlElement( name = "alias_list_name" )
|
||||
public String getAliasListName()
|
||||
{
|
||||
return mAliasListName;
|
||||
}
|
||||
/**
|
||||
* Indicates if this channel will automatically start processing on
|
||||
* application startup or if this channel is currently processing after
|
||||
* application startup.
|
||||
*
|
||||
* Note: this method is package private and is intended to be managed
|
||||
* by a co-package central processing manager
|
||||
*
|
||||
* @see controller.channel.ChannelProcessingManager
|
||||
*/
|
||||
void setEnabled(boolean enabled)
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alias list to be used for looking up the alias values for the
|
||||
* various identifiers produced by the decoder
|
||||
*/
|
||||
public void setAliasListName( String name )
|
||||
{
|
||||
mAliasListName = name;
|
||||
}
|
||||
/**
|
||||
* Returns the alias list that is used by this channel for looking up alias
|
||||
* values for the various identifiers produced by the decoder(s).
|
||||
*/
|
||||
@XmlElement(name = "alias_list_name")
|
||||
public String getAliasListName()
|
||||
{
|
||||
return mAliasListName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the primary decoder configuration used by this channel
|
||||
*/
|
||||
@XmlElement( name = "decode_configuration" )
|
||||
public DecodeConfiguration getDecodeConfiguration()
|
||||
{
|
||||
return mDecodeConfiguration;
|
||||
}
|
||||
/**
|
||||
* Sets the alias list to be used for looking up the alias values for the
|
||||
* various identifiers produced by the decoder
|
||||
*/
|
||||
public void setAliasListName(String name)
|
||||
{
|
||||
mAliasListName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the primary decoder configuration used by this channel
|
||||
*/
|
||||
public void setDecodeConfiguration( DecodeConfiguration config )
|
||||
{
|
||||
mDecodeConfiguration = config;
|
||||
}
|
||||
/**
|
||||
* Gets the primary decoder configuration used by this channel
|
||||
*/
|
||||
@XmlElement(name = "decode_configuration")
|
||||
public DecodeConfiguration getDecodeConfiguration()
|
||||
{
|
||||
return mDecodeConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the aux decoder configuration used by this channel. Aux decoders
|
||||
* operate on demodulated audio
|
||||
*/
|
||||
@XmlElement( name = "aux_decode_configuration" )
|
||||
public AuxDecodeConfiguration getAuxDecodeConfiguration()
|
||||
{
|
||||
return mAuxDecodeConfiguration;
|
||||
}
|
||||
/**
|
||||
* Sets the primary decoder configuration used by this channel
|
||||
*/
|
||||
public void setDecodeConfiguration(DecodeConfiguration config)
|
||||
{
|
||||
mDecodeConfiguration = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the decoder configuration used by this channel
|
||||
*/
|
||||
public void setAuxDecodeConfiguration( AuxDecodeConfiguration config )
|
||||
{
|
||||
mAuxDecodeConfiguration = config;
|
||||
}
|
||||
/**
|
||||
* Gets the aux decoder configuration used by this channel. Aux decoders
|
||||
* operate on demodulated audio
|
||||
*/
|
||||
@XmlElement(name = "aux_decode_configuration")
|
||||
public AuxDecodeConfiguration getAuxDecodeConfiguration()
|
||||
{
|
||||
return mAuxDecodeConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source configuration for this channel. A channel source
|
||||
* identifies where the processing chain will get source sample data from.
|
||||
*/
|
||||
@XmlElement( name = "source_configuration" )
|
||||
public SourceConfiguration getSourceConfiguration()
|
||||
{
|
||||
return mSourceConfiguration;
|
||||
}
|
||||
/**
|
||||
* Sets the decoder configuration used by this channel
|
||||
*/
|
||||
public void setAuxDecodeConfiguration(AuxDecodeConfiguration config)
|
||||
{
|
||||
mAuxDecodeConfiguration = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source configuration for this channel.
|
||||
*/
|
||||
public void setSourceConfiguration( SourceConfiguration config )
|
||||
{
|
||||
mSourceConfiguration = config;
|
||||
/**
|
||||
* Returns the source configuration for this channel. A channel source
|
||||
* identifies where the processing chain will get source sample data from.
|
||||
*/
|
||||
@XmlElement(name = "source_configuration")
|
||||
public SourceConfiguration getSourceConfiguration()
|
||||
{
|
||||
return mSourceConfiguration;
|
||||
}
|
||||
|
||||
//Clear the tune channel object so that it can be recreated if the
|
||||
//source configuration changes
|
||||
mTunerChannel = null;
|
||||
}
|
||||
/**
|
||||
* Sets the source configuration for this channel.
|
||||
*/
|
||||
public void setSourceConfiguration(SourceConfiguration config)
|
||||
{
|
||||
mSourceConfiguration = config;
|
||||
|
||||
/**
|
||||
* Returns the event logger configuration for this channel.
|
||||
*/
|
||||
@XmlElement( name = "event_log_configuration" )
|
||||
public EventLogConfiguration getEventLogConfiguration()
|
||||
{
|
||||
return mEventLogConfiguration;
|
||||
}
|
||||
//Clear the tune channel object so that it can be recreated if the
|
||||
//source configuration changes
|
||||
mTunerChannel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event logger configuration for this channel.
|
||||
*/
|
||||
public void setEventLogConfiguration( EventLogConfiguration config )
|
||||
{
|
||||
mEventLogConfiguration = config;
|
||||
}
|
||||
/**
|
||||
* Returns the event logger configuration for this channel.
|
||||
*/
|
||||
@XmlElement(name = "event_log_configuration")
|
||||
public EventLogConfiguration getEventLogConfiguration()
|
||||
{
|
||||
return mEventLogConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recorder configuration for this channel.
|
||||
*/
|
||||
@XmlElement( name = "record_configuration" )
|
||||
public RecordConfiguration getRecordConfiguration()
|
||||
{
|
||||
return mRecordConfiguration;
|
||||
}
|
||||
/**
|
||||
* Sets the event logger configuration for this channel.
|
||||
*/
|
||||
public void setEventLogConfiguration(EventLogConfiguration config)
|
||||
{
|
||||
mEventLogConfiguration = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the recorder configuration for this channel.
|
||||
*/
|
||||
public void setRecordConfiguration( RecordConfiguration config )
|
||||
{
|
||||
mRecordConfiguration = config;
|
||||
}
|
||||
/**
|
||||
* Returns the recorder configuration for this channel.
|
||||
*/
|
||||
@XmlElement(name = "record_configuration")
|
||||
public RecordConfiguration getRecordConfiguration()
|
||||
{
|
||||
return mRecordConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to construct a tuner channel object representing a
|
||||
* tuner or recording source frequency and bandwidth that can be used by
|
||||
* application components for graphically representing this channel.
|
||||
*
|
||||
* If the source configuration is not a tuner or recording, this method
|
||||
* returns null.
|
||||
*/
|
||||
@XmlTransient
|
||||
/**
|
||||
* Sets the recorder configuration for this channel.
|
||||
*/
|
||||
public void setRecordConfiguration(RecordConfiguration config)
|
||||
{
|
||||
mRecordConfiguration = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to construct a tuner channel object representing a
|
||||
* tuner or recording source frequency and bandwidth that can be used by
|
||||
* application components for graphically representing this channel.
|
||||
*
|
||||
* If the source configuration is not a tuner or recording, this method
|
||||
* returns null.
|
||||
*/
|
||||
@XmlTransient
|
||||
public TunerChannel getTunerChannel()
|
||||
{
|
||||
if( mTunerChannel == null )
|
||||
{
|
||||
if( mSourceConfiguration.getSourceType() == SourceType.TUNER )
|
||||
{
|
||||
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
|
||||
if(mTunerChannel == null)
|
||||
{
|
||||
if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
|
||||
{
|
||||
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
|
||||
|
||||
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
|
||||
mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
|
||||
}
|
||||
else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING )
|
||||
{
|
||||
SourceConfigRecording config =
|
||||
(SourceConfigRecording)mSourceConfiguration;
|
||||
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
|
||||
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
|
||||
}
|
||||
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
|
||||
{
|
||||
SourceConfigRecording config =
|
||||
(SourceConfigRecording)mSourceConfiguration;
|
||||
|
||||
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
|
||||
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
|
||||
}
|
||||
}
|
||||
|
||||
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
|
||||
mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
|
||||
}
|
||||
}
|
||||
|
||||
return mTunerChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to indicate if any part of this channel is contained
|
||||
* within the minimum and maximum frequency values.
|
||||
*/
|
||||
public boolean isWithin( long minimum, long maximum )
|
||||
{
|
||||
TunerChannel tunerChannel = getTunerChannel();
|
||||
|
||||
return tunerChannel != null &&
|
||||
tunerChannel.overlaps( minimum, maximum );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to make the current channel frequency correction value
|
||||
* available for use outside of a constructed processing chain while maintaining
|
||||
* linkage to the source channel. This hack allows the correction value to
|
||||
* be used to visually show in the spectral display.
|
||||
*/
|
||||
@Override
|
||||
public void frequencyChanged( FrequencyChangeEvent event )
|
||||
{
|
||||
if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE )
|
||||
{
|
||||
mChannelFrequencyCorrection = event.getValue().intValue();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Convenience method to indicate if any part of this channel is contained
|
||||
* within the minimum and maximum frequency values.
|
||||
*/
|
||||
public boolean isWithin(long minimum, long maximum)
|
||||
{
|
||||
TunerChannel tunerChannel = getTunerChannel();
|
||||
|
||||
@XmlTransient
|
||||
public int getChannelFrequencyCorrection()
|
||||
{
|
||||
return mChannelFrequencyCorrection;
|
||||
}
|
||||
return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to make the current channel frequency correction value
|
||||
* available for use outside of a constructed processing chain while maintaining
|
||||
* linkage to the source channel. This hack allows the correction value to
|
||||
* be used to visually show in the spectral display.
|
||||
*/
|
||||
@Override
|
||||
public void receive(FrequencyChangeEvent event)
|
||||
{
|
||||
if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
|
||||
{
|
||||
mChannelFrequencyCorrection = event.getValue().intValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel frequency correction value (when this channel is processing)
|
||||
* @return
|
||||
*/
|
||||
@XmlTransient
|
||||
public int getChannelFrequencyCorrection()
|
||||
{
|
||||
return mChannelFrequencyCorrection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets frequency correction value to 0
|
||||
*/
|
||||
public void resetFrequencyCorrection()
|
||||
{
|
||||
mChannelFrequencyCorrection = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener
|
||||
|
||||
if(processingChain == null)
|
||||
{
|
||||
processingChain = new ProcessingChain(channel.getName(), channel.getChannelType());
|
||||
processingChain = new ProcessingChain(channel.getChannelType());
|
||||
|
||||
/* Register global listeners */
|
||||
for(Listener<AudioPacket> listener : mAudioPacketListeners)
|
||||
@@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener
|
||||
processingChain.addMessageListener(listener);
|
||||
}
|
||||
|
||||
//Register channel to receive frequency correction events to show in the spectral display (hack!)
|
||||
processingChain.addFrequencyChangeListener(channel);
|
||||
|
||||
/* Processing Modules */
|
||||
List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this,
|
||||
mAliasModel, channel, processingChain.getChannelState().getMutableMetadata());
|
||||
@@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener
|
||||
|
||||
processingChain.removeRecordingModules();
|
||||
|
||||
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
||||
processingChain.removeFrequencyChangeListener(channel);
|
||||
channel.resetFrequencyCorrection();
|
||||
|
||||
mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP));
|
||||
|
||||
|
||||
@@ -1,269 +1,267 @@
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
package controller.channel;
|
||||
|
||||
import alias.AliasModel;
|
||||
import gui.editor.Editor;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.ComboBoxModel;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import alias.AliasModel;
|
||||
|
||||
public class NameConfigurationEditor extends Editor<Channel> implements DocumentListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger( NameConfigurationEditor.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(NameConfigurationEditor.class);
|
||||
|
||||
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>();
|
||||
private static ComboBoxModel<String> EMPTY_SYSTEM_MODEL = new DefaultComboBoxModel<>();
|
||||
private static ComboBoxModel<String> EMPTY_SITE_MODEL = new DefaultComboBoxModel<>();
|
||||
private static ComboBoxModel<String> EMPTY_ALIAS_LIST_MODEL = new DefaultComboBoxModel<>();
|
||||
|
||||
private AliasModel mAliasModel;
|
||||
private ChannelModel mChannelModel;
|
||||
|
||||
private JTextField mChannelName;
|
||||
private JComboBox<String> mSystemNameCombo;
|
||||
private JComboBox<String> mSiteNameCombo;
|
||||
private JComboBox<String> mAliasListCombo;
|
||||
|
||||
public NameConfigurationEditor( AliasModel aliasModel, ChannelModel model )
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mChannelModel = model;
|
||||
private AliasModel mAliasModel;
|
||||
private ChannelModel mChannelModel;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]" ) );
|
||||
private JTextField mChannelName;
|
||||
private JComboBox<String> mSystemNameCombo;
|
||||
private JComboBox<String> mSiteNameCombo;
|
||||
private JComboBox<String> mAliasListCombo;
|
||||
|
||||
mChannelName = new JTextField( "" );
|
||||
mChannelName.getDocument().addDocumentListener( this );
|
||||
mChannelName.setEnabled( false );
|
||||
public NameConfigurationEditor(AliasModel aliasModel, ChannelModel model)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mChannelModel = model;
|
||||
|
||||
add( new JLabel( "Name:" ) );
|
||||
add( mChannelName );
|
||||
init();
|
||||
}
|
||||
|
||||
mSystemNameCombo = new JComboBox<>( EMPTY_MODEL );
|
||||
mSystemNameCombo.setEnabled( false );
|
||||
mSystemNameCombo.setEditable( true );
|
||||
mSystemNameCombo.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
Object selected = mSystemNameCombo.getSelectedItem();
|
||||
|
||||
if( selected != null )
|
||||
{
|
||||
String system = (String)selected;
|
||||
|
||||
List<String> sites = mChannelModel.getSites( system );
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]"));
|
||||
|
||||
if( !sites.contains( getItem().getSite() ) )
|
||||
{
|
||||
sites.add( getItem().getSite() );
|
||||
}
|
||||
|
||||
if( sites.isEmpty() )
|
||||
{
|
||||
mSiteNameCombo.setModel( EMPTY_MODEL );
|
||||
}
|
||||
else
|
||||
{
|
||||
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>(
|
||||
sites.toArray( new String[ sites.size() ] ) ) );;
|
||||
}
|
||||
|
||||
mSiteNameCombo.setSelectedItem( getItem().getSite() );
|
||||
}
|
||||
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "System:" ) );
|
||||
add( mSystemNameCombo );
|
||||
mChannelName = new JTextField("");
|
||||
mChannelName.getDocument().addDocumentListener(this);
|
||||
mChannelName.setEnabled(false);
|
||||
|
||||
mAliasListCombo = new JComboBox<String>();
|
||||
mAliasListCombo.setEnabled( false );
|
||||
mAliasListCombo.setEditable( true );
|
||||
mAliasListCombo.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Alias List:" ) );
|
||||
add( mAliasListCombo );
|
||||
|
||||
mSiteNameCombo = new JComboBox<>( EMPTY_MODEL );
|
||||
mSiteNameCombo.setEnabled( false );
|
||||
mSiteNameCombo.setEditable( true );
|
||||
mSiteNameCombo.addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
} );
|
||||
|
||||
add( new JLabel( "Site:" ) );
|
||||
add( mSiteNameCombo );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if( hasItem() && isModified() )
|
||||
{
|
||||
getItem().setName( mChannelName.getText() );
|
||||
|
||||
Object system = mSystemNameCombo.getSelectedItem();
|
||||
getItem().setSystem( ( system == null ? null : (String)system ) );
|
||||
add(new JLabel("Name:"));
|
||||
add(mChannelName);
|
||||
|
||||
Object site = mSiteNameCombo.getSelectedItem();
|
||||
getItem().setSite( ( site == null ? null : (String)site ) );
|
||||
mSystemNameCombo = new JComboBox<>(EMPTY_SYSTEM_MODEL);
|
||||
mSystemNameCombo.setEnabled(false);
|
||||
mSystemNameCombo.setEditable(true);
|
||||
mSystemNameCombo.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Object selected = mSystemNameCombo.getSelectedItem();
|
||||
|
||||
Object aliasList = mAliasListCombo.getSelectedItem();
|
||||
getItem().setAliasListName( aliasList == null ? null : (String)aliasList );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
if(selected != null)
|
||||
{
|
||||
String system = (String)selected;
|
||||
|
||||
private void setControlsEnabled( boolean enabled )
|
||||
{
|
||||
if( mChannelName.isEnabled() != enabled )
|
||||
{
|
||||
mChannelName.setEnabled( enabled );
|
||||
}
|
||||
List<String> sites = mChannelModel.getSites(system);
|
||||
|
||||
if( mAliasListCombo.isEnabled() != enabled )
|
||||
{
|
||||
mAliasListCombo.setEnabled( enabled );
|
||||
}
|
||||
if(!sites.contains(getItem().getSite()))
|
||||
{
|
||||
sites.add(getItem().getSite());
|
||||
}
|
||||
|
||||
if( mSystemNameCombo.isEnabled() != enabled )
|
||||
{
|
||||
mSystemNameCombo.setEnabled( enabled );
|
||||
}
|
||||
|
||||
if( mSiteNameCombo.isEnabled() != enabled )
|
||||
{
|
||||
mSiteNameCombo.setEnabled( enabled );
|
||||
}
|
||||
}
|
||||
if(sites.isEmpty())
|
||||
{
|
||||
mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
sites.toArray(new String[sites.size()])));
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem( Channel channel )
|
||||
{
|
||||
super.setItem( channel );
|
||||
|
||||
if( hasItem() )
|
||||
{
|
||||
setControlsEnabled( true );
|
||||
|
||||
mChannelName.setText( channel.getName() );
|
||||
mSiteNameCombo.setSelectedItem(getItem().getSite());
|
||||
}
|
||||
|
||||
List<String> systems = mChannelModel.getSystems();
|
||||
|
||||
if( systems.isEmpty() )
|
||||
{
|
||||
mSystemNameCombo.setModel( EMPTY_MODEL );
|
||||
}
|
||||
else
|
||||
{
|
||||
mSystemNameCombo.setModel( new DefaultComboBoxModel<String>(
|
||||
systems.toArray( new String[ systems.size() ] ) ) );;
|
||||
}
|
||||
|
||||
mSystemNameCombo.setSelectedItem( channel.getSystem() );
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
List<String> sites = mChannelModel.getSites( channel.getSystem() );
|
||||
add(new JLabel("System:"));
|
||||
add(mSystemNameCombo);
|
||||
|
||||
if( sites.isEmpty() )
|
||||
{
|
||||
mSiteNameCombo.setModel( EMPTY_MODEL );
|
||||
}
|
||||
else
|
||||
{
|
||||
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>(
|
||||
sites.toArray( new String[ sites.size() ] ) ) );;
|
||||
}
|
||||
mAliasListCombo = new JComboBox<String>(EMPTY_ALIAS_LIST_MODEL);
|
||||
mAliasListCombo.setEnabled(false);
|
||||
mAliasListCombo.setEditable(true);
|
||||
mAliasListCombo.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
if( channel.getSite() != null )
|
||||
{
|
||||
mSiteNameCombo.setSelectedItem( channel.getSite() );
|
||||
}
|
||||
|
||||
List<String> lists = mAliasModel.getListNames();
|
||||
Collections.sort( lists );
|
||||
mAliasListCombo.setModel( new DefaultComboBoxModel<String>(
|
||||
lists.toArray( new String[ lists.size() ] ) ) );
|
||||
|
||||
if( channel.getAliasListName() != null )
|
||||
{
|
||||
mAliasListCombo.setSelectedItem( channel.getAliasListName() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled( false );
|
||||
mChannelName.setText( null );
|
||||
}
|
||||
|
||||
setModified( false );
|
||||
}
|
||||
add(new JLabel("Alias List:"));
|
||||
add(mAliasListCombo);
|
||||
|
||||
@Override
|
||||
public void insertUpdate( DocumentEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
mSiteNameCombo = new JComboBox<>(EMPTY_SITE_MODEL);
|
||||
mSiteNameCombo.setEnabled(false);
|
||||
mSiteNameCombo.setEditable(true);
|
||||
mSiteNameCombo.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public void changedUpdate( DocumentEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate( DocumentEvent e )
|
||||
{
|
||||
setModified( true );
|
||||
}
|
||||
add(new JLabel("Site:"));
|
||||
add(mSiteNameCombo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(hasItem() && isModified())
|
||||
{
|
||||
getItem().setName(mChannelName.getText());
|
||||
|
||||
Object system = mSystemNameCombo.getSelectedItem();
|
||||
getItem().setSystem((system == null ? null : (String)system));
|
||||
|
||||
Object site = mSiteNameCombo.getSelectedItem();
|
||||
getItem().setSite((site == null ? null : (String)site));
|
||||
|
||||
Object aliasList = mAliasListCombo.getSelectedItem();
|
||||
getItem().setAliasListName(aliasList == null ? null : (String)aliasList);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
private void setControlsEnabled(boolean enabled)
|
||||
{
|
||||
if(mChannelName.isEnabled() != enabled)
|
||||
{
|
||||
mChannelName.setEnabled(enabled);
|
||||
}
|
||||
|
||||
if(mAliasListCombo.isEnabled() != enabled)
|
||||
{
|
||||
mAliasListCombo.setEnabled(enabled);
|
||||
}
|
||||
|
||||
if(mSystemNameCombo.isEnabled() != enabled)
|
||||
{
|
||||
mSystemNameCombo.setEnabled(enabled);
|
||||
}
|
||||
|
||||
if(mSiteNameCombo.isEnabled() != enabled)
|
||||
{
|
||||
mSiteNameCombo.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(Channel channel)
|
||||
{
|
||||
super.setItem(channel);
|
||||
|
||||
if(hasItem())
|
||||
{
|
||||
setControlsEnabled(true);
|
||||
|
||||
mChannelName.setText(channel.getName());
|
||||
|
||||
List<String> systems = mChannelModel.getSystems();
|
||||
|
||||
if(systems.isEmpty())
|
||||
{
|
||||
mSystemNameCombo.setModel(EMPTY_SYSTEM_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSystemNameCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
systems.toArray(new String[systems.size()])));
|
||||
;
|
||||
}
|
||||
|
||||
mSystemNameCombo.setSelectedItem(channel.getSystem());
|
||||
|
||||
List<String> sites = mChannelModel.getSites(channel.getSystem());
|
||||
|
||||
if(sites.isEmpty())
|
||||
{
|
||||
mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
sites.toArray(new String[sites.size()])));
|
||||
;
|
||||
}
|
||||
|
||||
if(channel.getSite() != null)
|
||||
{
|
||||
mSiteNameCombo.setSelectedItem(channel.getSite());
|
||||
}
|
||||
|
||||
List<String> lists = mAliasModel.getListNames();
|
||||
Collections.sort(lists);
|
||||
mAliasListCombo.setModel(new DefaultComboBoxModel<String>(
|
||||
lists.toArray(new String[lists.size()])));
|
||||
|
||||
if(channel.getAliasListName() != null)
|
||||
{
|
||||
mAliasListCombo.setSelectedItem(channel.getAliasListName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
mChannelName.setText(null);
|
||||
}
|
||||
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e)
|
||||
{
|
||||
setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package dsp.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import sample.real.RealSampleListener;
|
||||
|
||||
public class FloatFIRFilter implements RealSampleListener
|
||||
@@ -139,7 +140,7 @@ public class FloatFIRFilter implements RealSampleListener
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
//Ensure we have an odd size
|
||||
assert( size % 2 == 1 );
|
||||
Validate.isTrue(size % 2 == 1);
|
||||
|
||||
mIndexMap = new int[ size ][ size ];
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package dsp.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import sample.real.RealSampleListener;
|
||||
|
||||
public class FloatHalfBandFilter implements RealSampleListener
|
||||
@@ -149,7 +150,7 @@ public class FloatHalfBandFilter implements RealSampleListener
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
//Ensure we have an odd size
|
||||
assert( size % 2 == 1 );
|
||||
Validate.isTrue(size % 2 == 1);
|
||||
|
||||
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package dsp.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import sample.real.RealSampleListener;
|
||||
|
||||
public class FloatHalfBandNoDecimateFilter implements RealSampleListener
|
||||
@@ -139,7 +140,7 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
//Ensure we have an odd size
|
||||
assert( size % 2 == 1 );
|
||||
Validate.isTrue(size % 2 == 1);
|
||||
|
||||
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
|
||||
|
||||
|
||||
@@ -1,407 +1,412 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2015 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/>
|
||||
* 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 dsp.filter.cic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import sample.complex.ComplexToComplexBufferAssembler;
|
||||
import sample.decimator.ComplexDecimator;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Filters;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import sample.complex.ComplexSampleListener;
|
||||
import sample.complex.ComplexToComplexBufferAssembler;
|
||||
import sample.decimator.ComplexDecimator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
|
||||
{
|
||||
public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
|
||||
59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,
|
||||
157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,
|
||||
257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,
|
||||
367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,
|
||||
467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,
|
||||
599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 };
|
||||
|
||||
private ArrayList<DecimatingStage> mDecimatingStages =
|
||||
new ArrayList<DecimatingStage>();
|
||||
|
||||
private DecimatingStage mFirstDecimatingStage;
|
||||
public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
|
||||
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
|
||||
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
|
||||
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
|
||||
367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
|
||||
467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593,
|
||||
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
|
||||
|
||||
private Output mOutput;
|
||||
private ArrayList<DecimatingStage> mDecimatingStages =
|
||||
new ArrayList<DecimatingStage>();
|
||||
|
||||
/**
|
||||
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
||||
* decimated, single paired i/q float sample output.
|
||||
*
|
||||
* Implements the CIC filter described in Understanding Digital Signal
|
||||
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
||||
* decimating stages each with a prime factor decimation rate. Multiple
|
||||
* stages are cascaded to achieve the overall decimation rate.
|
||||
*
|
||||
* This filter supports a maximum decimation rate of 700. This filter can
|
||||
* be adapted to higher decimation rates by adding additional prime factors
|
||||
* to the PRIMES array.
|
||||
*
|
||||
* @param decimation - overall decimation rate
|
||||
* @param order - filter order
|
||||
*/
|
||||
public ComplexPrimeCICDecimate( int decimation, int order,
|
||||
int passFrequency, int attenuation, WindowType windowType )
|
||||
{
|
||||
assert( decimation <= 700 );
|
||||
|
||||
private DecimatingStage mFirstDecimatingStage;
|
||||
|
||||
List<Integer> stageSizes = getPrimeFactors( decimation );
|
||||
|
||||
for( int x = 0; x < stageSizes.size(); x++ )
|
||||
{
|
||||
DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order );
|
||||
|
||||
mDecimatingStages.add( stage );
|
||||
|
||||
if( x == 0 )
|
||||
{
|
||||
/* Reference to first stage -- will receive all samples */
|
||||
mFirstDecimatingStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Wire the current stage to the previous stage */
|
||||
mDecimatingStages.get( x - 1 ).setListener( stage );
|
||||
}
|
||||
}
|
||||
|
||||
mOutput = new Output( 48000, passFrequency, attenuation, windowType );
|
||||
|
||||
mDecimatingStages.get( mDecimatingStages.size() - 1 )
|
||||
.setListener( mOutput );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for( DecimatingStage stage: mDecimatingStages )
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mDecimatingStages.clear();
|
||||
mDecimatingStages = null;
|
||||
mFirstDecimatingStage = null;
|
||||
|
||||
mOutput.dispose();
|
||||
mOutput = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive the output of this CIC decimation filter
|
||||
*/
|
||||
public void setListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mOutput.setListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes listener from output of this CIC decimation filter
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mOutput.removeListener();
|
||||
}
|
||||
private Output mOutput;
|
||||
|
||||
/**
|
||||
* Calculates the prime factors of the decimation rate up to a maximum
|
||||
* decimation rate of 700. If you wish to have decimation rates higher
|
||||
* than 700, then add additional prime factors to the PRIMES array.
|
||||
*
|
||||
* @param decimation - integral decimation rate
|
||||
* @return - ordered list (smallest to largest) of prime factors
|
||||
*/
|
||||
public static List<Integer> getPrimeFactors( int decimation )
|
||||
{
|
||||
ArrayList<Integer> stages = new ArrayList<Integer>();
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
while( decimation > 0 && pointer < PRIMES.length )
|
||||
{
|
||||
int prime = PRIMES[ pointer ];
|
||||
|
||||
if( decimation % prime == 0 )
|
||||
{
|
||||
stages.add( prime );
|
||||
|
||||
decimation /= prime;
|
||||
}
|
||||
else
|
||||
{
|
||||
pointer++;
|
||||
}
|
||||
}
|
||||
|
||||
return stages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
|
||||
*/
|
||||
@Override
|
||||
public void receive( ComplexBuffer buffer )
|
||||
/**
|
||||
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
||||
* decimated, single paired i/q float sample output.
|
||||
*
|
||||
* Implements the CIC filter described in Understanding Digital Signal
|
||||
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
||||
* decimating stages each with a prime factor decimation rate. Multiple
|
||||
* stages are cascaded to achieve the overall decimation rate.
|
||||
*
|
||||
* This filter supports a maximum decimation rate of 700. This filter can
|
||||
* be adapted to higher decimation rates by adding additional prime factors
|
||||
* to the PRIMES array.
|
||||
*
|
||||
* @param decimation - overall decimation rate
|
||||
* @param order - filter order
|
||||
*/
|
||||
public ComplexPrimeCICDecimate(int decimation, int order,
|
||||
int passFrequency, int attenuation, WindowType windowType)
|
||||
{
|
||||
if( mFirstDecimatingStage != null )
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
|
||||
for( int x = 0; x < samples.length; x += 2 )
|
||||
{
|
||||
mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimating stage combines multiple CIC stages with a decimator. The
|
||||
* number of stages is indicated by the order value and the size indicates
|
||||
* the decimation rate of this stage.
|
||||
*/
|
||||
public class DecimatingStage implements ComplexSampleListener
|
||||
{
|
||||
private ArrayList<Stage> mStages = new ArrayList<Stage>();
|
||||
private Stage mFirstStage;
|
||||
private ComplexDecimator mDecimator;
|
||||
|
||||
public DecimatingStage( int size, int order )
|
||||
{
|
||||
for( int x = 0; x < order; x++ )
|
||||
{
|
||||
Stage stage;
|
||||
|
||||
if( size == 2 )
|
||||
{
|
||||
stage = new TwoStage();
|
||||
}
|
||||
else
|
||||
{
|
||||
stage = new Stage( size );
|
||||
}
|
||||
|
||||
mStages.add( stage );
|
||||
|
||||
if( x == 0 )
|
||||
{
|
||||
mFirstStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
mStages.get( x - 1 ).setListener( stage );
|
||||
}
|
||||
}
|
||||
|
||||
mDecimator = new ComplexDecimator( size );
|
||||
|
||||
mStages.get( mStages.size() - 1 ).setListener( mDecimator );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for( Stage stage: mStages )
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mStages.clear();
|
||||
mDecimator.dispose();
|
||||
mDecimator = null;
|
||||
mFirstStage = null;
|
||||
mStages = null;
|
||||
}
|
||||
Validate.isTrue(decimation <= 700);
|
||||
|
||||
@Override
|
||||
public void receive( float i, float q )
|
||||
|
||||
List<Integer> stageSizes = getPrimeFactors(decimation);
|
||||
|
||||
for(int x = 0; x < stageSizes.size(); x++)
|
||||
{
|
||||
mFirstStage.receive( i, q );
|
||||
}
|
||||
|
||||
public void setListener( ComplexSampleListener listener )
|
||||
{
|
||||
mDecimator.setListener( listener );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
||||
* running average internally to implement the stage so that stage size has
|
||||
* essentially no impact on the computational requirements of the stage
|
||||
*/
|
||||
public class Stage implements ComplexSampleListener
|
||||
{
|
||||
protected ComplexSampleListener mListener;
|
||||
|
||||
private float[] mISamples;
|
||||
private float[] mQSamples;
|
||||
|
||||
protected float mISum;
|
||||
protected float mQSum;
|
||||
|
||||
private int mSamplePointer = 0;
|
||||
private int mSize;
|
||||
|
||||
protected float mGain;
|
||||
|
||||
public Stage()
|
||||
{
|
||||
}
|
||||
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
|
||||
|
||||
public Stage( int size )
|
||||
{
|
||||
mSize = size - 1;
|
||||
|
||||
mISamples = new float[ mSize ];
|
||||
mQSamples = new float[ mSize ];
|
||||
|
||||
mGain = 1.0f / (float)size;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public void receive( float i, float q )
|
||||
{
|
||||
mDecimatingStages.add(stage);
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
/* Reference to first stage -- will receive all samples */
|
||||
mFirstDecimatingStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Wire the current stage to the previous stage */
|
||||
mDecimatingStages.get(x - 1).setListener(stage);
|
||||
}
|
||||
}
|
||||
|
||||
mOutput = new Output(48000, passFrequency, attenuation, windowType);
|
||||
|
||||
mDecimatingStages.get(mDecimatingStages.size() - 1)
|
||||
.setListener(mOutput);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(DecimatingStage stage : mDecimatingStages)
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mDecimatingStages.clear();
|
||||
mDecimatingStages = null;
|
||||
mFirstDecimatingStage = null;
|
||||
|
||||
mOutput.dispose();
|
||||
mOutput = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive the output of this CIC decimation filter
|
||||
*/
|
||||
public void setListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mOutput.setListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes listener from output of this CIC decimation filter
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mOutput.removeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the prime factors of the decimation rate up to a maximum
|
||||
* decimation rate of 700. If you wish to have decimation rates higher
|
||||
* than 700, then add additional prime factors to the PRIMES array.
|
||||
*
|
||||
* @param decimation - integral decimation rate
|
||||
* @return - ordered list (smallest to largest) of prime factors
|
||||
*/
|
||||
public static List<Integer> getPrimeFactors(int decimation)
|
||||
{
|
||||
ArrayList<Integer> stages = new ArrayList<Integer>();
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
while(decimation > 0 && pointer < PRIMES.length)
|
||||
{
|
||||
int prime = PRIMES[pointer];
|
||||
|
||||
if(decimation % prime == 0)
|
||||
{
|
||||
stages.add(prime);
|
||||
|
||||
decimation /= prime;
|
||||
}
|
||||
else
|
||||
{
|
||||
pointer++;
|
||||
}
|
||||
}
|
||||
|
||||
return stages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ComplexBuffer buffer)
|
||||
{
|
||||
if(mFirstDecimatingStage != null)
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimating stage combines multiple CIC stages with a decimator. The
|
||||
* number of stages is indicated by the order value and the size indicates
|
||||
* the decimation rate of this stage.
|
||||
*/
|
||||
public class DecimatingStage implements ComplexSampleListener
|
||||
{
|
||||
private ArrayList<Stage> mStages = new ArrayList<Stage>();
|
||||
private Stage mFirstStage;
|
||||
private ComplexDecimator mDecimator;
|
||||
|
||||
public DecimatingStage(int size, int order)
|
||||
{
|
||||
for(int x = 0; x < order; x++)
|
||||
{
|
||||
Stage stage;
|
||||
|
||||
if(size == 2)
|
||||
{
|
||||
stage = new TwoStage();
|
||||
}
|
||||
else
|
||||
{
|
||||
stage = new Stage(size);
|
||||
}
|
||||
|
||||
mStages.add(stage);
|
||||
|
||||
if(x == 0)
|
||||
{
|
||||
mFirstStage = stage;
|
||||
}
|
||||
else
|
||||
{
|
||||
mStages.get(x - 1).setListener(stage);
|
||||
}
|
||||
}
|
||||
|
||||
mDecimator = new ComplexDecimator(size);
|
||||
|
||||
mStages.get(mStages.size() - 1).setListener(mDecimator);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
for(Stage stage : mStages)
|
||||
{
|
||||
stage.dispose();
|
||||
}
|
||||
|
||||
mStages.clear();
|
||||
mDecimator.dispose();
|
||||
mDecimator = null;
|
||||
mFirstStage = null;
|
||||
mStages = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
mFirstStage.receive(i, q);
|
||||
}
|
||||
|
||||
public void setListener(ComplexSampleListener listener)
|
||||
{
|
||||
mDecimator.setListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
||||
* running average internally to implement the stage so that stage size has
|
||||
* essentially no impact on the computational requirements of the stage
|
||||
*/
|
||||
public class Stage implements ComplexSampleListener
|
||||
{
|
||||
protected ComplexSampleListener mListener;
|
||||
|
||||
private float[] mISamples;
|
||||
private float[] mQSamples;
|
||||
|
||||
protected float mISum;
|
||||
protected float mQSum;
|
||||
|
||||
private int mSamplePointer = 0;
|
||||
private int mSize;
|
||||
|
||||
protected float mGain;
|
||||
|
||||
public Stage()
|
||||
{
|
||||
}
|
||||
|
||||
public Stage(int size)
|
||||
{
|
||||
mSize = size - 1;
|
||||
|
||||
mISamples = new float[mSize];
|
||||
mQSamples = new float[mSize];
|
||||
|
||||
mGain = 1.0f / (float)size;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
/* Subtract the oldest sample and add back in the newest */
|
||||
mISum = mISum - mISamples[ mSamplePointer ] + i;
|
||||
mQSum = mQSum - mQSamples[ mSamplePointer ] + q;
|
||||
mISum = mISum - mISamples[mSamplePointer] + i;
|
||||
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
||||
|
||||
/* Overwrite the oldest sample with the newest */
|
||||
mISamples[ mSamplePointer ] = i;
|
||||
mQSamples[ mSamplePointer ] = q;
|
||||
|
||||
mSamplePointer++;
|
||||
|
||||
if( mSamplePointer >= mSize )
|
||||
{
|
||||
mSamplePointer = 0;
|
||||
}
|
||||
mISamples[mSamplePointer] = i;
|
||||
mQSamples[mSamplePointer] = q;
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener( ComplexSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Size 2 stage that removes the unnecessary circular buffer management for
|
||||
* a two-stage.
|
||||
*/
|
||||
public class TwoStage extends Stage
|
||||
{
|
||||
public TwoStage()
|
||||
{
|
||||
super();
|
||||
|
||||
mGain = 0.5f;
|
||||
}
|
||||
|
||||
public void receive( float i, float q )
|
||||
{
|
||||
float iSum = mISum + i;
|
||||
float qSum = mQSum + q;
|
||||
|
||||
mISum = i;
|
||||
mQSum = q;
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( ( iSum * mGain ), ( qSum * mGain ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSamplePointer++;
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction and cleanup filter and broadcast
|
||||
* to registered listener.
|
||||
*/
|
||||
public class Output implements ComplexSampleListener
|
||||
{
|
||||
/* Decimated output buffers will contain 1024 samples */
|
||||
private ComplexToComplexBufferAssembler mAssembler =
|
||||
new ComplexToComplexBufferAssembler( 2048 );
|
||||
if(mSamplePointer >= mSize)
|
||||
{
|
||||
mSamplePointer = 0;
|
||||
}
|
||||
|
||||
private ComplexFIRFilter_CB_CB mCleanupFilter;
|
||||
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false );
|
||||
|
||||
public Output( int outputSampleRate, int passFrequency, int attenuation,
|
||||
WindowType windowType )
|
||||
{
|
||||
mCleanupFilter = new ComplexFIRFilter_CB_CB( FilterFactory
|
||||
.getCICCleanupFilter( outputSampleRate,
|
||||
passFrequency,
|
||||
attenuation,
|
||||
windowType ), 0.4f );
|
||||
|
||||
mAssembler.setListener( mCleanupFilter );
|
||||
mCleanupFilter.setListener( mHalfBandFilter );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mAssembler.dispose();
|
||||
mCleanupFilter.dispose();
|
||||
mHalfBandFilter.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver method for the output adapter to receive a filtered,
|
||||
* decimated sample, apply gain correction, apply cleanup filtering
|
||||
* and output the sample values as a complex sample.
|
||||
*/
|
||||
@Override
|
||||
public void receive( float inphase, float quadrature )
|
||||
{
|
||||
mAssembler.receive( inphase, quadrature );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive output samples
|
||||
*/
|
||||
public void setListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mHalfBandFilter.setListener( listener );
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive((mISum * mGain), (mQSum * mGain));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving output samples
|
||||
*/
|
||||
public void setListener(ComplexSampleListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Size 2 stage that removes the unnecessary circular buffer management for
|
||||
* a two-stage.
|
||||
*/
|
||||
public class TwoStage extends Stage
|
||||
{
|
||||
public TwoStage()
|
||||
{
|
||||
super();
|
||||
|
||||
mGain = 0.5f;
|
||||
}
|
||||
|
||||
public void receive(float i, float q)
|
||||
{
|
||||
float iSum = mISum + i;
|
||||
float qSum = mQSum + q;
|
||||
|
||||
mISum = i;
|
||||
mQSum = q;
|
||||
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive((iSum * mGain), (qSum * mGain));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Output adapter - applies gain correction and cleanup filter and broadcast
|
||||
* to registered listener.
|
||||
*/
|
||||
public class Output implements ComplexSampleListener
|
||||
{
|
||||
/* Decimated output buffers will contain 1024 samples */
|
||||
private ComplexToComplexBufferAssembler mAssembler =
|
||||
new ComplexToComplexBufferAssembler(2048);
|
||||
|
||||
private ComplexFIRFilter_CB_CB mCleanupFilter;
|
||||
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
|
||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
|
||||
|
||||
public Output(int outputSampleRate, int passFrequency, int attenuation,
|
||||
WindowType windowType)
|
||||
{
|
||||
mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory
|
||||
.getCICCleanupFilter(outputSampleRate,
|
||||
passFrequency,
|
||||
attenuation,
|
||||
windowType), 0.4f);
|
||||
|
||||
//Bypassing the CIC cleanup filter for now
|
||||
mAssembler.setListener(mHalfBandFilter);
|
||||
|
||||
// mAssembler.setListener( mCleanupFilter );
|
||||
// mCleanupFilter.setListener( mHalfBandFilter );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mAssembler.dispose();
|
||||
mCleanupFilter.dispose();
|
||||
mHalfBandFilter.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver method for the output adapter to receive a filtered,
|
||||
* decimated sample, apply gain correction, apply cleanup filtering
|
||||
* and output the sample values as a complex sample.
|
||||
*/
|
||||
@Override
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
mAssembler.receive(inphase, quadrature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive output samples
|
||||
*/
|
||||
public void setListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mHalfBandFilter.setListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving output samples
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mHalfBandFilter.removeListener();
|
||||
mHalfBandFilter.removeListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package dsp.filter.cic;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -81,7 +82,7 @@ public class RealPrimeCICDecimate
|
||||
public RealPrimeCICDecimate( int decimation, int order, int passFrequency,
|
||||
int attenuation, WindowType windowType, int outputBufferSize )
|
||||
{
|
||||
assert( decimation <= 700 );
|
||||
Validate.isTrue(decimation <= 700);
|
||||
|
||||
mOutputBufferSize = outputBufferSize;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
******************************************************************************/
|
||||
package dsp.filter.halfband.real;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class HalfBandFilter
|
||||
{
|
||||
private float[] mBuffer;
|
||||
@@ -122,7 +124,7 @@ public class HalfBandFilter
|
||||
private void generateIndexMap( int size )
|
||||
{
|
||||
//Ensure we have an odd size
|
||||
assert( size % 2 == 1 );
|
||||
Validate.isTrue(size % 2 == 1);
|
||||
|
||||
int mapWidth = ( ( size + 1 ) / 2 ) + 1;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dsp.filter.interpolator;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -31,7 +32,7 @@ public class RealInterpolator extends Interpolator
|
||||
public float filter( float[] samples, int offset, float mu )
|
||||
{
|
||||
/* Ensure we have enough samples in the array */
|
||||
assert( samples.length >= offset + 7 );
|
||||
Validate.isTrue(samples.length >= offset + 7);
|
||||
|
||||
/* Identify the filter bank that corresponds to mu */
|
||||
int index = (int)( NSTEPS * mu );
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
******************************************************************************/
|
||||
package dsp.filter.polyphase;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -53,7 +54,7 @@ public class PolyphaseFIRInterpolatingFilter
|
||||
public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation )
|
||||
{
|
||||
/* Ensure coefficients are an integral multiple of the interpolation factor */
|
||||
assert( coefficients.length % interpolation == 0 );
|
||||
Validate.isTrue(coefficients.length % interpolation == 0);
|
||||
|
||||
mTaps = coefficients;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
******************************************************************************/
|
||||
package dsp.gain;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -49,7 +50,7 @@ public class DirectGainControl implements GainController, RealSampleListener
|
||||
*/
|
||||
public DirectGainControl( float gain, float minimum, float maximum, float increment )
|
||||
{
|
||||
assert( minimum > 0 );
|
||||
Validate.isTrue(minimum > 0);
|
||||
|
||||
mDefaultGain = gain;
|
||||
mGain = gain;
|
||||
|
||||
86
src/edac/trellis/DMRNode.java
Normal file
86
src/edac/trellis/DMRNode.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
public class DMRNode extends Node
|
||||
{
|
||||
//Hamming error values indicating the number of bits set in each indexed value, 0-15
|
||||
public static final int[] HAMMING_ERROR_COUNT = new int[]{0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
|
||||
|
||||
/**
|
||||
* Transmitted quad-bit value, derived from ETSI TS 102-361-1, Table B.7 Trellis encoder state transition table
|
||||
*
|
||||
* Note: the B.7 table contains constellation point ID numbers that have to be converted into actual bit values
|
||||
* where the bits represent the dibit pair for the constellation point.
|
||||
*/
|
||||
public static final int[][] DMR_TRANSITION_MATRIX = new int[][]
|
||||
{
|
||||
{2,13,14,1,7,8,11,4},
|
||||
{14,1,7,8,11,4,2,13},
|
||||
{10,5,6,9,15,0,3,12},
|
||||
{6,9,15,0,3,12,10,5},
|
||||
{15,0,3,12,10,5,6,9},
|
||||
{3,12,10,5,6,9,15,0},
|
||||
{7,8,11,4,2,13,14,1},
|
||||
{11,4,2,13,14,1,7,8}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) 3/4-rate trellis node for decoding DMR
|
||||
*
|
||||
* @param inputValue for the trellis time instant
|
||||
* @param transmittedOutputValue the actual four-bit output value that was transmitted.
|
||||
*/
|
||||
public DMRNode(int inputValue, int transmittedOutputValue)
|
||||
{
|
||||
super(inputValue, transmittedOutputValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the error between the preceeding node and this node as the Hamming distance between the expected
|
||||
* four-bit output value versus the actual transmitted output value.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return error value for this node relative to the preceeding node argument
|
||||
*/
|
||||
@Override
|
||||
public int getError(Node precedingNode)
|
||||
{
|
||||
if(precedingNode != null)
|
||||
{
|
||||
int expectedOutputValue = getOutputValue(precedingNode);
|
||||
int errorMask = expectedOutputValue ^ getTransmittedOutputValue();
|
||||
return HAMMING_ERROR_COUNT[errorMask];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected output four-bit value when this node is preceeded by the argument node.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return expected output value
|
||||
*/
|
||||
@Override
|
||||
public int getOutputValue(Node precedingNode)
|
||||
{
|
||||
return DMR_TRANSITION_MATRIX[precedingNode.getInputValue()][getInputValue()];
|
||||
}
|
||||
}
|
||||
77
src/edac/trellis/Node.java
Normal file
77
src/edac/trellis/Node.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
public abstract class Node
|
||||
{
|
||||
private int mInputValue;
|
||||
private int mTransmittedOutputValue;
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) node. A node represents a time instant on the trellis and contains an input
|
||||
* value and the corresponding transmitted output value for the time instant. A node does not maintain the
|
||||
* state at the time instant -- the state is determined by the input value of a preceeding node.
|
||||
*
|
||||
* @param inputValue for this node
|
||||
* @param transmittedOutputValue for this node
|
||||
*/
|
||||
public Node(int inputValue, int transmittedOutputValue)
|
||||
{
|
||||
mInputValue = inputValue;
|
||||
mTransmittedOutputValue = transmittedOutputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input value for this node.
|
||||
*
|
||||
* @return input value
|
||||
*/
|
||||
public int getInputValue()
|
||||
{
|
||||
return mInputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmitted output value for this node
|
||||
* @return transmitted output value
|
||||
*/
|
||||
public int getTransmittedOutputValue()
|
||||
{
|
||||
return mTransmittedOutputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the error value for this node (time instant) by determining the expected output value that would have
|
||||
* been transmitted using the state from the preceding node argument and this node's input value and comparing that
|
||||
* expected value to the actual transmitted output value. This error is normally represented as the Hamming
|
||||
* distance between the two values.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return error value for this node relative to the preceeding node
|
||||
*/
|
||||
public abstract int getError(Node precedingNode);
|
||||
|
||||
/**
|
||||
* Expected output value when this node is preceeded by the node specified in the argument.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return expected output value
|
||||
*/
|
||||
public abstract int getOutputValue(Node precedingNode);
|
||||
}
|
||||
131
src/edac/trellis/Path.java
Normal file
131
src/edac/trellis/Path.java
Normal file
@@ -0,0 +1,131 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Path
|
||||
{
|
||||
private List<Node> mNodes = new ArrayList<>();
|
||||
private int mError = 0;
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) path. Represents a sequence of nodes that traverse various time instants and
|
||||
* states across a trellis.
|
||||
*
|
||||
* Note: each node can be shared across multiple paths. This class does not assume that any nodes are unique.
|
||||
* @param startingNode to start the path. This node normally contains an input value of zero, but is implementation
|
||||
* specific.
|
||||
*/
|
||||
public Path(Node startingNode)
|
||||
{
|
||||
add(startingNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) path. Represents a sequence of nodes that traverse various time instants and
|
||||
* states across a trellis.
|
||||
*
|
||||
* Note: each node can be shared across multiple paths. This class does not assume that any nodes are unique.
|
||||
*
|
||||
* @param original path to create a copy from. Each of the nodes in the original will be added to this path and
|
||||
* after construction both this path and the original will contain the exact same set of nodes.
|
||||
*/
|
||||
private Path(Path original)
|
||||
{
|
||||
for(Node node: original.getNodes())
|
||||
{
|
||||
add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cumulative error across all nodes contained within this path. Error value is updated as each new node is added
|
||||
* to this path.
|
||||
*
|
||||
* @return cumulative error value
|
||||
*/
|
||||
public int getError()
|
||||
{
|
||||
return mError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length or number of nodes contained within this path. Note: this value includes the starting node and may
|
||||
* also include a flushing node. The values contained in the starting and flushing nodes are normally not used
|
||||
* when decoding a path.
|
||||
*
|
||||
* @return number of nodes in this path
|
||||
*/
|
||||
public int getLength()
|
||||
{
|
||||
return mNodes.size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of nodes contained in this path.
|
||||
* @return nodes
|
||||
*/
|
||||
public List<Node> getNodes()
|
||||
{
|
||||
return mNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the node to this path and updates the cumulative error value for this path by adding the node's error
|
||||
* value to the running error total.
|
||||
*
|
||||
* @param node to add to this path
|
||||
*/
|
||||
public void add(Node node)
|
||||
{
|
||||
if(!mNodes.isEmpty())
|
||||
{
|
||||
mError += node.getError(getLastNode());
|
||||
}
|
||||
|
||||
mNodes.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Last node in this path.
|
||||
*
|
||||
* @return last node
|
||||
*/
|
||||
public Node getLastNode()
|
||||
{
|
||||
if(!mNodes.isEmpty())
|
||||
{
|
||||
return mNodes.get(mNodes.size() - 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to create an exact copy of this path.
|
||||
*
|
||||
* @return a new copy of this path.
|
||||
*/
|
||||
public Path copyOf()
|
||||
{
|
||||
return new Path(this);
|
||||
}
|
||||
}
|
||||
226
src/edac/trellis/ViterbiDecoder.java
Normal file
226
src/edac/trellis/ViterbiDecoder.java
Normal file
@@ -0,0 +1,226 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class ViterbiDecoder
|
||||
{
|
||||
private int mInputBitLength;
|
||||
private int mInputValueCount;
|
||||
private int mOutputBitLength;
|
||||
private int mOutputValueCount;
|
||||
|
||||
/**
|
||||
* Viterbi decoder for trellis coded modulation (TCM) encoded binary sequences.
|
||||
*
|
||||
* @param inputBitLength for coding (e.g. 3/4 rate input bit length is 3)
|
||||
* @param outputBitLength for coding (e.g. 3/4 rate output bit length is 4)
|
||||
*/
|
||||
public ViterbiDecoder(int inputBitLength, int outputBitLength)
|
||||
{
|
||||
mInputBitLength = inputBitLength;
|
||||
mOutputBitLength = outputBitLength;
|
||||
|
||||
mInputValueCount = (int)Math.pow(2.0, mInputBitLength);
|
||||
mOutputValueCount = (int)Math.pow(2.0, mOutputBitLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a node that represents an input value and the actual/transmitted output value.
|
||||
*
|
||||
* Note: this method must be implemented by a sub-class since the node requires a specific state transition
|
||||
* table/matrix implementation.
|
||||
*
|
||||
* @param inputValue for the specific trellis node
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return node for decoding
|
||||
*/
|
||||
protected abstract Node createNode(int inputValue, int transmittedOutputValue);
|
||||
|
||||
/**
|
||||
* Creates a flushing or final node that represents an actual/transmitted output value and a flushing input value.
|
||||
*
|
||||
* The final flushing input value is implementation-specific, therefore this will be implemented in any subclass
|
||||
* of this decoder. A normal flushing input value is zero.
|
||||
*
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return flushing node
|
||||
*/
|
||||
protected abstract Node createFlushingNode(int transmittedOutputValue);
|
||||
|
||||
/**
|
||||
* Creates a starting node that represents a starting state and notional transmitted output value.
|
||||
*
|
||||
* The starting input value is implementation-specific, therefore this will be implemented in any subclass
|
||||
* of this decoder. A normal starting input value is zero.
|
||||
*
|
||||
* @return starting node
|
||||
*/
|
||||
protected abstract Node createStartingNode();
|
||||
|
||||
/**
|
||||
* Size in bits of the input and state values (e.g. 3/4 rate input size is 3)
|
||||
*
|
||||
* @return input bit size
|
||||
*/
|
||||
public int getInputBitLength()
|
||||
{
|
||||
return mInputBitLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of input or state values where the set of values is in the range of 0 <> count - 1
|
||||
*
|
||||
* @return input or state values count
|
||||
*/
|
||||
public int getInputValueCount()
|
||||
{
|
||||
return mInputValueCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size in bits of the output constellation or transmitted symbol value (e.g. 3/4 rate output size is 4)
|
||||
*
|
||||
* @return output size in bits
|
||||
*/
|
||||
public int getOutputBitLength()
|
||||
{
|
||||
return mOutputBitLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of input or state values where the set of values is in the range of 0 <> count - 1
|
||||
*
|
||||
* @return input or state values count
|
||||
*/
|
||||
public int getOutputValueCount()
|
||||
{
|
||||
return mOutputValueCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the TCM encoded transmitted output values and returns a path the represents the most likely transmitted
|
||||
* sequence of nodes.
|
||||
*
|
||||
* @param transmittedOutputValues from the encoded message
|
||||
* @return most likely path representing the transmitted values
|
||||
*/
|
||||
public Path decode(int[] transmittedOutputValues)
|
||||
{
|
||||
Collection<Path> survivingPaths = new ArrayList<>();
|
||||
|
||||
survivingPaths.add(new Path(createStartingNode()));
|
||||
|
||||
//Add all but the last transmitted value to the starting path, creating a set of survivors at each iteration.
|
||||
for(int x = 0; x < transmittedOutputValues.length - 1; x++)
|
||||
{
|
||||
survivingPaths = add(survivingPaths, transmittedOutputValues[x]);
|
||||
}
|
||||
|
||||
//Flush the survivors with the final transmitted value and return the lone surviving path
|
||||
return flush(survivingPaths, transmittedOutputValues[transmittedOutputValues.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set of new nodes representing all possible input values and the transmitted output value argument
|
||||
* and iteratively adds the set of nodes to a copy of each of the surviving paths. A set of surviving paths is
|
||||
* returned that represents each path that terminates at a distinct input/state value that has the least error
|
||||
* value.
|
||||
*
|
||||
* @param survivingPaths from the previous time instant to evaluate for the next time instant in the trellis
|
||||
* using the transmitted output value.
|
||||
*
|
||||
* @param transmittedOutputValue that will be decoded for this time instant.
|
||||
*
|
||||
* @return paths that have survived (ie have the lowest error value) after adding nodes for the transmitted
|
||||
* output value
|
||||
*
|
||||
*/
|
||||
public Collection<Path> add(Collection<Path> survivingPaths, int transmittedOutputValue)
|
||||
{
|
||||
Map<Integer,Path> survivingPathMap = new HashMap<>();
|
||||
|
||||
for(int x = 0; x < getInputValueCount(); x++)
|
||||
{
|
||||
//Create a single/unique node for each possible input value combined with the transmitted output value
|
||||
Node node = createNode(x, transmittedOutputValue);
|
||||
|
||||
//Evaluate each surviving path by creating a copy and adding the node to it and see if it survives
|
||||
for(Path path: survivingPaths)
|
||||
{
|
||||
Path pathToEvaluate = path.copyOf();
|
||||
|
||||
pathToEvaluate.add(node);
|
||||
|
||||
if(survivingPathMap.containsKey(node.getInputValue()))
|
||||
{
|
||||
Path survivingPath = survivingPathMap.get(node.getInputValue());
|
||||
|
||||
//Replace the current survivor path if this evaluate path has a lower error value
|
||||
if(pathToEvaluate.getError() < survivingPath.getError())
|
||||
{
|
||||
survivingPathMap.put(node.getInputValue(), pathToEvaluate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//No survivor yet -- set this evaluate path as the first survivor
|
||||
survivingPathMap.put(node.getInputValue(), pathToEvaluate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return survivingPathMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes each of the surviving paths with a final/flushing node using the transmitted symbol. A final flushing
|
||||
* input value or state is implementation-specific, but normally uses a value of zero. Since all paths will be
|
||||
* evaluated against each other as terminating at the same state, only the path with the lowest error value will
|
||||
* be returned from this method.
|
||||
*
|
||||
* @param survivingPaths to flush with the final flushing node
|
||||
* @param transmittedOutputValue to use for the final flushing node
|
||||
*
|
||||
* @return the single survivor path that remains after adding the flushing node to each of the candidate argument
|
||||
* survivor paths.
|
||||
*/
|
||||
public Path flush(Collection<Path> survivingPaths, int transmittedOutputValue)
|
||||
{
|
||||
Node flushingNode = createFlushingNode(transmittedOutputValue);
|
||||
|
||||
Path bestPath = null;
|
||||
|
||||
for(Path survivingPath: survivingPaths)
|
||||
{
|
||||
survivingPath.add(flushingNode);
|
||||
|
||||
if(bestPath == null || (survivingPath.getError() < bestPath.getError()))
|
||||
{
|
||||
bestPath = survivingPath;
|
||||
}
|
||||
}
|
||||
|
||||
return bestPath;
|
||||
}
|
||||
}
|
||||
159
src/edac/trellis/ViterbiDecoder_3_4_DMR.java
Normal file
159
src/edac/trellis/ViterbiDecoder_3_4_DMR.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
import bits.BinaryMessage;
|
||||
import bits.CorrectedBinaryMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
|
||||
{
|
||||
/**
|
||||
* Viterbi decoder for Digital Mobile Radio (DMR) 3/4 rate Trellis Coded Modulation (TCM) encoded messages.
|
||||
*/
|
||||
public ViterbiDecoder_3_4_DMR()
|
||||
{
|
||||
super(3,4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a 3/4 rate trellis coded modulation (TCM) encoded DMR binary message containing 196 bits that have
|
||||
* already been deinterleaved.
|
||||
*
|
||||
* @param encodedMessage to decode that has already been deinterleaved.
|
||||
* @return decoded message
|
||||
*/
|
||||
public BinaryMessage decode(BinaryMessage encodedMessage)
|
||||
{
|
||||
int[] symbols = getSymbols(encodedMessage);
|
||||
|
||||
Path mostLikelyPath = decode(symbols);
|
||||
|
||||
return getMessage(mostLikelyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the decoded/corrected message from the surviving path argument.
|
||||
*
|
||||
* @param path to extract a message from
|
||||
* @return corrected binary message.
|
||||
*/
|
||||
private static CorrectedBinaryMessage getMessage(Path path)
|
||||
{
|
||||
List<Node> mNodes = path.getNodes();
|
||||
|
||||
//Each node contains an input value excluding the starting(0) and final flushing(0) nodes.
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage((mNodes.size() - 2) * 3);
|
||||
|
||||
if(mNodes.size() > 2)
|
||||
{
|
||||
for(int x = 1; x < mNodes.size() - 1; x++)
|
||||
{
|
||||
int inputValue = mNodes.get(x).getInputValue();
|
||||
|
||||
int messageOffset = 3 * (x - 1);
|
||||
|
||||
if((inputValue & 4) == 4)
|
||||
{
|
||||
message.set(messageOffset);
|
||||
}
|
||||
|
||||
if((inputValue & 2) == 2)
|
||||
{
|
||||
message.set(messageOffset + 1);
|
||||
}
|
||||
|
||||
if((inputValue & 1) == 1)
|
||||
{
|
||||
message.set(messageOffset + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Transfer the corrected error count to the message
|
||||
message.setCorrectedBitCount(path.getError());
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts (49) four-bit symbols (196 bits / 4) from the deinterleaved DMR binary message
|
||||
*
|
||||
* @param encodedMessage that deinterleaved and contains 196-bits of 3/4 TCM encoding
|
||||
* @return symbols
|
||||
*/
|
||||
public int[] getSymbols(BinaryMessage encodedMessage)
|
||||
{
|
||||
//Ensure we have an integral number of transmitted symbols (nibbles) in the message
|
||||
if(encodedMessage.size() % getOutputBitLength() != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Encoded message must contain an integral number of 4-bit symbols -" +
|
||||
" message size: " + encodedMessage.size());
|
||||
}
|
||||
|
||||
int[] symbols = new int[encodedMessage.size() / getOutputBitLength()];
|
||||
|
||||
int index = 0;
|
||||
int pointer = 0;
|
||||
|
||||
while(index < encodedMessage.size())
|
||||
{
|
||||
int symbol = encodedMessage.getInt(index, index + (getOutputBitLength() - 1));
|
||||
symbols[pointer++] = symbol;
|
||||
index += getOutputBitLength();
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR node
|
||||
* @param inputValue for the specific trellis node
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return created node
|
||||
*/
|
||||
@Override
|
||||
protected Node createNode(int inputValue, int transmittedOutputValue)
|
||||
{
|
||||
return new DMRNode(inputValue, transmittedOutputValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR flushing node. DMR uses an input value of zero to flush the final output value out of the
|
||||
* encoder.
|
||||
*
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected Node createFlushingNode(int transmittedOutputValue)
|
||||
{
|
||||
return new DMRNode(0, transmittedOutputValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR starting node. DMR uses a starting input value of zero for the initial node.
|
||||
*/
|
||||
@Override
|
||||
protected Node createStartingNode()
|
||||
{
|
||||
return new DMRNode(0,0);
|
||||
}
|
||||
}
|
||||
@@ -151,41 +151,4 @@ public class RadioReferenceService
|
||||
|
||||
return mClient.getUserFeedBroadcasts(mUserAuthorization);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
RadioReferenceService service = new RadioReferenceService("dsheirer", "1dodgeram");
|
||||
|
||||
Long expiration = service.getAccountExpirationDate();
|
||||
|
||||
mLog.debug("Expiration Date:" + (expiration != null ? new Date(expiration).toString() : "unknown"));
|
||||
|
||||
mLog.debug("Is Active: " + service.isAccountActive());
|
||||
|
||||
UserFeedBroadcast[] feeds = service.getUserFeedBroadcasts();
|
||||
|
||||
for(UserFeedBroadcast feed: feeds)
|
||||
{
|
||||
mLog.debug("Feed ID: " + feed.getFeedId() + " Desc:" + feed.getDescr());
|
||||
}
|
||||
}
|
||||
catch(AxisFault af)
|
||||
{
|
||||
mLog.debug("Hey, I caught an axis fault!");
|
||||
mLog.debug("Reason:" + af.getFaultReason());
|
||||
}
|
||||
catch(RemoteException re)
|
||||
{
|
||||
re.printStackTrace();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mLog.debug("Finished!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,82 @@
|
||||
/*******************************************************************************
|
||||
* 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 instrument;
|
||||
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import instrument.tap.Tap;
|
||||
import instrument.tap.TapGroup;
|
||||
import module.Module;
|
||||
import module.ProcessingChain;
|
||||
import source.Source;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import module.Module;
|
||||
import module.ProcessingChain;
|
||||
import source.Source;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
|
||||
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
||||
{
|
||||
public InstrumentableProcessingChain()
|
||||
{
|
||||
super( "instrumented", ChannelType.STANDARD );
|
||||
}
|
||||
public InstrumentableProcessingChain()
|
||||
{
|
||||
super(ChannelType.STANDARD);
|
||||
}
|
||||
|
||||
public void setSource( Source source ) throws IllegalStateException
|
||||
{
|
||||
mSource = source;
|
||||
}
|
||||
public void setSource(Source source) throws IllegalStateException
|
||||
{
|
||||
mSource = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
List<TapGroup> groups = new ArrayList<>();
|
||||
|
||||
for( Module module: getModules() )
|
||||
{
|
||||
if( module instanceof Instrumentable )
|
||||
{
|
||||
groups.addAll( ((Instrumentable)module).getTapGroups() );
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
List<TapGroup> groups = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
{
|
||||
for( Module module: getModules() )
|
||||
{
|
||||
if( module instanceof Instrumentable )
|
||||
{
|
||||
((Instrumentable)module).registerTap( tap );
|
||||
}
|
||||
}
|
||||
}
|
||||
for(Module module : getModules())
|
||||
{
|
||||
if(module instanceof Instrumentable)
|
||||
{
|
||||
groups.addAll(((Instrumentable)module).getTapGroups());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
{
|
||||
for( Module module: getModules() )
|
||||
{
|
||||
if( module instanceof Instrumentable )
|
||||
{
|
||||
((Instrumentable)module).unregisterTap( tap );
|
||||
}
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTap(Tap tap)
|
||||
{
|
||||
for(Module module : getModules())
|
||||
{
|
||||
if(module instanceof Instrumentable)
|
||||
{
|
||||
((Instrumentable)module).registerTap(tap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTap(Tap tap)
|
||||
{
|
||||
for(Module module : getModules())
|
||||
{
|
||||
if(module instanceof Instrumentable)
|
||||
{
|
||||
((Instrumentable)module).unregisterTap(tap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,17 @@ import audio.IAudioPacketProvider;
|
||||
import audio.squelch.ISquelchStateListener;
|
||||
import audio.squelch.ISquelchStateProvider;
|
||||
import audio.squelch.SquelchState;
|
||||
import channel.heartbeat.Heartbeat;
|
||||
import channel.heartbeat.IHeartbeatListener;
|
||||
import channel.heartbeat.IHeartbeatProvider;
|
||||
import channel.metadata.AttributeChangeRequest;
|
||||
import channel.metadata.IAttributeChangeRequestListener;
|
||||
import channel.metadata.IAttributeChangeRequestProvider;
|
||||
import channel.state.ChannelState;
|
||||
import channel.state.DecoderState;
|
||||
import channel.state.DecoderStateEvent;
|
||||
import channel.state.DecoderStateEvent.Event;
|
||||
import channel.state.IDecoderStateEventListener;
|
||||
import channel.state.IDecoderStateEventProvider;
|
||||
import channel.state.State;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.ChannelEvent;
|
||||
import controller.channel.IChannelEventListener;
|
||||
@@ -63,8 +64,6 @@ import sample.real.RealBuffer;
|
||||
import source.ComplexSource;
|
||||
import source.RealSource;
|
||||
import source.Source;
|
||||
import source.SourceException;
|
||||
import source.tuner.TunerChannelSource;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
import source.tuner.frequency.IFrequencyChangeProvider;
|
||||
@@ -103,12 +102,12 @@ public class ProcessingChain implements IChannelEventListener
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<FrequencyChangeEvent> mFrequencyChangeEventBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<RealBuffer> mFilteredRealBufferBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
||||
|
||||
private String mName;
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
protected Source mSource;
|
||||
@@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener
|
||||
/**
|
||||
* Creates a processing chain for managing a set of modules
|
||||
*
|
||||
* @param name for thread pool naming each thread
|
||||
* @param channelType
|
||||
*/
|
||||
public ProcessingChain(String name, ChannelType channelType)
|
||||
public ProcessingChain(ChannelType channelType)
|
||||
{
|
||||
mName = name;
|
||||
|
||||
mChannelState = new ChannelState(channelType);
|
||||
addModule(mChannelState);
|
||||
|
||||
@@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener
|
||||
}
|
||||
|
||||
mSource = source;
|
||||
|
||||
addModule(mSource);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,7 +320,18 @@ public class ProcessingChain implements IChannelEventListener
|
||||
|
||||
if(module instanceof IFrequencyChangeListener)
|
||||
{
|
||||
mFrequencyChangeEventBroadcaster.addListener(((IFrequencyChangeListener) module).getFrequencyChangeListener());
|
||||
Listener<FrequencyChangeEvent> frequencyChangeEventListener =
|
||||
((IFrequencyChangeListener)module).getFrequencyChangeListener();
|
||||
|
||||
if(frequencyChangeEventListener != null)
|
||||
{
|
||||
mFrequencyChangeEventBroadcaster.addListener(frequencyChangeEventListener);
|
||||
}
|
||||
}
|
||||
|
||||
if(module instanceof IHeartbeatListener)
|
||||
{
|
||||
mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener());
|
||||
}
|
||||
|
||||
if(module instanceof IMessageListener)
|
||||
@@ -387,6 +396,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mFrequencyChangeEventBroadcaster.removeListener(((IFrequencyChangeListener) module).getFrequencyChangeListener());
|
||||
}
|
||||
|
||||
if(module instanceof IHeartbeatListener)
|
||||
{
|
||||
mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
|
||||
}
|
||||
|
||||
if(module instanceof IMessageListener)
|
||||
{
|
||||
mMessageBroadcaster.removeListener(((IMessageListener) module).getMessageListener());
|
||||
@@ -444,6 +458,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
((IFrequencyChangeProvider) module).setFrequencyChangeListener(mFrequencyChangeEventBroadcaster);
|
||||
}
|
||||
|
||||
if(module instanceof IHeartbeatProvider)
|
||||
{
|
||||
((IHeartbeatProvider)module).setHeartbeatListener(mHeartbeatBroadcaster);
|
||||
}
|
||||
|
||||
if(module instanceof IMessageProvider)
|
||||
{
|
||||
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
|
||||
@@ -501,6 +520,11 @@ public class ProcessingChain implements IChannelEventListener
|
||||
((IFrequencyChangeProvider) module).setFrequencyChangeListener(null);
|
||||
}
|
||||
|
||||
if(module instanceof IHeartbeatProvider)
|
||||
{
|
||||
((IHeartbeatProvider)module).removeHeartbeatListener();
|
||||
}
|
||||
|
||||
if(module instanceof IMessageProvider)
|
||||
{
|
||||
((IMessageProvider) module).setMessageListener(null);
|
||||
@@ -557,25 +581,6 @@ public class ProcessingChain implements IChannelEventListener
|
||||
+ "sample type - cannot start processing chain");
|
||||
}
|
||||
|
||||
/* If this is a tuner source, broadcast the frequency to all
|
||||
* of the decoder state's and start the samples flowing */
|
||||
if(mSource instanceof TunerChannelSource)
|
||||
{
|
||||
TunerChannelSource tcs = (TunerChannelSource) mSource;
|
||||
|
||||
try
|
||||
{
|
||||
long frequency = tcs.getFrequency();
|
||||
|
||||
mDecoderStateEventBroadcaster.broadcast(
|
||||
new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, State.IDLE, frequency));
|
||||
}
|
||||
catch(SourceException e)
|
||||
{
|
||||
mLog.error("Error getting frequency from tuner channel source", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Start each of the modules */
|
||||
for(Module module : mModules)
|
||||
{
|
||||
@@ -588,8 +593,6 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mLog.error("Error starting module", e);
|
||||
}
|
||||
}
|
||||
|
||||
mSource.start(ThreadPool.SCHEDULED);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -606,9 +609,16 @@ public class ProcessingChain implements IChannelEventListener
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
/* Stop each of the modules */
|
||||
for(Module module : mModules)
|
||||
{
|
||||
module.stop();
|
||||
}
|
||||
|
||||
if(mSource != null)
|
||||
{
|
||||
mSource.stop();
|
||||
removeModule(mSource);
|
||||
|
||||
mSource.setOverflowListener(null);
|
||||
|
||||
switch(mSource.getSampleType())
|
||||
@@ -629,11 +639,6 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mSource = null;
|
||||
}
|
||||
|
||||
/* Stop each of the modules */
|
||||
for(Module module : mModules)
|
||||
{
|
||||
module.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,6 +765,24 @@ public class ProcessingChain implements IChannelEventListener
|
||||
mMessageBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the listener to receive frequency change events from the processing chain
|
||||
* @param listener to receive events
|
||||
*/
|
||||
public void addFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
mFrequencyChangeEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving frequency change events
|
||||
* @param listener to remove
|
||||
*/
|
||||
public void removeFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
mFrequencyChangeEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the listener to receive call events from all modules.
|
||||
*/
|
||||
|
||||
@@ -222,7 +222,7 @@ public class DecoderFactory
|
||||
|
||||
if(channelType == ChannelType.STANDARD)
|
||||
{
|
||||
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
|
||||
modules.add(new TrafficChannelManager(channelModel, decodeConfig,
|
||||
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
|
||||
(aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize()));
|
||||
}
|
||||
@@ -263,7 +263,7 @@ public class DecoderFactory
|
||||
|
||||
if(channelType == ChannelType.STANDARD)
|
||||
{
|
||||
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
|
||||
modules.add(new TrafficChannelManager(channelModel, decodeConfig,
|
||||
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
|
||||
(aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize()));
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class LJ1200DecoderState extends DecoderState
|
||||
super(aliasList);
|
||||
|
||||
mAddressAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO,
|
||||
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.LOJACK);
|
||||
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,21 +9,21 @@ import instrument.tap.stream.FloatTap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
import sample.real.RealSampleListener;
|
||||
import sample.real.RealSampleProvider;
|
||||
import source.tuner.TunerChannelSource;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
import dsp.gain.DirectGainControl;
|
||||
|
||||
public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
||||
IFrequencyChangeListener,
|
||||
RealSampleProvider,
|
||||
Instrumentable
|
||||
public class C4FMSymbolFilter implements Listener<RealBuffer>, IFrequencyChangeListener, RealSampleProvider, Instrumentable
|
||||
{
|
||||
private static final float TAPS[][] =
|
||||
private static final float TAPS[][] =
|
||||
{
|
||||
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
|
||||
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
|
||||
@@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
||||
|
||||
private RealSampleListener mListener;
|
||||
|
||||
private DirectGainControl mGainController =
|
||||
new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
|
||||
private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
|
||||
|
||||
private int mFrequencyAdjustmentRequested = 0;
|
||||
private int mFrequencyCorrection = 0;
|
||||
@@ -258,9 +257,8 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
||||
correction = -mFrequencyCorrectionMaximum;
|
||||
}
|
||||
|
||||
broadcast( new FrequencyChangeEvent(
|
||||
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
|
||||
|
||||
broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
|
||||
|
||||
mFrequencyAdjustmentRequested = 0;
|
||||
}
|
||||
}
|
||||
@@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a frequency change event to the registered listener
|
||||
* @param event
|
||||
*/
|
||||
public void broadcast( FrequencyChangeEvent event )
|
||||
{
|
||||
if( mFrequencyChangeListener != null )
|
||||
@@ -459,7 +461,11 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
||||
mFrequencyChangeListener.receive( event );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the listener for frequency change events.
|
||||
* @param listener
|
||||
*/
|
||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
|
||||
{
|
||||
mFrequencyChangeListener = listener;
|
||||
|
||||
@@ -4,8 +4,10 @@ import alias.AliasList;
|
||||
import instrument.Instrumentable;
|
||||
import module.decode.Decoder;
|
||||
import module.decode.DecoderType;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
import source.tuner.frequency.IFrequencyChangeProvider;
|
||||
|
||||
public abstract class P25Decoder extends Decoder implements Instrumentable
|
||||
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
|
||||
{
|
||||
private P25MessageProcessor mMessageProcessor;
|
||||
|
||||
|
||||
@@ -315,6 +315,8 @@ public class P25DecoderState extends DecoderState
|
||||
*/
|
||||
public void registerCallEvent(P25CallEvent event)
|
||||
{
|
||||
String channel = event.getChannel();
|
||||
|
||||
if(mChannelCallMap.containsKey(event.getChannel()))
|
||||
{
|
||||
P25CallEvent previousEvent = mChannelCallMap.remove(event.getChannel());
|
||||
@@ -419,7 +421,7 @@ public class P25DecoderState extends DecoderState
|
||||
mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(mCurrentChannel)
|
||||
.details((hdu.isEncrypted() ? "ENCRYPTED " : ""))
|
||||
.details((hdu.isEncryptedAudio() ? "ENCRYPTED " : ""))
|
||||
.frequency(mCurrentChannelFrequency)
|
||||
.to(to)
|
||||
.build();
|
||||
@@ -1105,7 +1107,7 @@ public class P25DecoderState extends DecoderState
|
||||
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
|
||||
.aliasList(getAliasList())
|
||||
.channel(gvcu.getChannelA())
|
||||
.details((gvcu.isEncrypted() ? "ENCRYPTED" : ""))
|
||||
.details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
|
||||
.frequency(gvcu.getDownlinkFrequencyA())
|
||||
.to(userA)
|
||||
.build());
|
||||
@@ -1120,7 +1122,7 @@ public class P25DecoderState extends DecoderState
|
||||
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
|
||||
.aliasList(getAliasList())
|
||||
.channel(gvcu.getChannelB())
|
||||
.details((gvcu.isEncrypted() ? "ENCRYPTED" : ""))
|
||||
.details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
|
||||
.frequency(gvcu.getDownlinkFrequencyB())
|
||||
.to(userB)
|
||||
.build());
|
||||
@@ -1149,7 +1151,7 @@ public class P25DecoderState extends DecoderState
|
||||
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
|
||||
.aliasList(getAliasList())
|
||||
.channel(gvcue.getTransmitChannel())
|
||||
.details((gvcue.isEncrypted() ? "ENCRYPTED" : ""))
|
||||
.details((gvcue.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
|
||||
.frequency(gvcue.getDownlinkFrequency())
|
||||
.to(group)
|
||||
.build());
|
||||
@@ -1185,7 +1187,7 @@ public class P25DecoderState extends DecoderState
|
||||
if(mCurrentCallEvent.getDetails() == null)
|
||||
{
|
||||
mCurrentCallEvent.setDetails(
|
||||
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") +
|
||||
(gvcuser.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
|
||||
(gvcuser.isEmergency() ? "EMERGENCY " : ""));
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
@@ -1397,7 +1399,7 @@ public class P25DecoderState extends DecoderState
|
||||
if(mCurrentCallEvent.getDetails() == null)
|
||||
{
|
||||
mCurrentCallEvent.setDetails(
|
||||
(tivcu.isEncrypted() ? "ENCRYPTED " : "") +
|
||||
(tivcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
|
||||
(tivcu.isEmergency() ? "EMERGENCY " : ""));
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
@@ -1480,7 +1482,7 @@ public class P25DecoderState extends DecoderState
|
||||
if(mCurrentCallEvent.getDetails() == null)
|
||||
{
|
||||
mCurrentCallEvent.setDetails(
|
||||
(uuvcu.isEncrypted() ? "ENCRYPTED " : "") +
|
||||
(uuvcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
|
||||
(uuvcu.isEmergency() ? "EMERGENCY " : ""));
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
@@ -1557,6 +1559,7 @@ public class P25DecoderState extends DecoderState
|
||||
break;
|
||||
case IDENTIFIER_UPDATE_NON_VUHF:
|
||||
case IDENTIFIER_UPDATE_VHF_UHF_BANDS:
|
||||
case IDENTIFIER_UPDATE_TDMA:
|
||||
IdentifierUpdate iu = (IdentifierUpdate)tsbk;
|
||||
|
||||
if(!mBands.containsKey(iu.getIdentifier()))
|
||||
@@ -1710,33 +1713,41 @@ public class P25DecoderState extends DecoderState
|
||||
.build());
|
||||
break;
|
||||
case SNDCP_RF_CONFIRMED_DATA:
|
||||
SNDCPUserData sud = (SNDCPUserData)pduc;
|
||||
|
||||
StringBuilder sbFrom = new StringBuilder();
|
||||
StringBuilder sbTo = new StringBuilder();
|
||||
|
||||
sbFrom.append(sud.getSourceIPAddress());
|
||||
sbTo.append(sud.getDestinationIPAddress());
|
||||
|
||||
if(sud.getIPProtocol() == IPProtocol.UDP)
|
||||
if(pduc instanceof SNDCPUserData)
|
||||
{
|
||||
sbFrom.append(":");
|
||||
sbFrom.append(sud.getUDPSourcePort());
|
||||
sbTo.append(":");
|
||||
sbTo.append(sud.getUDPDestinationPort());
|
||||
SNDCPUserData sud = (SNDCPUserData)pduc;
|
||||
|
||||
StringBuilder sbFrom = new StringBuilder();
|
||||
StringBuilder sbTo = new StringBuilder();
|
||||
|
||||
sbFrom.append(sud.getSourceIPAddress());
|
||||
sbTo.append(sud.getDestinationIPAddress());
|
||||
|
||||
if(sud.getIPProtocol() == IPProtocol.UDP)
|
||||
{
|
||||
sbFrom.append(":");
|
||||
sbFrom.append(sud.getUDPSourcePort());
|
||||
sbTo.append(":");
|
||||
sbTo.append(sud.getUDPDestinationPort());
|
||||
}
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(mCurrentChannel)
|
||||
.details("DATA: " + sud.getPayload() +
|
||||
" RADIO IP:" + sbTo.toString())
|
||||
.frequency(mCurrentChannelFrequency)
|
||||
.from(sbFrom.toString())
|
||||
.to(pduc.getLogicalLinkID())
|
||||
.build());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error - expected SNDCPUserData instance but class was: " + pduc.getClass() +
|
||||
" and PDU Type:" + pduc.getPDUType().name());
|
||||
}
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
|
||||
|
||||
broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(mCurrentChannel)
|
||||
.details("DATA: " + sud.getPayload() +
|
||||
" RADIO IP:" + sbTo.toString())
|
||||
.frequency(mCurrentChannelFrequency)
|
||||
.from(sbFrom.toString())
|
||||
.to(pduc.getLogicalLinkID())
|
||||
.build());
|
||||
break;
|
||||
case SNDCP_RF_UNCONFIRMED_DATA:
|
||||
break;
|
||||
@@ -2735,6 +2746,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(pgvcg.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(pgvcg.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(pgvcg.getPriority());
|
||||
details.append(pgvcg.isEncryptedChannel() ? " ENCRYPTED" : "");
|
||||
@@ -2742,7 +2754,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (pgvcg.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(pgvcg.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -2775,8 +2787,9 @@ public class P25DecoderState extends DecoderState
|
||||
{
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (gvcgu.isTDMAChannel1() ? " TDMA" : ""))
|
||||
.details((gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
|
||||
.channel(channel)
|
||||
.details((gvcgu.isTDMAChannel1() ? "TDMA " : "") +
|
||||
(gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
|
||||
.frequency(gvcgu.getDownlinkFrequency1())
|
||||
.to(to)
|
||||
.build();
|
||||
@@ -2801,8 +2814,9 @@ public class P25DecoderState extends DecoderState
|
||||
{
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel2 + (gvcgu.isTDMAChannel2() ? " TDMA" : ""))
|
||||
.details((gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
|
||||
.channel(channel2)
|
||||
.details((gvcgu.isTDMAChannel2() ? "TDMA " : "") +
|
||||
(gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
|
||||
.frequency(gvcgu.getDownlinkFrequency2())
|
||||
.to(to2)
|
||||
.build();
|
||||
@@ -2866,6 +2880,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(gdcg.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(gdcg.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(gdcg.getPriority()).append(" ");
|
||||
details.append(gdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -2873,7 +2888,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (gdcg.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(gdcg.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -2904,6 +2919,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(gvcg.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(gvcg.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(gvcg.getPriority()).append(" ");
|
||||
details.append(gvcg.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -2911,7 +2927,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (gvcg.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(gvcg.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -2928,8 +2944,7 @@ public class P25DecoderState extends DecoderState
|
||||
}
|
||||
break;
|
||||
case GROUP_VOICE_CHANNEL_GRANT_UPDATE:
|
||||
GroupVoiceChannelGrantUpdate gvcgu =
|
||||
(GroupVoiceChannelGrantUpdate)message;
|
||||
GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)message;
|
||||
|
||||
channel = gvcgu.getChannel1();
|
||||
from = null;
|
||||
@@ -2942,6 +2957,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(gvcgu.getPriority()).append(" ");
|
||||
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -2949,7 +2965,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(gvcgu.getChannel1() + (gvcgu.isTDMAChannel1() ? " TDMA" : ""))
|
||||
.channel(gvcgu.getChannel1())
|
||||
.details(details.toString())
|
||||
.frequency(gvcgu.getDownlinkFrequency1())
|
||||
.from(from)
|
||||
@@ -2977,6 +2993,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(gvcgu.getPriority()).append(" ");
|
||||
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -2984,7 +3001,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
P25CallEvent event2 = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(gvcgu.getChannel2() + (gvcgu.isTDMAChannel2() ? " TDMA" : ""))
|
||||
.channel(gvcgu.getChannel2())
|
||||
.details(details.toString())
|
||||
.frequency(gvcgu.getDownlinkFrequency2())
|
||||
.to(gvcgu.getGroupAddress2())
|
||||
@@ -3016,6 +3033,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(gvcgue.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(gvcgue.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(gvcgue.getPriority()).append(" ");
|
||||
details.append(gvcgue.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -3023,7 +3041,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (gvcgue.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(gvcgue.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -3053,6 +3071,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(idcg.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(idcg.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(idcg.getPriority()).append(" ");
|
||||
details.append(idcg.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -3060,7 +3079,7 @@ public class P25DecoderState extends DecoderState
|
||||
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (idcg.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(idcg.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -3090,13 +3109,14 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(sdcg.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(sdcg.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append(sdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
details.append(sdcg.getSessionMode().name());
|
||||
|
||||
P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (sdcg.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(sdcg.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -3128,6 +3148,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(tivcg.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(tivcg.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(tivcg.getPriority()).append(" ");
|
||||
details.append(tivcg.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -3137,7 +3158,7 @@ public class P25DecoderState extends DecoderState
|
||||
P25CallEvent event = new P25CallEvent.Builder(
|
||||
CallEventType.TELEPHONE_INTERCONNECT)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (tivcg.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(tivcg.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -3170,6 +3191,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(tivcgu.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(tivcgu.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(tivcgu.getPriority()).append(" ");
|
||||
details.append(tivcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -3179,7 +3201,7 @@ public class P25DecoderState extends DecoderState
|
||||
P25CallEvent event = new P25CallEvent.Builder(
|
||||
CallEventType.TELEPHONE_INTERCONNECT)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (tivcgu.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(tivcgu.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -3209,6 +3231,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(uuvcg.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(uuvcg.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(uuvcg.getPriority()).append(" ");
|
||||
details.append(uuvcg.isEncryptedChannel() ? " ENCRYPTED " : "");
|
||||
@@ -3217,7 +3240,7 @@ public class P25DecoderState extends DecoderState
|
||||
P25CallEvent event = new P25CallEvent.Builder(
|
||||
CallEventType.UNIT_TO_UNIT_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (uuvcg.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(uuvcg.getDownlinkFrequency())
|
||||
.from(from)
|
||||
@@ -3247,6 +3270,7 @@ public class P25DecoderState extends DecoderState
|
||||
else
|
||||
{
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append(uuvcgu.isTDMAChannel() ? "TDMA " : "");
|
||||
details.append(uuvcgu.isEmergency() ? "EMERGENCY " : "");
|
||||
details.append("PRI:").append(uuvcgu.getPriority()).append(" ");
|
||||
details.append(uuvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
|
||||
@@ -3255,7 +3279,7 @@ public class P25DecoderState extends DecoderState
|
||||
P25CallEvent event = new P25CallEvent.Builder(
|
||||
CallEventType.UNIT_TO_UNIT_CALL)
|
||||
.aliasList(getAliasList())
|
||||
.channel(channel + (uuvcgu.isTDMAChannel() ? " TDMA" : ""))
|
||||
.channel(channel)
|
||||
.details(details.toString())
|
||||
.frequency(uuvcgu.getDownlinkFrequency())
|
||||
.from(from)
|
||||
|
||||
@@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
private LSMDemodulator mDemodulator;
|
||||
private double mCorrection;
|
||||
|
||||
public CostasPhaseErrorDetector( FrameSync frameSync,
|
||||
LSMDemodulator demodulator, double correction )
|
||||
public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction )
|
||||
{
|
||||
super( frameSync.getSync() );
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 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/>
|
||||
* 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;
|
||||
|
||||
@@ -41,8 +42,7 @@ import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||
|
||||
public class P25_C4FMDecoder extends P25Decoder
|
||||
implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener
|
||||
public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import dsp.gain.ComplexFeedForwardGainControl;
|
||||
import dsp.psk.LSMDemodulator;
|
||||
import dsp.psk.QPSKPolarSlicer;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
|
||||
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||
{
|
||||
@@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
|
||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||
private ComplexBufferToStreamConverter mStreamConverter =
|
||||
new ComplexBufferToStreamConverter();
|
||||
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||
private ComplexFeedForwardGainControl mAGC =
|
||||
new ComplexFeedForwardGainControl( 32 );
|
||||
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
||||
@@ -107,8 +107,31 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
return new Listener<FrequencyChangeEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return mBasebandFilter;
|
||||
|
||||
@@ -12,6 +12,9 @@ import jmbe.iface.AudioConverter;
|
||||
import message.IMessageListener;
|
||||
import message.Message;
|
||||
import module.Module;
|
||||
import module.decode.p25.message.hdu.HDUMessage;
|
||||
import module.decode.p25.message.ldu.LDU1Message;
|
||||
import module.decode.p25.message.ldu.LDU2Message;
|
||||
import module.decode.p25.message.ldu.LDUMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -25,14 +28,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
|
||||
|
||||
private static final String IMBE_CODEC = "IMBE";
|
||||
|
||||
private static boolean mLibraryLoadStatusLogged = false;
|
||||
|
||||
private boolean mCanConvertAudio = false;
|
||||
private boolean mEncryptedCall = false;
|
||||
private boolean mEncryptedCallStateEstablished = false;
|
||||
|
||||
private AudioConverter mAudioConverter;
|
||||
private Listener<AudioPacket> mAudioPacketListener;
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
|
||||
private Metadata mMetadata;
|
||||
private LDU1Message mCachedLDU1Message = null;
|
||||
|
||||
public P25AudioModule(Metadata metadata)
|
||||
{
|
||||
@@ -76,32 +83,73 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary processing method for p25 imbe audio frame messages. Each LDU
|
||||
* audio message contains 9 imbe voice frames. Each frame is converted to
|
||||
* audio when the JMBE library is loaded, processed by auto-gain, and
|
||||
* broadcast to the audio packet listener.
|
||||
* Processes call header (HDU) and voice frame (LDU1/LDU2) messages to decode audio and to determine the
|
||||
* encrypted audio status of a call event. Only the HDU and LDU2 messages convey encrypted call status. If an
|
||||
* LDU1 message is received without a preceding HDU message, then the LDU1 message is cached until the first
|
||||
* LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are
|
||||
* then processed for audio if the call is unencrypted.
|
||||
*/
|
||||
public void receive(Message message)
|
||||
{
|
||||
if (mCanConvertAudio && mAudioPacketListener != null)
|
||||
if(mCanConvertAudio && mAudioPacketListener != null)
|
||||
{
|
||||
if (message instanceof LDUMessage)
|
||||
if(mEncryptedCallStateEstablished)
|
||||
{
|
||||
LDUMessage ldu = (LDUMessage) message;
|
||||
|
||||
/* Only process unencrypted audio frames */
|
||||
if (!(ldu.isValid() && ldu.isEncrypted()))
|
||||
if(message instanceof LDUMessage)
|
||||
{
|
||||
for (byte[] frame : ldu.getIMBEFrames())
|
||||
{
|
||||
float[] audio = mAudioConverter.decode(frame);
|
||||
|
||||
audio = mGain.apply(audio);
|
||||
|
||||
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
|
||||
}
|
||||
processAudio((LDUMessage)message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(message instanceof HDUMessage)
|
||||
{
|
||||
mEncryptedCallStateEstablished = true;
|
||||
mEncryptedCall = ((HDUMessage)message).isEncryptedAudio();
|
||||
}
|
||||
else if(message instanceof LDU1Message)
|
||||
{
|
||||
//When we receive an LDU1 message with first receiving the HDU message, cache the LDU1 Message
|
||||
//until we can determine the encrypted call state from the next LDU2 message
|
||||
mCachedLDU1Message = (LDU1Message)message;
|
||||
}
|
||||
else if(message instanceof LDU2Message)
|
||||
{
|
||||
mEncryptedCallStateEstablished = true;
|
||||
LDU2Message ldu2 = (LDU2Message)message;
|
||||
mEncryptedCall = ldu2.isEncryptedAudio();
|
||||
|
||||
if(mCachedLDU1Message != null)
|
||||
{
|
||||
processAudio(mCachedLDU1Message);
|
||||
mCachedLDU1Message = null;
|
||||
}
|
||||
|
||||
processAudio(ldu2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets.
|
||||
*/
|
||||
private void processAudio(LDUMessage ldu)
|
||||
{
|
||||
if(!mEncryptedCall)
|
||||
{
|
||||
for(byte[] frame : ldu.getIMBEFrames())
|
||||
{
|
||||
float[] audio = mAudioConverter.decode(frame);
|
||||
|
||||
audio = mGain.apply(audio);
|
||||
|
||||
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Encrypted audio processing not implemented
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,18 +167,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class temp = Class.forName("jmbe.JMBEAudioLibrary");
|
||||
|
||||
library = (AudioConversionLibrary) temp.newInstance();
|
||||
library = (AudioConversionLibrary)temp.newInstance();
|
||||
|
||||
if ((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 &&
|
||||
library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1)
|
||||
if((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 &&
|
||||
library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1)
|
||||
{
|
||||
mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
|
||||
|
||||
if (mAudioConverter != null)
|
||||
if(mAudioConverter != null)
|
||||
{
|
||||
mCanConvertAudio = true;
|
||||
|
||||
if (!mLibraryLoadStatusLogged)
|
||||
if(!mLibraryLoadStatusLogged)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("JMBE audio conversion library [");
|
||||
@@ -144,7 +192,7 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mLibraryLoadStatusLogged)
|
||||
if(!mLibraryLoadStatusLogged)
|
||||
{
|
||||
mLog.info("JMBE audio conversion library NOT FOUND");
|
||||
mLibraryLoadStatusLogged = true;
|
||||
@@ -156,28 +204,28 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
|
||||
mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion());
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e1)
|
||||
catch(ClassNotFoundException e1)
|
||||
{
|
||||
if (!mLibraryLoadStatusLogged)
|
||||
if(!mLibraryLoadStatusLogged)
|
||||
{
|
||||
mLog.error("Couldn't find/load JMBE audio conversion library");
|
||||
mLibraryLoadStatusLogged = true;
|
||||
}
|
||||
}
|
||||
catch (InstantiationException e1)
|
||||
catch(InstantiationException e1)
|
||||
{
|
||||
if (!mLibraryLoadStatusLogged)
|
||||
if(!mLibraryLoadStatusLogged)
|
||||
{
|
||||
mLog.error("Couldn't instantiate JMBE audio conversion library class");
|
||||
mLibraryLoadStatusLogged = true;
|
||||
}
|
||||
}
|
||||
catch (IllegalAccessException e1)
|
||||
catch(IllegalAccessException e1)
|
||||
{
|
||||
if (!mLibraryLoadStatusLogged)
|
||||
if(!mLibraryLoadStatusLogged)
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library due to "
|
||||
+ "security restrictions");
|
||||
+ "security restrictions");
|
||||
mLibraryLoadStatusLogged = true;
|
||||
}
|
||||
}
|
||||
@@ -196,19 +244,25 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for squelch state listener. Internally, the P25 audio module
|
||||
* doesn't have a squelch state. If there are IMBE audio frames, we have
|
||||
* audio. We use this listener to signal to the recorder manager that the
|
||||
* channel state is resetting and it should end the recording.
|
||||
* Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
|
||||
* flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
|
||||
* END audio packet so that downstream processors like the audio recorder can properly close out a call sequence.
|
||||
*/
|
||||
public class SquelchStateListener implements Listener<SquelchState>
|
||||
{
|
||||
@Override
|
||||
public void receive(SquelchState state)
|
||||
{
|
||||
if (state == SquelchState.SQUELCH && mAudioPacketListener != null)
|
||||
if(state == SquelchState.SQUELCH)
|
||||
{
|
||||
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
|
||||
if(mAudioPacketListener != null)
|
||||
{
|
||||
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
|
||||
}
|
||||
|
||||
mEncryptedCallStateEstablished = false;
|
||||
mEncryptedCall = false;
|
||||
mCachedLDU1Message = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ public class HDUMessage extends P25Message
|
||||
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
|
||||
}
|
||||
|
||||
public boolean isEncrypted()
|
||||
public boolean isEncryptedAudio()
|
||||
{
|
||||
return getEncryption() != Encryption.UNENCRYPTED;
|
||||
}
|
||||
|
||||
@@ -1,244 +1,246 @@
|
||||
package module.decode.p25.message.ldu;
|
||||
|
||||
import module.decode.p25.reference.DataUnitID;
|
||||
import module.decode.p25.reference.LinkControlOpcode;
|
||||
import module.decode.p25.reference.Vendor;
|
||||
import module.decode.p25.reference.VendorLinkControlOpcode;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import alias.AliasList;
|
||||
import bits.BinaryMessage;
|
||||
import edac.CRC;
|
||||
import edac.Hamming10;
|
||||
import edac.ReedSolomon_63_47_17;
|
||||
import module.decode.p25.reference.DataUnitID;
|
||||
import module.decode.p25.reference.LinkControlOpcode;
|
||||
import module.decode.p25.reference.Vendor;
|
||||
import module.decode.p25.reference.VendorLinkControlOpcode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LDU1Message extends LDUMessage
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( LDU1Message.class );
|
||||
|
||||
public static final int ENCRYPTION_FLAG = 352;
|
||||
public static final int IMPLICIT_VENDOR_FLAG = 353;
|
||||
public static final int[] OPCODE = { 354,355,356,357,362,363 };
|
||||
public static final int[] VENDOR = { 364,365,366,367,372,373,374,375 };
|
||||
|
||||
public static final int[] GOLAY_WORD_STARTS = {
|
||||
352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088,
|
||||
1098,1108,1118,1272,1282,1292,1302 };
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class);
|
||||
|
||||
public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 };
|
||||
public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 };
|
||||
public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 };
|
||||
public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 };
|
||||
public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 };
|
||||
public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 };
|
||||
public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 };
|
||||
public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 };
|
||||
public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 };
|
||||
public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 };
|
||||
public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 };
|
||||
public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 };
|
||||
public static final int[] RS_HEX_0 = { 904,905,906,907,908,909 };
|
||||
public static final int[] RS_HEX_1 = { 914,915,916,917,918,919 };
|
||||
public static final int[] RS_HEX_2 = { 924,925,926,927,928,929 };
|
||||
public static final int[] RS_HEX_3 = { 934,935,936,937,938,939 };
|
||||
public static final int[] RS_HEX_4 = { 1088,1089,1090,1091,1092,1093 };
|
||||
public static final int[] RS_HEX_5 = { 1098,1099,1100,1101,1102,1103 };
|
||||
public static final int[] RS_HEX_6 = { 1108,1109,1110,1111,1112,1113 };
|
||||
public static final int[] RS_HEX_7 = { 1118,1119,1120,1121,1122,1123 };
|
||||
public static final int[] RS_HEX_8 = { 1272,1273,1274,1275,1276,1277 };
|
||||
public static final int[] RS_HEX_9 = { 1282,1283,1284,1285,1286,1287 };
|
||||
public static final int[] RS_HEX_10 = { 1292,1293,1294,1295,1296,1297 };
|
||||
public static final int[] RS_HEX_11 = { 1302,1303,1304,1305,1306,1307 };
|
||||
public static final int ENCRYPTED_LINK_CONTROL_WORD_FLAG = 352;
|
||||
public static final int IMPLICIT_VENDOR_FLAG = 353;
|
||||
public static final int[] OPCODE = {354, 355, 356, 357, 362, 363};
|
||||
public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375};
|
||||
|
||||
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum
|
||||
* correctable errors are: Hamming Distance(13) / 2 = 6 */
|
||||
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
|
||||
new ReedSolomon_63_47_17( 6 );
|
||||
public static final int[] GOLAY_WORD_STARTS = {
|
||||
352, 362, 372, 382, 536, 546, 556, 566, 720, 730, 740, 750, 904, 914, 924, 934, 1088,
|
||||
1098, 1108, 1118, 1272, 1282, 1292, 1302};
|
||||
|
||||
public LDU1Message( BinaryMessage message, DataUnitID duid,
|
||||
AliasList aliasList )
|
||||
public static final int[] CW_HEX_0 = {352, 353, 354, 355, 356, 357};
|
||||
public static final int[] CW_HEX_1 = {362, 363, 364, 365, 366, 367};
|
||||
public static final int[] CW_HEX_2 = {372, 373, 374, 375, 376, 377};
|
||||
public static final int[] CW_HEX_3 = {382, 383, 384, 385, 386, 387};
|
||||
public static final int[] CW_HEX_4 = {536, 537, 538, 539, 540, 541};
|
||||
public static final int[] CW_HEX_5 = {546, 547, 548, 549, 550, 551};
|
||||
public static final int[] CW_HEX_6 = {556, 557, 558, 559, 560, 561};
|
||||
public static final int[] CW_HEX_7 = {566, 567, 568, 569, 570, 571};
|
||||
public static final int[] CW_HEX_8 = {720, 721, 722, 723, 724, 725};
|
||||
public static final int[] CW_HEX_9 = {730, 731, 732, 733, 734, 735};
|
||||
public static final int[] CW_HEX_10 = {740, 741, 742, 743, 744, 745};
|
||||
public static final int[] CW_HEX_11 = {750, 751, 752, 753, 754, 755};
|
||||
public static final int[] RS_HEX_0 = {904, 905, 906, 907, 908, 909};
|
||||
public static final int[] RS_HEX_1 = {914, 915, 916, 917, 918, 919};
|
||||
public static final int[] RS_HEX_2 = {924, 925, 926, 927, 928, 929};
|
||||
public static final int[] RS_HEX_3 = {934, 935, 936, 937, 938, 939};
|
||||
public static final int[] RS_HEX_4 = {1088, 1089, 1090, 1091, 1092, 1093};
|
||||
public static final int[] RS_HEX_5 = {1098, 1099, 1100, 1101, 1102, 1103};
|
||||
public static final int[] RS_HEX_6 = {1108, 1109, 1110, 1111, 1112, 1113};
|
||||
public static final int[] RS_HEX_7 = {1118, 1119, 1120, 1121, 1122, 1123};
|
||||
public static final int[] RS_HEX_8 = {1272, 1273, 1274, 1275, 1276, 1277};
|
||||
public static final int[] RS_HEX_9 = {1282, 1283, 1284, 1285, 1286, 1287};
|
||||
public static final int[] RS_HEX_10 = {1292, 1293, 1294, 1295, 1296, 1297};
|
||||
public static final int[] RS_HEX_11 = {1302, 1303, 1304, 1305, 1306, 1307};
|
||||
|
||||
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum
|
||||
* correctable errors are: Hamming Distance(13) / 2 = 6 */
|
||||
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
|
||||
new ReedSolomon_63_47_17(6);
|
||||
|
||||
public LDU1Message(BinaryMessage message, DataUnitID duid,
|
||||
AliasList aliasList)
|
||||
{
|
||||
super( message, duid, aliasList );
|
||||
super(message, duid, aliasList);
|
||||
|
||||
|
||||
/* NID CRC is checked in the message framer, thus a constructed message
|
||||
* means it passed the CRC */
|
||||
mCRC = new CRC[ 3 ];
|
||||
mCRC[ 0 ] = CRC.PASSED;
|
||||
|
||||
checkCRC();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass constructor from an existing LDU1Message
|
||||
*/
|
||||
protected LDU1Message( LDU1Message message )
|
||||
{
|
||||
super( message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList() );
|
||||
|
||||
mCRC = message.mCRC;
|
||||
}
|
||||
* means it passed the CRC */
|
||||
mCRC = new CRC[3];
|
||||
mCRC[0] = CRC.PASSED;
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted()
|
||||
{
|
||||
return mMessage.get( ENCRYPTION_FLAG );
|
||||
}
|
||||
|
||||
private void checkCRC()
|
||||
{
|
||||
mCRC[ 1 ] = CRC.PASSED;
|
||||
checkCRC();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass constructor from an existing LDU1Message
|
||||
*/
|
||||
protected LDU1Message(LDU1Message message)
|
||||
{
|
||||
super(message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList());
|
||||
|
||||
mCRC = message.mCRC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the contents of the Link Control Word are encrypted.
|
||||
*
|
||||
* @return true if the contents of the LCW are encrypted.
|
||||
*/
|
||||
public boolean isEncryptedLinkControlWord()
|
||||
{
|
||||
return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_FLAG);
|
||||
}
|
||||
|
||||
private void checkCRC()
|
||||
{
|
||||
mCRC[1] = CRC.PASSED;
|
||||
|
||||
/* Hamming( 10,6,3 ) error detection and correction */
|
||||
for( int index: GOLAY_WORD_STARTS )
|
||||
{
|
||||
int errors = Hamming10.checkAndCorrect( mMessage, index );
|
||||
|
||||
if( errors > 1 )
|
||||
{
|
||||
mCRC[ 1 ] = CRC.FAILED_CRC;
|
||||
}
|
||||
}
|
||||
for(int index : GOLAY_WORD_STARTS)
|
||||
{
|
||||
int errors = Hamming10.checkAndCorrect(mMessage, index);
|
||||
|
||||
if(errors > 1)
|
||||
{
|
||||
mCRC[1] = CRC.FAILED_CRC;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reed-Solomon( 24,16,9 ) error detection and correction
|
||||
* Check the Reed-Solomon parity bits. The RS decoder expects the code
|
||||
* words and reed solomon parity hex codewords in reverse order.
|
||||
*
|
||||
* Since this is a truncated RS(63) codes, we pad the code with zeros */
|
||||
int[] input = new int [63];
|
||||
int[] output = new int [63];
|
||||
|
||||
input[ 0 ] = mMessage.getInt( RS_HEX_11 );
|
||||
input[ 1 ] = mMessage.getInt( RS_HEX_10 );
|
||||
input[ 2 ] = mMessage.getInt( RS_HEX_9 );
|
||||
input[ 3 ] = mMessage.getInt( RS_HEX_8 );
|
||||
input[ 4 ] = mMessage.getInt( RS_HEX_7 );
|
||||
input[ 5 ] = mMessage.getInt( RS_HEX_6 );
|
||||
input[ 6 ] = mMessage.getInt( RS_HEX_5 );
|
||||
input[ 7 ] = mMessage.getInt( RS_HEX_4 );
|
||||
input[ 8 ] = mMessage.getInt( RS_HEX_3 );
|
||||
input[ 9 ] = mMessage.getInt( RS_HEX_2 );
|
||||
input[ 10 ] = mMessage.getInt( RS_HEX_1 );
|
||||
input[ 11 ] = mMessage.getInt( RS_HEX_0 );
|
||||
|
||||
input[ 12 ] = mMessage.getInt( CW_HEX_11 );
|
||||
input[ 13 ] = mMessage.getInt( CW_HEX_10 );
|
||||
input[ 14 ] = mMessage.getInt( CW_HEX_9 );
|
||||
input[ 15 ] = mMessage.getInt( CW_HEX_8 );
|
||||
input[ 16 ] = mMessage.getInt( CW_HEX_7 );
|
||||
input[ 17 ] = mMessage.getInt( CW_HEX_6 );
|
||||
input[ 18 ] = mMessage.getInt( CW_HEX_5 );
|
||||
input[ 19 ] = mMessage.getInt( CW_HEX_4 );
|
||||
input[ 20 ] = mMessage.getInt( CW_HEX_3 );
|
||||
input[ 21 ] = mMessage.getInt( CW_HEX_2 );
|
||||
input[ 22 ] = mMessage.getInt( CW_HEX_1 );
|
||||
input[ 23 ] = mMessage.getInt( CW_HEX_0 );
|
||||
int[] input = new int[63];
|
||||
int[] output = new int[63];
|
||||
|
||||
input[0] = mMessage.getInt(RS_HEX_11);
|
||||
input[1] = mMessage.getInt(RS_HEX_10);
|
||||
input[2] = mMessage.getInt(RS_HEX_9);
|
||||
input[3] = mMessage.getInt(RS_HEX_8);
|
||||
input[4] = mMessage.getInt(RS_HEX_7);
|
||||
input[5] = mMessage.getInt(RS_HEX_6);
|
||||
input[6] = mMessage.getInt(RS_HEX_5);
|
||||
input[7] = mMessage.getInt(RS_HEX_4);
|
||||
input[8] = mMessage.getInt(RS_HEX_3);
|
||||
input[9] = mMessage.getInt(RS_HEX_2);
|
||||
input[10] = mMessage.getInt(RS_HEX_1);
|
||||
input[11] = mMessage.getInt(RS_HEX_0);
|
||||
|
||||
input[12] = mMessage.getInt(CW_HEX_11);
|
||||
input[13] = mMessage.getInt(CW_HEX_10);
|
||||
input[14] = mMessage.getInt(CW_HEX_9);
|
||||
input[15] = mMessage.getInt(CW_HEX_8);
|
||||
input[16] = mMessage.getInt(CW_HEX_7);
|
||||
input[17] = mMessage.getInt(CW_HEX_6);
|
||||
input[18] = mMessage.getInt(CW_HEX_5);
|
||||
input[19] = mMessage.getInt(CW_HEX_4);
|
||||
input[20] = mMessage.getInt(CW_HEX_3);
|
||||
input[21] = mMessage.getInt(CW_HEX_2);
|
||||
input[22] = mMessage.getInt(CW_HEX_1);
|
||||
input[23] = mMessage.getInt(CW_HEX_0);
|
||||
/* indexes 24 - 62 are defaulted to zero */
|
||||
|
||||
boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output );
|
||||
boolean irrecoverableErrors = mReedSolomonDecoder.decode(input, output);
|
||||
|
||||
if( irrecoverableErrors )
|
||||
if(irrecoverableErrors)
|
||||
{
|
||||
mCRC[ 2 ] = CRC.FAILED_CRC;
|
||||
mCRC[2] = CRC.FAILED_CRC;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Since we've detected that we can correct the RS words, mark the
|
||||
* hamming crc as corrected as well, if it is currently failed */
|
||||
if( mCRC[ 1 ] == CRC.FAILED_CRC )
|
||||
{
|
||||
mCRC[ 1 ] = CRC.CORRECTED;
|
||||
}
|
||||
|
||||
mCRC[ 2 ] = CRC.PASSED;
|
||||
if(mCRC[1] == CRC.FAILED_CRC)
|
||||
{
|
||||
mCRC[1] = CRC.CORRECTED;
|
||||
}
|
||||
|
||||
mCRC[2] = CRC.PASSED;
|
||||
|
||||
/* Only fix the codeword data hex codewords */
|
||||
repairHexCodeword( input, output, 12, CW_HEX_11 );
|
||||
repairHexCodeword( input, output, 13, CW_HEX_10 );
|
||||
repairHexCodeword( input, output, 14, CW_HEX_9 );
|
||||
repairHexCodeword( input, output, 15, CW_HEX_8 );
|
||||
repairHexCodeword( input, output, 16, CW_HEX_7 );
|
||||
repairHexCodeword( input, output, 17, CW_HEX_6 );
|
||||
repairHexCodeword( input, output, 18, CW_HEX_5 );
|
||||
repairHexCodeword( input, output, 19, CW_HEX_4 );
|
||||
repairHexCodeword( input, output, 20, CW_HEX_3 );
|
||||
repairHexCodeword( input, output, 21, CW_HEX_2 );
|
||||
repairHexCodeword( input, output, 22, CW_HEX_1 );
|
||||
repairHexCodeword( input, output, 23, CW_HEX_0 );
|
||||
repairHexCodeword(input, output, 12, CW_HEX_11);
|
||||
repairHexCodeword(input, output, 13, CW_HEX_10);
|
||||
repairHexCodeword(input, output, 14, CW_HEX_9);
|
||||
repairHexCodeword(input, output, 15, CW_HEX_8);
|
||||
repairHexCodeword(input, output, 16, CW_HEX_7);
|
||||
repairHexCodeword(input, output, 17, CW_HEX_6);
|
||||
repairHexCodeword(input, output, 18, CW_HEX_5);
|
||||
repairHexCodeword(input, output, 19, CW_HEX_4);
|
||||
repairHexCodeword(input, output, 20, CW_HEX_3);
|
||||
repairHexCodeword(input, output, 21, CW_HEX_2);
|
||||
repairHexCodeword(input, output, 22, CW_HEX_1);
|
||||
repairHexCodeword(input, output, 23, CW_HEX_0);
|
||||
}
|
||||
}
|
||||
|
||||
private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet )
|
||||
{
|
||||
if( input[ index ] != output[ index ] )
|
||||
{
|
||||
mMessage.load( indexSet[ 0 ], 6, output[ index ] );
|
||||
mCRC[ 2 ] = CRC.CORRECTED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( getMessageStub() );
|
||||
sb.append( " " );
|
||||
sb.append( mMessage.toString() );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getMessageStub()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( super.getMessageStub() );
|
||||
|
||||
if( isEncrypted() )
|
||||
{
|
||||
sb.append( "ENCRYPTED" );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( isImplicitFormat() || getVendor() == Vendor.STANDARD )
|
||||
{
|
||||
sb.append( getOpcode().getLabel() );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "VENDOR:" + getVendor().getLabel() + " " );
|
||||
sb.append( getVendorOpcode().getLabel() );
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isImplicitFormat()
|
||||
{
|
||||
return mMessage.get( IMPLICIT_VENDOR_FLAG );
|
||||
}
|
||||
|
||||
public LinkControlOpcode getOpcode()
|
||||
{
|
||||
return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
|
||||
}
|
||||
private void repairHexCodeword(int[] input, int[] output, int index, int[] indexSet)
|
||||
{
|
||||
if(input[index] != output[index])
|
||||
{
|
||||
mMessage.load(indexSet[0], 6, output[index]);
|
||||
mCRC[2] = CRC.CORRECTED;
|
||||
}
|
||||
}
|
||||
|
||||
public VendorLinkControlOpcode getVendorOpcode()
|
||||
{
|
||||
return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
|
||||
}
|
||||
|
||||
public Vendor getVendor()
|
||||
{
|
||||
if( isImplicitFormat() )
|
||||
{
|
||||
return Vendor.STANDARD;
|
||||
}
|
||||
|
||||
return Vendor.fromValue( mMessage.getInt( VENDOR ) );
|
||||
}
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(getMessageStub());
|
||||
sb.append(" ");
|
||||
sb.append(mMessage.toString());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getMessageStub()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(super.getMessageStub());
|
||||
|
||||
if(isEncryptedLinkControlWord())
|
||||
{
|
||||
sb.append("ENCRYPTED");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(isImplicitFormat() || getVendor() == Vendor.STANDARD)
|
||||
{
|
||||
sb.append(getOpcode().getLabel());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("VENDOR:" + getVendor().getLabel() + " ");
|
||||
sb.append(getVendorOpcode().getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isImplicitFormat()
|
||||
{
|
||||
return mMessage.get(IMPLICIT_VENDOR_FLAG);
|
||||
}
|
||||
|
||||
public LinkControlOpcode getOpcode()
|
||||
{
|
||||
return LinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
|
||||
}
|
||||
|
||||
public VendorLinkControlOpcode getVendorOpcode()
|
||||
{
|
||||
return VendorLinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
|
||||
}
|
||||
|
||||
public Vendor getVendor()
|
||||
{
|
||||
if(isImplicitFormat())
|
||||
{
|
||||
return Vendor.STANDARD;
|
||||
}
|
||||
|
||||
return Vendor.fromValue(mMessage.getInt(VENDOR));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,11 @@ public class LDU2Message extends LDUMessage
|
||||
|
||||
checkCRC();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted()
|
||||
|
||||
/**
|
||||
* Indicates if the audio stream is encrypted
|
||||
*/
|
||||
public boolean isEncryptedAudio()
|
||||
{
|
||||
Encryption encryption = getEncryption();
|
||||
|
||||
|
||||
@@ -35,8 +35,6 @@ public abstract class LDUMessage extends P25Message
|
||||
super( message, duid, aliasList );
|
||||
}
|
||||
|
||||
public abstract boolean isEncrypted();
|
||||
|
||||
@Override
|
||||
public String getMessage()
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate
|
||||
|
||||
sb.append( " ID:" + getIdentifier() );
|
||||
sb.append( " BASE:" + getBaseFrequency() );
|
||||
sb.append( " BW:" + getBandwidth() );
|
||||
sb.append( " BANDWIDTH:" + getBandwidth() );
|
||||
sb.append( " SPACING:" + getChannelSpacing() );
|
||||
sb.append( " OFFSET:" + getTransmitOffset() );
|
||||
sb.append( " TYPE:" + getChannelType().toString() );
|
||||
@@ -137,7 +137,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate
|
||||
|
||||
sb.append( mAccessType.name() );
|
||||
sb.append( " BANDWIDTH:" + mBandwidth );
|
||||
sb.append( " SLOT/CARRIER:" + mSlotsPerCarrier );
|
||||
sb.append( " TIMESLOTS PER CARRIER:" + mSlotsPerCarrier );
|
||||
sb.append( " VOCODER:" + mVocoder.name() );
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -89,6 +89,7 @@ public class PlaylistManager implements ChannelEventListener
|
||||
@Override
|
||||
public void receive(AliasEvent t)
|
||||
{
|
||||
//Save the playlist for all alias events
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
});
|
||||
@@ -98,6 +99,7 @@ public class PlaylistManager implements ChannelEventListener
|
||||
@Override
|
||||
public void receive(ChannelMapEvent t)
|
||||
{
|
||||
//Save the playlist for all channel map events
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
});
|
||||
@@ -107,7 +109,23 @@ public class PlaylistManager implements ChannelEventListener
|
||||
@Override
|
||||
public void receive(BroadcastEvent broadcastEvent)
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
switch(broadcastEvent.getEvent())
|
||||
{
|
||||
case CONFIGURATION_ADD:
|
||||
case CONFIGURATION_CHANGE:
|
||||
case CONFIGURATION_DELETE:
|
||||
schedulePlaylistSave();
|
||||
break;
|
||||
case BROADCASTER_ADD:
|
||||
case BROADCASTER_QUEUE_CHANGE:
|
||||
case BROADCASTER_STATE_CHANGE:
|
||||
case BROADCASTER_STREAMED_COUNT_CHANGE:
|
||||
case BROADCASTER_AGED_OFF_COUNT_CHANGE:
|
||||
case BROADCASTER_DELETE:
|
||||
default:
|
||||
//Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,87 +25,169 @@ import properties.SystemProperties;
|
||||
import record.wave.ComplexBufferWaveRecorder;
|
||||
import record.wave.RealBufferWaveRecorder;
|
||||
import sample.Listener;
|
||||
import sample.OverflowableTransferQueue;
|
||||
import sample.real.IOverflowListener;
|
||||
import util.ThreadPool;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecorderManager implements Listener<AudioPacket>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class);
|
||||
|
||||
public static final int AUDIO_SAMPLE_RATE = 8000;
|
||||
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
|
||||
|
||||
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();
|
||||
private OverflowableTransferQueue<AudioPacket> mAudioPacketQueue = new OverflowableTransferQueue<>(1000, 100);
|
||||
private ScheduledFuture<?> mBufferProcessorFuture;
|
||||
|
||||
private boolean mCanStartNewRecorders = true;
|
||||
|
||||
/**
|
||||
* Manages all audio recording for all processing channels. Reconstructs audio streams and distributes channel
|
||||
* audio to each of the recorders, starting and stopping the recorders as needed.
|
||||
* Audio recording manager. Monitors stream of audio packets produced by decoding channels and automatically starts
|
||||
* audio recorders when the channel's metadata designates a call as recordable. Routes call audio to each recorder
|
||||
* based on audio packet metadata. Recorders are shutdown when the channel sends an end-call audio packet
|
||||
* indicating that the call is complete. A separate recording monitor periodically checks for idled recorders to
|
||||
* be stopped for cases when the channel fails to send an end-call audio packet.
|
||||
*/
|
||||
public RecorderManager()
|
||||
{
|
||||
mAudioPacketQueue.setOverflowListener(new IOverflowListener()
|
||||
{
|
||||
@Override
|
||||
public void sourceOverflow(boolean overflow)
|
||||
{
|
||||
if(overflow)
|
||||
{
|
||||
mLog.warn("overflow - audio packets will be dropped until recording catches up");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("audio recorder packet processing has returned to normal");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mBufferProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferProcessor(), 0,
|
||||
1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares this class for shutdown
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
if(mBufferProcessorFuture != null)
|
||||
{
|
||||
mBufferProcessorFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary ingest point for audio packets from all decoding channels
|
||||
* @param audioPacket to process
|
||||
*/
|
||||
@Override
|
||||
public void receive(AudioPacket audioPacket)
|
||||
{
|
||||
if(audioPacket.hasMetadata() &&
|
||||
audioPacket.getMetadata().isRecordable())
|
||||
if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable())
|
||||
{
|
||||
String identifier = audioPacket.getMetadata().getUniqueIdentifier();
|
||||
mAudioPacketQueue.offer(audioPacket);
|
||||
}
|
||||
}
|
||||
|
||||
if(mRecorders.containsKey(identifier))
|
||||
/**
|
||||
* Process any queued audio buffers and dispatch them to the audio recorders
|
||||
*/
|
||||
private void processBuffers()
|
||||
{
|
||||
List<AudioPacket> audioPackets = new ArrayList<>();
|
||||
|
||||
mAudioPacketQueue.drainTo(audioPackets, 50);
|
||||
|
||||
while(!audioPackets.isEmpty())
|
||||
{
|
||||
for(AudioPacket audioPacket: audioPackets)
|
||||
{
|
||||
RealBufferWaveRecorder recorder = mRecorders.get(identifier);
|
||||
String identifier = audioPacket.getMetadata().getUniqueIdentifier();
|
||||
|
||||
if(audioPacket.getType() == AudioPacket.Type.AUDIO)
|
||||
if(mRecorders.containsKey(identifier))
|
||||
{
|
||||
recorder.receive(audioPacket.getAudioBuffer());
|
||||
}
|
||||
else if(audioPacket.getType() == AudioPacket.Type.END)
|
||||
{
|
||||
RealBufferWaveRecorder finished = mRecorders.remove(identifier);
|
||||
finished.stop();
|
||||
}
|
||||
}
|
||||
else if(audioPacket.getType() == AudioPacket.Type.AUDIO)
|
||||
{
|
||||
if(mCanStartNewRecorders)
|
||||
{
|
||||
String filePrefix = getFilePrefix(audioPacket);
|
||||
RealBufferWaveRecorder recorder = mRecorders.get(identifier);
|
||||
|
||||
RealBufferWaveRecorder recorder = null;
|
||||
|
||||
try
|
||||
if(audioPacket.getType() == AudioPacket.Type.AUDIO)
|
||||
{
|
||||
recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix);
|
||||
|
||||
recorder.start(ThreadPool.SCHEDULED);
|
||||
|
||||
recorder.receive(audioPacket.getAudioBuffer());
|
||||
mRecorders.put(identifier, recorder);
|
||||
}
|
||||
catch(Exception ioe)
|
||||
else if(audioPacket.getType() == AudioPacket.Type.END)
|
||||
{
|
||||
mCanStartNewRecorders = false;
|
||||
RealBufferWaveRecorder finished = mRecorders.remove(identifier);
|
||||
finished.stop();
|
||||
}
|
||||
}
|
||||
else if(audioPacket.getType() == AudioPacket.Type.AUDIO)
|
||||
{
|
||||
if(mCanStartNewRecorders)
|
||||
{
|
||||
String filePrefix = getFilePrefix(audioPacket);
|
||||
|
||||
mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " +
|
||||
"is disabled", ioe);
|
||||
RealBufferWaveRecorder recorder = null;
|
||||
|
||||
if(recorder != null)
|
||||
try
|
||||
{
|
||||
recorder.stop();
|
||||
recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix);
|
||||
|
||||
recorder.start(ThreadPool.SCHEDULED);
|
||||
|
||||
recorder.receive(audioPacket.getAudioBuffer());
|
||||
mRecorders.put(identifier, recorder);
|
||||
}
|
||||
catch(Exception ioe)
|
||||
{
|
||||
mCanStartNewRecorders = false;
|
||||
|
||||
mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " +
|
||||
"is disabled", ioe);
|
||||
|
||||
if(recorder != null)
|
||||
{
|
||||
recorder.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audioPackets.clear();
|
||||
mAudioPacketQueue.drainTo(audioPackets, 50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes recorders that have not received any new audio buffers in the last 6 seconds.
|
||||
*/
|
||||
private void removeIdleRecorders()
|
||||
{
|
||||
Iterator<Map.Entry<String,RealBufferWaveRecorder>> it = mRecorders.entrySet().iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
Map.Entry<String,RealBufferWaveRecorder> entry = it.next();
|
||||
|
||||
if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis())
|
||||
{
|
||||
mLog.info("Removing idle recorder [" + entry.getKey() + "]");
|
||||
it.remove();
|
||||
entry.getValue().stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,4 +232,18 @@ public class RecorderManager implements Listener<AudioPacket>
|
||||
|
||||
return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes queued audio packets and distributes to each of the audio recorders. Removes any idle recorders
|
||||
* that have not been updated according to an idle threshold period
|
||||
*/
|
||||
public class BufferProcessor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
processBuffers();
|
||||
removeIdleRecorders();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
******************************************************************************/
|
||||
package record.wave;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.ConversionUtils;
|
||||
@@ -58,7 +59,7 @@ public class MonoWaveReader implements AutoCloseable
|
||||
*/
|
||||
public MonoWaveReader(Path path, boolean realTime) throws IOException
|
||||
{
|
||||
assert( path != null );
|
||||
Validate.isTrue(path != null);
|
||||
mPath = path;
|
||||
mRealTime = realTime;
|
||||
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 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/>
|
||||
* 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 record.wave;
|
||||
|
||||
import module.Module;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.ConversionUtils;
|
||||
import sample.Listener;
|
||||
import sample.real.IFilteredRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import util.TimeStamp;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -26,206 +37,175 @@ import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
import module.Module;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.ConversionUtils;
|
||||
import sample.Listener;
|
||||
import sample.real.IFilteredRealBufferListener;
|
||||
import sample.real.RealBuffer;
|
||||
import util.TimeStamp;
|
||||
|
||||
/**
|
||||
* WAVE audio recorder module for recording real sample buffers to a wave file
|
||||
*/
|
||||
public class RealBufferWaveRecorder extends Module
|
||||
implements IFilteredRealBufferListener, Listener<RealBuffer>
|
||||
public class RealBufferWaveRecorder extends Module
|
||||
implements IFilteredRealBufferListener, Listener<RealBuffer>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( RealBufferWaveRecorder.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RealBufferWaveRecorder.class);
|
||||
|
||||
private WaveWriter mWriter;
|
||||
private String mFilePrefix;
|
||||
private Path mFile;
|
||||
private AudioFormat mAudioFormat;
|
||||
|
||||
private BufferProcessor mBufferProcessor;
|
||||
private ScheduledFuture<?> mProcessorHandle;
|
||||
private LinkedBlockingQueue<RealBuffer> mBuffers =
|
||||
new LinkedBlockingQueue<>( 500 );
|
||||
private long mLastBufferReceived;
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
public RealBufferWaveRecorder( int sampleRate, String filePrefix )
|
||||
{
|
||||
mAudioFormat = new AudioFormat( sampleRate, //SampleRate
|
||||
16, //Sample Size
|
||||
1, //Channels
|
||||
true, //Signed
|
||||
false ); //Little Endian
|
||||
private AudioFormat mAudioFormat;
|
||||
|
||||
mFilePrefix = filePrefix;
|
||||
}
|
||||
private BufferProcessor mBufferProcessor;
|
||||
private ScheduledFuture<?> mProcessorHandle;
|
||||
private LinkedBlockingQueue<RealBuffer> mBuffers = new LinkedBlockingQueue<>(500);
|
||||
private long mLastBufferReceived;
|
||||
|
||||
/**
|
||||
* Timestamp of when the latest buffer was received by this recorder
|
||||
*/
|
||||
public long getLastBufferReceived()
|
||||
{
|
||||
return mLastBufferReceived;
|
||||
}
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
public Path getFile()
|
||||
{
|
||||
return mFile;
|
||||
}
|
||||
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
if( mRunning.compareAndSet( false, true ) )
|
||||
{
|
||||
if( mBufferProcessor == null )
|
||||
{
|
||||
mBufferProcessor = new BufferProcessor();
|
||||
}
|
||||
public RealBufferWaveRecorder(int sampleRate, String filePrefix)
|
||||
{
|
||||
mAudioFormat = new AudioFormat(sampleRate, //SampleRate
|
||||
16, //Sample Size
|
||||
1, //Channels
|
||||
true, //Signed
|
||||
false); //Little Endian
|
||||
|
||||
try
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( mFilePrefix );
|
||||
sb.append( "_" );
|
||||
sb.append( TimeStamp.getLongTimeStamp( "_" ) );
|
||||
sb.append( ".wav" );
|
||||
|
||||
mFile = Paths.get( sb.toString() );
|
||||
mFilePrefix = filePrefix;
|
||||
}
|
||||
|
||||
mWriter = new WaveWriter( mAudioFormat, mFile );
|
||||
/**
|
||||
* Indicates if the recorder is currently running.
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of when the latest buffer was received by this recorder
|
||||
*/
|
||||
public long getLastBufferReceived()
|
||||
{
|
||||
return mLastBufferReceived;
|
||||
}
|
||||
|
||||
public Path getFile()
|
||||
{
|
||||
return mFile;
|
||||
}
|
||||
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
if(mBufferProcessor == null)
|
||||
{
|
||||
mBufferProcessor = new BufferProcessor();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(mFilePrefix);
|
||||
sb.append("_");
|
||||
sb.append(TimeStamp.getLongTimeStamp("_"));
|
||||
sb.append(".wav");
|
||||
|
||||
mFile = Paths.get(sb.toString());
|
||||
|
||||
mWriter = new WaveWriter(mAudioFormat, mFile);
|
||||
|
||||
/* Schedule the processor to run every 500 milliseconds */
|
||||
mProcessorHandle = executor.scheduleAtFixedRate(
|
||||
mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
catch( IOException io )
|
||||
{
|
||||
mLog.error( "Error starting real buffer recorder", io );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
/* Signal the buffer processor to end with a poison pill */
|
||||
receive( new PoisonPill() );
|
||||
|
||||
mRunning.set( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( RealBuffer buffer )
|
||||
{
|
||||
if( mRunning.get() )
|
||||
{
|
||||
boolean success = mBuffers.offer( buffer );
|
||||
|
||||
if( !success )
|
||||
{
|
||||
mLog.error( "recorder buffer overflow - purging [" +
|
||||
mFile.toFile().getAbsolutePath() + "]" );
|
||||
|
||||
mBuffers.clear();
|
||||
}
|
||||
|
||||
mLastBufferReceived = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getFilteredRealBufferListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
public class BufferProcessor implements Runnable
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
RealBuffer buffer = mBuffers.poll();
|
||||
|
||||
while( buffer != null )
|
||||
{
|
||||
if( buffer instanceof PoisonPill )
|
||||
{
|
||||
buffer = null;
|
||||
|
||||
mBuffers.clear();
|
||||
|
||||
if( mWriter != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
mWriter.close();
|
||||
}
|
||||
catch( IOException io )
|
||||
{
|
||||
mLog.error( "Error stopping complex wave recorder [" +
|
||||
getFile() + "]", io );
|
||||
}
|
||||
}
|
||||
|
||||
if( mProcessorHandle != null )
|
||||
{
|
||||
mProcessorHandle.cancel( true );
|
||||
}
|
||||
|
||||
mProcessorHandle = null;
|
||||
mWriter = null;
|
||||
mFile = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
mWriter.write( ConversionUtils.convertToSigned16BitSamples( buffer ) );
|
||||
buffer = mBuffers.poll();
|
||||
}
|
||||
}
|
||||
mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch ( IOException ioe )
|
||||
{
|
||||
/* Stop this module if/when we get an IO exception */
|
||||
mBuffers.clear();
|
||||
stop();
|
||||
|
||||
mLog.error( "IOException while trying to write to the wave "
|
||||
+ "writer", ioe );
|
||||
}
|
||||
}
|
||||
catch(IOException io)
|
||||
{
|
||||
mLog.error("Error starting real buffer recorder", io);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
try
|
||||
{
|
||||
write();
|
||||
|
||||
if(mWriter != null)
|
||||
{
|
||||
mWriter.close();
|
||||
mWriter = null;
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error writing final audio buffers to recording during shutdown", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(RealBuffer buffer)
|
||||
{
|
||||
if(mRunning.get())
|
||||
{
|
||||
boolean success = mBuffers.offer(buffer);
|
||||
|
||||
if(!success)
|
||||
{
|
||||
mLog.error("recorder buffer overflow - purging [" + mFile.toFile().getAbsolutePath() + "]");
|
||||
mBuffers.clear();
|
||||
}
|
||||
|
||||
mLastBufferReceived = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getFilteredRealBufferListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes all audio currently in the queue to the file
|
||||
* @throws IOException if there are any errors writing the audio
|
||||
*/
|
||||
private void write() throws IOException
|
||||
{
|
||||
RealBuffer buffer = mBuffers.poll();
|
||||
|
||||
while(buffer != null)
|
||||
{
|
||||
mWriter.write(ConversionUtils.convertToSigned16BitSamples(buffer));
|
||||
buffer = mBuffers.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferProcessor implements Runnable
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
write();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
/* Stop this module if/when we get an IO exception */
|
||||
mBuffers.clear();
|
||||
stop();
|
||||
|
||||
mLog.error("IO Exception while trying to write to the wave writer", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used as a sentinel value to signal the buffer processor to end
|
||||
*/
|
||||
public class PoisonPill extends RealBuffer
|
||||
{
|
||||
public PoisonPill()
|
||||
{
|
||||
super( new float[ 1 ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* 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 record.wave;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
@@ -32,10 +35,7 @@ import javax.sound.sampled.AudioFormat;
|
||||
|
||||
public class WaveWriter implements AutoCloseable
|
||||
{
|
||||
// private final static Logger mLog = LoggerFactory.getLogger( WaveWriter.class );
|
||||
|
||||
private static final Pattern FILENAME_PATTERN =
|
||||
Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
|
||||
private static final Pattern FILENAME_PATTERN = Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
|
||||
public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE;
|
||||
|
||||
private AudioFormat mAudioFormat;
|
||||
@@ -56,11 +56,10 @@ public class WaveWriter implements AutoCloseable
|
||||
* @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes )
|
||||
* @throws IOException - if there are any IO issues
|
||||
*/
|
||||
public WaveWriter( AudioFormat format, Path file, long maxSize )
|
||||
throws IOException
|
||||
public WaveWriter( AudioFormat format, Path file, long maxSize ) throws IOException
|
||||
{
|
||||
assert( format != null );
|
||||
assert( file != null );
|
||||
Validate.isTrue(format != null);
|
||||
Validate.isTrue( file != null );
|
||||
|
||||
mAudioFormat = format;
|
||||
mFile = file;
|
||||
@@ -100,15 +99,11 @@ public class WaveWriter implements AutoCloseable
|
||||
|
||||
while( Files.exists( mFile ) )
|
||||
{
|
||||
mFile = Paths.get( mFile.toFile().getAbsolutePath()
|
||||
.replace( ".wav", "_" + version + ".wav" ) );
|
||||
|
||||
mFile = Paths.get( mFile.toFile().getAbsolutePath().replace( ".wav", "_" + version + ".wav" ) );
|
||||
version++;
|
||||
}
|
||||
|
||||
Files.createFile( mFile );
|
||||
|
||||
mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE ) );
|
||||
mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW ) );
|
||||
|
||||
ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat );
|
||||
|
||||
@@ -127,9 +122,18 @@ public class WaveWriter implements AutoCloseable
|
||||
{
|
||||
mFileChannel.force( true );
|
||||
mFileChannel.close();
|
||||
mFileChannel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@Override
|
||||
protected void finalize() throws IOException
|
||||
{
|
||||
mFileChannel.force( true );
|
||||
mFileChannel.close();
|
||||
mFileChannel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the buffer contents to the file. Assumes that the buffer is full
|
||||
* and the first byte of data is at position 0.
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,11 @@ public class Broadcaster<T> implements Listener<T>
|
||||
|
||||
public void addListener( Listener<T> listener )
|
||||
{
|
||||
if(listener == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Listener cannot be null");
|
||||
}
|
||||
|
||||
mListeners.add( listener );
|
||||
}
|
||||
|
||||
|
||||
@@ -124,10 +124,6 @@ public class OverflowableTransferQueue<E>
|
||||
mQueue.clear();
|
||||
mCounter.set(0);
|
||||
mOverflow.set(false);
|
||||
if(mOverflowListener != null)
|
||||
{
|
||||
mOverflowListener.sourceOverflow(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package sample.adapter;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import source.mixer.MixerChannel;
|
||||
|
||||
/**
|
||||
@@ -34,7 +35,7 @@ public class ChannelShortAdapter implements ISampleAdapter
|
||||
public ChannelShortAdapter( MixerChannel channel )
|
||||
{
|
||||
/* Only use with LEFT/RIGHT channels */
|
||||
assert( channel != MixerChannel.MONO );
|
||||
Validate.isTrue(channel != MixerChannel.MONO);
|
||||
|
||||
mMixerChannel = channel;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
******************************************************************************/
|
||||
package sample.complex;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import sample.Listener;
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,7 @@ public class ComplexToComplexBufferAssembler implements ComplexSampleListener
|
||||
|
||||
public ComplexToComplexBufferAssembler( int bufferSize )
|
||||
{
|
||||
assert( bufferSize % 2 == 0 );
|
||||
Validate.isTrue(bufferSize % 2 == 0);
|
||||
|
||||
mBufferSize = bufferSize;
|
||||
mBuffer = new float[ mBufferSize ];
|
||||
|
||||
@@ -17,14 +17,18 @@
|
||||
******************************************************************************/
|
||||
package source;
|
||||
|
||||
import channel.heartbeat.IHeartbeatProvider;
|
||||
import module.Module;
|
||||
import sample.SampleType;
|
||||
import sample.real.IOverflowListener;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
import source.tuner.frequency.IFrequencyChangeProvider;
|
||||
|
||||
/**
|
||||
* Abstract class to define the minimum functionality of a sample data provider.
|
||||
*/
|
||||
public abstract class Source extends Module
|
||||
public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider,
|
||||
IHeartbeatProvider
|
||||
{
|
||||
protected SampleType mSampleType;
|
||||
protected IOverflowListener mOverflowListener;
|
||||
|
||||
@@ -1,165 +1,191 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package source.mixer;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
|
||||
import channel.heartbeat.Heartbeat;
|
||||
import dsp.gain.AutomaticGainControl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.adapter.ISampleAdapter;
|
||||
import sample.real.RealBuffer;
|
||||
import source.RealSource;
|
||||
import source.SourceException;
|
||||
import dsp.gain.AutomaticGainControl;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.TargetDataLine;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class RealMixerSource extends RealSource
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( RealMixerSource.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RealMixerSource.class);
|
||||
|
||||
private long mFrequency = 0;
|
||||
private int mBufferSize = 16384;
|
||||
|
||||
private BufferReader mBufferReader = new BufferReader();
|
||||
private TargetDataLine mTargetDataLine;
|
||||
private AudioFormat mAudioFormat;
|
||||
private String mDisplayName;
|
||||
private int mBytesPerFrame = 0;
|
||||
private ISampleAdapter mSampleAdapter;
|
||||
private AutomaticGainControl mAGC = new AutomaticGainControl();
|
||||
|
||||
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners =
|
||||
new CopyOnWriteArrayList<Listener<RealBuffer>>();
|
||||
|
||||
/**
|
||||
* Real Mixer Source - constructs a reader on the mixer/sound card target
|
||||
* data line using the specified audio format (sample size, sample rate )
|
||||
* and broadcasts real buffers (float arrays) to all registered listeners.
|
||||
* Reads buffers sized to 10% of the sample rate specified in audio format.
|
||||
*
|
||||
* @param targetDataLine - mixer or sound card to be used
|
||||
*
|
||||
* @param format - audio format
|
||||
*
|
||||
* @param name - token name to use for this source
|
||||
*
|
||||
* @param sampleAdapter - adapter to convert byte array data read from the
|
||||
* mixer into float array data. Can optionally invert the channel data if
|
||||
* the left/right stereo channels are inverted.
|
||||
*/
|
||||
public RealMixerSource( TargetDataLine targetDataLine,
|
||||
AudioFormat format,
|
||||
ISampleAdapter sampleAdapter )
|
||||
private long mFrequency = 0;
|
||||
private int mBufferSize = 16384;
|
||||
|
||||
private BufferReader mBufferReader = new BufferReader();
|
||||
private TargetDataLine mTargetDataLine;
|
||||
private AudioFormat mAudioFormat;
|
||||
private int mBytesPerFrame = 0;
|
||||
private ISampleAdapter mSampleAdapter;
|
||||
private AutomaticGainControl mAGC = new AutomaticGainControl();
|
||||
private Listener<Heartbeat> mHeartbeatListener;
|
||||
private static final Heartbeat HEARTBEAT = new Heartbeat();
|
||||
|
||||
private CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
|
||||
|
||||
/**
|
||||
* Real Mixer Source - constructs a reader on the mixer/sound card target
|
||||
* data line using the specified audio format (sample size, sample rate )
|
||||
* and broadcasts real buffers (float arrays) to all registered listeners.
|
||||
* Reads buffers sized to 10% of the sample rate specified in audio format.
|
||||
*
|
||||
* @param targetDataLine - mixer or sound card to be used
|
||||
* @param format - audio format
|
||||
* @param sampleAdapter - adapter to convert byte array data read from the
|
||||
* mixer into float array data. Can optionally invert the channel data if
|
||||
* the left/right stereo channels are inverted.
|
||||
*/
|
||||
public RealMixerSource(TargetDataLine targetDataLine,
|
||||
AudioFormat format,
|
||||
ISampleAdapter sampleAdapter)
|
||||
{
|
||||
mTargetDataLine = targetDataLine;
|
||||
mTargetDataLine = targetDataLine;
|
||||
mAudioFormat = format;
|
||||
mSampleAdapter = sampleAdapter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
|
||||
public void setListener( Listener<RealBuffer> listener )
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
mSampleListeners.add( listener );
|
||||
|
||||
/* If this is the first listener, start the reader thread */
|
||||
if( !mBufferReader.isRunning() )
|
||||
{
|
||||
Thread thread = new Thread( mBufferReader );
|
||||
thread.setDaemon( true );
|
||||
thread.setName( "Sample Reader" );
|
||||
thread.start();
|
||||
}
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
public void removeListener( Listener<RealBuffer> listener )
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
mSampleListeners.remove( listener );
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
//Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive heartbeats from this source.
|
||||
*
|
||||
* @param listener to receive heartbeats
|
||||
*/
|
||||
@Override
|
||||
public void setHeartbeatListener(Listener<Heartbeat> listener)
|
||||
{
|
||||
mHeartbeatListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the currently registered heartbeat listener
|
||||
*/
|
||||
@Override
|
||||
public void removeHeartbeatListener()
|
||||
{
|
||||
mHeartbeatListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
|
||||
public void setListener(Listener<RealBuffer> listener)
|
||||
{
|
||||
mSampleListeners.add(listener);
|
||||
|
||||
/* If this is the first listener, start the reader thread */
|
||||
if(!mBufferReader.isRunning())
|
||||
{
|
||||
Thread thread = new Thread(mBufferReader);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("Sample Reader");
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(Listener<RealBuffer> listener)
|
||||
{
|
||||
mSampleListeners.remove(listener);
|
||||
|
||||
/* If this is the laster listener, stop the reader thread */
|
||||
if( mSampleListeners.isEmpty() )
|
||||
{
|
||||
mBufferReader.stop();
|
||||
}
|
||||
if(mSampleListeners.isEmpty())
|
||||
{
|
||||
mBufferReader.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcast( RealBuffer samples )
|
||||
public void broadcast(RealBuffer samples)
|
||||
{
|
||||
Iterator<Listener<RealBuffer>> it = mSampleListeners.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
Listener<RealBuffer> next = it.next();
|
||||
Iterator<Listener<RealBuffer>> it = mSampleListeners.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
Listener<RealBuffer> next = it.next();
|
||||
|
||||
/* if this is the last (or only) listener, send him the original
|
||||
* buffer, otherwise send him a copy of the buffer */
|
||||
if( it.hasNext() )
|
||||
{
|
||||
// next.receive( mAGC.process( samples.copyOf() ) );
|
||||
next.receive( samples.copyOf() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// next.receive( mAGC.process( samples ) );
|
||||
next.receive( samples );
|
||||
}
|
||||
}
|
||||
if(it.hasNext())
|
||||
{
|
||||
next.receive(samples.copyOf());
|
||||
}
|
||||
else
|
||||
{
|
||||
next.receive(samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
|
||||
public int getSampleRate()
|
||||
{
|
||||
if( mTargetDataLine != null )
|
||||
{
|
||||
return (int)mTargetDataLine.getFormat().getSampleRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if(mTargetDataLine != null)
|
||||
{
|
||||
return (int)mTargetDataLine.getFormat().getSampleRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +193,7 @@ public class RealMixerSource extends RealSource
|
||||
*/
|
||||
public long getFrequency() throws SourceException
|
||||
{
|
||||
return mFrequency;
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,122 +201,128 @@ public class RealMixerSource extends RealSource
|
||||
* be useful if you are streaming an external audio source in through the
|
||||
* sound card and you want to specify a frequency for that source
|
||||
*/
|
||||
public void setFrequency( long frequency )
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mFrequency = frequency;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reader thread. Performs blocking read against the mixer target data
|
||||
* line, converts the samples to an array of floats using the specified
|
||||
* adapter. Dispatches float arrays to all registered listeners.
|
||||
*/
|
||||
public class BufferReader implements Runnable
|
||||
{
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Reader thread. Performs blocking read against the mixer target data
|
||||
* line, converts the samples to an array of floats using the specified
|
||||
* adapter. Dispatches float arrays to all registered listeners.
|
||||
*/
|
||||
public class BufferReader implements Runnable
|
||||
{
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if( mRunning.compareAndSet( false, true ) )
|
||||
{
|
||||
if( mTargetDataLine == null )
|
||||
{
|
||||
mRunning.set( false );
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
//Send a heartbeat every time we run
|
||||
if(mHeartbeatListener != null)
|
||||
{
|
||||
mHeartbeatListener.receive(HEARTBEAT);
|
||||
}
|
||||
|
||||
mLog.error( "ComplexMixerSource - mixer target data line"
|
||||
+ " is null" );
|
||||
}
|
||||
else
|
||||
{
|
||||
mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
|
||||
if(mTargetDataLine == null)
|
||||
{
|
||||
mRunning.set(false);
|
||||
|
||||
if( mBytesPerFrame == AudioSystem.NOT_SPECIFIED )
|
||||
{
|
||||
mBytesPerFrame = 2;
|
||||
}
|
||||
mLog.error("ComplexMixerSource - mixer target data line"
|
||||
+ " is null");
|
||||
}
|
||||
else
|
||||
{
|
||||
mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
|
||||
|
||||
if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED)
|
||||
{
|
||||
mBytesPerFrame = 2;
|
||||
}
|
||||
|
||||
/* Set buffer size to 1/10 second of samples */
|
||||
mBufferSize = (int)( mAudioFormat.getSampleRate()
|
||||
* 0.05 ) * mBytesPerFrame;
|
||||
mBufferSize = (int)(mAudioFormat.getSampleRate()
|
||||
* 0.05) * mBytesPerFrame;
|
||||
|
||||
/* We'll reuse the same buffer for each read */
|
||||
byte[] buffer = new byte[ mBufferSize ];
|
||||
|
||||
try
|
||||
{
|
||||
mTargetDataLine.open( mAudioFormat );
|
||||
byte[] buffer = new byte[mBufferSize];
|
||||
|
||||
mTargetDataLine.start();
|
||||
}
|
||||
catch ( LineUnavailableException e )
|
||||
try
|
||||
{
|
||||
mLog.error( "ComplexMixerSource - mixer target data line"
|
||||
+ "not available to read data from", e );
|
||||
|
||||
mRunning.set( false );
|
||||
mTargetDataLine.open(mAudioFormat);
|
||||
|
||||
mTargetDataLine.start();
|
||||
}
|
||||
|
||||
while( mRunning.get() )
|
||||
{
|
||||
try
|
||||
catch(LineUnavailableException e)
|
||||
{
|
||||
mLog.error("ComplexMixerSource - mixer target data line"
|
||||
+ "not available to read data from", e);
|
||||
|
||||
mRunning.set(false);
|
||||
}
|
||||
|
||||
while(mRunning.get())
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Blocking read - waits until the buffer fills */
|
||||
mTargetDataLine.read( buffer, 0, buffer.length );
|
||||
mTargetDataLine.read(buffer, 0, buffer.length);
|
||||
|
||||
/* Convert samples to float array */
|
||||
float[] samples =
|
||||
mSampleAdapter.convert( buffer );
|
||||
float[] samples =
|
||||
mSampleAdapter.convert(buffer);
|
||||
|
||||
/* Dispatch samples to registered listeners */
|
||||
broadcast( new RealBuffer( samples ) );
|
||||
broadcast(new RealBuffer(samples));
|
||||
}
|
||||
catch ( Exception e )
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error( "ComplexMixerSource - error while reading"
|
||||
+ "from the mixer target data line", e );
|
||||
|
||||
mRunning.set( false );
|
||||
mLog.error("ComplexMixerSource - error while reading"
|
||||
+ "from the mixer target data line", e);
|
||||
|
||||
mRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the data line if it is still open */
|
||||
if( mTargetDataLine != null && mTargetDataLine.isOpen() )
|
||||
{
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
if(mTargetDataLine != null && mTargetDataLine.isOpen())
|
||||
{
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the reader thread
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if( mRunning.compareAndSet( true, false ) )
|
||||
{
|
||||
mTargetDataLine.stop();
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Stops the reader thread
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mRunning.compareAndSet(true, false))
|
||||
{
|
||||
mTargetDataLine.stop();
|
||||
mTargetDataLine.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the reader thread is running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Indicates if the reader thread is running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
if( mBufferReader != null )
|
||||
{
|
||||
mBufferReader.stop();
|
||||
mSampleListeners.clear();
|
||||
}
|
||||
if(mBufferReader != null)
|
||||
{
|
||||
mBufferReader.stop();
|
||||
mSampleListeners.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
******************************************************************************/
|
||||
package source.tuner;
|
||||
|
||||
import channel.heartbeat.Heartbeat;
|
||||
import channel.heartbeat.IHeartbeatProvider;
|
||||
import dsp.filter.FilterFactory;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import dsp.filter.cic.ComplexPrimeCICDecimate;
|
||||
@@ -34,7 +36,9 @@ import source.ComplexSource;
|
||||
import source.SourceException;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||
import source.tuner.frequency.IFrequencyChangeListener;
|
||||
import source.tuner.frequency.IFrequencyChangeProcessor;
|
||||
import source.tuner.frequency.IFrequencyChangeProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -56,6 +60,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
|
||||
private static int CHANNEL_RATE = 48000;
|
||||
private static int CHANNEL_PASS_FREQUENCY = 12000;
|
||||
private static final Heartbeat HEARTBEAT = new Heartbeat();
|
||||
|
||||
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
|
||||
|
||||
@@ -64,7 +69,9 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
private Oscillator mMixer;
|
||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
private Listener<Heartbeat> mHeartbeatListener;
|
||||
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
|
||||
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
|
||||
private ScheduledFuture<?> mTaskHandle;
|
||||
|
||||
private long mTunerFrequency = 0;
|
||||
@@ -75,7 +82,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private boolean mExpended = false;
|
||||
private int mCount;
|
||||
|
||||
/**
|
||||
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
|
||||
@@ -109,7 +115,8 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
|
||||
|
||||
/* Fire a sample rate change event to setup the decimation chain */
|
||||
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
|
||||
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
|
||||
mTuner.getTunerController().getSampleRate()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,6 +138,10 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
|
||||
if(mRunning.compareAndSet(false, true))
|
||||
{
|
||||
//Broadcast current frequency and sample rate settings so all downstream components are aware
|
||||
mDownstreamFrequencyEventProcessor.broadcastCurrentFrequency();
|
||||
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
|
||||
|
||||
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
|
||||
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
|
||||
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
|
||||
@@ -184,6 +195,20 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
|
||||
* @param correction current frequency correction value.
|
||||
*/
|
||||
private void setFrequencyCorrection(int correction)
|
||||
{
|
||||
mChannelFrequencyCorrection = correction;
|
||||
|
||||
updateMixerFrequencyOffset();
|
||||
|
||||
mDownstreamFrequencyEventProcessor.broadcast(
|
||||
new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
|
||||
}
|
||||
|
||||
public Tuner getTuner()
|
||||
{
|
||||
return mTuner;
|
||||
@@ -242,50 +267,49 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
mTunerFrequency = event.getValue().longValue();
|
||||
updateMixerFrequencyOffset();
|
||||
break;
|
||||
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
mChannelFrequencyCorrection = event.getValue().intValue();
|
||||
|
||||
updateMixerFrequencyOffset();
|
||||
|
||||
if(mFrequencyChangeProcessor != null)
|
||||
{
|
||||
mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
|
||||
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
|
||||
}
|
||||
//Reset frequency correction so that downstream components can recalculate the value
|
||||
setFrequencyCorrection(0);
|
||||
break;
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
int sampleRate = event.getValue().intValue();
|
||||
|
||||
if(mTunerSampleRate != sampleRate)
|
||||
{
|
||||
mMixer.setSampleRate(sampleRate);
|
||||
|
||||
/* Get new decimation filter */
|
||||
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
|
||||
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
|
||||
|
||||
/* re-add the original output listener */
|
||||
mDecimationFilter.setListener(mListener);
|
||||
|
||||
mTunerSampleRate = sampleRate;
|
||||
}
|
||||
setSampleRate(sampleRate);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sample rate to the requested value and notifies any downstream components of the change
|
||||
* @param sampleRate to set
|
||||
*/
|
||||
private void setSampleRate(int sampleRate)
|
||||
{
|
||||
if(mTunerSampleRate != sampleRate)
|
||||
{
|
||||
mMixer.setSampleRate(sampleRate);
|
||||
|
||||
/* Get new decimation filter */
|
||||
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
|
||||
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
|
||||
|
||||
/* re-add the original output listener */
|
||||
mDecimationFilter.setListener(mListener);
|
||||
|
||||
mTunerSampleRate = sampleRate;
|
||||
|
||||
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the local mixer frequency offset from the tuned frequency,
|
||||
* channel's requested frequency, and channel frequency correction.
|
||||
*/
|
||||
private void updateMixerFrequencyOffset()
|
||||
{
|
||||
long offset = mTunerFrequency -
|
||||
mTunerChannel.getFrequency() -
|
||||
mChannelFrequencyCorrection;
|
||||
|
||||
long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection;
|
||||
mMixer.setFrequency(offset);
|
||||
}
|
||||
|
||||
@@ -299,6 +323,159 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
return mTunerChannel.getFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IFrequencyChangeProvider to enable this source to broadcast frequency change events to downstream
|
||||
* listeners.
|
||||
*
|
||||
* @param listener to receive downstream events
|
||||
*/
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
mDownstreamFrequencyEventProcessor.setFrequencyChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IFrequencyChangeProvider to remove the frequency change listener from receiving down-stream frequency
|
||||
* change events.
|
||||
*/
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
mDownstreamFrequencyEventProcessor.removeFrequencyChangeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IFrequencyChangeListener to receive frequency change events containing requests from downstream
|
||||
* listeners to change frequency values.
|
||||
* @return listener
|
||||
*/
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive heartbeats from this source.
|
||||
* @param listener to receive heartbeats
|
||||
*/
|
||||
@Override
|
||||
public void setHeartbeatListener(Listener<Heartbeat> listener)
|
||||
{
|
||||
mHeartbeatListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the currently registered heartbeat listener
|
||||
*/
|
||||
@Override
|
||||
public void removeHeartbeatListener()
|
||||
{
|
||||
mHeartbeatListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Managers frequency change requests and notifications from/to any downstream component. Downstream
|
||||
* components are those that receive samples from this tuner channel source. These downstream components will be
|
||||
* notified of any frequency or sample rate change events and will also be able to request frequency correction
|
||||
* updates.
|
||||
*/
|
||||
public class DownstreamProcessor implements IFrequencyChangeListener, IFrequencyChangeProvider,
|
||||
Listener<FrequencyChangeEvent>
|
||||
{
|
||||
//Listener to receive downstream events
|
||||
private Listener<FrequencyChangeEvent> mListener;
|
||||
|
||||
/**
|
||||
* Broadcasts the frequency change event to the downstream frequency change listener
|
||||
* @param event to broadcast
|
||||
*/
|
||||
public void broadcast(FrequencyChangeEvent event)
|
||||
{
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the current frequency of this tuner channel source to the downstream listener
|
||||
*/
|
||||
public void broadcastCurrentFrequency()
|
||||
{
|
||||
try
|
||||
{
|
||||
long frequency = getFrequency();
|
||||
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency));
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error obtaining frequency from tuner to broadcast downstream");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the current decimated sample rate of this tuner channel source
|
||||
*/
|
||||
public void broadcastCurrentSampleRate()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Note: downstream sample rate is currently a fixed value -- it will change in the future
|
||||
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, getSampleRate()));
|
||||
}
|
||||
catch(SourceException se)
|
||||
{
|
||||
mLog.error("Error obtaining sample rate from tuner to broadcast downstream");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the downstream listener to receive frequency change events from this tuner channel source
|
||||
* @param listener to receive events
|
||||
*/
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the downstream listener from receiving frequency change events.
|
||||
*/
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for receiving frequency change events from downstream components
|
||||
*/
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes frequency change events from downstream components.
|
||||
* @param event to process
|
||||
*/
|
||||
@Override
|
||||
public void receive(FrequencyChangeEvent event)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
//Frequency correction requests are the only change requests supported from downstream components
|
||||
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
setFrequencyCorrection(event.getValue().intValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates an inbound buffer of I/Q samples from the source down to the
|
||||
* standard 48000 channel sample rate
|
||||
@@ -323,6 +500,13 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
{
|
||||
if(mProcessing)
|
||||
{
|
||||
//Send a heartbeat every time this runs to allow downstream components to perform periodic
|
||||
//state monitoring functions on this thread
|
||||
if(mHeartbeatListener != null)
|
||||
{
|
||||
mHeartbeatListener.receive(HEARTBEAT);
|
||||
}
|
||||
|
||||
mBuffer.drainTo(mSampleBuffers, 20);
|
||||
|
||||
for(Buffer buffer : mSampleBuffers)
|
||||
|
||||
@@ -113,7 +113,16 @@ public class FCD2TunerController extends FCDTunerController
|
||||
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
|
||||
|
||||
setFrequencyCorrection(fcd2.getFrequencyCorrection());
|
||||
setFrequency(fcd2.getFrequency());
|
||||
|
||||
long frequency = MINIMUM_TUNABLE_FREQUENCY;
|
||||
|
||||
if(fcd2.getFrequency() >= MINIMUM_TUNABLE_FREQUENCY &&
|
||||
fcd2.getFrequency() <= MAXIMUM_TUNABLE_FREQUENCY)
|
||||
{
|
||||
frequency = fcd2.getFrequency();
|
||||
}
|
||||
|
||||
setFrequency(frequency);
|
||||
setLNAGain(fcd2.getGainLNA());
|
||||
setMixerGain(fcd2.getGainMixer());
|
||||
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* 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 source.tuner.frequency;
|
||||
|
||||
import sample.Listener;
|
||||
|
||||
/**
|
||||
* Provider of Frequency Change Events
|
||||
*/
|
||||
public interface IFrequencyChangeProvider
|
||||
{
|
||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
|
||||
public void removeFrequencyChangeListener();
|
||||
void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
|
||||
void removeFrequencyChangeListener();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class USBTransferProcessor implements TransferCallback
|
||||
private static final int TRANSFER_BUFFER_POOL_SIZE = 16;
|
||||
|
||||
//Maximum number of filled buffers for the blocking queue
|
||||
private static final int FILLED_BUFFER_MAX_CAPACITY = 300;
|
||||
private static final int FILLED_BUFFER_MAX_CAPACITY = 500;
|
||||
|
||||
//Threshold for resetting buffer overflow condition
|
||||
private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
|
||||
@@ -200,7 +200,7 @@ public class USBTransferProcessor implements TransferCallback
|
||||
|
||||
|
||||
//Unregister from LibUSB processor so that it auto-stops LibUSB processing
|
||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this);
|
||||
TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this);
|
||||
|
||||
//Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed.
|
||||
int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS,
|
||||
@@ -415,23 +415,27 @@ public class USBTransferProcessor implements TransferCallback
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
mFilledBuffers.drainTo(mBuffersToDispatch, 15);
|
||||
mFilledBuffers.drainTo(mBuffersToDispatch, 50);
|
||||
|
||||
for(byte[] buffer : mBuffersToDispatch)
|
||||
while(!mBuffersToDispatch.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
float[] complexSamples = mSampleAdapter.convert(buffer);
|
||||
for(byte[] buffer : mBuffersToDispatch)
|
||||
{
|
||||
float[] complexSamples = mSampleAdapter.convert(buffer);
|
||||
|
||||
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples));
|
||||
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e);
|
||||
}
|
||||
|
||||
mBuffersToDispatch.clear();
|
||||
mBuffersToDispatch.clear();
|
||||
mFilledBuffers.drainTo(mBuffersToDispatch, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,143 +1,179 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 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/>
|
||||
* 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 source.wave;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import channel.heartbeat.Heartbeat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.ConversionUtils;
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexBuffer;
|
||||
import source.ComplexSource;
|
||||
import source.IControllableFileSource;
|
||||
import source.IFrameLocationListener;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( ComplexWaveSource.class );
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger(ComplexWaveSource.class);
|
||||
|
||||
private IFrameLocationListener mFrameLocationListener;
|
||||
private int mBytesPerFrame;
|
||||
private int mFrameCounter = 0;
|
||||
private IFrameLocationListener mFrameLocationListener;
|
||||
private int mBytesPerFrame;
|
||||
private int mFrameCounter = 0;
|
||||
private long mFrequency = 0;
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
private AudioInputStream mInputStream;
|
||||
private File mFile;
|
||||
|
||||
public ComplexWaveSource( File file ) throws IOException
|
||||
|
||||
public ComplexWaveSource(File file) throws IOException
|
||||
{
|
||||
mFile = file;
|
||||
mFile = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
stop();
|
||||
start( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
try
|
||||
{
|
||||
open();
|
||||
}
|
||||
catch ( IOException | UnsupportedAudioFileException e )
|
||||
{
|
||||
mLog.error( "Error starting complex wave source" );
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
close();
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
mLog.error( "Error stopping complex wave source" );
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrameCount() throws IOException
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
//Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Not implemented
|
||||
*/
|
||||
@Override
|
||||
public void setHeartbeatListener(Listener<Heartbeat> listener)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented
|
||||
*/
|
||||
@Override
|
||||
public void removeHeartbeatListener()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
stop();
|
||||
start(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
try
|
||||
{
|
||||
open();
|
||||
}
|
||||
catch(IOException | UnsupportedAudioFileException e)
|
||||
{
|
||||
mLog.error("Error starting complex wave source");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
close();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error stopping complex wave source");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFrameCount() throws IOException
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleRate()
|
||||
{
|
||||
if( mInputStream != null )
|
||||
{
|
||||
return (int)mInputStream.getFormat().getSampleRate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
if(mInputStream != null)
|
||||
{
|
||||
return (int)mInputStream.getFormat().getSampleRate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the frequency set for this file. Normally returns zero, but
|
||||
* the value can be set with setFrequency() method.
|
||||
*/
|
||||
/**
|
||||
* Returns the frequency set for this file. Normally returns zero, but
|
||||
* the value can be set with setFrequency() method.
|
||||
*/
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value returned from getFrequency() for this source.
|
||||
*/
|
||||
public void setFrequency( long frequency )
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mFrequency = frequency;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the source file
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
if( mInputStream != null )
|
||||
{
|
||||
mInputStream.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException( "Can't close wave source - was not opened" );
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
if(mInputStream != null)
|
||||
{
|
||||
mInputStream.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Can't close wave source - was not opened");
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,121 +181,121 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
|
||||
*/
|
||||
public void open() throws IOException, UnsupportedAudioFileException
|
||||
{
|
||||
if( mInputStream == null )
|
||||
{
|
||||
mInputStream = AudioSystem.getAudioInputStream( mFile );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException( "Can't open wave source - is already opened" );
|
||||
}
|
||||
if(mInputStream == null)
|
||||
{
|
||||
mInputStream = AudioSystem.getAudioInputStream(mFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Can't open wave source - is already opened");
|
||||
}
|
||||
|
||||
|
||||
AudioFormat format = mInputStream.getFormat();
|
||||
|
||||
|
||||
mBytesPerFrame = format.getFrameSize();
|
||||
|
||||
if( format.getChannels() != 2 || format.getSampleSizeInBits() != 16 )
|
||||
if(format.getChannels() != 2 || format.getSampleSizeInBits() != 16)
|
||||
{
|
||||
throw new IOException( "Unsupported Wave Format - EXPECTED: 2 " +
|
||||
"channels 16-bit samples FOUND: " +
|
||||
mInputStream.getFormat().getChannels() + " channels " +
|
||||
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" );
|
||||
throw new IOException("Unsupported Wave Format - EXPECTED: 2 " +
|
||||
"channels 16-bit samples FOUND: " +
|
||||
mInputStream.getFormat().getChannels() + " channels " +
|
||||
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples");
|
||||
}
|
||||
|
||||
/* Broadcast that we're at frame location 0 */
|
||||
broadcast( 0 );
|
||||
broadcast(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the number of frames and sends a buffer to the listener
|
||||
*/
|
||||
@Override
|
||||
public void next( int frames ) throws IOException
|
||||
{
|
||||
next( frames, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the number of frames and optionally sends the buffer to the listener
|
||||
*/
|
||||
public void next( int frames, boolean broadcast ) throws IOException
|
||||
@Override
|
||||
public void next(int frames) throws IOException
|
||||
{
|
||||
if( mInputStream != null )
|
||||
next(frames, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the number of frames and optionally sends the buffer to the listener
|
||||
*/
|
||||
public void next(int frames, boolean broadcast) throws IOException
|
||||
{
|
||||
if(mInputStream != null)
|
||||
{
|
||||
byte[] buffer = new byte[ mBytesPerFrame * frames ];
|
||||
|
||||
byte[] buffer = new byte[mBytesPerFrame * frames];
|
||||
|
||||
/* Fill the buffer with samples from the file */
|
||||
int samplesRead = mInputStream.read( buffer );
|
||||
int samplesRead = mInputStream.read(buffer);
|
||||
|
||||
mFrameCounter += samplesRead;
|
||||
|
||||
broadcast( mFrameCounter );
|
||||
|
||||
if( broadcast && mListener != null )
|
||||
{
|
||||
if( samplesRead < buffer.length )
|
||||
{
|
||||
buffer = Arrays.copyOf( buffer, samplesRead );
|
||||
}
|
||||
mFrameCounter += samplesRead;
|
||||
|
||||
float[] samples = ConversionUtils
|
||||
.convertFromSigned16BitSamples( buffer );
|
||||
|
||||
mListener.receive( new ComplexBuffer( samples ) );
|
||||
}
|
||||
broadcast(mFrameCounter);
|
||||
|
||||
if(broadcast && mListener != null)
|
||||
{
|
||||
if(samplesRead < buffer.length)
|
||||
{
|
||||
buffer = Arrays.copyOf(buffer, samplesRead);
|
||||
}
|
||||
|
||||
float[] samples = ConversionUtils
|
||||
.convertFromSigned16BitSamples(buffer);
|
||||
|
||||
mListener.receive(new ComplexBuffer(samples));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers the listener to receive sample buffers as they are read from
|
||||
* Registers the listener to receive sample buffers as they are read from
|
||||
* the wave file
|
||||
*/
|
||||
@Override
|
||||
public void setListener( Listener<ComplexBuffer> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the listener from receiving sample buffers
|
||||
*/
|
||||
public void removeListener( Listener<ComplexBuffer> listener )
|
||||
@Override
|
||||
public void setListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mListener = null;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Unregisters the listener from receiving sample buffers
|
||||
*/
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile()
|
||||
{
|
||||
return mFile;
|
||||
}
|
||||
@Override
|
||||
public File getFile()
|
||||
{
|
||||
return mFile;
|
||||
}
|
||||
|
||||
private void broadcast( int byteLocation )
|
||||
{
|
||||
int frameLocation = (int)( byteLocation / mBytesPerFrame );
|
||||
|
||||
if( mFrameLocationListener != null )
|
||||
{
|
||||
mFrameLocationListener.frameLocationUpdated( frameLocation );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener( IFrameLocationListener listener )
|
||||
{
|
||||
mFrameLocationListener = listener;
|
||||
}
|
||||
private void broadcast(int byteLocation)
|
||||
{
|
||||
int frameLocation = (int)(byteLocation / mBytesPerFrame);
|
||||
|
||||
@Override
|
||||
public void removeListener( IFrameLocationListener listener )
|
||||
{
|
||||
mFrameLocationListener = null;
|
||||
}
|
||||
if(mFrameLocationListener != null)
|
||||
{
|
||||
mFrameLocationListener.frameLocationUpdated(frameLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener(IFrameLocationListener listener)
|
||||
{
|
||||
mFrameLocationListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(IFrameLocationListener listener)
|
||||
{
|
||||
mFrameLocationListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,148 +1,181 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 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 source.wave;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import channel.heartbeat.Heartbeat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.ConversionUtils;
|
||||
import sample.Listener;
|
||||
import sample.real.RealBuffer;
|
||||
import source.IControllableFileSource;
|
||||
import source.IFrameLocationListener;
|
||||
import source.RealSource;
|
||||
import source.tuner.frequency.FrequencyChangeEvent;
|
||||
|
||||
public class RealWaveSource extends RealSource
|
||||
implements IControllableFileSource, AutoCloseable
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( RealWaveSource.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RealWaveSource.class);
|
||||
|
||||
private IFrameLocationListener mFrameLocationListener;
|
||||
private int mBytesPerFrame;
|
||||
private int mFrameCounter = 0;
|
||||
private IFrameLocationListener mFrameLocationListener;
|
||||
private int mBytesPerFrame;
|
||||
private int mFrameCounter = 0;
|
||||
private long mFrequency = 0;
|
||||
private Listener<RealBuffer> mListener;
|
||||
private AudioInputStream mInputStream;
|
||||
private File mFile;
|
||||
|
||||
public RealWaveSource( File file ) throws IOException
|
||||
|
||||
public RealWaveSource(File file) throws IOException
|
||||
{
|
||||
mFile = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
//Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
//Not implemented
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented
|
||||
*/
|
||||
@Override
|
||||
public void setHeartbeatListener(Listener<Heartbeat> listener)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented
|
||||
*/
|
||||
@Override
|
||||
public void removeHeartbeatListener()
|
||||
{
|
||||
mFile = file;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
stop();
|
||||
start( null );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
try
|
||||
{
|
||||
open();
|
||||
}
|
||||
catch ( IOException | UnsupportedAudioFileException e )
|
||||
{
|
||||
mLog.error( "Error starting real wave source", e );
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
stop();
|
||||
start(null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
close();
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
mLog.error( "Error stopping real wave source", e );
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
try
|
||||
{
|
||||
open();
|
||||
}
|
||||
catch(IOException | UnsupportedAudioFileException e)
|
||||
{
|
||||
mLog.error("Error starting real wave source", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getFrameCount() throws IOException
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
close();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error stopping real wave source", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public long getFrameCount() throws IOException
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleRate()
|
||||
{
|
||||
if( mInputStream != null )
|
||||
{
|
||||
return (int)mInputStream.getFormat().getSampleRate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
if(mInputStream != null)
|
||||
{
|
||||
return (int)mInputStream.getFormat().getSampleRate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the frequency set for this file. Normally returns zero, but
|
||||
* the value can be set with setFrequency() method.
|
||||
*/
|
||||
/**
|
||||
* Returns the frequency set for this file. Normally returns zero, but
|
||||
* the value can be set with setFrequency() method.
|
||||
*/
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value returned from getFrequency() for this source.
|
||||
*/
|
||||
public void setFrequency( long frequency )
|
||||
public void setFrequency(long frequency)
|
||||
{
|
||||
mFrequency = frequency;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the source file
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
if( mInputStream != null )
|
||||
{
|
||||
mInputStream.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException( "Can't close wave source - was not opened" );
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
if(mInputStream != null)
|
||||
{
|
||||
mInputStream.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Can't close wave source - was not opened");
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,126 +183,126 @@ public class RealWaveSource extends RealSource
|
||||
*/
|
||||
public void open() throws IOException, UnsupportedAudioFileException
|
||||
{
|
||||
if( mInputStream == null )
|
||||
{
|
||||
mInputStream = AudioSystem.getAudioInputStream( mFile );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException( "Can't open wave source - is already opened" );
|
||||
}
|
||||
if(mInputStream == null)
|
||||
{
|
||||
mInputStream = AudioSystem.getAudioInputStream(mFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Can't open wave source - is already opened");
|
||||
}
|
||||
|
||||
|
||||
AudioFormat format = mInputStream.getFormat();
|
||||
|
||||
|
||||
mBytesPerFrame = format.getFrameSize();
|
||||
|
||||
if( format.getChannels() != 1 || format.getSampleSizeInBits() != 16 )
|
||||
if(format.getChannels() != 1 || format.getSampleSizeInBits() != 16)
|
||||
{
|
||||
throw new IOException( "Unsupported Wave Format - EXPECTED: 1 " +
|
||||
"channel 16-bit samples FOUND: " +
|
||||
mInputStream.getFormat().getChannels() + " channels " +
|
||||
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" );
|
||||
throw new IOException("Unsupported Wave Format - EXPECTED: 1 " +
|
||||
"channel 16-bit samples FOUND: " +
|
||||
mInputStream.getFormat().getChannels() + " channels " +
|
||||
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples");
|
||||
}
|
||||
|
||||
/* Broadcast that we're at frame location 0 */
|
||||
broadcast( 0 );
|
||||
broadcast(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the number of frames and sends a buffer to the listener
|
||||
*/
|
||||
@Override
|
||||
public void next( int frames ) throws IOException
|
||||
{
|
||||
next( frames, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the number of frames and optionally sends the buffer to the listener
|
||||
*/
|
||||
public void next( int frames, boolean broadcast ) throws IOException
|
||||
@Override
|
||||
public void next(int frames) throws IOException
|
||||
{
|
||||
if( mInputStream != null )
|
||||
{
|
||||
byte[] buffer = new byte[ mBytesPerFrame * frames ];
|
||||
|
||||
/* Fill the buffer with samples from the file */
|
||||
int samplesRead = mInputStream.read( buffer );
|
||||
|
||||
mFrameCounter += samplesRead;
|
||||
|
||||
broadcast( mFrameCounter );
|
||||
|
||||
if( broadcast && mListener != null )
|
||||
{
|
||||
if( samplesRead < buffer.length )
|
||||
{
|
||||
if( samplesRead == -1 )
|
||||
{
|
||||
throw new IOException( "End of recording" );
|
||||
}
|
||||
|
||||
buffer = Arrays.copyOf( buffer, samplesRead );
|
||||
}
|
||||
next(frames, true);
|
||||
}
|
||||
|
||||
float[] samples = ConversionUtils
|
||||
.convertFromSigned16BitSamples( buffer );
|
||||
|
||||
mListener.receive( new RealBuffer( samples ) );
|
||||
}
|
||||
/**
|
||||
* Reads the number of frames and optionally sends the buffer to the listener
|
||||
*/
|
||||
public void next(int frames, boolean broadcast) throws IOException
|
||||
{
|
||||
if(mInputStream != null)
|
||||
{
|
||||
byte[] buffer = new byte[mBytesPerFrame * frames];
|
||||
|
||||
/* Fill the buffer with samples from the file */
|
||||
int samplesRead = mInputStream.read(buffer);
|
||||
|
||||
mFrameCounter += samplesRead;
|
||||
|
||||
broadcast(mFrameCounter);
|
||||
|
||||
if(broadcast && mListener != null)
|
||||
{
|
||||
if(samplesRead < buffer.length)
|
||||
{
|
||||
if(samplesRead == -1)
|
||||
{
|
||||
throw new IOException("End of recording");
|
||||
}
|
||||
|
||||
buffer = Arrays.copyOf(buffer, samplesRead);
|
||||
}
|
||||
|
||||
float[] samples = ConversionUtils
|
||||
.convertFromSigned16BitSamples(buffer);
|
||||
|
||||
mListener.receive(new RealBuffer(samples));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers the listener to receive sample buffers as they are read from
|
||||
* Registers the listener to receive sample buffers as they are read from
|
||||
* the wave file
|
||||
*/
|
||||
@Override
|
||||
public void setListener( Listener<RealBuffer> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the listener from receiving sample buffers
|
||||
*/
|
||||
public void removeListener( Listener<RealBuffer> listener )
|
||||
@Override
|
||||
public void setListener(Listener<RealBuffer> listener)
|
||||
{
|
||||
mListener = null;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Unregisters the listener from receiving sample buffers
|
||||
*/
|
||||
public void removeListener(Listener<RealBuffer> listener)
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile()
|
||||
{
|
||||
return mFile;
|
||||
}
|
||||
|
||||
private void broadcast( int byteLocation )
|
||||
{
|
||||
int frameLocation = (int)( byteLocation / mBytesPerFrame );
|
||||
|
||||
if( mFrameLocationListener != null )
|
||||
{
|
||||
mFrameLocationListener.frameLocationUpdated( frameLocation );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener( IFrameLocationListener listener )
|
||||
{
|
||||
mFrameLocationListener = listener;
|
||||
}
|
||||
@Override
|
||||
public File getFile()
|
||||
{
|
||||
return mFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener( IFrameLocationListener listener )
|
||||
{
|
||||
mFrameLocationListener = null;
|
||||
}
|
||||
private void broadcast(int byteLocation)
|
||||
{
|
||||
int frameLocation = (int)(byteLocation / mBytesPerFrame);
|
||||
|
||||
if(mFrameLocationListener != null)
|
||||
{
|
||||
mFrameLocationListener.frameLocationUpdated(frameLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener(IFrameLocationListener listener)
|
||||
{
|
||||
mFrameLocationListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(IFrameLocationListener listener)
|
||||
{
|
||||
mFrameLocationListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,492 +1,483 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 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/>
|
||||
* 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 spectrum;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import settings.SettingsManager;
|
||||
import dsp.filter.smoothing.GaussianSmoothingFilter;
|
||||
import dsp.filter.smoothing.NoSmoothingFilter;
|
||||
import dsp.filter.smoothing.RectangularSmoothingFilter;
|
||||
import dsp.filter.smoothing.SmoothingFilter;
|
||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||
import dsp.filter.smoothing.TriangularSmoothingFilter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import settings.SettingsManager;
|
||||
|
||||
public class SpectrumPanel extends JPanel
|
||||
implements DFTResultsListener,
|
||||
SettingChangeListener,
|
||||
SpectralDisplayAdjuster
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( SpectrumPanel.class );
|
||||
|
||||
private static final RenderingHints RENDERING_HINTS =
|
||||
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class);
|
||||
|
||||
static
|
||||
{
|
||||
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY );
|
||||
}
|
||||
|
||||
private Color mColorSpectrumBackground;
|
||||
private Color mColorSpectrumGradientTop;
|
||||
private Color mColorSpectrumGradientBottom;
|
||||
private Color mColorSpectrumLine;
|
||||
private static final RenderingHints RENDERING_HINTS =
|
||||
new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
//Defines the panel inset along the bottom for frequency display
|
||||
private float mSpectrumInset = 20.0f;
|
||||
|
||||
//Current DFT output bins in dB
|
||||
private float[] mDisplayFFTBins = new float[ 1 ];
|
||||
|
||||
//Averaging across multiple DFT result sets
|
||||
private int mAveraging = 4;
|
||||
|
||||
//Smoothing across bins in the same DFT result set
|
||||
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
|
||||
|
||||
//Reference dB value set according to the source sample size
|
||||
private float mDBScale;
|
||||
|
||||
private int mZoom = 0;
|
||||
private int mZoomWindowOffset = 0;;
|
||||
|
||||
private SettingsManager mSettingsManager;
|
||||
|
||||
public SpectrumPanel( SettingsManager settingsManager )
|
||||
static
|
||||
{
|
||||
mSettingsManager = settingsManager;
|
||||
|
||||
if( mSettingsManager != null )
|
||||
{
|
||||
mSettingsManager.addListener( this );
|
||||
}
|
||||
|
||||
setSampleSize( 16.0 );
|
||||
|
||||
mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT );
|
||||
|
||||
getColors();
|
||||
|
||||
setAveraging( mAveraging );
|
||||
RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
if( mSettingsManager != null )
|
||||
{
|
||||
mSettingsManager.removeListener( this );
|
||||
}
|
||||
|
||||
mSettingsManager = null;
|
||||
}
|
||||
|
||||
|
||||
private Color mColorSpectrumBackground;
|
||||
private Color mColorSpectrumGradientTop;
|
||||
private Color mColorSpectrumGradientBottom;
|
||||
private Color mColorSpectrumLine;
|
||||
|
||||
//Defines the panel inset along the bottom for frequency display
|
||||
private float mSpectrumInset = 20.0f;
|
||||
|
||||
//Current DFT output bins in dB
|
||||
private float[] mDisplayFFTBins = new float[1];
|
||||
|
||||
//Averaging across multiple DFT result sets
|
||||
private int mAveraging = 4;
|
||||
|
||||
//Smoothing across bins in the same DFT result set
|
||||
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
|
||||
|
||||
//Reference dB value set according to the source sample size
|
||||
private float mDBScale;
|
||||
|
||||
private int mZoom = 0;
|
||||
private int mZoomWindowOffset = 0;
|
||||
;
|
||||
|
||||
private SettingsManager mSettingsManager;
|
||||
|
||||
public SpectrumPanel(SettingsManager settingsManager)
|
||||
{
|
||||
mSettingsManager = settingsManager;
|
||||
|
||||
if(mSettingsManager != null)
|
||||
{
|
||||
mSettingsManager.addListener(this);
|
||||
}
|
||||
|
||||
setSampleSize(16.0);
|
||||
|
||||
mSmoothingFilter.setPointSize(SmoothingFilter.SMOOTHING_DEFAULT);
|
||||
|
||||
getColors();
|
||||
|
||||
setAveraging(mAveraging);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
if(mSettingsManager != null)
|
||||
{
|
||||
mSettingsManager.removeListener(this);
|
||||
}
|
||||
|
||||
mSettingsManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* DFTResultsListener interface for receiving the processed data
|
||||
* to display
|
||||
*/
|
||||
public void receive( float[] currentFFTBins )
|
||||
public void receive(float[] currentFFTBins)
|
||||
{
|
||||
//Prevent arrays of NaN values from being rendered. The first few
|
||||
//DFT result sets on startup will contain NaN values
|
||||
if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) )
|
||||
{
|
||||
currentFFTBins = new float[ currentFFTBins.length ];
|
||||
}
|
||||
//Prevent arrays of NaN values from being rendered. The first few
|
||||
//DFT result sets on startup will contain NaN values
|
||||
if(Float.isInfinite(currentFFTBins[0]) || Float.isNaN(currentFFTBins[0]))
|
||||
{
|
||||
currentFFTBins = new float[currentFFTBins.length];
|
||||
}
|
||||
|
||||
//Construct and/or resize our DFT results variables
|
||||
if( mDisplayFFTBins == null ||
|
||||
mDisplayFFTBins.length != currentFFTBins.length )
|
||||
{
|
||||
mDisplayFFTBins = currentFFTBins;
|
||||
}
|
||||
//Construct and/or resize our DFT results variables
|
||||
if(mDisplayFFTBins == null ||
|
||||
mDisplayFFTBins.length != currentFFTBins.length)
|
||||
{
|
||||
mDisplayFFTBins = currentFFTBins;
|
||||
}
|
||||
|
||||
//Apply smoothing across the bins of the DFT results
|
||||
float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins );
|
||||
//Apply smoothing across the bins of the DFT results
|
||||
float[] smoothedBins = mSmoothingFilter.filter(currentFFTBins);
|
||||
|
||||
//Apply averaging over multiple DFT output frames
|
||||
if( mAveraging > 1 )
|
||||
{
|
||||
float gain = 1.0f / (float)mAveraging;
|
||||
|
||||
for( int x = 0; x < mDisplayFFTBins.length; x++ )
|
||||
{
|
||||
mDisplayFFTBins[ x ] +=
|
||||
( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mDisplayFFTBins = smoothedBins;
|
||||
}
|
||||
|
||||
repaint();
|
||||
//Apply averaging over multiple DFT output frames
|
||||
if(mAveraging > 1)
|
||||
{
|
||||
float gain = 1.0f / (float)mAveraging;
|
||||
|
||||
for(int x = 0; x < mDisplayFFTBins.length; x++)
|
||||
{
|
||||
mDisplayFFTBins[x] +=
|
||||
(smoothedBins[x] - mDisplayFFTBins[x]) * gain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mDisplayFFTBins = smoothedBins;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent( Graphics g )
|
||||
public void paintComponent(Graphics g)
|
||||
{
|
||||
super.paintComponent( g );
|
||||
|
||||
Graphics2D graphics = (Graphics2D) g;
|
||||
super.paintComponent(g);
|
||||
|
||||
graphics.setBackground( mColorSpectrumBackground );
|
||||
Graphics2D graphics = (Graphics2D)g;
|
||||
|
||||
graphics.setRenderingHints( RENDERING_HINTS );
|
||||
|
||||
drawSpectrum( graphics );
|
||||
graphics.setBackground(mColorSpectrumBackground);
|
||||
|
||||
graphics.setRenderingHints(RENDERING_HINTS);
|
||||
|
||||
drawSpectrum(graphics);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draws the current fft spectrum with a line and a gradient fill.
|
||||
*/
|
||||
private void drawSpectrum( Graphics2D graphics )
|
||||
private void drawSpectrum(Graphics2D graphics)
|
||||
{
|
||||
Dimension size = getSize();
|
||||
Dimension size = getSize();
|
||||
|
||||
//Draw the background
|
||||
Rectangle background = new Rectangle( 0, 0, size.width, size.height );
|
||||
graphics.setColor( mColorSpectrumBackground );
|
||||
graphics.draw( background );
|
||||
graphics.fill( background );
|
||||
//Draw the background
|
||||
Rectangle background = new Rectangle(0, 0, size.width, size.height);
|
||||
graphics.setColor(mColorSpectrumBackground);
|
||||
graphics.draw(background);
|
||||
graphics.fill(background);
|
||||
|
||||
//Define the gradient
|
||||
GradientPaint gradient = new GradientPaint( 0,
|
||||
( getSize().height - mSpectrumInset ) / 2,
|
||||
mColorSpectrumGradientTop,
|
||||
0,
|
||||
getSize().height,
|
||||
mColorSpectrumGradientBottom );
|
||||
|
||||
graphics.setBackground( mColorSpectrumBackground );
|
||||
|
||||
GeneralPath spectrumShape = new GeneralPath();
|
||||
//Define the gradient
|
||||
GradientPaint gradient = new GradientPaint(0,
|
||||
(getSize().height - mSpectrumInset) / 2,
|
||||
mColorSpectrumGradientTop,
|
||||
0,
|
||||
getSize().height,
|
||||
mColorSpectrumGradientBottom);
|
||||
|
||||
//Start at the lower right inset point
|
||||
spectrumShape.moveTo( size.getWidth(),
|
||||
size.getHeight() - mSpectrumInset );
|
||||
|
||||
//Draw to the lower left
|
||||
spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset );
|
||||
graphics.setBackground(mColorSpectrumBackground);
|
||||
|
||||
float[] bins = getBins();
|
||||
|
||||
//If we have FFT data to display ...
|
||||
if( bins != null )
|
||||
{
|
||||
float insideHeight = size.height - mSpectrumInset;
|
||||
GeneralPath spectrumShape = new GeneralPath();
|
||||
|
||||
float scalor = insideHeight / -mDBScale;
|
||||
//Start at the lower right inset point
|
||||
spectrumShape.moveTo(size.getWidth(),
|
||||
size.getHeight() - mSpectrumInset);
|
||||
|
||||
//Draw to the lower left
|
||||
spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
|
||||
|
||||
float[] bins = getBins();
|
||||
|
||||
//If we have FFT data to display ...
|
||||
if(bins != null)
|
||||
{
|
||||
float insideHeight = size.height - mSpectrumInset;
|
||||
|
||||
float scalor = insideHeight / -mDBScale;
|
||||
|
||||
/* Calculate based on bin size - 1, since bin 0 is rendered at zero
|
||||
* and the last bin is rendered at the width */
|
||||
float binSize = (float)size.width / ( (float)( bins.length ) );
|
||||
* and the last bin is rendered at the width */
|
||||
float binSize = (float)size.width / ((float)(bins.length));
|
||||
|
||||
for( int x = 0; x < bins.length; x++ )
|
||||
{
|
||||
float height;
|
||||
|
||||
height = bins[ x ] * scalor;
|
||||
|
||||
if( height > insideHeight )
|
||||
{
|
||||
height = insideHeight;
|
||||
}
|
||||
|
||||
if( height < 0 )
|
||||
{
|
||||
height = 0;
|
||||
}
|
||||
|
||||
float xAxis = (float)x * binSize;
|
||||
for(int x = 0; x < bins.length; x++)
|
||||
{
|
||||
float height;
|
||||
|
||||
spectrumShape.lineTo( xAxis, height );
|
||||
}
|
||||
}
|
||||
//Otherwise show an empty spectrum
|
||||
else
|
||||
{
|
||||
//Draw Left Size
|
||||
graphics.setPaint( gradient );
|
||||
spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset );
|
||||
//Draw Middle
|
||||
spectrumShape.lineTo( size.getWidth(),
|
||||
size.getHeight() - mSpectrumInset );
|
||||
}
|
||||
|
||||
//Draw Right Side
|
||||
spectrumShape.lineTo( size.getWidth(),
|
||||
size.getHeight() - mSpectrumInset );
|
||||
|
||||
graphics.setPaint( gradient );
|
||||
graphics.draw( spectrumShape );
|
||||
graphics.fill( spectrumShape );
|
||||
|
||||
graphics.setPaint( mColorSpectrumLine );
|
||||
height = bins[x] * scalor;
|
||||
|
||||
//Draw the bottom line under the spectrum
|
||||
graphics.draw( new Line2D.Float( 0,
|
||||
size.height - mSpectrumInset,
|
||||
size.width,
|
||||
size.height - mSpectrumInset ) );
|
||||
if(height > insideHeight)
|
||||
{
|
||||
height = insideHeight;
|
||||
}
|
||||
|
||||
if(height < 0)
|
||||
{
|
||||
height = 0;
|
||||
}
|
||||
|
||||
float xAxis = (float)x * binSize;
|
||||
|
||||
spectrumShape.lineTo(xAxis, height);
|
||||
}
|
||||
}
|
||||
//Otherwise show an empty spectrum
|
||||
else
|
||||
{
|
||||
//Draw Left Size
|
||||
graphics.setPaint(gradient);
|
||||
spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
|
||||
//Draw Middle
|
||||
spectrumShape.lineTo(size.getWidth(),
|
||||
size.getHeight() - mSpectrumInset);
|
||||
}
|
||||
|
||||
//Draw Right Side
|
||||
spectrumShape.lineTo(size.getWidth(),
|
||||
size.getHeight() - mSpectrumInset);
|
||||
|
||||
graphics.setPaint(gradient);
|
||||
graphics.draw(spectrumShape);
|
||||
graphics.fill(spectrumShape);
|
||||
|
||||
graphics.setPaint(mColorSpectrumLine);
|
||||
|
||||
//Draw the bottom line under the spectrum
|
||||
graphics.draw(new Line2D.Float(0,
|
||||
size.height - mSpectrumInset,
|
||||
size.width,
|
||||
size.height - mSpectrumInset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current zoom level
|
||||
*
|
||||
*
|
||||
* 0 No Zoom
|
||||
* 1 2x Zoom
|
||||
* 2 4x Zoom
|
||||
* 3 8x Zoom
|
||||
* 4 16x Zoom
|
||||
* 5 32x Zoom
|
||||
*
|
||||
* @param zoom level, 0 - 5.
|
||||
* 6 64x Zoom
|
||||
*
|
||||
* @param zoom level, 0 - 6.
|
||||
*/
|
||||
public void setZoom( int zoom )
|
||||
public void setZoom(int zoom)
|
||||
{
|
||||
assert( 0 <= zoom && zoom <= 5 );
|
||||
|
||||
mZoom = zoom;
|
||||
Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom);
|
||||
|
||||
mZoom = zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset to define which subset of zoomed bins to display
|
||||
*
|
||||
*
|
||||
* @param offset
|
||||
*/
|
||||
public void setZoomWindowOffset( int offset )
|
||||
public void setZoomWindowOffset(int offset)
|
||||
{
|
||||
mZoomWindowOffset = offset;
|
||||
mZoomWindowOffset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source sample size in bits which defines the lowest dB value to
|
||||
* display in the spectrum
|
||||
*
|
||||
* @param sampleSize in bits
|
||||
*/
|
||||
public void setSampleSize( double sampleSize )
|
||||
{
|
||||
assert( 2.0 <= sampleSize && sampleSize <= 32.0 );
|
||||
|
||||
mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the number of DFT result sets to average across
|
||||
*/
|
||||
public void setAveraging( int size )
|
||||
{
|
||||
mAveraging = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* DFT result sets averaging value
|
||||
*/
|
||||
public int getAveraging()
|
||||
{
|
||||
return mAveraging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the spectral display
|
||||
*/
|
||||
public void clearSpectrum()
|
||||
{
|
||||
mDisplayFFTBins = null;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void getColors()
|
||||
{
|
||||
mColorSpectrumBackground =
|
||||
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
|
||||
|
||||
mColorSpectrumGradientBottom =
|
||||
getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM );
|
||||
|
||||
mColorSpectrumGradientTop =
|
||||
getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP );
|
||||
|
||||
mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current setting for the named color setting
|
||||
*/
|
||||
private Color getColor( ColorSettingName name )
|
||||
{
|
||||
ColorSetting setting = mSettingsManager.getColorSetting( name );
|
||||
|
||||
return setting.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface to receive setting change notifications
|
||||
*/
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
/**
|
||||
* Sets the source sample size in bits which defines the lowest dB value to
|
||||
* display in the spectrum
|
||||
*
|
||||
* @param sampleSize in bits
|
||||
*/
|
||||
public void setSampleSize(double sampleSize)
|
||||
{
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( ( (ColorSetting) setting ).getColorSettingName() )
|
||||
{
|
||||
case SPECTRUM_BACKGROUND:
|
||||
mColorSpectrumBackground = colorSetting.getColor();
|
||||
break;
|
||||
case SPECTRUM_GRADIENT_BOTTOM:
|
||||
mColorSpectrumGradientBottom = colorSetting.getColor();
|
||||
break;
|
||||
case SPECTRUM_GRADIENT_TOP:
|
||||
mColorSpectrumGradientTop = colorSetting.getColor();
|
||||
break;
|
||||
case SPECTRUM_LINE:
|
||||
mColorSpectrumLine = colorSetting.getColor();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
|
||||
|
||||
mDBScale = (float)(20.0 * Math.log10(Math.pow(2.0, sampleSize - 1)));
|
||||
}
|
||||
|
||||
private int getZoomMultiplier()
|
||||
{
|
||||
return (int)Math.pow( 2.0, mZoom );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DFT result bins, or a zoomed and offset version of the bins
|
||||
* when the display is zoomed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the number of DFT result sets to average across
|
||||
*/
|
||||
public void setAveraging(int size)
|
||||
{
|
||||
mAveraging = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* DFT result sets averaging value
|
||||
*/
|
||||
public int getAveraging()
|
||||
{
|
||||
return mAveraging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the spectral display
|
||||
*/
|
||||
public void clearSpectrum()
|
||||
{
|
||||
mDisplayFFTBins = null;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void getColors()
|
||||
{
|
||||
mColorSpectrumBackground =
|
||||
getColor(ColorSettingName.SPECTRUM_BACKGROUND);
|
||||
|
||||
mColorSpectrumGradientBottom =
|
||||
getColor(ColorSettingName.SPECTRUM_GRADIENT_BOTTOM);
|
||||
|
||||
mColorSpectrumGradientTop =
|
||||
getColor(ColorSettingName.SPECTRUM_GRADIENT_TOP);
|
||||
|
||||
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current setting for the named color setting
|
||||
*/
|
||||
private Color getColor(ColorSettingName name)
|
||||
{
|
||||
ColorSetting setting = mSettingsManager.getColorSetting(name);
|
||||
|
||||
return setting.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface to receive setting change notifications
|
||||
*/
|
||||
@Override
|
||||
public void settingChanged(Setting setting)
|
||||
{
|
||||
if(setting instanceof ColorSetting)
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch(((ColorSetting)setting).getColorSettingName())
|
||||
{
|
||||
case SPECTRUM_BACKGROUND:
|
||||
mColorSpectrumBackground = colorSetting.getColor();
|
||||
break;
|
||||
case SPECTRUM_GRADIENT_BOTTOM:
|
||||
mColorSpectrumGradientBottom = colorSetting.getColor();
|
||||
break;
|
||||
case SPECTRUM_GRADIENT_TOP:
|
||||
mColorSpectrumGradientTop = colorSetting.getColor();
|
||||
break;
|
||||
case SPECTRUM_LINE:
|
||||
mColorSpectrumLine = colorSetting.getColor();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getZoomMultiplier()
|
||||
{
|
||||
return (int)Math.pow(2.0, mZoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DFT result bins, or a zoomed and offset version of the bins
|
||||
* when the display is zoomed.
|
||||
*/
|
||||
private float[] getBins()
|
||||
{
|
||||
if( mZoom == 0 )
|
||||
{
|
||||
return mDisplayFFTBins;
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = mDisplayFFTBins.length / getZoomMultiplier();
|
||||
|
||||
int offset = mZoomWindowOffset;
|
||||
|
||||
if( ( offset + length ) >= mDisplayFFTBins.length )
|
||||
{
|
||||
offset = mDisplayFFTBins.length - length;
|
||||
}
|
||||
|
||||
if( offset < 0 )
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
if(mZoom == 0)
|
||||
{
|
||||
return mDisplayFFTBins;
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = mDisplayFFTBins.length / getZoomMultiplier();
|
||||
|
||||
return Arrays.copyOfRange( mDisplayFFTBins, offset, offset + length );
|
||||
}
|
||||
int offset = mZoomWindowOffset;
|
||||
|
||||
if((offset + length) >= mDisplayFFTBins.length)
|
||||
{
|
||||
offset = mDisplayFFTBins.length - length;
|
||||
}
|
||||
|
||||
if(offset < 0)
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
return Arrays.copyOfRange(mDisplayFFTBins, offset, offset + length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingDeleted( Setting setting ) { /* not implemented */ }
|
||||
@Override
|
||||
public void settingDeleted(Setting setting)
|
||||
{ /* not implemented */ }
|
||||
|
||||
/**
|
||||
* Returns the current inter-bin smoothing setting
|
||||
*/
|
||||
@Override
|
||||
public int getSmoothing()
|
||||
{
|
||||
return mSmoothingFilter.getPointSize();
|
||||
}
|
||||
/**
|
||||
* Returns the current inter-bin smoothing setting
|
||||
*/
|
||||
@Override
|
||||
public int getSmoothing()
|
||||
{
|
||||
return mSmoothingFilter.getPointSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bin smoothing value to define how wide the smoothing window is
|
||||
* when averaging across DFT bins
|
||||
*/
|
||||
@Override
|
||||
public void setSmoothing( int smoothing )
|
||||
{
|
||||
mSmoothingFilter.setPointSize( smoothing );
|
||||
}
|
||||
/**
|
||||
* Sets the bin smoothing value to define how wide the smoothing window is
|
||||
* when averaging across DFT bins
|
||||
*/
|
||||
@Override
|
||||
public void setSmoothing(int smoothing)
|
||||
{
|
||||
mSmoothingFilter.setPointSize(smoothing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current smoothing filter type
|
||||
*/
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return mSmoothingFilter.getSmoothingType();
|
||||
}
|
||||
/**
|
||||
* Returns the current smoothing filter type
|
||||
*/
|
||||
@Override
|
||||
public SmoothingType getSmoothingType()
|
||||
{
|
||||
return mSmoothingFilter.getSmoothingType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the smoothing filter type
|
||||
*/
|
||||
@Override
|
||||
public void setSmoothingType( SmoothingType type )
|
||||
{
|
||||
if( mSmoothingFilter.getSmoothingType() != type )
|
||||
{
|
||||
int pointSize = getSmoothing();
|
||||
/**
|
||||
* Sets the smoothing filter type
|
||||
*/
|
||||
@Override
|
||||
public void setSmoothingType(SmoothingType type)
|
||||
{
|
||||
if(mSmoothingFilter.getSmoothingType() != type)
|
||||
{
|
||||
int pointSize = getSmoothing();
|
||||
|
||||
synchronized ( mSmoothingFilter )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case GAUSSIAN:
|
||||
mSmoothingFilter = new GaussianSmoothingFilter();
|
||||
break;
|
||||
case RECTANGLE:
|
||||
mSmoothingFilter = new RectangularSmoothingFilter();
|
||||
break;
|
||||
case TRIANGLE:
|
||||
mSmoothingFilter = new TriangularSmoothingFilter();
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
mSmoothingFilter = new NoSmoothingFilter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setSmoothing( pointSize );
|
||||
}
|
||||
}
|
||||
synchronized(mSmoothingFilter)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case GAUSSIAN:
|
||||
mSmoothingFilter = new GaussianSmoothingFilter();
|
||||
break;
|
||||
case RECTANGLE:
|
||||
mSmoothingFilter = new RectangularSmoothingFilter();
|
||||
break;
|
||||
case TRIANGLE:
|
||||
mSmoothingFilter = new TriangularSmoothingFilter();
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
mSmoothingFilter = new NoSmoothingFilter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setSmoothing(pointSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
******************************************************************************/
|
||||
package util;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class XTEA
|
||||
@@ -58,7 +60,7 @@ public class XTEA
|
||||
*/
|
||||
public void setKey(byte[] key)
|
||||
{
|
||||
assert(key.length == 16);
|
||||
Validate.isTrue(key.length == 16);
|
||||
|
||||
int keyInts[] = new int[8];
|
||||
|
||||
@@ -94,7 +96,7 @@ public class XTEA
|
||||
*/
|
||||
public byte[] encrypt(byte[] plainText)
|
||||
{
|
||||
assert(plainText.length == 8);
|
||||
Validate.isTrue(plainText.length == 8);
|
||||
|
||||
//Convert the 8 bytes of plaintext to 2 integers
|
||||
int result0 = (plainText[3] << 0) & MASK0 | (plainText[2] << 8) & MASK1 |
|
||||
|
||||
Reference in New Issue
Block a user