mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
53 Commits
v0.2.0-bet
...
v0.2.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62aca27e66 | ||
|
|
cdcc487095 | ||
|
|
d5b6589e36 | ||
|
|
be7ab871d1 | ||
|
|
294a830aaf | ||
|
|
a7da60cd27 | ||
|
|
894ebeb040 | ||
|
|
682fe0c779 | ||
|
|
88d3088ebc | ||
|
|
dc3390460a | ||
|
|
8db226cd7b | ||
|
|
b7a54598b1 | ||
|
|
dd22397f75 | ||
|
|
d8a20b5adb | ||
|
|
c670f1d855 | ||
|
|
1dc0125ef4 | ||
|
|
225e747d6e | ||
|
|
36f9953156 | ||
|
|
47c452e7e6 | ||
|
|
06c8bba97e | ||
|
|
a25bb83dea | ||
|
|
8e11cb4554 | ||
|
|
2892e4609e | ||
|
|
d6e161f3e9 | ||
|
|
ba952af721 | ||
|
|
d9be346d7f | ||
|
|
133adec27a | ||
|
|
981a8fec55 | ||
|
|
9c94266adb | ||
|
|
4460f1ee30 | ||
|
|
ddfefe4b85 | ||
|
|
debdc39a5f | ||
|
|
6f76f6fca4 | ||
|
|
1290b8c4d3 | ||
|
|
b95b3ff415 | ||
|
|
3e8db5b216 | ||
|
|
f0c11f2590 | ||
|
|
99a5fc9bcf | ||
|
|
f5fadc5004 | ||
|
|
c922029233 | ||
|
|
94d85f636d | ||
|
|
1db6779dec | ||
|
|
676d26f4d8 | ||
|
|
abca4ebbd7 | ||
|
|
cffc6b6eed | ||
|
|
39ebec78a8 | ||
|
|
4066a4b3c3 | ||
|
|
c12f535f3c | ||
|
|
810bb7384e | ||
|
|
0c5692cb1e | ||
|
|
663378ad20 | ||
|
|
5807bb191e | ||
|
|
ab9ef2d8cc |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
/*.class
|
/*.class
|
||||||
/classes*
|
/classes*
|
||||||
/product*
|
/product*
|
||||||
|
/bin/
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -1,19 +1,22 @@
|
|||||||
# sdrtrunk
|
# sdrtrunk #
|
||||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
A cross-platform java application for decoding, monitoring and recording trunked
|
||||||
|
mobile and related radio protocols using Software Defined Radios (SDR).
|
||||||
|
|
||||||
|
# Documentation #
|
||||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
|
* [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)
|
||||||
* [Download](https://drive.google.com/folderview?id=0B7BHsssXUq8eOHBTNndCczZTd0k&usp=sharing)
|
* [Wiki](https://github.com/DSheirer/sdrtrunk/wiki)
|
||||||
|
|
||||||
|
# Support Group and Error Reporting #
|
||||||
|
You can get help with setting up the sdrtrunk application or report any errors
|
||||||
|
that you encounter while running the application in the sdrtrunk Google Group at:
|
||||||
|
|
||||||
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||||
|
|
||||||
|
# Updates #
|
||||||
|
Subscribe to this group to receive updates about new releases and the ongoing
|
||||||
|
application development effort.
|
||||||
|
|
||||||
|
* [Subscribe for Updates](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>
|
|
||||||
|
|||||||
116
build/build.xml
116
build/build.xml
@@ -6,114 +6,116 @@
|
|||||||
<!-- version properties -->
|
<!-- version properties -->
|
||||||
<property name="major" value="0"/>
|
<property name="major" value="0"/>
|
||||||
<property name="minor" value="2"/>
|
<property name="minor" value="2"/>
|
||||||
<property name="build" value="0"/>
|
<property name="patch" value="0"/>
|
||||||
<property name="release" value="beta.2"/>
|
<property name="release" value="beta.7"/>
|
||||||
|
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}-${release}"/>
|
||||||
|
|
||||||
<!-- Build properties -->
|
<!-- Build properties -->
|
||||||
<property name="classes" location="../classes"/>
|
<property name="repo" location=".."/>
|
||||||
<property name="configs" location="../config"/>
|
<property name="source.code" location="${repo}/src"/>
|
||||||
<property name="images" location="../images"/>
|
<property name="source.configs" location="${repo}/config"/>
|
||||||
<property name="libs" location="../imports"/>
|
<property name="source.images" location="${repo}/images"/>
|
||||||
<property name="license" location="../license"/>
|
<property name="source.libs" location="${repo}/imports"/>
|
||||||
<property name="product" location="../product"/>
|
<property name="source.license" location="${repo}/license"/>
|
||||||
<property name="scripts" location="../scripts"/>
|
<property name="source.scripts" location="${repo}/scripts"/>
|
||||||
<property name="src" location="../src"/>
|
|
||||||
|
<property name="output" location="${repo}/product"/>
|
||||||
|
<property name="output.classes" location="${repo}/classes"/>
|
||||||
|
<property name="output.folder" location="${output}/sdrtrunk"/>
|
||||||
|
<property name="output.config" location="${output.folder}/config"/>
|
||||||
|
<property name="output.images" location="${output.folder}/images"/>
|
||||||
|
<property name="output.libs" location="${output.folder}/libs"/>
|
||||||
|
|
||||||
<path id="classpath">
|
<path id="classpath">
|
||||||
<fileset dir="${libs}" includes="*.jar" />
|
<fileset dir="${source.libs}" includes="*.jar" />
|
||||||
</path>
|
</path>
|
||||||
|
|
||||||
<target name="init">
|
<target name="init">
|
||||||
<!-- Create the classes folder to contain the compiled java classes -->
|
<!-- Create the classes folder to contain the compiled java classes -->
|
||||||
<mkdir dir="${classes}"/>
|
<mkdir dir="${output.classes}"/>
|
||||||
|
|
||||||
<!-- Create version file -->
|
<!-- Create version file -->
|
||||||
<echo message="${major}.${minor}.${build}-${release}" file="${classes}/sdrtrunk-version"/>
|
<echo message="${major}.${minor}.${patch}-${release}" file="${output.classes}/sdrtrunk-version"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="compile" depends="clean,init"
|
<target name="compile" depends="clean,init" description="Compile java classes" >
|
||||||
description="Compile java classes" >
|
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false" />
|
||||||
<!-- Compile the java code from ${src} into ${classes} -->
|
|
||||||
<javac srcdir="${src}"
|
|
||||||
destdir="${classes}"
|
|
||||||
classpathref="classpath"
|
|
||||||
includeantruntime="false" />
|
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="SDRTrunk" depends="compile" description="Create SDRTrunk application" >
|
<target name="SDRTrunk" depends="compile" description="Create SDRTrunk application" >
|
||||||
<!-- Create the distribution directory -->
|
<!-- Create the distribution directory -->
|
||||||
<mkdir dir="${product}"/>
|
<mkdir dir="${output.folder}"/>
|
||||||
|
|
||||||
<!-- Create jar file -->
|
<!-- Create jar file -->
|
||||||
<jar jarfile="${product}/SDRTrunk.jar" basedir="${classes}">
|
<jar jarfile="${output.folder}/sdrtrunk.jar" basedir="${output.classes}">
|
||||||
<manifest>
|
<manifest>
|
||||||
|
<attribute name="Title" value="sdrtrunk"/>
|
||||||
<attribute name="Main-Class" value="gui.SDRTrunk"/>
|
<attribute name="Main-Class" value="gui.SDRTrunk"/>
|
||||||
|
<attribute name="Version" value="${major}.${minor}.${patch}-${release}"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
<fileset dir="${configs}">
|
<fileset dir="${source.configs}">
|
||||||
<exclude name="*.rules"/>
|
<exclude name="*.rules"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
|
|
||||||
<fileset dir="${license}">
|
<fileset dir="${source.license}">
|
||||||
<include name="license.txt"/>
|
<include name="license.txt"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
</jar>
|
</jar>
|
||||||
|
|
||||||
<!-- Copy third-party libraries -->
|
<!-- Copy third-party libraries -->
|
||||||
<mkdir dir="${product}/libs"/>
|
<mkdir dir="${output.libs}"/>
|
||||||
<copy todir="${product}/libs">
|
<copy todir="${output.libs}">
|
||||||
<fileset dir="${libs}" includes="*.jar" />
|
<fileset dir="${source.libs}" includes="*.jar" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<!-- Copy config files -->
|
<!-- Copy config files -->
|
||||||
<mkdir dir="${product}/config"/>
|
<mkdir dir="${output.config}"/>
|
||||||
|
|
||||||
<copy todir="${product}/config">
|
<copy todir="${output.config}">
|
||||||
<fileset dir="${configs}">
|
<fileset dir="${source.configs}">
|
||||||
<include name="*.rules"/>
|
<include name="*.rules"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<!-- Copy image files -->
|
<!-- Copy image files -->
|
||||||
<mkdir dir="${product}/images"/>
|
<mkdir dir="${output.images}"/>
|
||||||
<copy todir="${product}/images">
|
<copy todir="${output.images}">
|
||||||
<fileset dir="${images}" />
|
<fileset dir="${source.images}" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<!-- Copy license file -->
|
<!-- Copy license file -->
|
||||||
<copy todir="${product}">
|
<copy todir="${output.folder}">
|
||||||
<fileset dir="${license}" />
|
<fileset dir="${source.license}">
|
||||||
|
<include name="license.txt"/>
|
||||||
|
</fileset>
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<!-- Copy program scripts -->
|
<!-- Copy program scripts -->
|
||||||
<copy todir="${product}">
|
<copy todir="${output.folder}">
|
||||||
<fileset dir="${scripts}" />
|
<fileset dir="${source.scripts}" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<!-- Set linux permissions on start script -->
|
|
||||||
<chmod file="${product}/run_sdrtrunk_linux.sh" perm="777" />
|
|
||||||
|
|
||||||
<tstamp prefix="build" />
|
|
||||||
|
|
||||||
<property name="filename" value="sdrtrunk_${build.DSTAMP}_${build.TSTAMP}_${major}.${minor}.${build}-${release}"/>
|
|
||||||
|
|
||||||
<!-- Bundle the files into .zip -->
|
<!-- Bundle the files into .zip -->
|
||||||
<zip destfile="${product}/${filename}.zip" basedir="${product}" />
|
<zip destfile="${output}/${filename}.zip" basedir="${output}" />
|
||||||
|
|
||||||
|
<!-- Remove linux start script before tar task -->
|
||||||
|
<delete file="${output.folder}/run_sdrtrunk_linux.sh"/>
|
||||||
|
|
||||||
|
<!-- Bundle files into .tar.gz and copy/set permissions on linux start script -->
|
||||||
|
<tar destfile="${output}/${filename}.tar.gz" compression="gzip">
|
||||||
|
<tarfileset dir="${output.folder}" prefix="sdrtrunk" />
|
||||||
|
<tarfileset dir="${source.scripts}" prefix="sdrtrunk" filemode="777">
|
||||||
|
<include name="run_sdrtrunk_linux.sh"/>
|
||||||
|
</tarfileset>
|
||||||
|
</tar>
|
||||||
|
|
||||||
<!-- Bundle the files into .tar.gz to preserve linux script permissions -->
|
<delete dir="${output.folder}"/>
|
||||||
<tar destfile="${product}/${filename}.tar.gz" compression="gzip">
|
|
||||||
<tarfileset dir="${product}" filemode="777">
|
|
||||||
<include name="*.sh"/>
|
|
||||||
</tarfileset>
|
|
||||||
<tarfileset dir="${product}">
|
|
||||||
<exclude name="*.sh"/>
|
|
||||||
<exclude name="${filename}.zip"/>
|
|
||||||
</tarfileset>
|
|
||||||
</tar>
|
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="clean" description="Clean previous build artifacts" >
|
<target name="clean" description="Clean previous build artifacts" >
|
||||||
<delete dir="${classes}"/>
|
<delete dir="${output.classes}"/>
|
||||||
<delete dir="${product}"/>
|
<delete dir="${output}"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
2
scripts/run_sdrtrunk_linux.sh
Normal file → Executable file
2
scripts/run_sdrtrunk_linux.sh
Normal file → Executable file
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk&
|
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk
|
||||||
@@ -1 +1 @@
|
|||||||
java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
|
java -XX:+UseG1GC -cp "sdrtrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
|
||||||
@@ -27,7 +27,6 @@ import audio.inverted.AudioType;
|
|||||||
public class TalkgroupID extends AliasID
|
public class TalkgroupID extends AliasID
|
||||||
{
|
{
|
||||||
private String mTalkgroup;
|
private String mTalkgroup;
|
||||||
private AudioType mAudioType;
|
|
||||||
|
|
||||||
public TalkgroupID()
|
public TalkgroupID()
|
||||||
{
|
{
|
||||||
@@ -44,20 +43,9 @@ public class TalkgroupID extends AliasID
|
|||||||
mTalkgroup = talkgroup;
|
mTalkgroup = talkgroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@XmlAttribute
|
|
||||||
public AudioType getAudioType()
|
|
||||||
{
|
|
||||||
return mAudioType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAudioType( AudioType type )
|
|
||||||
{
|
|
||||||
mAudioType = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasAudioType()
|
public boolean hasAudioType()
|
||||||
{
|
{
|
||||||
return mAudioType != null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
@@ -66,13 +54,6 @@ public class TalkgroupID extends AliasID
|
|||||||
|
|
||||||
sb.append( "Talkgroup: " + mTalkgroup );
|
sb.append( "Talkgroup: " + mTalkgroup );
|
||||||
|
|
||||||
if( mAudioType != null )
|
|
||||||
{
|
|
||||||
sb.append( " [Audio " );
|
|
||||||
sb.append( mAudioType.getShortDisplayString() );
|
|
||||||
sb.append( "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,9 +20,7 @@ package alias.id.talkgroup;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
import javax.swing.DefaultComboBoxModel;
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComboBox;
|
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
@@ -30,7 +28,6 @@ import javax.swing.JTextArea;
|
|||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import audio.inverted.AudioType;
|
|
||||||
import controller.ConfigurableNode;
|
import controller.ConfigurableNode;
|
||||||
|
|
||||||
public class TalkgroupIDEditor extends JPanel implements ActionListener
|
public class TalkgroupIDEditor extends JPanel implements ActionListener
|
||||||
@@ -38,19 +35,15 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private TalkgroupIDNode mTalkgroupIDNode;
|
private TalkgroupIDNode mTalkgroupIDNode;
|
||||||
|
|
||||||
private JLabel mLabelName;
|
|
||||||
private JTextField mTextTalkgroupID;
|
private JTextField mTextTalkgroupID;
|
||||||
private JLabel mLabelAudio;
|
|
||||||
private JComboBox<AudioType> mComboAudioTypes;
|
|
||||||
|
|
||||||
private String mHelpText = "Enter a formatted talkgroup identifier.\n\n"
|
private String mHelpText = "Enter a formatted talkgroup identifier.\n\n"
|
||||||
|
+ "P25 Talkgroup Format: hexadecimal value, 4 characters for "
|
||||||
|
+ "talkgroup or 6 characters for a mobile radio\n\n"
|
||||||
+ "LTR Talkgroup Format: A-HH-TTT (A=Area, H=Home Repeater, "
|
+ "LTR Talkgroup Format: A-HH-TTT (A=Area, H=Home Repeater, "
|
||||||
+ "T=Talkgroup)\n\n"
|
+ "T=Talkgroup)\n\n"
|
||||||
+ "Passport Talkgroup Format: xxxxx (up to 5-digit number)\n\n"
|
+ "Passport Talkgroup Format: xxxxx (up to 5-digit number)\n\n"
|
||||||
+ "Wildcard: use one or more asterisks (*) for any talkgroup digits.\n\n"
|
+ "Wildcard: use one or more asterisks (*) for any talkgroup digits.";
|
||||||
+ "Audio: if the talkgroup uses simple frequency inversion, specify"
|
|
||||||
+ " the inversion frequency from the dropdown list to automatically"
|
|
||||||
+ " un-invert audio for this specific talkgroup";
|
|
||||||
|
|
||||||
public TalkgroupIDEditor( TalkgroupIDNode talkgroupIDNode )
|
public TalkgroupIDEditor( TalkgroupIDNode talkgroupIDNode )
|
||||||
{
|
{
|
||||||
@@ -72,22 +65,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
|
|
||||||
add( mTextTalkgroupID, "growx,push" );
|
add( mTextTalkgroupID, "growx,push" );
|
||||||
|
|
||||||
add( new JLabel( "Audio:" ) );
|
|
||||||
|
|
||||||
mComboAudioTypes = new JComboBox<AudioType>();
|
|
||||||
|
|
||||||
mComboAudioTypes.setModel(
|
|
||||||
new DefaultComboBoxModel<AudioType>( AudioType.values() ) );
|
|
||||||
|
|
||||||
AudioType audioType = mTalkgroupIDNode.getTalkgroupID().getAudioType();
|
|
||||||
|
|
||||||
if( audioType != null )
|
|
||||||
{
|
|
||||||
mComboAudioTypes.setSelectedItem( audioType );
|
|
||||||
}
|
|
||||||
|
|
||||||
add( mComboAudioTypes, "growx,push" );
|
|
||||||
|
|
||||||
JButton btnSave = new JButton( "Save" );
|
JButton btnSave = new JButton( "Save" );
|
||||||
btnSave.addActionListener( TalkgroupIDEditor.this );
|
btnSave.addActionListener( TalkgroupIDEditor.this );
|
||||||
add( btnSave, "growx,push" );
|
add( btnSave, "growx,push" );
|
||||||
@@ -117,14 +94,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
|
|
||||||
((ConfigurableNode)mTalkgroupIDNode.getParent()).sort();
|
((ConfigurableNode)mTalkgroupIDNode.getParent()).sort();
|
||||||
|
|
||||||
AudioType selected = mComboAudioTypes
|
|
||||||
.getItemAt( mComboAudioTypes.getSelectedIndex() );
|
|
||||||
|
|
||||||
if( selected != null )
|
|
||||||
{
|
|
||||||
mTalkgroupIDNode.getTalkgroupID().setAudioType( selected );
|
|
||||||
}
|
|
||||||
|
|
||||||
mTalkgroupIDNode.save();
|
mTalkgroupIDNode.save();
|
||||||
|
|
||||||
mTalkgroupIDNode.show();
|
mTalkgroupIDNode.show();
|
||||||
@@ -139,9 +108,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
{
|
{
|
||||||
mTextTalkgroupID.setText(
|
mTextTalkgroupID.setText(
|
||||||
mTalkgroupIDNode.getTalkgroupID().getTalkgroup() );
|
mTalkgroupIDNode.getTalkgroupID().getTalkgroup() );
|
||||||
|
|
||||||
mComboAudioTypes.setSelectedItem(
|
|
||||||
mTalkgroupIDNode.getTalkgroupID().getAudioType() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mTalkgroupIDNode.refresh();
|
mTalkgroupIDNode.refresh();
|
||||||
|
|||||||
@@ -48,8 +48,11 @@ public class AudioChannelPanel extends JPanel
|
|||||||
private JLabel mFromLabel = new JLabel( "FROM:" );
|
private JLabel mFromLabel = new JLabel( "FROM:" );
|
||||||
private JLabel mFrom = new JLabel( "" );
|
private JLabel mFrom = new JLabel( "" );
|
||||||
private JLabel mFromAlias = new JLabel( "" );
|
private JLabel mFromAlias = new JLabel( "" );
|
||||||
|
|
||||||
|
private enum AudioStatus { STARTED,STOPPED };
|
||||||
|
private AudioStatus mAudioStatus = AudioStatus.STOPPED;
|
||||||
|
|
||||||
private boolean mConfigured = false;
|
private AudioMetadata mAudioMetadata;
|
||||||
|
|
||||||
public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput )
|
public AudioChannelPanel( SettingsManager settingsManager, AudioOutput audioOutput )
|
||||||
{
|
{
|
||||||
@@ -134,15 +137,19 @@ public class AudioChannelPanel extends JPanel
|
|||||||
{
|
{
|
||||||
switch( audioEvent.getType() )
|
switch( audioEvent.getType() )
|
||||||
{
|
{
|
||||||
case AUDIO_STOPPED:
|
case AUDIO_STARTED:
|
||||||
EventQueue.invokeLater( new Runnable()
|
synchronized( mAudioStatus )
|
||||||
{
|
{
|
||||||
@Override
|
mAudioStatus = AudioStatus.STARTED;
|
||||||
public void run()
|
updateLabels();
|
||||||
{
|
}
|
||||||
resetLabels();
|
break;
|
||||||
}
|
case AUDIO_STOPPED:
|
||||||
} );
|
synchronized( mAudioStatus )
|
||||||
|
{
|
||||||
|
mAudioStatus = AudioStatus.STOPPED;
|
||||||
|
resetLabels();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case AUDIO_MUTED:
|
case AUDIO_MUTED:
|
||||||
case AUDIO_UNMUTED:
|
case AUDIO_UNMUTED:
|
||||||
@@ -166,13 +173,55 @@ public class AudioChannelPanel extends JPanel
|
|||||||
*/
|
*/
|
||||||
private void resetLabels()
|
private void resetLabels()
|
||||||
{
|
{
|
||||||
mFrom.setText( "" );
|
EventQueue.invokeLater( new Runnable()
|
||||||
updateAlias( mFromAlias, null );
|
{
|
||||||
|
@Override
|
||||||
mTo.setText( "" );
|
public void run()
|
||||||
updateAlias( mToAlias, null );
|
{
|
||||||
|
mFrom.setText( "" );
|
||||||
mConfigured = false;
|
updateAlias( mFromAlias, null );
|
||||||
|
|
||||||
|
mTo.setText( "" );
|
||||||
|
updateAlias( mToAlias, null );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLabels()
|
||||||
|
{
|
||||||
|
if( mAudioMetadata != null )
|
||||||
|
{
|
||||||
|
final Metadata from = mAudioMetadata.getMetadata( MetadataType.FROM );
|
||||||
|
|
||||||
|
final Metadata to = mAudioMetadata.getMetadata( MetadataType.TO );
|
||||||
|
|
||||||
|
EventQueue.invokeLater( new Runnable()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
if( from != null )
|
||||||
|
{
|
||||||
|
mFrom.setText( from.getValue() );
|
||||||
|
updateAlias( mFromAlias, from.getAlias() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFrom.setText( "-----" );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( to != null )
|
||||||
|
{
|
||||||
|
mTo.setText( to.getValue() );
|
||||||
|
updateAlias( mToAlias, to.getAlias() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mTo.setText( "-----" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,32 +263,17 @@ public class AudioChannelPanel extends JPanel
|
|||||||
@Override
|
@Override
|
||||||
public void receive( AudioMetadata audioMetadata )
|
public void receive( AudioMetadata audioMetadata )
|
||||||
{
|
{
|
||||||
if( !mConfigured || audioMetadata.isUpdated() )
|
mAudioMetadata = audioMetadata;
|
||||||
|
|
||||||
|
if( audioMetadata.isUpdated() )
|
||||||
{
|
{
|
||||||
final Metadata from = audioMetadata.getMetadata( MetadataType.FROM );
|
synchronized( mAudioStatus )
|
||||||
|
|
||||||
final Metadata to = audioMetadata.getMetadata( MetadataType.TO );
|
|
||||||
|
|
||||||
EventQueue.invokeLater( new Runnable()
|
|
||||||
{
|
{
|
||||||
@Override
|
if( mAudioStatus == AudioStatus.STARTED )
|
||||||
public void run()
|
|
||||||
{
|
{
|
||||||
if( from != null )
|
updateLabels();
|
||||||
{
|
|
||||||
mFrom.setText( from.getValue() );
|
|
||||||
updateAlias( mFromAlias, from.getAlias() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( to != null )
|
|
||||||
{
|
|
||||||
mTo.setText( to.getValue() );
|
|
||||||
updateAlias( mToAlias, to.getAlias() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} );
|
}
|
||||||
|
|
||||||
mConfigured = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package audio;
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,16 +15,14 @@ package audio;
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
package audio;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -37,10 +33,12 @@ import javax.sound.sampled.Mixer;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import properties.SystemProperties;
|
||||||
import sample.Broadcaster;
|
import sample.Broadcaster;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import source.mixer.MixerChannel;
|
import source.mixer.MixerChannel;
|
||||||
import source.mixer.MixerChannelConfiguration;
|
import source.mixer.MixerChannelConfiguration;
|
||||||
|
import source.mixer.MixerManager;
|
||||||
import audio.AudioEvent.Type;
|
import audio.AudioEvent.Type;
|
||||||
import audio.output.AudioOutput;
|
import audio.output.AudioOutput;
|
||||||
import audio.output.MonoAudioOutput;
|
import audio.output.MonoAudioOutput;
|
||||||
@@ -52,8 +50,10 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
|
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
|
||||||
|
|
||||||
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
|
public static final int AUDIO_TIMEOUT = 1000; //1 second
|
||||||
public static final int NO_SOURCE = 0;
|
|
||||||
|
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
|
||||||
|
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
|
||||||
|
|
||||||
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
||||||
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null );
|
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null );
|
||||||
@@ -62,19 +62,19 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null );
|
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null );
|
||||||
|
|
||||||
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
|
||||||
private Map<Integer,AudioChannelAssignment> mChannelAssignments = new HashMap<>();
|
private Map<Integer,AudioOutputConnection> mChannelConnectionMap = new HashMap<>();
|
||||||
private int mLowestPrioritySource;
|
private List<AudioOutputConnection> mAudioOutputConnections = new ArrayList<>();
|
||||||
|
private AudioOutputConnection mLowestPriorityConnection;
|
||||||
|
private int mAvailableConnectionCount;
|
||||||
|
|
||||||
private List<AudioOutput> mAvailableAudioOutputs = new ArrayList<>();
|
|
||||||
private Map<String,AudioOutput> mAudioOutputMap = new HashMap<>();
|
private Map<String,AudioOutput> mAudioOutputMap = new HashMap<>();
|
||||||
private List<String> mChannelNames = new ArrayList<>();
|
|
||||||
|
|
||||||
private Broadcaster<AudioEvent> mControllerBroadcaster = new Broadcaster<>();
|
private Broadcaster<AudioEvent> mControllerBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
private ScheduledFuture<?> mProcessingTask;
|
private ScheduledFuture<?> mProcessingTask;
|
||||||
|
|
||||||
private MixerChannelConfiguration mMixerChannel;
|
private MixerChannelConfiguration mMixerChannelConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes all audio produced by the decoding channels and routes audio
|
* Processes all audio produced by the decoding channels and routes audio
|
||||||
@@ -85,26 +85,66 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
{
|
{
|
||||||
mThreadPoolManager = manager;
|
mThreadPoolManager = manager;
|
||||||
|
|
||||||
//TODO: save audio system preferences to the settings manager
|
loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSettings()
|
||||||
|
{
|
||||||
|
MixerChannelConfiguration configuration = null;
|
||||||
|
|
||||||
/* Use the system default mixer and mono channel as default startup */
|
SystemProperties properties = SystemProperties.getInstance();
|
||||||
Mixer mixer = AudioSystem.getMixer( null );
|
|
||||||
|
|
||||||
if( mixer != null )
|
Mixer defaultMixer = AudioSystem.getMixer( null );
|
||||||
|
|
||||||
|
String mixer = properties.get( AUDIO_MIXER_PROPERTY, defaultMixer.getMixerInfo().getName() );
|
||||||
|
|
||||||
|
String channels = properties.get( AUDIO_CHANNELS_PROPERTY, MixerChannel.MONO.name() );
|
||||||
|
|
||||||
|
MixerChannelConfiguration[] mixerConfigurations =
|
||||||
|
MixerManager.getInstance().getOutputMixers();
|
||||||
|
|
||||||
|
for( MixerChannelConfiguration mixerConfig: mixerConfigurations )
|
||||||
{
|
{
|
||||||
mMixerChannel = new MixerChannelConfiguration( mixer, MixerChannel.MONO );
|
if( mixerConfig.matches( mixer, channels ) )
|
||||||
|
{
|
||||||
|
configuration = mixerConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( configuration == null )
|
||||||
|
{
|
||||||
|
configuration = getDefaultConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setMixerChannelConfiguration( configuration );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
mLog.error( "Couldn't set stored audio mixer/channel configuration"
|
||||||
|
+ " - using default", e );
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
setMixerChannelConfiguration( mMixerChannel );
|
setMixerChannelConfiguration( getDefaultConfiguration() );
|
||||||
}
|
}
|
||||||
catch ( AudioException e )
|
catch( Exception e2 )
|
||||||
{
|
{
|
||||||
mLog.error( "Couldn't create an audio output device", e );
|
mLog.error( "Couldn't set default audio mixer/channel "
|
||||||
|
+ "configuration - no audio will be available", e2 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MixerChannelConfiguration getDefaultConfiguration()
|
||||||
|
{
|
||||||
|
/* Use the system default mixer and mono channel as default startup */
|
||||||
|
Mixer defaultMixer = AudioSystem.getMixer( null );
|
||||||
|
|
||||||
|
return new MixerChannelConfiguration( defaultMixer, MixerChannel.MONO );
|
||||||
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
if( mThreadPoolManager != null && mProcessingTask != null )
|
if( mThreadPoolManager != null && mProcessingTask != null )
|
||||||
@@ -116,6 +156,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
mThreadPoolManager = null;
|
mThreadPoolManager = null;
|
||||||
mProcessingTask = null;
|
mProcessingTask = null;
|
||||||
|
|
||||||
|
mChannelConnectionMap.clear();
|
||||||
|
|
||||||
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
|
{
|
||||||
|
connection.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioOutputConnections.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -124,207 +173,44 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
mAudioPacketQueue.add( packet );
|
mAudioPacketQueue.add( packet );
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AudioPacketProcessor implements Runnable
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
removeCompletedChannelAssignments();
|
|
||||||
|
|
||||||
if( mAudioPacketQueue != null )
|
|
||||||
{
|
|
||||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
|
||||||
|
|
||||||
mAudioPacketQueue.drainTo( packets );
|
|
||||||
|
|
||||||
for( AudioPacket packet: packets )
|
|
||||||
{
|
|
||||||
/* Don't process any packet's marked as do not monitor */
|
|
||||||
if( !packet.getAudioMetadata().isDoNotMonitor() )
|
|
||||||
{
|
|
||||||
int source = packet.getAudioMetadata().getSource();
|
|
||||||
|
|
||||||
if( packet.getType() != AudioPacket.Type.END )
|
|
||||||
{
|
|
||||||
/* Existing channel assignment */
|
|
||||||
if( mChannelAssignments.containsKey( source ) )
|
|
||||||
{
|
|
||||||
mChannelAssignments.get( source ).getAudioOutput()
|
|
||||||
.receive( packet );
|
|
||||||
}
|
|
||||||
/* Start a new channel assignment */
|
|
||||||
else if( !mAvailableAudioOutputs.isEmpty() )
|
|
||||||
{
|
|
||||||
AudioOutput channel = mAvailableAudioOutputs.remove( 0 );
|
|
||||||
|
|
||||||
mChannelAssignments.put( packet.getAudioMetadata().getSource(),
|
|
||||||
new AudioChannelAssignment( channel, packet.getAudioMetadata().getPriority() ) );
|
|
||||||
|
|
||||||
updateLowestPriorityAssignment();
|
|
||||||
|
|
||||||
channel.receive( packet );
|
|
||||||
}
|
|
||||||
/* Override the lowest priority assignment */
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if( mLowestPrioritySource > -1 )
|
|
||||||
{
|
|
||||||
int priority = packet.getAudioMetadata().getPriority();
|
|
||||||
|
|
||||||
if( packet.getAudioMetadata().isSelected() ||
|
|
||||||
priority < mChannelAssignments
|
|
||||||
.get( mLowestPrioritySource ).getPriority() )
|
|
||||||
{
|
|
||||||
AudioChannelAssignment assignment =
|
|
||||||
mChannelAssignments.remove( mLowestPrioritySource );
|
|
||||||
|
|
||||||
if( assignment != null )
|
|
||||||
{
|
|
||||||
assignment.setPriority( priority );
|
|
||||||
|
|
||||||
int overrideSource = packet.getAudioMetadata().getSource();
|
|
||||||
|
|
||||||
mChannelAssignments.put( overrideSource, assignment );
|
|
||||||
|
|
||||||
mLowestPrioritySource = overrideSource;
|
|
||||||
|
|
||||||
assignment.getAudioOutput().receive( packet );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLowestPrioritySource = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks each audio channel assignment and removes any completed channels.
|
* Checks each audio channel assignment and disconnects any inactive connections
|
||||||
*/
|
*/
|
||||||
private void removeCompletedChannelAssignments()
|
private void disconnectInactiveChannelAssignments()
|
||||||
{
|
{
|
||||||
if( !mChannelAssignments.isEmpty() )
|
boolean changed = false;
|
||||||
|
|
||||||
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
{
|
{
|
||||||
Iterator<Entry<Integer,AudioChannelAssignment>> it =
|
if( connection.isInactive() &&
|
||||||
mChannelAssignments.entrySet().iterator();
|
mChannelConnectionMap.containsKey( connection.getSource() ) )
|
||||||
|
|
||||||
Entry<Integer,AudioChannelAssignment> entry;
|
|
||||||
|
|
||||||
boolean removed = false;
|
|
||||||
|
|
||||||
while( it.hasNext() )
|
|
||||||
{
|
{
|
||||||
entry = it.next();
|
mChannelConnectionMap.remove( connection.getSource() );
|
||||||
|
connection.disconnect();
|
||||||
if( entry.getValue().isComplete() )
|
changed = true;
|
||||||
{
|
|
||||||
mAvailableAudioOutputs.add( entry.getValue().getAudioOutput() );
|
|
||||||
entry.getValue().dispose();
|
|
||||||
it.remove();
|
|
||||||
removed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( removed )
|
|
||||||
{
|
|
||||||
updateLowestPriorityAssignment();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( changed )
|
||||||
|
{
|
||||||
|
updateLowestPriorityAssignment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the lowest priority channel assignment ... the one that can
|
* Identifies the lowest priority channel connection
|
||||||
* be overridden with a higher priority call
|
|
||||||
*/
|
*/
|
||||||
private void updateLowestPriorityAssignment()
|
private void updateLowestPriorityAssignment()
|
||||||
{
|
{
|
||||||
int lowestPriority = -1;
|
mLowestPriorityConnection = null;
|
||||||
|
|
||||||
mLowestPrioritySource = -1;
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
|
|
||||||
for( int source: mChannelAssignments.keySet() )
|
|
||||||
{
|
{
|
||||||
AudioChannelAssignment assignment = mChannelAssignments.get( source );
|
if( connection.isConnected() &&
|
||||||
|
( mLowestPriorityConnection == null ||
|
||||||
if( assignment.getPriority() > lowestPriority )
|
mLowestPriorityConnection.getPriority() > connection.getPriority() ) )
|
||||||
{
|
{
|
||||||
lowestPriority = assignment.getPriority();
|
mLowestPriorityConnection = connection;
|
||||||
|
|
||||||
mLowestPrioritySource = source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endAllAudioAssignments()
|
|
||||||
{
|
|
||||||
for( AudioOutput output: mAudioOutputMap.values() )
|
|
||||||
{
|
|
||||||
output.receive( new AudioPacket( AudioPacket.Type.END, null ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AudioChannelAssignment implements Listener<AudioEvent>
|
|
||||||
{
|
|
||||||
private static final long CALL_END_DELAY = 2000; //2 seconds
|
|
||||||
|
|
||||||
private AudioOutput mAudioOutput;
|
|
||||||
private int mPriority;
|
|
||||||
private long mAudioStoppedTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
public AudioChannelAssignment( AudioOutput audioOutput, int priority )
|
|
||||||
{
|
|
||||||
mAudioOutput = audioOutput;
|
|
||||||
mPriority = priority;
|
|
||||||
|
|
||||||
mAudioOutput.addAudioEventListener( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isComplete()
|
|
||||||
{
|
|
||||||
return mAudioStoppedTime != Long.MAX_VALUE &&
|
|
||||||
mAudioStoppedTime + CALL_END_DELAY < System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
mAudioOutput.removeAudioEventListener( this );
|
|
||||||
mAudioOutput.reset();
|
|
||||||
mAudioOutput = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AudioOutput getAudioOutput()
|
|
||||||
{
|
|
||||||
return mAudioOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPriority()
|
|
||||||
{
|
|
||||||
return mPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPriority( int priority )
|
|
||||||
{
|
|
||||||
mPriority = priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive( AudioEvent event )
|
|
||||||
{
|
|
||||||
switch( event.getType() )
|
|
||||||
{
|
|
||||||
case AUDIO_STARTED:
|
|
||||||
case AUDIO_STOPPED:
|
|
||||||
case AUDIO_CONTINUATION:
|
|
||||||
mAudioStoppedTime = System.currentTimeMillis();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,41 +228,29 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
{
|
{
|
||||||
mThreadPoolManager.cancel( mProcessingTask );
|
mThreadPoolManager.cancel( mProcessingTask );
|
||||||
}
|
}
|
||||||
|
|
||||||
endAllAudioAssignments();
|
|
||||||
|
|
||||||
for( Entry<Integer,AudioChannelAssignment> e: mChannelAssignments.entrySet() )
|
disposeCurrentConfiguration();
|
||||||
{
|
|
||||||
e.getValue().getAudioOutput().dispose();
|
|
||||||
}
|
|
||||||
mChannelAssignments.clear();
|
|
||||||
|
|
||||||
for( AudioOutput output: mAvailableAudioOutputs )
|
|
||||||
{
|
|
||||||
output.dispose();
|
|
||||||
}
|
|
||||||
mAvailableAudioOutputs.clear();
|
|
||||||
|
|
||||||
mChannelNames.clear();
|
|
||||||
mAudioOutputMap.clear();
|
|
||||||
|
|
||||||
switch( entry.getMixerChannel() )
|
switch( entry.getMixerChannel() )
|
||||||
{
|
{
|
||||||
case MONO:
|
case MONO:
|
||||||
AudioOutput mono = new MonoAudioOutput( entry.getMixer() );
|
AudioOutput mono = new MonoAudioOutput( mThreadPoolManager,
|
||||||
mAvailableAudioOutputs.add( mono );
|
entry.getMixer() );
|
||||||
mChannelNames.add( mono.getChannelName() );
|
mAudioOutputConnections.add( new AudioOutputConnection( mono ) );
|
||||||
|
mAvailableConnectionCount++;
|
||||||
mAudioOutputMap.put( mono.getChannelName(), mono );
|
mAudioOutputMap.put( mono.getChannelName(), mono );
|
||||||
break;
|
break;
|
||||||
case STEREO:
|
case STEREO:
|
||||||
AudioOutput left = new StereoAudioOutput( entry.getMixer(), MixerChannel.LEFT );
|
AudioOutput left = new StereoAudioOutput( mThreadPoolManager,
|
||||||
mAvailableAudioOutputs.add( left );
|
entry.getMixer(), MixerChannel.LEFT );
|
||||||
mChannelNames.add( left.getChannelName() );
|
mAudioOutputConnections.add( new AudioOutputConnection( left ) );
|
||||||
|
mAvailableConnectionCount++;
|
||||||
mAudioOutputMap.put( left.getChannelName(), left );
|
mAudioOutputMap.put( left.getChannelName(), left );
|
||||||
|
|
||||||
AudioOutput right = new StereoAudioOutput( entry.getMixer(), MixerChannel.RIGHT );
|
AudioOutput right = new StereoAudioOutput( mThreadPoolManager,
|
||||||
mAvailableAudioOutputs.add( right );
|
entry.getMixer(), MixerChannel.RIGHT );
|
||||||
mChannelNames.add( right.getChannelName() );
|
mAudioOutputConnections.add( new AudioOutputConnection( right ) );
|
||||||
|
mAvailableConnectionCount++;
|
||||||
mAudioOutputMap.put( right.getChannelName(), right );
|
mAudioOutputMap.put( right.getChannelName(), right );
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -389,13 +263,42 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
TimeUnit.MILLISECONDS );
|
TimeUnit.MILLISECONDS );
|
||||||
|
|
||||||
mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_COMPLETE );
|
mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_COMPLETE );
|
||||||
|
|
||||||
|
SystemProperties properties = SystemProperties.getInstance();
|
||||||
|
|
||||||
|
properties.set( AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName() );
|
||||||
|
properties.set( AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name() );
|
||||||
|
properties.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all channel assignments and terminates all audio outputs in
|
||||||
|
* preparation for complete shutdown or change to another mixer/channel
|
||||||
|
* configuration
|
||||||
|
*/
|
||||||
|
private void disposeCurrentConfiguration()
|
||||||
|
{
|
||||||
|
mChannelConnectionMap.clear();
|
||||||
|
|
||||||
|
for( AudioOutputConnection connection : mAudioOutputConnections )
|
||||||
|
{
|
||||||
|
connection.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mAvailableConnectionCount = 0;
|
||||||
|
|
||||||
|
mAudioOutputConnections.clear();
|
||||||
|
|
||||||
|
mAudioOutputMap.clear();
|
||||||
|
|
||||||
|
mLowestPriorityConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
||||||
{
|
{
|
||||||
return mMixerChannel;
|
return mMixerChannelConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -426,4 +329,220 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
{
|
{
|
||||||
mControllerBroadcaster.removeListener( listener );
|
mControllerBroadcaster.removeListener( listener );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @return an audio output connection or null
|
||||||
|
*/
|
||||||
|
private AudioOutputConnection getConnection( AudioPacket packet )
|
||||||
|
{
|
||||||
|
int source = packet.getAudioMetadata().getSource();
|
||||||
|
|
||||||
|
//Use an existing connection
|
||||||
|
if( mChannelConnectionMap.containsKey( source ) )
|
||||||
|
{
|
||||||
|
return mChannelConnectionMap.get( source );
|
||||||
|
}
|
||||||
|
|
||||||
|
//Connect to and use an available connection
|
||||||
|
if( mAvailableConnectionCount > 0 )
|
||||||
|
{
|
||||||
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
|
{
|
||||||
|
if( connection.isDisconnected() )
|
||||||
|
{
|
||||||
|
connection.connect( source );
|
||||||
|
mChannelConnectionMap.put( source, connection );
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Preempt an existing connection and connect it to this higher priority source
|
||||||
|
else //Check for a channel priority override
|
||||||
|
{
|
||||||
|
int priority = packet.getAudioMetadata().getPriority();
|
||||||
|
|
||||||
|
if( mLowestPriorityConnection != null &&
|
||||||
|
priority < mLowestPriorityConnection.getPriority() )
|
||||||
|
{
|
||||||
|
AudioOutputConnection connection = mLowestPriorityConnection;
|
||||||
|
|
||||||
|
mChannelConnectionMap.remove( connection.getSource() );
|
||||||
|
|
||||||
|
connection.connect( source );
|
||||||
|
|
||||||
|
mChannelConnectionMap.put( source, connection );
|
||||||
|
|
||||||
|
updateLowestPriorityAssignment();
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AudioPacketProcessor implements Runnable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
disconnectInactiveChannelAssignments();
|
||||||
|
|
||||||
|
if( mAudioPacketQueue != null )
|
||||||
|
{
|
||||||
|
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
||||||
|
|
||||||
|
mAudioPacketQueue.drainTo( packets );
|
||||||
|
|
||||||
|
for( AudioPacket packet: packets )
|
||||||
|
{
|
||||||
|
/* Don't process any packet's marked as do not monitor */
|
||||||
|
if( !packet.getAudioMetadata().isDoNotMonitor() &&
|
||||||
|
packet.getType() == AudioPacket.Type.AUDIO )
|
||||||
|
{
|
||||||
|
AudioOutputConnection connection = getConnection( packet );
|
||||||
|
|
||||||
|
if( connection != null )
|
||||||
|
{
|
||||||
|
connection.receive( packet );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
mLog.error( "Encountered error while processing audio packets", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio output connection manages a connection between a source and an audio
|
||||||
|
* output and maintains current state information about the audio activity
|
||||||
|
* received form the source.
|
||||||
|
*/
|
||||||
|
public class AudioOutputConnection
|
||||||
|
{
|
||||||
|
private static final int DISCONNECTED = -1;
|
||||||
|
|
||||||
|
private AudioOutput mAudioOutput;
|
||||||
|
private int mPriority = 0;
|
||||||
|
private int mSource = DISCONNECTED;
|
||||||
|
|
||||||
|
public AudioOutputConnection( AudioOutput audioOutput )
|
||||||
|
{
|
||||||
|
mAudioOutput = audioOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive( AudioPacket packet )
|
||||||
|
{
|
||||||
|
if( packet.hasAudioMetadata() &&
|
||||||
|
packet.getAudioMetadata().getSource() == mSource )
|
||||||
|
{
|
||||||
|
int priority = packet.getAudioMetadata().getPriority();
|
||||||
|
|
||||||
|
if( mPriority != priority )
|
||||||
|
{
|
||||||
|
mPriority = priority;
|
||||||
|
updateLowestPriorityAssignment();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mAudioOutput != null )
|
||||||
|
{
|
||||||
|
mAudioOutput.receive( packet );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( packet.hasAudioMetadata() )
|
||||||
|
{
|
||||||
|
mLog.error( "Received audio packet from channel source [" +
|
||||||
|
packet.getAudioMetadata().getSource() +
|
||||||
|
"] however this assignment is currently connected "
|
||||||
|
+ "to channel source [" + mSource + "]" );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error( "Received audio packet with no metadata - "
|
||||||
|
+ "cannot route audio packet" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates the audio output and prepares this connection for disposal
|
||||||
|
*/
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
mAudioOutput.dispose();
|
||||||
|
mAudioOutput = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this assignment is currently disconnected from a channel source
|
||||||
|
*/
|
||||||
|
public boolean isDisconnected()
|
||||||
|
{
|
||||||
|
return mSource == DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this assignment is currently connected to a channel source
|
||||||
|
*/
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return !isDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 )
|
||||||
|
{
|
||||||
|
mSource = source;
|
||||||
|
|
||||||
|
mAudioOutput.updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently connected source or -1 if disconnected
|
||||||
|
*/
|
||||||
|
public int getSource()
|
||||||
|
{
|
||||||
|
return mSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects this assignment from the source and prevents any audio
|
||||||
|
* from being routed to the audio output until another source is assigned
|
||||||
|
*/
|
||||||
|
public void disconnect()
|
||||||
|
{
|
||||||
|
mSource = DISCONNECTED;
|
||||||
|
mPriority = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if audio output is current inactive, meaning that the
|
||||||
|
* audio output hasn't recently processed any audio packets.
|
||||||
|
*/
|
||||||
|
public boolean isInactive()
|
||||||
|
{
|
||||||
|
return ( mAudioOutput.getLastActivityTimestamp() + AUDIO_TIMEOUT ) < System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority()
|
||||||
|
{
|
||||||
|
return mPriority;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ public class AudioPacket
|
|||||||
return mAudioData;
|
return mAudioData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasAudioBuffer()
|
||||||
|
{
|
||||||
|
return mAudioData != null;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Type
|
public enum Type
|
||||||
{
|
{
|
||||||
AUDIO,
|
AUDIO,
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
package audio.metadata;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import sample.Listener;
|
|
||||||
import alias.Alias;
|
|
||||||
import alias.priority.Priority;
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,15 +15,30 @@ import alias.priority.Priority;
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
package audio.metadata;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import sample.Listener;
|
||||||
|
import alias.Alias;
|
||||||
|
import alias.priority.Priority;
|
||||||
|
|
||||||
public class AudioMetadata implements Listener<Metadata>
|
public class AudioMetadata implements Listener<Metadata>
|
||||||
{
|
{
|
||||||
protected static final Logger mLog = LoggerFactory.getLogger( AudioMetadata.class );
|
protected static final Logger mLog = LoggerFactory.getLogger( AudioMetadata.class );
|
||||||
|
|
||||||
private int mSource;
|
private int mSource;
|
||||||
private List<Metadata> mMetadata = new ArrayList<>();
|
private Map<MetadataType,Metadata> mMetadata = new HashMap<>();
|
||||||
private boolean mUpdated = false;
|
private boolean mUpdated = false;
|
||||||
|
|
||||||
/* Channel Selectect == highest priority */
|
/* Channel Selected == highest priority */
|
||||||
private boolean mSelected = false;
|
private boolean mSelected = false;
|
||||||
|
|
||||||
/* Call Priority */
|
/* Call Priority */
|
||||||
@@ -92,7 +94,8 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
copy.mPriority = mPriority;
|
copy.mPriority = mPriority;
|
||||||
copy.mSelected = mSelected;
|
copy.mSelected = mSelected;
|
||||||
copy.mRecordable = mRecordable;
|
copy.mRecordable = mRecordable;
|
||||||
copy.mMetadata.addAll( mMetadata );
|
|
||||||
|
copy.mMetadata.putAll( mMetadata );
|
||||||
copy.mUpdated = mUpdated;
|
copy.mUpdated = mUpdated;
|
||||||
copy.mIdentifier = new String( mIdentifier );
|
copy.mIdentifier = new String( mIdentifier );
|
||||||
|
|
||||||
@@ -106,27 +109,36 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
*/
|
*/
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
Iterator<Metadata> it = mMetadata.iterator();
|
|
||||||
|
|
||||||
while( it.hasNext() )
|
|
||||||
{
|
|
||||||
if( it.next().isTemporal() )
|
|
||||||
{
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset recordable state, identifier and audio call priority levels
|
/* Reset recordable state, identifier and audio call priority levels
|
||||||
* and reprocess the metadata */
|
* and reprocess the metadata */
|
||||||
mRecordable = mSourceRecordable;
|
mRecordable = mSourceRecordable;
|
||||||
setIdentifier( mSource );
|
setIdentifier( mSource );
|
||||||
mPriority = Priority.DEFAULT_PRIORITY;
|
mPriority = Priority.DEFAULT_PRIORITY;
|
||||||
|
|
||||||
for( Metadata metadata: mMetadata )
|
List<MetadataType> toRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for( MetadataType type: mMetadata.keySet() )
|
||||||
{
|
{
|
||||||
processMetadata( metadata );
|
Metadata metadata = mMetadata.get( type );
|
||||||
|
|
||||||
|
if( metadata != null )
|
||||||
|
{
|
||||||
|
if( metadata.isTemporal() )
|
||||||
|
{
|
||||||
|
toRemove.add( type );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processMetadata( metadata );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for( MetadataType type: toRemove )
|
||||||
|
{
|
||||||
|
mMetadata.remove( type );
|
||||||
|
}
|
||||||
|
|
||||||
mUpdated = true;
|
mUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +197,14 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mMetadata.add( metadata );
|
Metadata existing = mMetadata.get( metadata.getMetadataType() );
|
||||||
processMetadata( metadata );
|
|
||||||
|
if( existing == null || !existing.equals( metadata ) )
|
||||||
|
{
|
||||||
|
mMetadata.put( metadata.getMetadataType(), metadata );
|
||||||
|
processMetadata( metadata );
|
||||||
|
setUpdated( true );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mUpdated = true;
|
mUpdated = true;
|
||||||
@@ -195,26 +213,17 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
/**
|
/**
|
||||||
* Returns the complete set of metadata
|
* Returns the complete set of metadata
|
||||||
*/
|
*/
|
||||||
public List<Metadata> getMetadata()
|
public Map<MetadataType,Metadata> getMetadata()
|
||||||
{
|
{
|
||||||
return mMetadata;
|
return mMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first metadata object matching the type, or null if the
|
* Returns the metadata object matching the type or null
|
||||||
* metadata type is not found.
|
|
||||||
*/
|
*/
|
||||||
public Metadata getMetadata( MetadataType type )
|
public Metadata getMetadata( MetadataType type )
|
||||||
{
|
{
|
||||||
for( Metadata metadata: mMetadata )
|
return mMetadata.get( type );
|
||||||
{
|
|
||||||
if( metadata.getMetadataType() == type )
|
|
||||||
{
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,11 +305,11 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
sb.append( getPriority() );
|
sb.append( getPriority() );
|
||||||
sb.append( " updated:" + isUpdated() );
|
sb.append( " updated:" + isUpdated() );
|
||||||
sb.append( " " );
|
sb.append( " " );
|
||||||
for( Metadata m: getMetadata() )
|
for( Entry<MetadataType,Metadata> entry: getMetadata().entrySet() )
|
||||||
{
|
{
|
||||||
sb.append( m.getKey() );
|
sb.append( entry.getValue().getKey() );
|
||||||
sb.append( ":" );
|
sb.append( ":" );
|
||||||
sb.append( m.getValue() );
|
sb.append( entry.getValue().getValue() );
|
||||||
sb.append( " " );
|
sb.append( " " );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class Metadata
|
|||||||
mValueAlias = alias;
|
mValueAlias = alias;
|
||||||
mTemporal = temporal;
|
mTemporal = temporal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constucts a metadata object with the temporal settings. Temporal
|
* Constucts a metadata object with the temporal settings. Temporal
|
||||||
* indicates that this metadata will be removed at the end of a reset
|
* indicates that this metadata will be removed at the end of a reset
|
||||||
@@ -103,4 +103,44 @@ public class Metadata
|
|||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equals( Metadata other )
|
||||||
|
{
|
||||||
|
if( mMetadataType != other.getMetadataType() )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mTemporal != other.isTemporal() )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mValue != null && other.getValue() != null &&
|
||||||
|
!mValue.contentEquals( other.getValue() ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ( mValue == null && other.getValue() != null ) ||
|
||||||
|
( mValue != null && other.getValue() == null ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mValueAlias != null && other.getAlias() != null &&
|
||||||
|
!mValueAlias.getName().contentEquals( other.getAlias().getName() ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ( mValueAlias == null && other.getAlias() != null ) ||
|
||||||
|
( mValueAlias != null && other.getAlias() == null ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,150 @@
|
|||||||
package audio.output;
|
package audio.output;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
import javax.sound.sampled.BooleanControl;
|
||||||
|
import javax.sound.sampled.Control;
|
||||||
import javax.sound.sampled.FloatControl;
|
import javax.sound.sampled.FloatControl;
|
||||||
|
import javax.sound.sampled.Line;
|
||||||
|
import javax.sound.sampled.LineEvent;
|
||||||
|
import javax.sound.sampled.LineListener;
|
||||||
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import javax.sound.sampled.Mixer;
|
||||||
|
import javax.sound.sampled.SourceDataLine;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sample.Broadcaster;
|
import sample.Broadcaster;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
|
import source.mixer.MixerChannel;
|
||||||
import audio.AudioEvent;
|
import audio.AudioEvent;
|
||||||
import audio.AudioEvent.Type;
|
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
|
import audio.AudioPacket.Type;
|
||||||
import audio.metadata.AudioMetadata;
|
import audio.metadata.AudioMetadata;
|
||||||
|
import controller.ThreadPoolManager;
|
||||||
|
import controller.ThreadPoolManager.ThreadType;
|
||||||
|
|
||||||
public abstract class AudioOutput implements Listener<AudioPacket>
|
public abstract class AudioOutput implements Listener<AudioPacket>, LineListener
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
|
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
|
||||||
|
|
||||||
private AtomicBoolean mSquelched = new AtomicBoolean( false );
|
private LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
||||||
|
private int mBufferStartThreshold;
|
||||||
|
private int mBufferStopThreshold;
|
||||||
|
|
||||||
private Listener<AudioMetadata> mAudioMetadataListener;
|
private Listener<AudioMetadata> mAudioMetadataListener;
|
||||||
|
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
protected LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
private ScheduledFuture<?> mProcessorTask;
|
||||||
protected ScheduledExecutorService mExecutorService;
|
|
||||||
protected boolean mCanProcessAudio;
|
|
||||||
protected long mLastActivity;
|
|
||||||
|
|
||||||
|
private SourceDataLine mOutput;
|
||||||
|
private Mixer mMixer;
|
||||||
|
private MixerChannel mMixerChannel;
|
||||||
|
private FloatControl mGainControl;
|
||||||
|
private BooleanControl mMuteControl;
|
||||||
|
|
||||||
|
private AudioEvent mAudioStartEvent;
|
||||||
|
private AudioEvent mAudioStopEvent;
|
||||||
|
|
||||||
|
private boolean mCanProcessAudio = false;
|
||||||
|
private long mLastActivity = System.currentTimeMillis();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single audio channel playback with automatic starting and stopping of the
|
||||||
|
* underlying sourcedataline specified by the mixer and mixer channel
|
||||||
|
* arguments.
|
||||||
|
*
|
||||||
|
* Maintains an internal non-blocking audio packet queue and processes this
|
||||||
|
* queue 25 times a second (every 40 ms).
|
||||||
|
*
|
||||||
|
* @param threadPoolManager for assigning buffer processing schedule task
|
||||||
|
* @param mixer to obtain source data line
|
||||||
|
* @param mixerChannel either mono or left/right stereo
|
||||||
|
* @param audioFormat to use during playback
|
||||||
|
* @param lineInfo to use when obtaining the source data line
|
||||||
|
* @param requestedBufferSize of approximately 1 second of audio
|
||||||
|
*/
|
||||||
|
public AudioOutput( ThreadPoolManager threadPoolManager,
|
||||||
|
Mixer mixer,
|
||||||
|
MixerChannel mixerChannel,
|
||||||
|
AudioFormat audioFormat,
|
||||||
|
Line.Info lineInfo,
|
||||||
|
int requestedBufferSize )
|
||||||
|
{
|
||||||
|
mThreadPoolManager = threadPoolManager;
|
||||||
|
mMixer = mixer;
|
||||||
|
mMixerChannel = mixerChannel;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mOutput = (SourceDataLine)mMixer.getLine( lineInfo );
|
||||||
|
|
||||||
|
if( mOutput != null )
|
||||||
|
{
|
||||||
|
mOutput.open( audioFormat, requestedBufferSize );
|
||||||
|
|
||||||
|
//Start threshold: buffer is full with 10% or less of capacity remaining
|
||||||
|
mBufferStartThreshold = (int)( mOutput.getBufferSize() * 0.10 );
|
||||||
|
|
||||||
|
//Stop threshold: buffer is empty with 90% or more capacity available
|
||||||
|
mBufferStopThreshold = (int)( mOutput.getBufferSize() * 0.90 );
|
||||||
|
|
||||||
|
mOutput.addLineListener( this );
|
||||||
|
|
||||||
|
if( mOutput != null )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
|
||||||
|
mGainControl = (FloatControl)gain;
|
||||||
|
}
|
||||||
|
catch( IllegalArgumentException iae )
|
||||||
|
{
|
||||||
|
mLog.warn( "Couldn't obtain MASTER GAIN control for stereo line [" +
|
||||||
|
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
|
||||||
|
mMuteControl = (BooleanControl)mute;
|
||||||
|
}
|
||||||
|
catch( IllegalArgumentException iae )
|
||||||
|
{
|
||||||
|
mLog.warn( "Couldn't obtain MUTE control for stereo line [" +
|
||||||
|
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run the queue processor task every 40 milliseconds or 25 times a second */
|
||||||
|
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
|
ThreadType.AUDIO_PROCESSING, new BufferProcessor(),
|
||||||
|
40, TimeUnit.MILLISECONDS );
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
||||||
|
getChannelName() );
|
||||||
|
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
||||||
|
getChannelName() );
|
||||||
|
|
||||||
|
mCanProcessAudio = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( LineUnavailableException e )
|
||||||
|
{
|
||||||
|
mLog.error( "Couldn't obtain audio source data line for "
|
||||||
|
+ "audio output - mixer [" + mMixer.getMixerInfo().getName() + "]" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
||||||
@@ -39,52 +155,52 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
{
|
{
|
||||||
mCanProcessAudio = false;
|
mCanProcessAudio = false;
|
||||||
|
|
||||||
mBuffer.clear();
|
if( mProcessorTask != null && mThreadPoolManager != null )
|
||||||
|
|
||||||
if( mExecutorService != null )
|
|
||||||
{
|
{
|
||||||
mExecutorService.shutdown();
|
mThreadPoolManager.cancel( mProcessorTask );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mThreadPoolManager = null;
|
||||||
|
mProcessorTask = null;
|
||||||
|
|
||||||
|
mBuffer.clear();
|
||||||
|
|
||||||
mAudioEventBroadcaster.dispose();
|
mAudioEventBroadcaster.dispose();
|
||||||
mAudioEventBroadcaster = null;
|
mAudioEventBroadcaster = null;
|
||||||
mAudioMetadataListener = null;
|
mAudioMetadataListener = null;
|
||||||
}
|
|
||||||
|
|
||||||
public abstract String getChannelName();
|
|
||||||
|
|
||||||
public abstract void setMuted( boolean muted );
|
|
||||||
public abstract boolean isMuted();
|
|
||||||
|
|
||||||
public abstract FloatControl getGainControl();
|
|
||||||
public abstract boolean hasGainControl();
|
|
||||||
|
|
||||||
protected void broadcast( AudioMetadata metadata )
|
|
||||||
{
|
|
||||||
if( mAudioMetadataListener != null )
|
|
||||||
{
|
|
||||||
mAudioMetadataListener.receive( metadata );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void broadcast( AudioEvent audioEvent )
|
|
||||||
{
|
|
||||||
mAudioEventBroadcaster.broadcast( audioEvent );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSquelched( boolean squelched )
|
|
||||||
{
|
|
||||||
mSquelched.set( squelched );
|
|
||||||
|
|
||||||
broadcast( new AudioEvent( squelched ? Type.AUDIO_SQUELCHED :
|
if( mOutput != null )
|
||||||
Type.AUDIO_UNSQUELCHED, getChannelName() ) );
|
{
|
||||||
|
mOutput.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
mOutput = null;
|
||||||
|
mGainControl = null;
|
||||||
|
mMuteControl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSquelched()
|
/**
|
||||||
|
* Converts the audio packet data into a byte buffer format appropriate for
|
||||||
|
* the underlying source data line.
|
||||||
|
*/
|
||||||
|
protected abstract ByteBuffer convert( AudioPacket packet );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio output channel name
|
||||||
|
*/
|
||||||
|
public String getChannelName()
|
||||||
{
|
{
|
||||||
return mSquelched.get();
|
return mMixerChannel.getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixer Channel for this audio output
|
||||||
|
*/
|
||||||
|
protected MixerChannel getMixerChannel()
|
||||||
|
{
|
||||||
|
return mMixerChannel;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a single listener to receive audio start and audio stop events
|
* Registers a single listener to receive audio start and audio stop events
|
||||||
*/
|
*/
|
||||||
@@ -98,6 +214,14 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
mAudioEventBroadcaster.removeListener( listener );
|
mAudioEventBroadcaster.removeListener( listener );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts an audio event to the registered listener
|
||||||
|
*/
|
||||||
|
private void broadcast( AudioEvent audioEvent )
|
||||||
|
{
|
||||||
|
mAudioEventBroadcaster.broadcast( audioEvent );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a single listener to receive the audio metadata from each
|
* Registers a single listener to receive the audio metadata from each
|
||||||
* audio packet
|
* audio packet
|
||||||
@@ -113,21 +237,200 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp when most recent buffer was received
|
* Broadcasts audio metadata to the registered listener
|
||||||
*/
|
*/
|
||||||
public long getLastBufferReceived()
|
private void broadcast( AudioMetadata metadata )
|
||||||
|
{
|
||||||
|
if( mAudioMetadataListener != null )
|
||||||
|
{
|
||||||
|
mAudioMetadataListener.receive( metadata );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp of either last buffer received or last buffer processed
|
||||||
|
*/
|
||||||
|
public long getLastActivityTimestamp()
|
||||||
{
|
{
|
||||||
return mLastActivity;
|
return mLastActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the last activity timestamp to current system time
|
||||||
|
*/
|
||||||
|
public void updateTimestamp()
|
||||||
|
{
|
||||||
|
mLastActivity = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive( AudioPacket packet )
|
public void receive( AudioPacket packet )
|
||||||
{
|
{
|
||||||
if( mCanProcessAudio )
|
if( mCanProcessAudio )
|
||||||
{
|
{
|
||||||
mLastActivity = System.currentTimeMillis();
|
//Update the activity timestamp so that this audio output doesn't
|
||||||
|
//get disconnected before it starts processing the audio stream
|
||||||
|
updateTimestamp();
|
||||||
|
|
||||||
mBuffer.add( packet );
|
mBuffer.add( packet );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class BufferProcessor implements Runnable
|
||||||
|
{
|
||||||
|
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||||
|
|
||||||
|
public BufferProcessor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/* The processing flag ensures that only one instance of the
|
||||||
|
* processor can run at any given time */
|
||||||
|
if( mProcessing.compareAndSet( false, true ) )
|
||||||
|
{
|
||||||
|
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
||||||
|
|
||||||
|
mBuffer.drainTo( packets );
|
||||||
|
|
||||||
|
for( AudioPacket packet: packets )
|
||||||
|
{
|
||||||
|
if( packet.getType() == Type.AUDIO )
|
||||||
|
{
|
||||||
|
broadcast( packet.getAudioMetadata() );
|
||||||
|
|
||||||
|
ByteBuffer buffer = convert( packet );
|
||||||
|
|
||||||
|
int wrote = 0;
|
||||||
|
|
||||||
|
if( !mOutput.isRunning() )
|
||||||
|
{
|
||||||
|
int toWrite = mOutput.available();
|
||||||
|
|
||||||
|
if( toWrite > buffer.array().length )
|
||||||
|
{
|
||||||
|
toWrite = buffer.array().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Top off the buffer and check if we can start it
|
||||||
|
wrote += mOutput.write( buffer.array(), 0, toWrite );
|
||||||
|
|
||||||
|
checkStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mOutput.isRunning() && wrote < buffer.array().length )
|
||||||
|
{
|
||||||
|
//Blocking write
|
||||||
|
wrote += mOutput.write( buffer.array(), wrote,
|
||||||
|
buffer.array().length - wrote );
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStop();
|
||||||
|
|
||||||
|
mProcessing.set( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
mLog.error( "Error while processing audio buffers", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts audio playback once audio buffer is almost full and remaining
|
||||||
|
* capacity falls below the start threshold.
|
||||||
|
*/
|
||||||
|
private void checkStart()
|
||||||
|
{
|
||||||
|
if( mCanProcessAudio &&
|
||||||
|
!mOutput.isRunning() &&
|
||||||
|
mOutput.available() <= mBufferStartThreshold )
|
||||||
|
{
|
||||||
|
mOutput.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops audio playback and drains the audio buffer to empty when the
|
||||||
|
* audio buffer is mostly empty and the available buffer capacity
|
||||||
|
* exceeds the stop threshold
|
||||||
|
*/
|
||||||
|
private void checkStop()
|
||||||
|
{
|
||||||
|
if( mCanProcessAudio &&
|
||||||
|
mOutput.isRunning() &&
|
||||||
|
mOutput.available() >= mBufferStopThreshold )
|
||||||
|
{
|
||||||
|
mOutput.drain();
|
||||||
|
mOutput.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the mute state for this audio output channel
|
||||||
|
*/
|
||||||
|
public void setMuted( boolean muted )
|
||||||
|
{
|
||||||
|
if( mMuteControl != null )
|
||||||
|
{
|
||||||
|
mMuteControl.setValue( muted );
|
||||||
|
|
||||||
|
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
|
||||||
|
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current mute state for this audio output channel
|
||||||
|
*/
|
||||||
|
public boolean isMuted()
|
||||||
|
{
|
||||||
|
if( mMuteControl != null )
|
||||||
|
{
|
||||||
|
return mMuteControl.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gain/volume control for this audio output channel, if one is available.
|
||||||
|
*/
|
||||||
|
public FloatControl getGainControl()
|
||||||
|
{
|
||||||
|
return mGainControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasGainControl()
|
||||||
|
{
|
||||||
|
return mGainControl != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors the source data line playback state and broadcasts audio events
|
||||||
|
* to the registered listener as the state changes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update( LineEvent event )
|
||||||
|
{
|
||||||
|
LineEvent.Type type = event.getType();
|
||||||
|
|
||||||
|
if( type == LineEvent.Type.START )
|
||||||
|
{
|
||||||
|
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
||||||
|
}
|
||||||
|
else if( type == LineEvent.Type.STOP )
|
||||||
|
{
|
||||||
|
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,267 +3,55 @@ package audio.output;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
|
||||||
import javax.sound.sampled.Control;
|
|
||||||
import javax.sound.sampled.FloatControl;
|
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.sound.sampled.SourceDataLine;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import source.mixer.MixerChannel;
|
import source.mixer.MixerChannel;
|
||||||
import audio.AudioEvent;
|
|
||||||
import audio.AudioFormats;
|
import audio.AudioFormats;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.AudioPacket.Type;
|
import controller.ThreadPoolManager;
|
||||||
import audio.output.StereoAudioOutput.QueueProcessor;
|
|
||||||
import controller.NamingThreadFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mono Audio output with automatic flow control based on the availability of
|
* Mono Audio output implementation
|
||||||
* audio data packets.
|
|
||||||
*/
|
*/
|
||||||
public class MonoAudioOutput extends AudioOutput
|
public class MonoAudioOutput extends AudioOutput
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( MonoAudioOutput.class );
|
|
||||||
|
|
||||||
/* Output line buffer size is set at one half second of audio at 48 kHz and
|
|
||||||
* two bytes per sample */
|
|
||||||
private final static int BUFFER_SIZE = 48000;
|
private final static int BUFFER_SIZE = 48000;
|
||||||
|
|
||||||
/* The queue processor will run every 20 milliseconds checking for inbound
|
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
|
||||||
* audio and automatically starting or stopping audio playback. We set the
|
|
||||||
* data line available space threshold to start when we're 20 ms away from
|
|
||||||
* being full, and the stop threshold when 20 ms away from being empty */
|
|
||||||
private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25;
|
|
||||||
private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
|
|
||||||
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
|
|
||||||
|
|
||||||
private SourceDataLine mOutput;
|
|
||||||
private FloatControl mGainControl;
|
|
||||||
private BooleanControl mMuteControl;
|
|
||||||
|
|
||||||
private AudioEvent mAudioStartEvent;
|
|
||||||
private AudioEvent mAudioStopEvent;
|
|
||||||
private AudioEvent mAudioContinuationEvent;
|
|
||||||
|
|
||||||
public MonoAudioOutput( Mixer mixer )
|
|
||||||
{
|
{
|
||||||
super();
|
super( threadPoolManager,
|
||||||
|
mixer,
|
||||||
|
MixerChannel.MONO,
|
||||||
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO,
|
||||||
|
AudioFormats.MONO_SOURCE_DATALINE_INFO,
|
||||||
|
BUFFER_SIZE );
|
||||||
|
}
|
||||||
|
|
||||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
/**
|
||||||
getChannelName() );
|
* Converts the audio packet data into mono audio frames.
|
||||||
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
*/
|
||||||
getChannelName() );
|
protected ByteBuffer convert( AudioPacket packet )
|
||||||
mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION,
|
{
|
||||||
getChannelName() );
|
if( packet.hasAudioBuffer() )
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
mOutput = (SourceDataLine)mixer.getLine(
|
float[] samples = packet.getAudioBuffer().getSamples();
|
||||||
AudioFormats.MONO_SOURCE_DATALINE_INFO );
|
|
||||||
|
|
||||||
if( mOutput != null )
|
/* Little-endian byte buffer */
|
||||||
{
|
ByteBuffer buffer =
|
||||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
|
ByteBuffer.allocate( samples.length * 2 )
|
||||||
|
.order( ByteOrder.LITTLE_ENDIAN );
|
||||||
mCanProcessAudio = true;
|
|
||||||
|
|
||||||
mExecutorService = Executors.newSingleThreadScheduledExecutor(
|
|
||||||
new NamingThreadFactory( "audio (mono) output" ) );
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
||||||
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
|
|
||||||
0, 40, TimeUnit.MILLISECONDS );
|
for( float sample: samples )
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( LineUnavailableException e )
|
|
||||||
{
|
|
||||||
mLog.error( "Couldn't obtain source data line for 48kHz PCM mono "
|
|
||||||
+ "audio output" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
|
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||||
mGainControl = (FloatControl)gain;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MASTER GAIN control for mono line [" +
|
|
||||||
mixer.getMixerInfo().getName() + "]" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
return buffer;
|
||||||
{
|
|
||||||
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
|
|
||||||
mMuteControl = (BooleanControl)mute;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MUTE control for mono line [" +
|
|
||||||
mixer.getMixerInfo().getName() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
mExecutorService = Executors.newSingleThreadScheduledExecutor(
|
|
||||||
new NamingThreadFactory( "audio (mono) output" ) );
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
|
||||||
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
|
|
||||||
0, 40, TimeUnit.MILLISECONDS );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getChannelName()
|
|
||||||
{
|
|
||||||
return MixerChannel.MONO.getLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
super.dispose();
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
mOutput.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QueueProcessor implements Runnable
|
|
||||||
{
|
|
||||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
|
||||||
|
|
||||||
public QueueProcessor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
/* The mProcessing flag ensures that only one instance of the
|
|
||||||
* processor can run at any given time */
|
|
||||||
if( mProcessing.compareAndSet( false, true ) )
|
|
||||||
{
|
|
||||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
|
||||||
|
|
||||||
mBuffer.drainTo( packets );
|
|
||||||
|
|
||||||
if( packets.size() > 0 )
|
|
||||||
{
|
|
||||||
broadcast( mAudioContinuationEvent );
|
|
||||||
|
|
||||||
for( AudioPacket packet: packets )
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio )
|
|
||||||
{
|
|
||||||
if( isSquelched() )
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
else if( packet.getType() == Type.AUDIO )
|
|
||||||
{
|
|
||||||
float[] samples = packet.getAudioBuffer().getSamples();
|
|
||||||
|
|
||||||
/* Little-endian byte buffer */
|
|
||||||
ByteBuffer buffer =
|
|
||||||
ByteBuffer.allocate( samples.length * 2 )
|
|
||||||
.order( ByteOrder.LITTLE_ENDIAN );
|
|
||||||
|
|
||||||
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
|
||||||
|
|
||||||
for( float sample: samples )
|
|
||||||
{
|
|
||||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
mOutput.write( buffer.array(), 0,
|
|
||||||
buffer.array().length );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
|
|
||||||
broadcast( packet.getAudioMetadata() );
|
|
||||||
|
|
||||||
mLastActivity = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
mProcessing.set( false );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkStart()
|
return null;
|
||||||
{
|
|
||||||
if( mCanProcessAudio && !isSquelched() &&
|
|
||||||
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
|
|
||||||
{
|
|
||||||
mOutput.start();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkStop()
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio && mOutput.isRunning() &&
|
|
||||||
( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
|
|
||||||
{
|
|
||||||
mOutput.drain();
|
|
||||||
mOutput.stop();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMuted( boolean muted )
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
mMuteControl.setValue( muted );
|
|
||||||
|
|
||||||
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
|
|
||||||
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMuted()
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
return mMuteControl.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FloatControl getGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl != null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,283 +3,69 @@ package audio.output;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
|
||||||
import javax.sound.sampled.Control;
|
|
||||||
import javax.sound.sampled.FloatControl;
|
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.sound.sampled.SourceDataLine;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import source.mixer.MixerChannel;
|
import source.mixer.MixerChannel;
|
||||||
import audio.AudioEvent;
|
|
||||||
import audio.AudioFormats;
|
import audio.AudioFormats;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.AudioPacket.Type;
|
import controller.ThreadPoolManager;
|
||||||
import controller.NamingThreadFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mono Audio output in stereo format with automatic flow control based on the
|
* Stereo audio output implementation.
|
||||||
* availability of audio data packets.
|
|
||||||
*/
|
*/
|
||||||
public class StereoAudioOutput extends AudioOutput
|
public class StereoAudioOutput extends AudioOutput
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( StereoAudioOutput.class );
|
|
||||||
|
|
||||||
/* Output line buffer size is set at one half second of audio at 48 kHz and
|
|
||||||
* two bytes per sample for two channels */
|
|
||||||
private final static int BUFFER_SIZE = 96000;
|
private final static int BUFFER_SIZE = 96000;
|
||||||
|
|
||||||
/* The queue processor will run every 20 milliseconds checking for inbound
|
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
|
||||||
* audio and automatically starting or stopping audio playback. We set the
|
Mixer mixer, MixerChannel channel )
|
||||||
* data line available space threshold to start when we're 20 ms away from
|
|
||||||
* being full, and the stop threshold when 20 ms away from being empty */
|
|
||||||
private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25;
|
|
||||||
private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
|
|
||||||
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
|
|
||||||
|
|
||||||
private SourceDataLine mOutput;
|
|
||||||
private FloatControl mGainControl;
|
|
||||||
private BooleanControl mMuteControl;
|
|
||||||
|
|
||||||
private AudioEvent mAudioStartEvent;
|
|
||||||
private AudioEvent mAudioStopEvent;
|
|
||||||
private AudioEvent mAudioContinuationEvent;
|
|
||||||
|
|
||||||
private MixerChannel mMixerChannel;
|
|
||||||
|
|
||||||
public StereoAudioOutput( Mixer mixer, MixerChannel channel )
|
|
||||||
{
|
{
|
||||||
super();
|
super( threadPoolManager,
|
||||||
|
mixer,
|
||||||
assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
|
channel,
|
||||||
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO,
|
||||||
mMixerChannel = channel;
|
AudioFormats.STEREO_SOURCE_DATALINE_INFO,
|
||||||
|
BUFFER_SIZE );
|
||||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
|
||||||
getChannelName() );
|
|
||||||
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
|
||||||
getChannelName() );
|
|
||||||
mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION,
|
|
||||||
getChannelName() );
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mOutput = (SourceDataLine)mixer.getLine(
|
|
||||||
AudioFormats.STEREO_SOURCE_DATALINE_INFO );
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO, BUFFER_SIZE );
|
|
||||||
mCanProcessAudio = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( LineUnavailableException e )
|
|
||||||
{
|
|
||||||
mLog.error( "Couldn't obtain source data line for 48kHz PCM stereo "
|
|
||||||
+ "audio output - mixer [" + mixer.getMixerInfo().getName() +
|
|
||||||
"] channel [" + mMixerChannel.name() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
|
|
||||||
mGainControl = (FloatControl)gain;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MASTER GAIN control for stereo line [" +
|
|
||||||
mixer.getMixerInfo().getName() + " | " + channel.name() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
|
|
||||||
mMuteControl = (BooleanControl)mute;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MUTE control for stereo line [" +
|
|
||||||
mixer.getMixerInfo().getName() + " | " + channel.name() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
mExecutorService = Executors.newSingleThreadScheduledExecutor(
|
|
||||||
new NamingThreadFactory( "audio (stereo) output" ) );
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
|
||||||
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
|
|
||||||
0, 40, TimeUnit.MILLISECONDS );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String getChannelName()
|
* Converts the audio packet data into stereo audio frames with the mixer
|
||||||
|
* channel containing the audio and the other channel containing zero
|
||||||
|
* valued (silent) samples.
|
||||||
|
*/
|
||||||
|
protected ByteBuffer convert( AudioPacket packet )
|
||||||
{
|
{
|
||||||
return mMixerChannel.getLabel();
|
if( packet.hasAudioBuffer() )
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
super.dispose();
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
{
|
||||||
mOutput.close();
|
float[] samples = packet.getAudioBuffer().getSamples();
|
||||||
}
|
|
||||||
}
|
/* Little-endian byte buffer */
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate( samples.length * 4 )
|
||||||
private ByteBuffer convert( float[] samples )
|
|
||||||
{
|
|
||||||
/* Little-endian byte buffer */
|
|
||||||
ByteBuffer buffer =
|
|
||||||
ByteBuffer.allocate( samples.length * 4 )
|
|
||||||
.order( ByteOrder.LITTLE_ENDIAN );
|
.order( ByteOrder.LITTLE_ENDIAN );
|
||||||
|
|
||||||
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
|
||||||
|
|
||||||
if( mMixerChannel == MixerChannel.LEFT )
|
|
||||||
{
|
|
||||||
for( float sample: samples )
|
|
||||||
{
|
|
||||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
|
||||||
shortBuffer.put( (short)0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for( float sample: samples )
|
|
||||||
{
|
|
||||||
shortBuffer.put( (short)0 );
|
|
||||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QueueProcessor implements Runnable
|
|
||||||
{
|
|
||||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
|
||||||
|
|
||||||
public QueueProcessor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
/* The mProcessing flag ensures that only one instance of the
|
|
||||||
* processor can run at any given time */
|
|
||||||
if( mProcessing.compareAndSet( false, true ) )
|
|
||||||
{
|
|
||||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
|
||||||
|
|
||||||
mBuffer.drainTo( packets );
|
|
||||||
|
|
||||||
if( packets.size() > 0 )
|
|
||||||
{
|
|
||||||
broadcast( mAudioContinuationEvent );
|
|
||||||
|
|
||||||
for( AudioPacket packet: packets )
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio )
|
|
||||||
{
|
|
||||||
if( isSquelched() )
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
else if( packet.getType() == Type.AUDIO )
|
|
||||||
{
|
|
||||||
ByteBuffer buffer = convert(
|
|
||||||
packet.getAudioBuffer().getSamples() );
|
|
||||||
|
|
||||||
mOutput.write( buffer.array(), 0,
|
|
||||||
buffer.array().length );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
|
|
||||||
broadcast( packet.getAudioMetadata() );
|
|
||||||
|
|
||||||
mLastActivity = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
mProcessing.set( false );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkStart()
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio && !isSquelched() &&
|
|
||||||
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
|
|
||||||
{
|
|
||||||
mOutput.start();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkStop()
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio && mOutput.isRunning() &&
|
|
||||||
( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
|
|
||||||
{
|
|
||||||
mOutput.drain();
|
|
||||||
mOutput.stop();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMuted( boolean muted )
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
mMuteControl.setValue( muted );
|
|
||||||
|
|
||||||
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
|
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
||||||
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if( getMixerChannel() == MixerChannel.LEFT )
|
||||||
public boolean isMuted()
|
{
|
||||||
{
|
for( float sample: samples )
|
||||||
if( mMuteControl != null )
|
{
|
||||||
{
|
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||||
return mMuteControl.getValue();
|
shortBuffer.put( (short)0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for( float sample: samples )
|
||||||
|
{
|
||||||
|
shortBuffer.put( (short)0 );
|
||||||
|
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FloatControl getGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl != null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,16 @@ public class MessageFramer implements Listener<Boolean>,
|
|||||||
mMessageLength = messageLength;
|
mMessageLength = messageLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
for( MessageAssembler assembler: mMessageAssemblers )
|
||||||
|
{
|
||||||
|
assembler.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mMessageAssemblers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
mBroadcaster.dispose();
|
mBroadcaster.dispose();
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import module.decode.DecoderType;
|
|||||||
import module.decode.config.DecodeConfigFactory;
|
import module.decode.config.DecodeConfigFactory;
|
||||||
import playlist.PlaylistNode;
|
import playlist.PlaylistNode;
|
||||||
import source.config.SourceConfigTuner;
|
import source.config.SourceConfigTuner;
|
||||||
import source.recording.RecordingGroupNode;
|
|
||||||
import source.tuner.Tuner;
|
import source.tuner.Tuner;
|
||||||
import source.tuner.TunerGroupNode;
|
import source.tuner.TunerGroupNode;
|
||||||
import source.tuner.TunerNode;
|
import source.tuner.TunerNode;
|
||||||
@@ -60,7 +59,7 @@ public class ConfigurationControllerModel extends DefaultTreeModel
|
|||||||
|
|
||||||
private PlaylistNode mPlaylistNode;
|
private PlaylistNode mPlaylistNode;
|
||||||
private TunerGroupNode mTunerGroupNode;
|
private TunerGroupNode mTunerGroupNode;
|
||||||
private RecordingGroupNode mRecordingGroupNode;
|
// private RecordingGroupNode mRecordingGroupNode;
|
||||||
|
|
||||||
private IconManager mIconManagerFrame;
|
private IconManager mIconManagerFrame;
|
||||||
|
|
||||||
@@ -121,15 +120,15 @@ public class ConfigurationControllerModel extends DefaultTreeModel
|
|||||||
/**
|
/**
|
||||||
* Add the recording group node
|
* Add the recording group node
|
||||||
*/
|
*/
|
||||||
mRecordingGroupNode = new RecordingGroupNode();
|
// mRecordingGroupNode = new RecordingGroupNode();
|
||||||
insertNodeInto( mRecordingGroupNode, (MutableTreeNode)root, 1 );
|
// insertNodeInto( mRecordingGroupNode, (MutableTreeNode)root, 1 );
|
||||||
mRecordingGroupNode.loadRecordings();
|
// mRecordingGroupNode.loadRecordings();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the playlist node
|
* Add the playlist node
|
||||||
*/
|
*/
|
||||||
mPlaylistNode = new PlaylistNode();
|
mPlaylistNode = new PlaylistNode();
|
||||||
insertNodeInto( mPlaylistNode, (MutableTreeNode)root, 2 );
|
insertNodeInto( mPlaylistNode, (MutableTreeNode)root, 1 );
|
||||||
mPlaylistNode.loadPlaylist();
|
mPlaylistNode.loadPlaylist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,24 +36,20 @@ public class ThreadPoolManager
|
|||||||
private HashMap<ScheduledFuture<?>,ThreadType> mTasks =
|
private HashMap<ScheduledFuture<?>,ThreadType> mTasks =
|
||||||
new HashMap<ScheduledFuture<?>,ThreadType>();
|
new HashMap<ScheduledFuture<?>,ThreadType>();
|
||||||
|
|
||||||
private ScheduledExecutorService mExecutor;
|
private ScheduledExecutorService mExecutor =
|
||||||
|
Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
||||||
|
new NamingThreadFactory( "sdrtrunk" ) );
|
||||||
|
|
||||||
public ThreadPoolManager()
|
public ThreadPoolManager()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScheduledFuture<?> scheduleFixedRate( ThreadType type,
|
public ScheduledFuture<?> scheduleFixedRate( ThreadType type,
|
||||||
Runnable command,
|
Runnable command,
|
||||||
long period,
|
long period,
|
||||||
TimeUnit unit )
|
TimeUnit unit )
|
||||||
throws RejectedExecutionException
|
throws RejectedExecutionException
|
||||||
{
|
{
|
||||||
if( mExecutor == null )
|
|
||||||
{
|
|
||||||
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
|
||||||
new NamingThreadFactory( "sdrtrunk" ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledFuture<?> task =
|
ScheduledFuture<?> task =
|
||||||
mExecutor.scheduleAtFixedRate( command, 0, period, unit );
|
mExecutor.scheduleAtFixedRate( command, 0, period, unit );
|
||||||
|
|
||||||
@@ -65,20 +61,16 @@ public class ThreadPoolManager
|
|||||||
public void scheduleOnce( Runnable command, long delay, TimeUnit unit )
|
public void scheduleOnce( Runnable command, long delay, TimeUnit unit )
|
||||||
throws RejectedExecutionException
|
throws RejectedExecutionException
|
||||||
{
|
{
|
||||||
if( mExecutor == null )
|
|
||||||
{
|
|
||||||
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
|
||||||
new NamingThreadFactory( "sdrtrunk" ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
mExecutor.schedule( command, delay, unit );
|
mExecutor.schedule( command, delay, unit );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel( ScheduledFuture<?> task )
|
public boolean cancel( ScheduledFuture<?> task )
|
||||||
{
|
{
|
||||||
task.cancel( true );
|
boolean success = task.cancel( true );
|
||||||
|
|
||||||
mTasks.remove( task );
|
mTasks.remove( task );
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTaskCount( ThreadType type )
|
public int getTaskCount( ThreadType type )
|
||||||
|
|||||||
@@ -297,6 +297,9 @@ public class Channel extends Configuration
|
|||||||
|
|
||||||
/* Broadcast channel deleted event */
|
/* Broadcast channel deleted event */
|
||||||
fireChannelEvent( Event.CHANNEL_DELETED );
|
fireChannelEvent( Event.CHANNEL_DELETED );
|
||||||
|
|
||||||
|
mProcessingChain.dispose();
|
||||||
|
mProcessingChain = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -809,8 +812,7 @@ public class Channel extends Configuration
|
|||||||
{
|
{
|
||||||
if( mProcessingChain == null )
|
if( mProcessingChain == null )
|
||||||
{
|
{
|
||||||
mProcessingChain = new ProcessingChain(
|
mProcessingChain = new ProcessingChain( getName() );
|
||||||
mResourceManager.getThreadPoolManager() );
|
|
||||||
|
|
||||||
/* Get the optional alias list for the decode modules to use */
|
/* Get the optional alias list for the decode modules to use */
|
||||||
AliasList aliasList = mResourceManager.getPlaylistManager()
|
AliasList aliasList = mResourceManager.getPlaylistManager()
|
||||||
@@ -901,6 +903,8 @@ public class Channel extends Configuration
|
|||||||
{
|
{
|
||||||
if( !mEnabled && mProcessingChain != null )
|
if( !mEnabled && mProcessingChain != null )
|
||||||
{
|
{
|
||||||
|
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
|
||||||
|
|
||||||
mProcessingChain.stop();
|
mProcessingChain.stop();
|
||||||
|
|
||||||
if( mChannelType == ChannelType.STANDARD )
|
if( mChannelType == ChannelType.STANDARD )
|
||||||
@@ -910,8 +914,6 @@ public class Channel extends Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
mSelected = false;
|
mSelected = false;
|
||||||
|
|
||||||
fireChannelEvent( Event.CHANNEL_PROCESSING_STOPPED );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ public class SDRTrunk
|
|||||||
|
|
||||||
if( version != null )
|
if( version != null )
|
||||||
{
|
{
|
||||||
sb.append( " V" );
|
sb.append( " v" );
|
||||||
sb.append( version );
|
sb.append( version );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import source.Source;
|
|||||||
|
|
||||||
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
||||||
{
|
{
|
||||||
public InstrumentableProcessingChain()
|
public InstrumentableProcessingChain( String name )
|
||||||
{
|
{
|
||||||
|
super( name );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSource( Source source ) throws IllegalStateException
|
public void setSource( Source source ) throws IllegalStateException
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public class DecoderViewFrame extends JInternalFrame
|
|||||||
|
|
||||||
mDecodeConfig = decodeConfig;
|
mDecodeConfig = decodeConfig;
|
||||||
|
|
||||||
mProcessingChain = new InstrumentableProcessingChain();
|
mProcessingChain = new InstrumentableProcessingChain( "instrumented" );
|
||||||
|
|
||||||
List<Module> modules = DecoderFactory
|
List<Module> modules = DecoderFactory
|
||||||
.getModules( ChannelType.STANDARD,
|
.getModules( ChannelType.STANDARD,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,52 +17,94 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package message;
|
package message;
|
||||||
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import map.Plottable;
|
import map.Plottable;
|
||||||
import module.decode.event.MessageDetailsProvider;
|
|
||||||
import alias.Alias;
|
import alias.Alias;
|
||||||
|
|
||||||
public abstract class Message implements MessageDetailsProvider
|
public abstract class Message
|
||||||
{
|
{
|
||||||
protected long mTimeReceived;
|
protected long mTimeReceived;
|
||||||
protected MessageType mType;
|
|
||||||
|
|
||||||
public Message()
|
public Message()
|
||||||
{
|
|
||||||
this( MessageType.UN_KNWN );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Message( MessageType type )
|
|
||||||
{
|
{
|
||||||
mTimeReceived = System.currentTimeMillis();
|
mTimeReceived = System.currentTimeMillis();
|
||||||
mType = type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Plottable getPlottable();
|
|
||||||
|
|
||||||
public abstract String toString();
|
|
||||||
|
|
||||||
public abstract boolean isValid();
|
|
||||||
|
|
||||||
public long getTimeReceived()
|
public long getTimeReceived()
|
||||||
{
|
{
|
||||||
return mTimeReceived;
|
return mTimeReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getDateReceived()
|
/**
|
||||||
|
* Map plottable
|
||||||
|
*/
|
||||||
|
public Plottable getPlottable()
|
||||||
{
|
{
|
||||||
return new Date( mTimeReceived );
|
//TODO: move this to an IPlottable interface that only gets implemented as needed
|
||||||
}
|
return null;
|
||||||
|
|
||||||
public MessageType getType()
|
|
||||||
{
|
|
||||||
return mType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoded textual representation of the message
|
||||||
|
*/
|
||||||
|
public abstract String toString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message is valid and has passed crc/integrity checks
|
||||||
|
*/
|
||||||
|
public abstract boolean isValid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status of the CRC check of the message
|
||||||
|
*/
|
||||||
|
public abstract String getErrorStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsed Message
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public abstract String getMessage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw ( 0 & 1 ) message bits
|
||||||
|
*/
|
||||||
|
public abstract String getBinaryMessage();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoded protocol
|
||||||
|
*/
|
||||||
|
public abstract String getProtocol();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event - call, data, idle, etc.
|
||||||
|
*/
|
||||||
|
public abstract String getEventType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted from identifier
|
||||||
|
*/
|
||||||
|
public abstract String getFromID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From identifier alias (from AliasManager)
|
||||||
|
*/
|
||||||
|
public abstract Alias getFromIDAlias();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted to identifier
|
||||||
|
*/
|
||||||
|
public abstract String getToID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To identifier alias (from AliasManager)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public abstract Alias getToIDAlias();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a listing of aliases contained in the message.
|
* Provides a listing of aliases contained in the message.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ package module;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import message.IMessageListener;
|
import message.IMessageListener;
|
||||||
@@ -61,7 +62,7 @@ import audio.metadata.Metadata;
|
|||||||
import audio.squelch.ISquelchStateListener;
|
import audio.squelch.ISquelchStateListener;
|
||||||
import audio.squelch.ISquelchStateProvider;
|
import audio.squelch.ISquelchStateProvider;
|
||||||
import audio.squelch.SquelchState;
|
import audio.squelch.SquelchState;
|
||||||
import controller.ThreadPoolManager;
|
import controller.NamingThreadFactory;
|
||||||
import controller.channel.ChannelEvent;
|
import controller.channel.ChannelEvent;
|
||||||
import controller.channel.IChannelEventListener;
|
import controller.channel.IChannelEventListener;
|
||||||
import controller.channel.IChannelEventProvider;
|
import controller.channel.IChannelEventProvider;
|
||||||
@@ -99,24 +100,19 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>();
|
private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
private ScheduledExecutorService mScheduledExecutorService;
|
||||||
private ScheduledFuture<?> mBufferProcessingTask;
|
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
|
|
||||||
protected Source mSource;
|
protected Source mSource;
|
||||||
private List<Module> mModules = new ArrayList<>();
|
private List<Module> mModules = new ArrayList<>();
|
||||||
private IFrequencyCorrectionController mFrequencyCorrectionController;
|
private IFrequencyCorrectionController mFrequencyCorrectionController;
|
||||||
|
|
||||||
public ProcessingChain()
|
public ProcessingChain( String name )
|
||||||
{
|
{
|
||||||
this( new ThreadPoolManager() );
|
mScheduledExecutorService = Executors.newScheduledThreadPool( 1,
|
||||||
|
new NamingThreadFactory( "channel " + name ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProcessingChain( ThreadPoolManager threadManager )
|
|
||||||
{
|
|
||||||
mThreadPoolManager = threadManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
@@ -128,14 +124,9 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
|
|
||||||
mModules.clear();
|
mModules.clear();
|
||||||
|
|
||||||
// mComplexReceiver.dispose();
|
mScheduledExecutorService.shutdownNow();
|
||||||
// mComplexReceiver = null;
|
mScheduledExecutorService = null;
|
||||||
//
|
|
||||||
// mRealReceiver.dispose();
|
|
||||||
// mRealReceiver = null;
|
|
||||||
|
|
||||||
mThreadPoolManager = null;
|
|
||||||
mBufferProcessingTask = null;
|
|
||||||
mFrequencyCorrectionController = null;
|
mFrequencyCorrectionController = null;
|
||||||
|
|
||||||
mAudioPacketBroadcaster.dispose();
|
mAudioPacketBroadcaster.dispose();
|
||||||
@@ -447,9 +438,11 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
* of the decoder state's */
|
* of the decoder state's */
|
||||||
if( mSource instanceof TunerChannelSource )
|
if( mSource instanceof TunerChannelSource )
|
||||||
{
|
{
|
||||||
|
TunerChannelSource tunerChannelSource = (TunerChannelSource)mSource;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long frequency = ((TunerChannelSource)mSource).getFrequency();
|
long frequency = tunerChannelSource.getFrequency();
|
||||||
|
|
||||||
mDecoderStateEventBroadcaster.broadcast(
|
mDecoderStateEventBroadcaster.broadcast(
|
||||||
new DecoderStateEvent( this, Event.SOURCE_FREQUENCY,
|
new DecoderStateEvent( this, Event.SOURCE_FREQUENCY,
|
||||||
@@ -459,6 +452,8 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
{
|
{
|
||||||
mLog.error( "Error getting frequency from tuner channel source", e );
|
mLog.error( "Error getting frequency from tuner channel source", e );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tunerChannelSource.start( mScheduledExecutorService );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -496,12 +491,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
mSource = null;
|
mSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mBufferProcessingTask != null )
|
|
||||||
{
|
|
||||||
mThreadPoolManager.cancel( mBufferProcessingTask );
|
|
||||||
mBufferProcessingTask = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stop each of the modules */
|
/* Stop each of the modules */
|
||||||
for( Module module: mModules )
|
for( Module module: mModules )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -228,7 +228,8 @@ public class DecoderFactory
|
|||||||
Sync sync = mptConfig.getSync();
|
Sync sync = mptConfig.getSync();
|
||||||
|
|
||||||
modules.add( new MPT1327Decoder( aliasList, sync ) );
|
modules.add( new MPT1327Decoder( aliasList, sync ) );
|
||||||
modules.add( new MPT1327DecoderState( aliasList, channelMap, channelType ) );
|
modules.add( new MPT1327DecoderState( aliasList, channelMap,
|
||||||
|
channelType, mptConfig.getCallTimeout() * 1000 ) );
|
||||||
|
|
||||||
if( channelType == ChannelType.STANDARD )
|
if( channelType == ChannelType.STANDARD )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -310,6 +310,11 @@ public abstract class CallEvent implements Comparable<CallEvent>
|
|||||||
@Override
|
@Override
|
||||||
public int compareTo( CallEvent other )
|
public int compareTo( CallEvent other )
|
||||||
{
|
{
|
||||||
|
if( other != null && other == this )
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return Long.compare( mEventStart, other.getEventStartTime() );
|
return Long.compare( mEventStart, other.getEventStartTime() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package module.decode.event;
|
|||||||
|
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
@@ -174,7 +175,7 @@ public class MessageActivityModel extends AbstractTableModel
|
|||||||
switch( columnIndex )
|
switch( columnIndex )
|
||||||
{
|
{
|
||||||
case sTIME:
|
case sTIME:
|
||||||
return mSDFTime.format( message.getDateReceived() );
|
return mSDFTime.format( new Date( message.getTimeReceived() ) );
|
||||||
case sPROTOCOL:
|
case sPROTOCOL:
|
||||||
return message.getProtocol();
|
return message.getProtocol();
|
||||||
case sERROR_STATUS:
|
case sERROR_STATUS:
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
******************************************************************************/
|
|
||||||
package module.decode.event;
|
|
||||||
|
|
||||||
import alias.Alias;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter methods to support the Message Activity Table/Model
|
|
||||||
*/
|
|
||||||
public interface MessageDetailsProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Indicates if the message is valid and has passed crc/integrity checks
|
|
||||||
*/
|
|
||||||
public abstract boolean isValid();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status of the CRC check of the message
|
|
||||||
*/
|
|
||||||
public abstract String getErrorStatus();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parsed Message
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public abstract String getMessage();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raw ( 0 & 1 ) message bits
|
|
||||||
*/
|
|
||||||
public abstract String getBinaryMessage();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decoded protocol
|
|
||||||
*/
|
|
||||||
public abstract String getProtocol();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event - call, data, idle, etc.
|
|
||||||
*/
|
|
||||||
public abstract String getEventType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatted from identifier
|
|
||||||
*/
|
|
||||||
public abstract String getFromID();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From identifier alias (from AliasManager)
|
|
||||||
*/
|
|
||||||
public abstract Alias getFromIDAlias();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatted to identifier
|
|
||||||
*/
|
|
||||||
public abstract String getToID();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To identifier alias (from AliasManager)
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public abstract Alias getToIDAlias();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -51,17 +51,16 @@ public class LTRCallEvent extends CallEvent
|
|||||||
*/
|
*/
|
||||||
public boolean isMatchingTalkgroup( LTRNetMessage message )
|
public boolean isMatchingTalkgroup( LTRNetMessage message )
|
||||||
{
|
{
|
||||||
String talkgroup = message.getTalkgroupID();
|
return isMatchingTalkgroup( message.getTalkgroupID() );
|
||||||
|
|
||||||
if( talkgroup != null && getToID() != null &&
|
|
||||||
talkgroup.contentEquals( getToID() ) )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMatchingTalkgroup( String talkgroup )
|
||||||
|
{
|
||||||
|
return talkgroup != null &&
|
||||||
|
getToID() != null &&
|
||||||
|
talkgroup.contentEquals( getToID() );
|
||||||
|
}
|
||||||
|
|
||||||
private Alias getAlias( String talkgroup )
|
private Alias getAlias( String talkgroup )
|
||||||
{
|
{
|
||||||
if( hasAliasList() )
|
if( hasAliasList() )
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ public class LTRNetDecoder extends Decoder implements IRealBufferListener, Instr
|
|||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
|
mLTRMessageFramer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public class LTRStandardDecoder extends Decoder implements IRealBufferListener
|
|||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
|
mLTRMessageFramer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,21 +17,31 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package module.decode.ltrstandard;
|
package module.decode.ltrstandard;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import message.Message;
|
import message.Message;
|
||||||
import module.decode.DecoderType;
|
import module.decode.DecoderType;
|
||||||
import module.decode.event.CallEvent.CallEventType;
|
import module.decode.event.CallEvent.CallEventType;
|
||||||
import module.decode.ltrnet.LTRCallEvent;
|
import module.decode.ltrnet.LTRCallEvent;
|
||||||
|
import module.decode.ltrstandard.message.CallEndMessage;
|
||||||
|
import module.decode.ltrstandard.message.CallMessage;
|
||||||
|
import module.decode.ltrstandard.message.IdleMessage;
|
||||||
|
import module.decode.ltrstandard.message.LTRStandardMessage;
|
||||||
import module.decode.state.ChangedAttribute;
|
import module.decode.state.ChangedAttribute;
|
||||||
import module.decode.state.DecoderState;
|
import module.decode.state.DecoderState;
|
||||||
import module.decode.state.DecoderStateEvent;
|
import module.decode.state.DecoderStateEvent;
|
||||||
import module.decode.state.DecoderStateEvent.Event;
|
import module.decode.state.DecoderStateEvent.Event;
|
||||||
import module.decode.state.State;
|
import module.decode.state.State;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import alias.Alias;
|
import alias.Alias;
|
||||||
import alias.AliasList;
|
import alias.AliasList;
|
||||||
import audio.metadata.Metadata;
|
import audio.metadata.Metadata;
|
||||||
@@ -39,15 +49,17 @@ import audio.metadata.MetadataType;
|
|||||||
|
|
||||||
public class LTRStandardDecoderState extends DecoderState
|
public class LTRStandardDecoderState extends DecoderState
|
||||||
{
|
{
|
||||||
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
|
private final static Logger mLog =
|
||||||
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
|
LoggerFactory.getLogger( LTRStandardDecoderState.class );
|
||||||
private TreeSet<String> mTalkgroups = new TreeSet<String>();
|
|
||||||
private TreeSet<Integer> mActiveLCNs = new TreeSet<Integer>();
|
private Map<Integer,LTRCallEvent> mActiveCalls = new HashMap<>();
|
||||||
|
private Set<String> mTalkgroupsFirstHeard = new HashSet<>();
|
||||||
|
private Set<String> mTalkgroups = new TreeSet<>();
|
||||||
|
|
||||||
private String mTalkgroup;
|
private String mTalkgroup;
|
||||||
private Alias mTalkgroupAlias;
|
private Alias mTalkgroupAlias;
|
||||||
private int mChannelNumber;
|
|
||||||
private long mFrequency;
|
private long mFrequency;
|
||||||
|
private LCNTracker mLCNTracker = new LCNTracker();
|
||||||
|
|
||||||
public LTRStandardDecoderState( AliasList aliasList )
|
public LTRStandardDecoderState( AliasList aliasList )
|
||||||
{
|
{
|
||||||
@@ -70,171 +82,128 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private LTRCallEvent getCurrentCallEvent()
|
|
||||||
{
|
|
||||||
return (LTRCallEvent)mCurrentCallEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive( Message message )
|
public void receive( Message message )
|
||||||
{
|
{
|
||||||
if( message.isValid() )
|
if( message.isValid() && message instanceof LTRStandardMessage )
|
||||||
{
|
{
|
||||||
if( message instanceof LTRStandardOSWMessage )
|
switch( ((LTRStandardMessage)message).getMessageType() )
|
||||||
{
|
{
|
||||||
LTRStandardOSWMessage ltr = ((LTRStandardOSWMessage)message);
|
case CA_STRT:
|
||||||
|
CallMessage start = (CallMessage)message;
|
||||||
switch( ltr.getMessageType() )
|
|
||||||
{
|
int channel = start.getChannel();
|
||||||
case CA_STRT:
|
|
||||||
if( 0 < ltr.getChannel() && ltr.getChannel() <=20 )
|
mLCNTracker.processCallChannel( channel );
|
||||||
{
|
|
||||||
/* If we haven't captured the LCN yet ... */
|
mLCNTracker.processFreeChannel( start.getFree() );
|
||||||
if( mChannelNumber == 0 )
|
|
||||||
{
|
|
||||||
mChannelNumber = ltr.getChannel();
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If this call event is for this channel, then update
|
//Only process calls on this LCN, or call detects for
|
||||||
* talkgroup and alias */
|
//talkgroups that are homed on this LCN
|
||||||
if( ltr.getChannel() == mChannelNumber )
|
if( mLCNTracker.isValidChannel( channel ) &&
|
||||||
{
|
( mLCNTracker.isCurrentChannel( channel ) ||
|
||||||
mTalkgroup = ltr.getTalkgroupID();
|
mLCNTracker.isCurrentChannel( start.getHomeRepeater() ) ) )
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
{
|
||||||
|
LTRCallEvent event = mActiveCalls.get( channel );
|
||||||
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
|
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
if( event == null ||
|
||||||
|
!event.isMatchingTalkgroup( start.getToID() ) )
|
||||||
LTRCallEvent current = getCurrentCallEvent();
|
{
|
||||||
|
//Check for different talkgroup
|
||||||
|
if( event != null )
|
||||||
|
{
|
||||||
|
event.end();
|
||||||
|
mActiveCalls.remove( channel );
|
||||||
|
}
|
||||||
|
|
||||||
/* Detect when we haven't reset from the previous
|
boolean current = mLCNTracker.isCurrentChannel( channel );
|
||||||
* call and now have a new call coming in with
|
|
||||||
* a different talkgroup */
|
event = new LTRCallEvent.Builder(
|
||||||
if( current != null && isDifferentTalkgroup( mTalkgroup ) )
|
DecoderType.LTR_STANDARD,
|
||||||
{
|
current ? CallEventType.CALL : CallEventType.CALL_DETECT )
|
||||||
mCurrentCallEvent.end();
|
.to( start.getToID() )
|
||||||
broadcast( mCurrentCallEvent );
|
.aliasList( getAliasList() )
|
||||||
mCurrentCallEvent = null;
|
.channel( start.getChannelFormatted() )
|
||||||
}
|
.frequency( current ? mFrequency : 0 )
|
||||||
|
.build();
|
||||||
if( current == null )
|
|
||||||
{
|
mActiveCalls.put( channel, event );
|
||||||
mCurrentCallEvent =
|
|
||||||
new LTRCallEvent.Builder(
|
broadcast( event );
|
||||||
DecoderType.LTR_STANDARD, CallEventType.CALL )
|
|
||||||
.to( ltr.getTalkgroupID() )
|
if( mLCNTracker.isCurrentChannel( channel ) )
|
||||||
.aliasList( getAliasList() )
|
{
|
||||||
.channel( String.valueOf( ltr.getChannel() ) )
|
mTalkgroup = start.getToID();
|
||||||
.frequency( mFrequency )
|
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||||
.build();
|
|
||||||
|
processTalkgroup( mTalkgroup );
|
||||||
|
|
||||||
|
mTalkgroupAlias = start.getToIDAlias();
|
||||||
|
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||||
|
|
||||||
broadcast( mCurrentCallEvent );
|
broadcast( new Metadata( MetadataType.TO,
|
||||||
|
mTalkgroup, mTalkgroupAlias, true ) );
|
||||||
broadcast( new Metadata( MetadataType.TO,
|
}
|
||||||
mTalkgroup, mTalkgroupAlias, true ) );
|
}
|
||||||
|
|
||||||
|
broadcast( new DecoderStateEvent( this,
|
||||||
|
Event.CONTINUATION, State.CALL ) );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CA_ENDD:
|
||||||
|
CallEndMessage end = (CallEndMessage)message;
|
||||||
|
|
||||||
broadcast( new DecoderStateEvent( this,
|
//Home channel is 31 for call end -- use the free channel
|
||||||
Event.START, State.CALL ) );
|
//as the call end channel
|
||||||
}
|
int repeater = end.getFree();
|
||||||
else
|
|
||||||
{
|
mLCNTracker.processCallChannel( repeater );
|
||||||
broadcast( new DecoderStateEvent( this,
|
|
||||||
Event.CONTINUATION, State.CALL ) );
|
if( mLCNTracker.isCurrentChannel( repeater ) )
|
||||||
}
|
{
|
||||||
}
|
mTalkgroup = end.getToID();
|
||||||
}
|
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||||
break;
|
|
||||||
case CA_ENDD:
|
processTalkgroup( mTalkgroup );
|
||||||
mTalkgroup = ltr.getTalkgroupID();
|
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
mTalkgroupAlias = end.getToIDAlias();
|
||||||
|
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||||
|
|
||||||
mTalkgroupAlias = ltr.getTalkgroupIDAlias();
|
LTRCallEvent event = mActiveCalls.remove( repeater );
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
|
||||||
|
if( event != null )
|
||||||
|
{
|
||||||
|
event.end();
|
||||||
|
broadcast( event );
|
||||||
|
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SY_IDLE:
|
||||||
|
IdleMessage idle = (IdleMessage)message;
|
||||||
|
|
||||||
if( mCurrentCallEvent != null )
|
int lcn = idle.getChannel();
|
||||||
{
|
|
||||||
mCurrentCallEvent.end();
|
|
||||||
broadcast( mCurrentCallEvent );
|
|
||||||
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
|
||||||
mCurrentCallEvent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int home = ltr.getHomeRepeater();
|
|
||||||
|
|
||||||
if( 0 < home && home <= 20 )
|
|
||||||
{
|
|
||||||
String talkgroup = ltr.getTalkgroupID( false );
|
|
||||||
|
|
||||||
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
|
|
||||||
{
|
|
||||||
mTalkgroups.add( talkgroup );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mTalkgroupsFirstHeard.add( talkgroup );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int channel = ltr.getChannel();
|
mLCNTracker.processCallChannel( lcn );
|
||||||
|
|
||||||
if( 0 < channel && channel <= 20 )
|
break;
|
||||||
{
|
case UN_KNWN:
|
||||||
if( channel == home )
|
default:
|
||||||
{
|
break;
|
||||||
if( mChannelNumber != channel )
|
|
||||||
{
|
|
||||||
mChannelNumber = channel;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
|
||||||
}
|
|
||||||
|
|
||||||
mActiveLCNs.add( channel );
|
|
||||||
}
|
|
||||||
|
|
||||||
int free = ltr.getFree();
|
|
||||||
|
|
||||||
if( 0 < free && free <= 20 )
|
|
||||||
{
|
|
||||||
mActiveLCNs.add( free );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case SY_IDLE:
|
|
||||||
int idleChannel = ltr.getChannel();
|
|
||||||
|
|
||||||
if( idleChannel == ltr.getHomeRepeater() &&
|
|
||||||
idleChannel == ltr.getFree() )
|
|
||||||
{
|
|
||||||
if( mChannelNumber != idleChannel )
|
|
||||||
{
|
|
||||||
mChannelNumber = idleChannel;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
|
||||||
}
|
|
||||||
|
|
||||||
mActiveLCNs.add( idleChannel );
|
|
||||||
}
|
|
||||||
broadcast( new DecoderStateEvent( this,
|
|
||||||
Event.CONTINUATION, State.IDLE ) );
|
|
||||||
break;
|
|
||||||
case UN_KNWN:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void processTalkgroup( String talkgroup )
|
||||||
* Indicates if the talkgroup is different than the talkgroup specified in
|
|
||||||
* the current call event
|
|
||||||
*/
|
|
||||||
private boolean isDifferentTalkgroup( String talkgroup )
|
|
||||||
{
|
{
|
||||||
return talkgroup != null &&
|
if( mTalkgroupsFirstHeard.contains( talkgroup ) )
|
||||||
mCurrentCallEvent != null &&
|
{
|
||||||
mCurrentCallEvent.getToID() != null &&
|
mTalkgroups.add( talkgroup );
|
||||||
!mCurrentCallEvent.getToID().contentEquals( talkgroup );
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mTalkgroupsFirstHeard.add( talkgroup );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,8 +214,7 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
mActiveCalls.clear();
|
mActiveCalls.clear();
|
||||||
mTalkgroupsFirstHeard.clear();
|
mTalkgroupsFirstHeard.clear();
|
||||||
mTalkgroups.clear();
|
mTalkgroups.clear();
|
||||||
mActiveLCNs.clear();
|
mLCNTracker.reset();
|
||||||
mChannelNumber = 0;
|
|
||||||
|
|
||||||
resetState();
|
resetState();
|
||||||
}
|
}
|
||||||
@@ -256,13 +224,20 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
*/
|
*/
|
||||||
private void resetState()
|
private void resetState()
|
||||||
{
|
{
|
||||||
if( mCurrentCallEvent != null )
|
for( Integer key: mActiveCalls.keySet() )
|
||||||
{
|
{
|
||||||
mCurrentCallEvent.setEnd( System.currentTimeMillis() );
|
LTRCallEvent event = mActiveCalls.get( key );
|
||||||
broadcast( mCurrentCallEvent );
|
|
||||||
mCurrentCallEvent = null;
|
if( event != null )
|
||||||
|
{
|
||||||
|
event.end();
|
||||||
|
|
||||||
|
broadcast( event );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mActiveCalls.clear();
|
||||||
|
|
||||||
mTalkgroup = null;
|
mTalkgroup = null;
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||||
|
|
||||||
@@ -282,12 +257,12 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
|
|
||||||
public boolean hasChannelNumber()
|
public boolean hasChannelNumber()
|
||||||
{
|
{
|
||||||
return mChannelNumber != 0;
|
return mLCNTracker.getCurrentChannel() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getChannelNumber()
|
public int getChannelNumber()
|
||||||
{
|
{
|
||||||
return mChannelNumber;
|
return mLCNTracker.getCurrentChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -322,28 +297,28 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
|
|
||||||
sb.append( "Monitored LCN: " );
|
sb.append( "Monitored LCN: " );
|
||||||
|
|
||||||
if( mChannelNumber > 0 )
|
if( hasChannelNumber() )
|
||||||
{
|
{
|
||||||
sb.append( mChannelNumber );
|
sb.append( getChannelNumber() );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.append( "unknown" );
|
sb.append( "*Insufficient Data*" );
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append( "\n" );
|
sb.append( "\n" );
|
||||||
|
|
||||||
Integer[] lcns = mActiveLCNs.toArray( new Integer[0] );
|
|
||||||
|
|
||||||
sb.append( "Active LCNs:\t" );
|
sb.append( "Active LCNs:\t" );
|
||||||
|
|
||||||
|
List<Integer> lcns = mLCNTracker.getActiveLCNs();
|
||||||
|
|
||||||
if( lcns.length > 0 )
|
if( lcns.size() > 0 )
|
||||||
{
|
{
|
||||||
sb.append( Arrays.toString( lcns ) );
|
sb.append( mLCNTracker.getActiveLCNs() );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.append( "none" );
|
sb.append( "*Insufficient Data*" );
|
||||||
}
|
}
|
||||||
sb.append( "\n\n" );
|
sb.append( "\n\n" );
|
||||||
|
|
||||||
@@ -355,19 +330,15 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Iterator<String> it = mTalkgroups.iterator();
|
for( String talkgroup: mTalkgroups )
|
||||||
|
|
||||||
while( it.hasNext() )
|
|
||||||
{
|
{
|
||||||
String tgid = it.next();
|
|
||||||
|
|
||||||
sb.append( " " );
|
sb.append( " " );
|
||||||
sb.append( formatTalkgroup( tgid ) );
|
sb.append( talkgroup );
|
||||||
sb.append( " " );
|
sb.append( " " );
|
||||||
|
|
||||||
if( hasAliasList() )
|
if( hasAliasList() )
|
||||||
{
|
{
|
||||||
Alias alias = getAliasList().getTalkgroupAlias( tgid );
|
Alias alias = getAliasList().getTalkgroupAlias( talkgroup );
|
||||||
|
|
||||||
if( alias != null )
|
if( alias != null )
|
||||||
{
|
{
|
||||||
@@ -376,30 +347,147 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
}
|
}
|
||||||
|
|
||||||
sb.append( "\n" );
|
sb.append( "\n" );
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatTalkgroup( String talkgroup )
|
/**
|
||||||
|
* Tracks the set of call and free channels for a system in order to
|
||||||
|
* dynamically determine the current LCN and minimize false triggers from
|
||||||
|
* bogus decoded LTR messages. Tracks the number of occurances of each
|
||||||
|
* LCN and uses a dynamic threshold to determine LCN validity.
|
||||||
|
*/
|
||||||
|
public class LCNTracker
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
private static final int DEFAULT_COUNT = 10;
|
||||||
|
private int[] mCallLCNCounts;
|
||||||
|
private int[] mFreeLCNCounts;
|
||||||
|
private int mCallHighestCount;
|
||||||
|
private int mFreeHighestCount;
|
||||||
|
private int mCurrentLCN;
|
||||||
|
|
||||||
if( talkgroup.length() == 6 )
|
public LCNTracker()
|
||||||
{
|
{
|
||||||
sb.append( talkgroup.substring( 0, 1 ) );
|
reset();
|
||||||
sb.append( "-" );
|
|
||||||
sb.append( talkgroup.substring( 1, 3 ) );
|
|
||||||
sb.append( "-" );
|
|
||||||
sb.append( talkgroup.substring( 3, 6 ) );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
public void logStatistics()
|
||||||
{
|
{
|
||||||
return talkgroup;
|
for( int x = 1; x <= 20; x++ )
|
||||||
|
{
|
||||||
|
mLog.debug( "Call " + x + ": " + mCallLCNCounts[ x ] );
|
||||||
|
}
|
||||||
|
mLog.debug( "Call Highest Count: " + mCallHighestCount );
|
||||||
|
for( int x = 1; x <= 20; x++ )
|
||||||
|
{
|
||||||
|
mLog.debug( "Free " + x + ": " + mFreeLCNCounts[ x ] );
|
||||||
|
}
|
||||||
|
mLog.debug( "Free Highest Count: " + mFreeHighestCount );
|
||||||
|
|
||||||
|
mLog.debug( "Current LCN: " + mCurrentLCN );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
//Dim channel arrays to 21 -- ignore the 0 index
|
||||||
|
mCallLCNCounts = new int[ 21 ];
|
||||||
|
mFreeLCNCounts = new int[ 21 ];
|
||||||
|
mCallHighestCount = 3;
|
||||||
|
mFreeHighestCount = DEFAULT_COUNT;
|
||||||
|
mCurrentLCN = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCurrentChannel( int channel )
|
||||||
|
{
|
||||||
|
if( mCurrentLCN == 0 )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return channel == mCurrentLCN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentChannel()
|
||||||
|
{
|
||||||
|
return mCurrentLCN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the channel is valid based on the observed current and
|
||||||
|
* free channels reported by the system. Tracks the count of each
|
||||||
|
* reported free channel the highest free channel count is used as a
|
||||||
|
* threshold where a channel is valid when its count exceeds 20% of the
|
||||||
|
* highest free channel count. A channel is also valid when it is
|
||||||
|
* the currently monitored channel.
|
||||||
|
*/
|
||||||
|
public boolean isValidChannel( int channel )
|
||||||
|
{
|
||||||
|
if( isCurrentChannel( channel ) )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = mFreeLCNCounts[ channel ];
|
||||||
|
|
||||||
|
int threshold = (int)( (double)mFreeHighestCount * 0.2 );
|
||||||
|
|
||||||
|
return count >= threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processCallChannel( int channel )
|
||||||
|
{
|
||||||
|
if( 1 <= channel && channel <= 20 )
|
||||||
|
{
|
||||||
|
mCallLCNCounts[ channel ]++;
|
||||||
|
|
||||||
|
if( mCallLCNCounts[ channel ] > mCallHighestCount )
|
||||||
|
{
|
||||||
|
mCallHighestCount = mCallLCNCounts[ channel ];
|
||||||
|
mCurrentLCN = channel;
|
||||||
|
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_NUMBER );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processFreeChannel( int channel )
|
||||||
|
{
|
||||||
|
if( 1 <= channel && channel <= 20 )
|
||||||
|
{
|
||||||
|
mFreeLCNCounts[ channel ]++;
|
||||||
|
|
||||||
|
if( mFreeLCNCounts[ channel ] > mFreeHighestCount )
|
||||||
|
{
|
||||||
|
mFreeHighestCount = mFreeLCNCounts[ channel ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getActiveLCNs()
|
||||||
|
{
|
||||||
|
List<Integer> active = new ArrayList<>();
|
||||||
|
|
||||||
|
if( mFreeHighestCount > DEFAULT_COUNT )
|
||||||
|
{
|
||||||
|
for( int x = 1; x <= 20; x++ )
|
||||||
|
{
|
||||||
|
if( mCurrentLCN != 0 && isCurrentChannel( x ) )
|
||||||
|
{
|
||||||
|
active.add( x );
|
||||||
|
}
|
||||||
|
else if( isValidChannel( x ) )
|
||||||
|
{
|
||||||
|
active.add( x );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return active;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,238 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
******************************************************************************/
|
|
||||||
package module.decode.ltrstandard;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import map.Plottable;
|
|
||||||
import message.MessageDirection;
|
|
||||||
import message.MessageType;
|
|
||||||
import alias.Alias;
|
|
||||||
import alias.AliasList;
|
|
||||||
import bits.BinaryMessage;
|
|
||||||
|
|
||||||
public class LTRStandardISWMessage extends LTRStandardMessage
|
|
||||||
{
|
|
||||||
private static final int sCALL_END = 31;
|
|
||||||
private static final int sIDLE = 255;
|
|
||||||
|
|
||||||
public LTRStandardISWMessage( BinaryMessage message, AliasList list )
|
|
||||||
{
|
|
||||||
super( message, MessageDirection.OSW, list );
|
|
||||||
mMessageType = getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append( mDatestampFormatter.format(
|
|
||||||
new Date( System.currentTimeMillis() ) ) );
|
|
||||||
sb.append( " LTR OSW [" );
|
|
||||||
sb.append( mCRC.getAbbreviation() );
|
|
||||||
sb.append( "] " );
|
|
||||||
sb.append( getMessage() );
|
|
||||||
pad( sb, 100 );
|
|
||||||
sb.append( mMessage.toString() );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
switch( mMessageType )
|
|
||||||
{
|
|
||||||
case SY_IDLE:
|
|
||||||
sb.append( "IDLE AREA:" );
|
|
||||||
sb.append( getArea() );
|
|
||||||
sb.append( " LCN:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " HOME:" );
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
sb.append( " GRP:" );
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
sb.append( " FREE:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
break;
|
|
||||||
case CA_STRT:
|
|
||||||
sb.append( "CALL LCN:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " TG [ " );
|
|
||||||
sb.append( getTalkgroupID() );
|
|
||||||
sb.append( "/" );
|
|
||||||
sb.append( getTalkgroupIDAlias() );
|
|
||||||
sb.append( " ] FREE:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
break;
|
|
||||||
case CA_ENDD:
|
|
||||||
sb.append( "END* AREA:" );
|
|
||||||
sb.append( getArea() );
|
|
||||||
sb.append( " LCN:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " TG [" );
|
|
||||||
sb.append( getTalkgroupID() );
|
|
||||||
sb.append( "/" );
|
|
||||||
sb.append( getTalkgroupIDAlias() );
|
|
||||||
sb.append( "] FREE:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
sb.append( "UNKNOWN A:" );
|
|
||||||
sb.append( getArea() );
|
|
||||||
sb.append( " C:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " H:" );
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
sb.append( " G:" );
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
sb.append( " F:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTalkgroupID()
|
|
||||||
{
|
|
||||||
return getTalkgroupID( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTalkgroupID( boolean format )
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append( getArea() );
|
|
||||||
|
|
||||||
if( format )
|
|
||||||
{
|
|
||||||
sb.append( "-" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
|
|
||||||
if( format )
|
|
||||||
{
|
|
||||||
sb.append( "-" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Alias getTalkgroupIDAlias()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append( getArea() );
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
|
|
||||||
return mAliasList.getTalkgroupAlias( sb.toString() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageType getType()
|
|
||||||
{
|
|
||||||
MessageType retVal = MessageType.UN_KNWN;
|
|
||||||
|
|
||||||
int group = getGroup();
|
|
||||||
|
|
||||||
if( group == sIDLE )
|
|
||||||
{
|
|
||||||
retVal = MessageType.SY_IDLE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int channel = getChannel();
|
|
||||||
|
|
||||||
if( channel == sCALL_END )
|
|
||||||
{
|
|
||||||
retVal = MessageType.CA_ENDD;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
retVal = MessageType.CA_STRT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFromID()
|
|
||||||
{
|
|
||||||
if( mMessageType == MessageType.CA_STRT ||
|
|
||||||
mMessageType == MessageType.CA_ENDD )
|
|
||||||
{
|
|
||||||
return getTalkgroupID();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Alias getFromIDAlias()
|
|
||||||
{
|
|
||||||
if( mMessageType == MessageType.CA_STRT ||
|
|
||||||
mMessageType == MessageType.CA_ENDD )
|
|
||||||
{
|
|
||||||
return getTalkgroupIDAlias();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plottable getPlottable()
|
|
||||||
{
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a listing of aliases contained in the message.
|
|
||||||
*/
|
|
||||||
public List<Alias> getAliases()
|
|
||||||
{
|
|
||||||
List<Alias> aliases = new ArrayList<Alias>();
|
|
||||||
|
|
||||||
Alias from = getFromIDAlias();
|
|
||||||
|
|
||||||
if( from != null )
|
|
||||||
{
|
|
||||||
aliases.add( from );
|
|
||||||
}
|
|
||||||
|
|
||||||
Alias to = getToIDAlias();
|
|
||||||
|
|
||||||
if( to != null )
|
|
||||||
{
|
|
||||||
aliases.add( to );
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
******************************************************************************/
|
|
||||||
package module.decode.ltrstandard;
|
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
|
|
||||||
import message.Message;
|
|
||||||
import message.MessageDirection;
|
|
||||||
import message.MessageType;
|
|
||||||
import module.decode.DecoderType;
|
|
||||||
import alias.Alias;
|
|
||||||
import alias.AliasList;
|
|
||||||
import bits.BinaryMessage;
|
|
||||||
import edac.CRC;
|
|
||||||
import edac.CRCLTR;
|
|
||||||
|
|
||||||
public abstract class LTRStandardMessage extends Message
|
|
||||||
{
|
|
||||||
protected static final String sUNKNOWN = "**UNKNOWN**";
|
|
||||||
protected static final int sINT_NULL_VALUE = -1;
|
|
||||||
protected static final double sDOUBLE_NULL_VALUE = -1.0D;
|
|
||||||
protected SimpleDateFormat mDatestampFormatter =
|
|
||||||
new SimpleDateFormat( "yyyyMMdd HHmmss" );
|
|
||||||
protected DecimalFormat mDecimalFormatter = new DecimalFormat("0.00000");
|
|
||||||
|
|
||||||
protected static final int[] sSYNC = { 8,7,6,5,4,3,2,1,0 };
|
|
||||||
protected static final int[] sAREA = { 9 };
|
|
||||||
protected static final int[] sCHANNEL = { 14,13,12,11,10 };
|
|
||||||
protected static final int[] sHOME_REPEATER = { 19,18,17,16,15 };
|
|
||||||
protected static final int[] sGROUP = { 27,26,25,24,23,22,21,20 };
|
|
||||||
protected static final int[] sFREE = { 32,31,30,29,28 };
|
|
||||||
protected static final int[] sCRC = { 39,38,37,36,35,34,33 };
|
|
||||||
|
|
||||||
protected BinaryMessage mMessage;
|
|
||||||
protected MessageType mMessageType;
|
|
||||||
protected CRC mCRC;
|
|
||||||
protected AliasList mAliasList;
|
|
||||||
|
|
||||||
public LTRStandardMessage( BinaryMessage message,
|
|
||||||
MessageDirection direction,
|
|
||||||
AliasList list )
|
|
||||||
{
|
|
||||||
mMessage = message;
|
|
||||||
mCRC = CRCLTR.check( message, direction );
|
|
||||||
mAliasList = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageType getMessageType()
|
|
||||||
{
|
|
||||||
return mMessageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid()
|
|
||||||
{
|
|
||||||
return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends spaces to the end of the stringbuilder to make it length long
|
|
||||||
*/
|
|
||||||
protected void pad( StringBuilder sb, int length )
|
|
||||||
{
|
|
||||||
while( sb.length() < length )
|
|
||||||
{
|
|
||||||
sb.append( " " );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBinaryMessage()
|
|
||||||
{
|
|
||||||
return mMessage.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a value formatted in hex notation.
|
|
||||||
*
|
|
||||||
* @param value - integer value
|
|
||||||
* @return - hex digits returned
|
|
||||||
*/
|
|
||||||
protected static String getHex( int value, int digits )
|
|
||||||
{
|
|
||||||
return String.format( "%0" + digits + "X", value );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getInt( int[] bits )
|
|
||||||
{
|
|
||||||
int retVal = 0;
|
|
||||||
|
|
||||||
for( int x = 0; x < bits.length; x++ )
|
|
||||||
{
|
|
||||||
if( mMessage.get( bits[ x ] ) )
|
|
||||||
{
|
|
||||||
retVal += 1<<x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pads an integer value with additional zeroes to make it decimalPlaces long
|
|
||||||
*/
|
|
||||||
public String format( int number, int decimalPlaces )
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
|
|
||||||
|
|
||||||
for( int x = 0; x < paddingRequired; x++)
|
|
||||||
{
|
|
||||||
sb.append( "0" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append( number );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String format( String val, int places )
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append( val );
|
|
||||||
|
|
||||||
while( sb.length() < places )
|
|
||||||
{
|
|
||||||
sb.append( " " );
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CRC getCRC()
|
|
||||||
{
|
|
||||||
return mCRC;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getArea()
|
|
||||||
{
|
|
||||||
return getInt( sAREA );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getChannel()
|
|
||||||
{
|
|
||||||
return getInt( sCHANNEL );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHomeRepeater()
|
|
||||||
{
|
|
||||||
return getInt( sHOME_REPEATER );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGroup()
|
|
||||||
{
|
|
||||||
return getInt( sGROUP );
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTalkgroupID()
|
|
||||||
{
|
|
||||||
return getTalkgroupID( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTalkgroupID( boolean format )
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append( getArea() );
|
|
||||||
|
|
||||||
if( format )
|
|
||||||
{
|
|
||||||
sb.append( "-" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
|
|
||||||
if( format )
|
|
||||||
{
|
|
||||||
sb.append( "-" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Alias getTalkgroupIDAlias()
|
|
||||||
{
|
|
||||||
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFree()
|
|
||||||
{
|
|
||||||
return getInt( sFREE );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCRCChecksum()
|
|
||||||
{
|
|
||||||
return getInt( sCRC );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProtocol()
|
|
||||||
{
|
|
||||||
return DecoderType.LTR_STANDARD.getDisplayString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getEventType()
|
|
||||||
{
|
|
||||||
if( mMessageType != null )
|
|
||||||
{
|
|
||||||
return mMessageType.getDisplayText();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return MessageType.UN_KNWN.getDisplayText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToID()
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Alias getToIDAlias()
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getErrorStatus()
|
|
||||||
{
|
|
||||||
return mCRC.getDisplayText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import message.Message;
|
import message.Message;
|
||||||
import message.MessageType;
|
import message.MessageType;
|
||||||
|
import module.decode.ltrstandard.message.LTRStandardMessage;
|
||||||
import filter.Filter;
|
import filter.Filter;
|
||||||
import filter.FilterElement;
|
import filter.FilterElement;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,9 +19,16 @@ package module.decode.ltrstandard;
|
|||||||
|
|
||||||
import message.Message;
|
import message.Message;
|
||||||
import message.MessageDirection;
|
import message.MessageDirection;
|
||||||
|
import module.decode.ltrstandard.message.CallEndMessage;
|
||||||
|
import module.decode.ltrstandard.message.CallMessage;
|
||||||
|
import module.decode.ltrstandard.message.IdleMessage;
|
||||||
|
import module.decode.ltrstandard.message.LTRStandardMessage;
|
||||||
|
import module.decode.ltrstandard.message.UnknownMessage;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import alias.AliasList;
|
import alias.AliasList;
|
||||||
import bits.BinaryMessage;
|
import bits.BinaryMessage;
|
||||||
|
import edac.CRC;
|
||||||
|
import edac.CRCLTR;
|
||||||
|
|
||||||
public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
|
public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
|
||||||
{
|
{
|
||||||
@@ -29,6 +36,12 @@ public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
|
|||||||
private AliasList mAliasList;
|
private AliasList mAliasList;
|
||||||
private Listener<Message> mMessageListener;
|
private Listener<Message> mMessageListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes raw binary messages and converts them to the correct message class
|
||||||
|
*
|
||||||
|
* @param direction - inbound (ISW) or outbound (OSW)
|
||||||
|
* @param list - alias list
|
||||||
|
*/
|
||||||
public LTRStandardMessageProcessor( MessageDirection direction, AliasList list )
|
public LTRStandardMessageProcessor( MessageDirection direction, AliasList list )
|
||||||
{
|
{
|
||||||
mDirection = direction;
|
mDirection = direction;
|
||||||
@@ -36,27 +49,66 @@ public class LTRStandardMessageProcessor implements Listener<BinaryMessage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive( BinaryMessage buffer )
|
public void receive( BinaryMessage binaryMessage )
|
||||||
{
|
{
|
||||||
if( mMessageListener != null )
|
if( mMessageListener != null )
|
||||||
{
|
{
|
||||||
LTRStandardMessage message;
|
//Inbound Status Word (ISW) is a bit-flipped version of the Outbound
|
||||||
|
//(OSW), so flip the bits and process it as an OSW
|
||||||
if( mDirection == MessageDirection.OSW )
|
if( mDirection == MessageDirection.ISW )
|
||||||
{
|
{
|
||||||
message = new LTRStandardOSWMessage( buffer, mAliasList );
|
binaryMessage.flip( 0, 40 );
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
CRC crc = CRCLTR.check( binaryMessage, mDirection );
|
||||||
|
|
||||||
|
if( crc != CRC.FAILED_CRC &&
|
||||||
|
crc != CRC.FAILED_PARITY )
|
||||||
{
|
{
|
||||||
buffer.flip( 0, 40 );
|
LTRStandardMessage message;
|
||||||
|
|
||||||
message = new LTRStandardISWMessage( buffer, mAliasList );
|
int channel = binaryMessage.getInt( LTRStandardMessage.CHANNEL );
|
||||||
|
int home = binaryMessage.getInt( LTRStandardMessage.HOME_REPEATER );
|
||||||
|
int free = binaryMessage.getInt( LTRStandardMessage.FREE );
|
||||||
|
int group = binaryMessage.getInt( LTRStandardMessage.GROUP );
|
||||||
|
|
||||||
|
if( isValidChannel( channel ) &&
|
||||||
|
isValidChannel( home ) &&
|
||||||
|
isValidChannel( free ) )
|
||||||
|
{
|
||||||
|
if( channel == free && group == 255 )
|
||||||
|
{
|
||||||
|
message = new IdleMessage( binaryMessage, mDirection, mAliasList, crc );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message = new CallMessage( binaryMessage, mDirection, mAliasList, crc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( channel == 31 &&
|
||||||
|
isValidChannel( home ) &&
|
||||||
|
isValidChannel( free ) )
|
||||||
|
{
|
||||||
|
message = new CallEndMessage( binaryMessage, mDirection, mAliasList, crc );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message = new UnknownMessage( binaryMessage, mDirection, mAliasList, crc );
|
||||||
|
}
|
||||||
|
|
||||||
|
mMessageListener.receive( message );
|
||||||
}
|
}
|
||||||
|
|
||||||
mMessageListener.receive( message );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the channel (LCN) number to ensure it is in the range 1 -20
|
||||||
|
*/
|
||||||
|
private boolean isValidChannel( int channel )
|
||||||
|
{
|
||||||
|
return ( 1 <= channel && channel <= 20 );
|
||||||
|
}
|
||||||
|
|
||||||
public void setMessageListener( Listener<Message> listener )
|
public void setMessageListener( Listener<Message> listener )
|
||||||
{
|
{
|
||||||
mMessageListener = listener;
|
mMessageListener = listener;
|
||||||
|
|||||||
@@ -1,248 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
******************************************************************************/
|
|
||||||
package module.decode.ltrstandard;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import map.Plottable;
|
|
||||||
import message.MessageDirection;
|
|
||||||
import message.MessageType;
|
|
||||||
import alias.Alias;
|
|
||||||
import alias.AliasList;
|
|
||||||
import bits.BinaryMessage;
|
|
||||||
|
|
||||||
public class LTRStandardOSWMessage extends LTRStandardMessage
|
|
||||||
{
|
|
||||||
private static final int sCALL_END = 31;
|
|
||||||
private static final int sIDLE = 255;
|
|
||||||
|
|
||||||
public LTRStandardOSWMessage( BinaryMessage message, AliasList list )
|
|
||||||
{
|
|
||||||
super( message, MessageDirection.OSW, list );
|
|
||||||
mMessageType = getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append( mDatestampFormatter.format(
|
|
||||||
new Date( System.currentTimeMillis() ) ) );
|
|
||||||
sb.append( " LTR OSW [" );
|
|
||||||
sb.append( mCRC.getAbbreviation() );
|
|
||||||
sb.append( "] " );
|
|
||||||
sb.append( getMessage() );
|
|
||||||
pad( sb, 100 );
|
|
||||||
sb.append( mMessage.toString() );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
switch( mMessageType )
|
|
||||||
{
|
|
||||||
case SY_IDLE:
|
|
||||||
sb.append( "IDLE AREA:" );
|
|
||||||
sb.append( getArea() );
|
|
||||||
sb.append( " LCN:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " HOME:" );
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
sb.append( " GRP:" );
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
sb.append( " FREE:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
break;
|
|
||||||
case CA_STRT:
|
|
||||||
sb.append( "CALL LCN:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " TG [ " );
|
|
||||||
sb.append( getTalkgroupID() );
|
|
||||||
sb.append( "/" );
|
|
||||||
Alias alias = getTalkgroupIDAlias();
|
|
||||||
if( alias != null )
|
|
||||||
{
|
|
||||||
sb.append( getTalkgroupIDAlias().getName() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.append( "UNKNOWN" );
|
|
||||||
}
|
|
||||||
sb.append( " ] FREE:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
break;
|
|
||||||
case CA_ENDD:
|
|
||||||
sb.append( "END* AREA:" );
|
|
||||||
sb.append( getArea() );
|
|
||||||
sb.append( " LCN:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " TG [" );
|
|
||||||
sb.append( getTalkgroupID() );
|
|
||||||
sb.append( "/" );
|
|
||||||
Alias endAlias = getTalkgroupIDAlias();
|
|
||||||
if( endAlias != null )
|
|
||||||
{
|
|
||||||
sb.append( getTalkgroupIDAlias().getName() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.append( "UNKNOWN" );
|
|
||||||
}
|
|
||||||
sb.append( "] FREE:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
sb.append( "UNKNOWN A:" );
|
|
||||||
sb.append( getArea() );
|
|
||||||
sb.append( " C:" );
|
|
||||||
sb.append( format( getChannel(), 2 ) );
|
|
||||||
sb.append( " H:" );
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
sb.append( " G:" );
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
sb.append( " F:" );
|
|
||||||
sb.append( format( getFree(), 2 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTalkgroupID()
|
|
||||||
{
|
|
||||||
return getTalkgroupID( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTalkgroupID( boolean format )
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append( getArea() );
|
|
||||||
|
|
||||||
if( format )
|
|
||||||
{
|
|
||||||
sb.append( "-" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append( format( getHomeRepeater(), 2 ) );
|
|
||||||
|
|
||||||
if( format )
|
|
||||||
{
|
|
||||||
sb.append( "-" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append( format( getGroup(), 3 ) );
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Alias getTalkgroupIDAlias()
|
|
||||||
{
|
|
||||||
return mAliasList.getTalkgroupAlias( getTalkgroupID() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageType getType()
|
|
||||||
{
|
|
||||||
MessageType retVal = MessageType.UN_KNWN;
|
|
||||||
|
|
||||||
/* Group 255 when the channel equals the free is the IDLE message */
|
|
||||||
if( getGroup() == sIDLE && getChannel() == getFree() )
|
|
||||||
{
|
|
||||||
retVal = MessageType.SY_IDLE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int channel = getChannel();
|
|
||||||
|
|
||||||
if( channel == sCALL_END )
|
|
||||||
{
|
|
||||||
retVal = MessageType.CA_ENDD;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
retVal = MessageType.CA_STRT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFromID()
|
|
||||||
{
|
|
||||||
if( mMessageType == MessageType.CA_STRT ||
|
|
||||||
mMessageType == MessageType.CA_ENDD )
|
|
||||||
{
|
|
||||||
return getTalkgroupID();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Alias getFromIDAlias()
|
|
||||||
{
|
|
||||||
if( mMessageType == MessageType.CA_STRT ||
|
|
||||||
mMessageType == MessageType.CA_ENDD )
|
|
||||||
{
|
|
||||||
return getTalkgroupIDAlias();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plottable getPlottable()
|
|
||||||
{
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a listing of aliases contained in the message.
|
|
||||||
*/
|
|
||||||
public List<Alias> getAliases()
|
|
||||||
{
|
|
||||||
List<Alias> aliases = new ArrayList<Alias>();
|
|
||||||
|
|
||||||
Alias from = getFromIDAlias();
|
|
||||||
|
|
||||||
if( from != null )
|
|
||||||
{
|
|
||||||
aliases.add( from );
|
|
||||||
}
|
|
||||||
|
|
||||||
Alias to = getToIDAlias();
|
|
||||||
|
|
||||||
if( to != null )
|
|
||||||
{
|
|
||||||
aliases.add( to );
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
67
src/module/decode/ltrstandard/message/CallEndMessage.java
Normal file
67
src/module/decode/ltrstandard/message/CallEndMessage.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 module.decode.ltrstandard.message;
|
||||||
|
|
||||||
|
import message.MessageDirection;
|
||||||
|
import message.MessageType;
|
||||||
|
import alias.Alias;
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
|
import edac.CRC;
|
||||||
|
|
||||||
|
public class CallEndMessage extends LTRStandardMessage
|
||||||
|
{
|
||||||
|
public CallEndMessage( BinaryMessage message, MessageDirection direction,
|
||||||
|
AliasList list, CRC crc )
|
||||||
|
{
|
||||||
|
super( message, direction, list, crc );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageType getMessageType()
|
||||||
|
{
|
||||||
|
return MessageType.CA_ENDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "END* AREA:" );
|
||||||
|
sb.append( getArea() );
|
||||||
|
sb.append( " LCN:" );
|
||||||
|
sb.append( getChannelFormatted() );
|
||||||
|
sb.append( " TG [" );
|
||||||
|
sb.append( getToID() );
|
||||||
|
sb.append( "/" );
|
||||||
|
Alias endAlias = getToIDAlias();
|
||||||
|
if( endAlias != null )
|
||||||
|
{
|
||||||
|
sb.append( getToIDAlias().getName() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append( "UNKNOWN" );
|
||||||
|
}
|
||||||
|
sb.append( "] FREE:" );
|
||||||
|
sb.append( getFreeFormatted() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/module/decode/ltrstandard/message/CallMessage.java
Normal file
68
src/module/decode/ltrstandard/message/CallMessage.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 module.decode.ltrstandard.message;
|
||||||
|
|
||||||
|
import message.MessageDirection;
|
||||||
|
import message.MessageType;
|
||||||
|
import alias.Alias;
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
|
import edac.CRC;
|
||||||
|
|
||||||
|
public class CallMessage extends LTRStandardMessage
|
||||||
|
{
|
||||||
|
public CallMessage( BinaryMessage message, MessageDirection direction,
|
||||||
|
AliasList list, CRC crc )
|
||||||
|
{
|
||||||
|
super( message, direction, list, crc );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageType getMessageType()
|
||||||
|
{
|
||||||
|
return MessageType.CA_STRT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "CALL LCN:" );
|
||||||
|
sb.append( getChannelFormatted() );
|
||||||
|
sb.append( " TG [ " );
|
||||||
|
sb.append( getToID() );
|
||||||
|
sb.append( "/" );
|
||||||
|
|
||||||
|
Alias alias = getToIDAlias();
|
||||||
|
|
||||||
|
if( alias != null )
|
||||||
|
{
|
||||||
|
sb.append( getToIDAlias().getName() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append( "UNKNOWN" );
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append( " ] FREE:" );
|
||||||
|
sb.append( getFreeFormatted() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/module/decode/ltrstandard/message/IdleMessage.java
Normal file
58
src/module/decode/ltrstandard/message/IdleMessage.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 module.decode.ltrstandard.message;
|
||||||
|
|
||||||
|
import message.MessageDirection;
|
||||||
|
import message.MessageType;
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
|
import edac.CRC;
|
||||||
|
|
||||||
|
public class IdleMessage extends LTRStandardMessage
|
||||||
|
{
|
||||||
|
public IdleMessage( BinaryMessage message, MessageDirection direction,
|
||||||
|
AliasList list, CRC crc )
|
||||||
|
{
|
||||||
|
super( message, direction, list, crc );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageType getMessageType()
|
||||||
|
{
|
||||||
|
return MessageType.SY_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "IDLE AREA:" );
|
||||||
|
sb.append( getArea() );
|
||||||
|
sb.append( " LCN:" );
|
||||||
|
sb.append( getChannelFormatted() );
|
||||||
|
sb.append( " HOME:" );
|
||||||
|
sb.append( getHomeRepeaterFormatted() );
|
||||||
|
sb.append( " GRP:" );
|
||||||
|
sb.append( getGroupFormatted() );
|
||||||
|
sb.append( " FREE:" );
|
||||||
|
sb.append( getFreeFormatted() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
213
src/module/decode/ltrstandard/message/LTRStandardMessage.java
Normal file
213
src/module/decode/ltrstandard/message/LTRStandardMessage.java
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 module.decode.ltrstandard.message;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import message.Message;
|
||||||
|
import message.MessageDirection;
|
||||||
|
import message.MessageType;
|
||||||
|
import module.decode.DecoderType;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import alias.Alias;
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
|
import edac.CRC;
|
||||||
|
|
||||||
|
public abstract class LTRStandardMessage extends Message
|
||||||
|
{
|
||||||
|
protected SimpleDateFormat mDatestampFormatter =
|
||||||
|
new SimpleDateFormat( "yyyyMMdd HHmmss" );
|
||||||
|
|
||||||
|
public static final int[] SYNC = { 0,1,2,3,4,5,6,7,8 };
|
||||||
|
public static final int[] AREA = { 9 };
|
||||||
|
public static final int[] CHANNEL = { 10,11,12,13,14 };
|
||||||
|
public static final int[] HOME_REPEATER = { 15,16,17,18,19 };
|
||||||
|
public static final int[] GROUP = { 20,21,22,23,24,25,26,27 };
|
||||||
|
public static final int[] FREE = { 28,29,30,31,32 };
|
||||||
|
public static final int[] CHECKSUM = { 33,34,35,36,37,38,39 };
|
||||||
|
|
||||||
|
protected BinaryMessage mMessage;
|
||||||
|
protected MessageDirection mMessageDirection;
|
||||||
|
protected AliasList mAliasList;
|
||||||
|
protected CRC mCRC;
|
||||||
|
|
||||||
|
public LTRStandardMessage( BinaryMessage message,
|
||||||
|
MessageDirection direction,
|
||||||
|
AliasList list,
|
||||||
|
CRC crc )
|
||||||
|
{
|
||||||
|
mMessage = message;
|
||||||
|
mMessageDirection = direction;
|
||||||
|
mAliasList = list;
|
||||||
|
mCRC = crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract MessageType getMessageType();
|
||||||
|
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mCRC != CRC.FAILED_CRC && mCRC != CRC.FAILED_PARITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append( mDatestampFormatter.format(
|
||||||
|
new Date( System.currentTimeMillis() ) ) );
|
||||||
|
sb.append( " LTR " );
|
||||||
|
sb.append( mMessageDirection.name() );
|
||||||
|
sb.append( " [" );
|
||||||
|
sb.append( mCRC.getAbbreviation() );
|
||||||
|
sb.append( "] " );
|
||||||
|
sb.append( getMessage() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBinaryMessage()
|
||||||
|
{
|
||||||
|
return mMessage.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CRC getCRC()
|
||||||
|
{
|
||||||
|
return mCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getArea()
|
||||||
|
{
|
||||||
|
return mMessage.getInt( AREA );
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChannel()
|
||||||
|
{
|
||||||
|
return mMessage.getInt( CHANNEL );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChannelFormatted()
|
||||||
|
{
|
||||||
|
return StringUtils.leftPad( String.valueOf( getChannel() ), 2, "0" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHomeRepeater()
|
||||||
|
{
|
||||||
|
return mMessage.getInt( HOME_REPEATER );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHomeRepeaterFormatted()
|
||||||
|
{
|
||||||
|
return StringUtils.leftPad( String.valueOf( getHomeRepeater() ), 2, "0" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGroup()
|
||||||
|
{
|
||||||
|
return mMessage.getInt( GROUP );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupFormatted()
|
||||||
|
{
|
||||||
|
return StringUtils.leftPad( String.valueOf( getGroup() ), 3, "0" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFree()
|
||||||
|
{
|
||||||
|
return mMessage.getInt( FREE );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFreeFormatted()
|
||||||
|
{
|
||||||
|
return StringUtils.leftPad( String.valueOf( getFree() ), 2, "0" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFromID()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Alias getFromIDAlias()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToID()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( getArea() );
|
||||||
|
sb.append( "-" );
|
||||||
|
sb.append( getHomeRepeaterFormatted() );
|
||||||
|
sb.append( "-" );
|
||||||
|
sb.append( getGroupFormatted() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Alias getToIDAlias()
|
||||||
|
{
|
||||||
|
return mAliasList.getTalkgroupAlias( getToID() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Alias> getAliases()
|
||||||
|
{
|
||||||
|
List<Alias> aliases = new ArrayList<>();
|
||||||
|
|
||||||
|
Alias talkgroupAlias = getToIDAlias();
|
||||||
|
|
||||||
|
if( talkgroupAlias != null )
|
||||||
|
{
|
||||||
|
aliases.add( talkgroupAlias );
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCRCChecksum()
|
||||||
|
{
|
||||||
|
return mMessage.getInt( CHECKSUM );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocol()
|
||||||
|
{
|
||||||
|
return DecoderType.LTR_STANDARD.getDisplayString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventType()
|
||||||
|
{
|
||||||
|
return getMessageType().getDisplayText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorStatus()
|
||||||
|
{
|
||||||
|
return mCRC.getDisplayText();
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/module/decode/ltrstandard/message/UnknownMessage.java
Normal file
58
src/module/decode/ltrstandard/message/UnknownMessage.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 module.decode.ltrstandard.message;
|
||||||
|
|
||||||
|
import message.MessageDirection;
|
||||||
|
import message.MessageType;
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
|
import edac.CRC;
|
||||||
|
|
||||||
|
public class UnknownMessage extends LTRStandardMessage
|
||||||
|
{
|
||||||
|
public UnknownMessage( BinaryMessage message, MessageDirection direction,
|
||||||
|
AliasList list, CRC crc )
|
||||||
|
{
|
||||||
|
super( message, direction, list, crc );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageType getMessageType()
|
||||||
|
{
|
||||||
|
return MessageType.UN_KNWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "UNKNOWN AREA:" );
|
||||||
|
sb.append( getArea() );
|
||||||
|
sb.append( " LCN:" );
|
||||||
|
sb.append( getChannelFormatted() );
|
||||||
|
sb.append( " HOME:" );
|
||||||
|
sb.append( getHomeRepeaterFormatted() );
|
||||||
|
sb.append( " GRP:" );
|
||||||
|
sb.append( getGroupFormatted() );
|
||||||
|
sb.append( " FREE:" );
|
||||||
|
sb.append( getFreeFormatted() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -301,8 +301,8 @@ public class MPT1327Decoder extends Decoder
|
|||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
// TODO Auto-generated method stub
|
mControlMessageFramer.reset();
|
||||||
|
mTrafficMessageFramer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ import java.util.TreeSet;
|
|||||||
|
|
||||||
import message.Message;
|
import message.Message;
|
||||||
import module.decode.DecoderType;
|
import module.decode.DecoderType;
|
||||||
|
import module.decode.config.DecodeConfiguration;
|
||||||
import module.decode.event.CallEvent;
|
import module.decode.event.CallEvent;
|
||||||
import module.decode.event.CallEvent.CallEventType;
|
import module.decode.event.CallEvent.CallEventType;
|
||||||
import module.decode.mpt1327.MPT1327Message.IdentType;
|
import module.decode.mpt1327.MPT1327Message.IdentType;
|
||||||
|
import module.decode.state.ChangeChannelTimeoutEvent;
|
||||||
import module.decode.state.ChangedAttribute;
|
import module.decode.state.ChangedAttribute;
|
||||||
import module.decode.state.DecoderState;
|
import module.decode.state.DecoderState;
|
||||||
import module.decode.state.DecoderStateEvent;
|
import module.decode.state.DecoderStateEvent;
|
||||||
@@ -41,6 +43,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import alias.Alias;
|
import alias.Alias;
|
||||||
import alias.AliasList;
|
import alias.AliasList;
|
||||||
|
import audio.metadata.Metadata;
|
||||||
|
import audio.metadata.MetadataType;
|
||||||
import controller.channel.Channel.ChannelType;
|
import controller.channel.Channel.ChannelType;
|
||||||
import controller.channel.map.ChannelMap;
|
import controller.channel.map.ChannelMap;
|
||||||
|
|
||||||
@@ -60,15 +64,18 @@ public class MPT1327DecoderState extends DecoderState
|
|||||||
private ChannelMap mChannelMap;
|
private ChannelMap mChannelMap;
|
||||||
|
|
||||||
private long mFrequency = 0;
|
private long mFrequency = 0;
|
||||||
|
private long mCallTimeout;
|
||||||
|
|
||||||
public MPT1327DecoderState( AliasList aliasList, ChannelMap channelMap,
|
public MPT1327DecoderState( AliasList aliasList,
|
||||||
ChannelType channelType )
|
ChannelMap channelMap,
|
||||||
|
ChannelType channelType,
|
||||||
|
long callTimeout )
|
||||||
{
|
{
|
||||||
super( aliasList );
|
super( aliasList );
|
||||||
|
|
||||||
mChannelMap = channelMap;
|
mChannelMap = channelMap;
|
||||||
|
|
||||||
mChannelType = channelType;
|
mChannelType = channelType;
|
||||||
|
mCallTimeout = callTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -252,8 +259,42 @@ public class MPT1327DecoderState extends DecoderState
|
|||||||
|
|
||||||
/* Traffic Channel Events */
|
/* Traffic Channel Events */
|
||||||
case CLEAR:
|
case CLEAR:
|
||||||
|
mChannelNumber = mpt.getChannel();
|
||||||
|
|
||||||
|
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||||
|
break;
|
||||||
case MAINT:
|
case MAINT:
|
||||||
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
|
if( mChannelType == ChannelType.STANDARD )
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* When we receive a MAINT message and we're configured
|
||||||
|
* as a standard channel, we need to apply the call
|
||||||
|
* timeout specified by the user. Otherwise we'll
|
||||||
|
* be using the shorter default call timeout
|
||||||
|
*/
|
||||||
|
broadcast( new ChangeChannelTimeoutEvent( this,
|
||||||
|
mChannelType, mCallTimeout ) );
|
||||||
|
|
||||||
|
if( mCurrentCallEvent == null )
|
||||||
|
{
|
||||||
|
mCurrentCallEvent = new MPT1327CallEvent.Builder( CallEventType.CALL )
|
||||||
|
.aliasList( getAliasList() )
|
||||||
|
.channel( String.valueOf( mChannelNumber ) )
|
||||||
|
.details( "MONITORED TRAFFIC CHANNEL" )
|
||||||
|
.frequency( mFrequency )
|
||||||
|
.to( mpt.getToID() )
|
||||||
|
.build();
|
||||||
|
|
||||||
|
broadcast( mCurrentCallEvent );
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(new Metadata(MetadataType.TO,
|
||||||
|
mpt.getToID(), mpt.getToIDAlias(), true));
|
||||||
|
|
||||||
|
broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
|
||||||
|
|
||||||
|
setToTalkgroup( mpt.getToID() );
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -276,6 +317,25 @@ public class MPT1327DecoderState extends DecoderState
|
|||||||
|
|
||||||
mToTalkgroup = null;
|
mToTalkgroup = null;
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a standard channel, reset the fade timeout to the default
|
||||||
|
* timeout. Once processing is underway, if we get a MAINT message,
|
||||||
|
* this indicates we're processing a traffic channel as a standard
|
||||||
|
* channel, so we'll issue a different call timeout then.
|
||||||
|
*/
|
||||||
|
if( mChannelType == ChannelType.STANDARD )
|
||||||
|
{
|
||||||
|
broadcast( new ChangeChannelTimeoutEvent( this, mChannelType,
|
||||||
|
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000 ) );
|
||||||
|
|
||||||
|
if( mCurrentCallEvent != null )
|
||||||
|
{
|
||||||
|
mCurrentCallEvent.end();
|
||||||
|
broadcast( mCurrentCallEvent );
|
||||||
|
mCurrentCallEvent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSite()
|
public String getSite()
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import module.decode.p25.message.ldu.lc.TelephoneInterconnectVoiceChannelUser;
|
|||||||
import module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser;
|
import module.decode.p25.message.ldu.lc.UnitToUnitVoiceChannelUser;
|
||||||
import module.decode.p25.message.pdu.PDUMessage;
|
import module.decode.p25.message.pdu.PDUMessage;
|
||||||
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
|
import module.decode.p25.message.pdu.confirmed.PDUConfirmedMessage;
|
||||||
|
import module.decode.p25.message.pdu.confirmed.PDUTypeUnknown;
|
||||||
|
import module.decode.p25.message.pdu.confirmed.PacketData;
|
||||||
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextAccept;
|
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextAccept;
|
||||||
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextReject;
|
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextReject;
|
||||||
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextRequest;
|
import module.decode.p25.message.pdu.confirmed.SNDCPActivateTDSContextRequest;
|
||||||
@@ -118,7 +120,6 @@ import module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdat
|
|||||||
import module.decode.p25.reference.IPProtocol;
|
import module.decode.p25.reference.IPProtocol;
|
||||||
import module.decode.p25.reference.LinkControlOpcode;
|
import module.decode.p25.reference.LinkControlOpcode;
|
||||||
import module.decode.p25.reference.Response;
|
import module.decode.p25.reference.Response;
|
||||||
import module.decode.p25.reference.Service;
|
|
||||||
import module.decode.p25.reference.Vendor;
|
import module.decode.p25.reference.Vendor;
|
||||||
import module.decode.state.ChangedAttribute;
|
import module.decode.state.ChangedAttribute;
|
||||||
import module.decode.state.DecoderState;
|
import module.decode.state.DecoderState;
|
||||||
@@ -406,12 +407,12 @@ public class P25DecoderState extends DecoderState
|
|||||||
{
|
{
|
||||||
if( hdu.isValid() )
|
if( hdu.isValid() )
|
||||||
{
|
{
|
||||||
State state = hdu.isEncrypted() ? State.ENCRYPTED : State.CALL;
|
broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) );
|
||||||
|
|
||||||
broadcast( new DecoderStateEvent( this, Event.START, state ) );
|
|
||||||
|
|
||||||
String to = hdu.getToID();
|
String to = hdu.getToID();
|
||||||
|
|
||||||
|
updateTo( to );
|
||||||
|
|
||||||
if( mCurrentCallEvent == null )
|
if( mCurrentCallEvent == null )
|
||||||
{
|
{
|
||||||
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL )
|
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL )
|
||||||
@@ -425,7 +426,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
broadcast( mCurrentCallEvent );
|
broadcast( mCurrentCallEvent );
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTo( to );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +437,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
{
|
{
|
||||||
if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION )
|
if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION )
|
||||||
{
|
{
|
||||||
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
|
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||||
|
|
||||||
if( mCurrentCallEvent != null )
|
if( mCurrentCallEvent != null )
|
||||||
{
|
{
|
||||||
@@ -615,12 +615,14 @@ public class P25DecoderState extends DecoderState
|
|||||||
module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser =
|
module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser =
|
||||||
(module.decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc;
|
(module.decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc;
|
||||||
|
|
||||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION,
|
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
|
||||||
( gvcuser.isEncrypted() ? State.ENCRYPTED : State.CALL ) ) );
|
|
||||||
|
|
||||||
String from = gvcuser.getSourceAddress();
|
String from = gvcuser.getSourceAddress();
|
||||||
String to = gvcuser.getGroupAddress();
|
String to = gvcuser.getGroupAddress();
|
||||||
|
|
||||||
|
updateFrom( from );
|
||||||
|
updateTo( to );
|
||||||
|
|
||||||
if( mCurrentCallEvent == null )
|
if( mCurrentCallEvent == null )
|
||||||
{
|
{
|
||||||
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL )
|
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL )
|
||||||
@@ -636,9 +638,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
|
|
||||||
broadcast( mCurrentCallEvent );
|
broadcast( mCurrentCallEvent );
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFrom( from );
|
|
||||||
updateTo( to );
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MESSAGE_UPDATE:
|
case MESSAGE_UPDATE:
|
||||||
@@ -845,6 +844,8 @@ public class P25DecoderState extends DecoderState
|
|||||||
|
|
||||||
String to = tivcu.getAddress();
|
String to = tivcu.getAddress();
|
||||||
|
|
||||||
|
updateTo( to );
|
||||||
|
|
||||||
if( mCurrentCallEvent == null )
|
if( mCurrentCallEvent == null )
|
||||||
{
|
{
|
||||||
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT )
|
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT )
|
||||||
@@ -858,8 +859,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
|
|
||||||
broadcast( mCurrentCallEvent );
|
broadcast( mCurrentCallEvent );
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTo( to );
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UNIT_AUTHENTICATION_COMMAND:
|
case UNIT_AUTHENTICATION_COMMAND:
|
||||||
@@ -922,7 +921,10 @@ public class P25DecoderState extends DecoderState
|
|||||||
(module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser)tdulc;
|
(module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser)tdulc;
|
||||||
|
|
||||||
String from = uuvcu.getSourceAddress();
|
String from = uuvcu.getSourceAddress();
|
||||||
|
updateFrom( from );
|
||||||
|
|
||||||
String to = uuvcu.getTargetAddress();
|
String to = uuvcu.getTargetAddress();
|
||||||
|
updateTo( to );
|
||||||
|
|
||||||
if( mCurrentCallEvent != null )
|
if( mCurrentCallEvent != null )
|
||||||
{
|
{
|
||||||
@@ -936,9 +938,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
.to( to )
|
.to( to )
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFrom( from );
|
|
||||||
updateTo( to );
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -950,35 +949,14 @@ public class P25DecoderState extends DecoderState
|
|||||||
* Log optional vendor format messages that we don't yet support so that we
|
* Log optional vendor format messages that we don't yet support so that we
|
||||||
* can understand those messages and eventually add support.
|
* can understand those messages and eventually add support.
|
||||||
*/
|
*/
|
||||||
private void logAlternateVendorMessage( P25Message p25 )
|
private void logAlternateVendorMessage( P25Message message )
|
||||||
{
|
{
|
||||||
// if( p25 instanceof TDULinkControlMessage )
|
// if( message.isValid() )
|
||||||
// {
|
// {
|
||||||
// TDULinkControlMessage tdulc = (TDULinkControlMessage)p25;
|
// mLog.info( "PLEASE NOTIFY DEVELOPER - UNRECOGNIZED P25 VENDOR FORMAT -"
|
||||||
//
|
// + " DUID:" + message.getDUID()
|
||||||
// mLog.debug( "TDULC - VENDOR [" + tdulc.getVendor() +
|
// + " MSG:" + message.getBinaryMessage()
|
||||||
// "] SPECIFIC FORMAT FOR " + tdulc.getVendorOpcode().getLabel() +
|
// + " CLASS:" + message.getClass() );
|
||||||
// " " + tdulc.getBinaryMessage() );
|
|
||||||
// }
|
|
||||||
// else if( p25 instanceof LDU1Message )
|
|
||||||
// {
|
|
||||||
// LDU1Message ldu1 = (LDU1Message)p25;
|
|
||||||
//
|
|
||||||
// mLog.debug( "LDU1 - VENDOR [" + ldu1.getVendor() +
|
|
||||||
// "] SPECIFIC FORMAT FOR " + ldu1.getVendorOpcode().getLabel() +
|
|
||||||
// " " + ldu1.getBinaryMessage() );
|
|
||||||
// }
|
|
||||||
// else if( p25 instanceof PDUMessage )
|
|
||||||
// {
|
|
||||||
// PDUMessage pdu = (PDUMessage)p25;
|
|
||||||
//
|
|
||||||
// mLog.debug( "PDU - VENDOR [" + pdu.getVendor() +
|
|
||||||
// "] SPECIFIC FORMAT FOR " + pdu.getVendorOpcode().getLabel() +
|
|
||||||
// " " + pdu.getBinaryMessage() );
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// mLog.debug( "P25 - ALTERNATE VENDOR MESSAGE [" + p25.getClass() + "]" );
|
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -989,15 +967,11 @@ public class P25DecoderState extends DecoderState
|
|||||||
*/
|
*/
|
||||||
private void processLDU( LDUMessage ldu )
|
private void processLDU( LDUMessage ldu )
|
||||||
{
|
{
|
||||||
State state = ( ldu.isValid() && ldu.isEncrypted() ) ?
|
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) );
|
||||||
State.ENCRYPTED : State.CALL;
|
|
||||||
|
|
||||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, state ) );
|
|
||||||
|
|
||||||
if( mCurrentCallEvent == null )
|
if( mCurrentCallEvent == null )
|
||||||
{
|
{
|
||||||
mCurrentCallEvent = new P25CallEvent.Builder(
|
mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL )
|
||||||
( state == State.CALL ) ? CallEventType.CALL : CallEventType.ENCRYPTED_CALL )
|
|
||||||
.aliasList( getAliasList() )
|
.aliasList( getAliasList() )
|
||||||
.channel( mCurrentChannel )
|
.channel( mCurrentChannel )
|
||||||
.frequency( mCurrentChannelFrequency )
|
.frequency( mCurrentChannelFrequency )
|
||||||
@@ -1057,7 +1031,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CALL_TERMINATION_OR_CANCELLATION:
|
case CALL_TERMINATION_OR_CANCELLATION:
|
||||||
broadcast( new DecoderStateEvent( this, Event.END, State.END ) );
|
broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) );
|
||||||
mCurrentCallEvent = null;
|
mCurrentCallEvent = null;
|
||||||
|
|
||||||
if( !( ldu instanceof CallTermination ) )
|
if( !( ldu instanceof CallTermination ) )
|
||||||
@@ -1115,42 +1089,43 @@ public class P25DecoderState extends DecoderState
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GROUP_VOICE_CHANNEL_UPDATE:
|
case GROUP_VOICE_CHANNEL_UPDATE:
|
||||||
if( mChannelType == ChannelType.STANDARD &&
|
if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate )
|
||||||
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate )
|
|
||||||
{
|
{
|
||||||
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu =
|
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu =
|
||||||
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu;
|
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu;
|
||||||
|
|
||||||
String userA = gvcu.getGroupAddressA();
|
String userA = gvcu.getGroupAddressA();
|
||||||
|
|
||||||
if( !mCallDetectTalkgroups.contains( userA ) )
|
|
||||||
{
|
|
||||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
|
||||||
.aliasList( getAliasList() )
|
|
||||||
.channel( gvcu.getChannelA() )
|
|
||||||
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
|
||||||
.frequency( gvcu.getDownlinkFrequencyA() )
|
|
||||||
.to( userA )
|
|
||||||
.build() );
|
|
||||||
|
|
||||||
mCallDetectTalkgroups.add( userA );
|
|
||||||
}
|
|
||||||
|
|
||||||
String userB = gvcu.getGroupAddressB();
|
String userB = gvcu.getGroupAddressB();
|
||||||
|
|
||||||
if( userB != null &&
|
if( mChannelType == ChannelType.STANDARD )
|
||||||
!userB.contentEquals( "0000" ) &&
|
|
||||||
!mCallDetectTalkgroups.contains( userB ) )
|
|
||||||
{
|
{
|
||||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
if( !mCallDetectTalkgroups.contains( userA ) )
|
||||||
.aliasList( getAliasList() )
|
{
|
||||||
.channel( gvcu.getChannelB() )
|
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||||
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
.aliasList( getAliasList() )
|
||||||
.frequency( gvcu.getDownlinkFrequencyB() )
|
.channel( gvcu.getChannelA() )
|
||||||
.to( userB )
|
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||||
.build() );
|
.frequency( gvcu.getDownlinkFrequencyA() )
|
||||||
|
.to( userA )
|
||||||
|
.build() );
|
||||||
|
|
||||||
|
mCallDetectTalkgroups.add( userA );
|
||||||
|
}
|
||||||
|
|
||||||
mCallDetectTalkgroups.add( userB );
|
if( userB != null &&
|
||||||
|
!userB.contentEquals( "0000" ) &&
|
||||||
|
!mCallDetectTalkgroups.contains( userB ) )
|
||||||
|
{
|
||||||
|
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||||
|
.aliasList( getAliasList() )
|
||||||
|
.channel( gvcu.getChannelB() )
|
||||||
|
.details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||||
|
.frequency( gvcu.getDownlinkFrequencyB() )
|
||||||
|
.to( userB )
|
||||||
|
.build() );
|
||||||
|
|
||||||
|
mCallDetectTalkgroups.add( userB );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1159,25 +1134,27 @@ public class P25DecoderState extends DecoderState
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT:
|
case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT:
|
||||||
if( mChannelType == ChannelType.STANDARD &&
|
if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit )
|
||||||
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit )
|
|
||||||
{
|
{
|
||||||
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue =
|
module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue =
|
||||||
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu;
|
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu;
|
||||||
|
|
||||||
String group = gvcue.getGroupAddress();
|
String group = gvcue.getGroupAddress();
|
||||||
|
|
||||||
if( !mCallDetectTalkgroups.contains( group ) )
|
if( mChannelType == ChannelType.STANDARD )
|
||||||
{
|
{
|
||||||
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
if( !mCallDetectTalkgroups.contains( group ) )
|
||||||
.aliasList( getAliasList() )
|
{
|
||||||
.channel( gvcue.getTransmitChannel() )
|
broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT )
|
||||||
.details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) )
|
.aliasList( getAliasList() )
|
||||||
.frequency( gvcue.getDownlinkFrequency() )
|
.channel( gvcue.getTransmitChannel() )
|
||||||
.to( group )
|
.details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) )
|
||||||
.build() );
|
.frequency( gvcue.getDownlinkFrequency() )
|
||||||
|
.to( group )
|
||||||
mCallDetectTalkgroups.add( group );
|
.build() );
|
||||||
|
|
||||||
|
mCallDetectTalkgroups.add( group );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1187,29 +1164,32 @@ public class P25DecoderState extends DecoderState
|
|||||||
break;
|
break;
|
||||||
case GROUP_VOICE_CHANNEL_USER:
|
case GROUP_VOICE_CHANNEL_USER:
|
||||||
/* Indicates the current user of the current channel */
|
/* Indicates the current user of the current channel */
|
||||||
if( mChannelType == ChannelType.STANDARD &&
|
if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser )
|
||||||
ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser )
|
|
||||||
{
|
{
|
||||||
module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser =
|
module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser =
|
||||||
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu;
|
(module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu;
|
||||||
|
|
||||||
if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL )
|
|
||||||
{
|
|
||||||
mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL );
|
|
||||||
broadcast( mCurrentCallEvent );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mCurrentCallEvent.getDetails() == null )
|
|
||||||
{
|
|
||||||
mCurrentCallEvent.setDetails(
|
|
||||||
( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) +
|
|
||||||
( gvcuser.isEmergency() ? "EMERGENCY " : "" ) );
|
|
||||||
|
|
||||||
broadcast( mCurrentCallEvent );
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFrom( gvcuser.getSourceAddress() );
|
updateFrom( gvcuser.getSourceAddress() );
|
||||||
|
|
||||||
updateTo( gvcuser.getGroupAddress() );
|
updateTo( gvcuser.getGroupAddress() );
|
||||||
|
|
||||||
|
if( mChannelType == ChannelType.STANDARD )
|
||||||
|
{
|
||||||
|
if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL )
|
||||||
|
{
|
||||||
|
mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL );
|
||||||
|
broadcast( mCurrentCallEvent );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mCurrentCallEvent.getDetails() == null )
|
||||||
|
{
|
||||||
|
mCurrentCallEvent.setDetails(
|
||||||
|
( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) +
|
||||||
|
( gvcuser.isEmergency() ? "EMERGENCY " : "" ) );
|
||||||
|
|
||||||
|
broadcast( mCurrentCallEvent );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1405,6 +1385,8 @@ public class P25DecoderState extends DecoderState
|
|||||||
TelephoneInterconnectVoiceChannelUser tivcu =
|
TelephoneInterconnectVoiceChannelUser tivcu =
|
||||||
(TelephoneInterconnectVoiceChannelUser)ldu;
|
(TelephoneInterconnectVoiceChannelUser)ldu;
|
||||||
|
|
||||||
|
updateTo( tivcu.getAddress() );
|
||||||
|
|
||||||
if( mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT )
|
if( mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT )
|
||||||
{
|
{
|
||||||
mCurrentCallEvent.setCallEventType( CallEventType.TELEPHONE_INTERCONNECT );
|
mCurrentCallEvent.setCallEventType( CallEventType.TELEPHONE_INTERCONNECT );
|
||||||
@@ -1419,8 +1401,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
|
|
||||||
broadcast( mCurrentCallEvent );
|
broadcast( mCurrentCallEvent );
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTo( tivcu.getAddress() );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1486,6 +1466,10 @@ public class P25DecoderState extends DecoderState
|
|||||||
UnitToUnitVoiceChannelUser uuvcu =
|
UnitToUnitVoiceChannelUser uuvcu =
|
||||||
(UnitToUnitVoiceChannelUser)ldu;
|
(UnitToUnitVoiceChannelUser)ldu;
|
||||||
|
|
||||||
|
updateFrom( uuvcu.getSourceAddress() );
|
||||||
|
|
||||||
|
updateTo( uuvcu.getTargetAddress() );
|
||||||
|
|
||||||
if( mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL )
|
if( mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL )
|
||||||
{
|
{
|
||||||
mCurrentCallEvent.setCallEventType( CallEventType.UNIT_TO_UNIT_CALL );
|
mCurrentCallEvent.setCallEventType( CallEventType.UNIT_TO_UNIT_CALL );
|
||||||
@@ -1500,9 +1484,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
|
|
||||||
broadcast( mCurrentCallEvent );
|
broadcast( mCurrentCallEvent );
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFrom( uuvcu.getSourceAddress() );
|
|
||||||
updateTo( uuvcu.getTargetAddress() );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1521,7 +1502,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
private void processTSBK( TSBKMessage tsbk )
|
private void processTSBK( TSBKMessage tsbk )
|
||||||
{
|
{
|
||||||
/* Trunking Signalling Block Messages - indicates Control Channel */
|
/* Trunking Signalling Block Messages - indicates Control Channel */
|
||||||
broadcast( new DecoderStateEvent( this, Event.START, State.CONTROL ) );
|
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) );
|
||||||
|
|
||||||
if( tsbk.getVendor() == Vendor.STANDARD )
|
if( tsbk.getVendor() == Vendor.STANDARD )
|
||||||
{
|
{
|
||||||
@@ -1650,7 +1631,11 @@ public class P25DecoderState extends DecoderState
|
|||||||
*/
|
*/
|
||||||
private void processPDU( PDUMessage pdu )
|
private void processPDU( PDUMessage pdu )
|
||||||
{
|
{
|
||||||
if( pdu instanceof PDUConfirmedMessage )
|
if( pdu instanceof PacketData || pdu instanceof PDUTypeUnknown )
|
||||||
|
{
|
||||||
|
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) );
|
||||||
|
}
|
||||||
|
else if( pdu instanceof PDUConfirmedMessage )
|
||||||
{
|
{
|
||||||
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) );
|
broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) );
|
||||||
|
|
||||||
@@ -1658,7 +1643,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
|
|
||||||
switch( pduc.getPDUType() )
|
switch( pduc.getPDUType() )
|
||||||
{
|
{
|
||||||
case SN_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||||
SNDCPActivateTDSContextAccept satca =
|
SNDCPActivateTDSContextAccept satca =
|
||||||
(SNDCPActivateTDSContextAccept)pduc;
|
(SNDCPActivateTDSContextAccept)pduc;
|
||||||
|
|
||||||
@@ -1670,7 +1655,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
.to( satca.getLogicalLinkID() )
|
.to( satca.getLogicalLinkID() )
|
||||||
.build() );
|
.build() );
|
||||||
break;
|
break;
|
||||||
case SN_ACTIVATE_TDS_CONTEXT_REJECT:
|
case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT:
|
||||||
SNDCPActivateTDSContextReject satcr =
|
SNDCPActivateTDSContextReject satcr =
|
||||||
(SNDCPActivateTDSContextReject)pduc;
|
(SNDCPActivateTDSContextReject)pduc;
|
||||||
|
|
||||||
@@ -1695,7 +1680,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
.from( satcreq.getLogicalLinkID() )
|
.from( satcreq.getLogicalLinkID() )
|
||||||
.build() );
|
.build() );
|
||||||
break;
|
break;
|
||||||
case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||||
SNDCPDeactivateTDSContext sdtca =
|
SNDCPDeactivateTDSContext sdtca =
|
||||||
(SNDCPDeactivateTDSContext)pduc;
|
(SNDCPDeactivateTDSContext)pduc;
|
||||||
|
|
||||||
@@ -1707,7 +1692,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
.from( sdtca.getLogicalLinkID() )
|
.from( sdtca.getLogicalLinkID() )
|
||||||
.build() );
|
.build() );
|
||||||
break;
|
break;
|
||||||
case SN_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
||||||
SNDCPDeactivateTDSContext sdtcreq =
|
SNDCPDeactivateTDSContext sdtcreq =
|
||||||
(SNDCPDeactivateTDSContext)pduc;
|
(SNDCPDeactivateTDSContext)pduc;
|
||||||
|
|
||||||
@@ -1719,7 +1704,7 @@ public class P25DecoderState extends DecoderState
|
|||||||
.from( sdtcreq.getLogicalLinkID() )
|
.from( sdtcreq.getLogicalLinkID() )
|
||||||
.build() );
|
.build() );
|
||||||
break;
|
break;
|
||||||
case SN_RF_CONFIRMED_DATA:
|
case SNDCP_RF_CONFIRMED_DATA:
|
||||||
SNDCPUserData sud = (SNDCPUserData)pduc;
|
SNDCPUserData sud = (SNDCPUserData)pduc;
|
||||||
|
|
||||||
StringBuilder sbFrom = new StringBuilder();
|
StringBuilder sbFrom = new StringBuilder();
|
||||||
@@ -1748,8 +1733,10 @@ public class P25DecoderState extends DecoderState
|
|||||||
.to( pduc.getLogicalLinkID() )
|
.to( pduc.getLogicalLinkID() )
|
||||||
.build() );
|
.build() );
|
||||||
break;
|
break;
|
||||||
|
case SNDCP_RF_UNCONFIRMED_DATA:
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() );
|
// mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3100,16 +3087,9 @@ public class P25DecoderState extends DecoderState
|
|||||||
if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) )
|
if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) )
|
||||||
{
|
{
|
||||||
mToTalkgroup = to;
|
mToTalkgroup = to;
|
||||||
|
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP );
|
broadcast( ChangedAttribute.TO_TALKGROUP );
|
||||||
|
|
||||||
if( mCurrentCallEvent != null &&
|
|
||||||
( mCurrentCallEvent.getToID() == null ||
|
|
||||||
!mCurrentCallEvent.getToID().contentEquals( to ) ) )
|
|
||||||
{
|
|
||||||
mCurrentCallEvent.setToID( to );
|
|
||||||
broadcast( mCurrentCallEvent );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( hasAliasList() )
|
if( hasAliasList() )
|
||||||
{
|
{
|
||||||
mToAlias = getAliasList().getTalkgroupAlias( mToTalkgroup );
|
mToAlias = getAliasList().getTalkgroupAlias( mToTalkgroup );
|
||||||
@@ -3118,12 +3098,20 @@ public class P25DecoderState extends DecoderState
|
|||||||
{
|
{
|
||||||
mToAlias = null;
|
mToAlias = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Send audio metadata update */
|
broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS );
|
||||||
broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) );
|
|
||||||
|
/* Send audio metadata update */
|
||||||
|
broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) );
|
||||||
|
|
||||||
|
if( mCurrentCallEvent != null &&
|
||||||
|
( mCurrentCallEvent.getToID() == null ||
|
||||||
|
!mCurrentCallEvent.getToID().contentEquals( to ) ) )
|
||||||
|
{
|
||||||
|
mCurrentCallEvent.setToID( to );
|
||||||
|
broadcast( mCurrentCallEvent );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3139,14 +3127,6 @@ public class P25DecoderState extends DecoderState
|
|||||||
mFromTalkgroup = from;
|
mFromTalkgroup = from;
|
||||||
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
broadcast( ChangedAttribute.FROM_TALKGROUP );
|
||||||
|
|
||||||
if( mCurrentCallEvent != null &&
|
|
||||||
mCurrentCallEvent.getFromID() == null ||
|
|
||||||
!mCurrentCallEvent.getFromID().contentEquals( from ) )
|
|
||||||
{
|
|
||||||
mCurrentCallEvent.setFromID( from );
|
|
||||||
broadcast( mCurrentCallEvent );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( hasAliasList() )
|
if( hasAliasList() )
|
||||||
{
|
{
|
||||||
mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup );
|
mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup );
|
||||||
@@ -3157,10 +3137,19 @@ public class P25DecoderState extends DecoderState
|
|||||||
}
|
}
|
||||||
|
|
||||||
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS );
|
||||||
|
|
||||||
|
/* Send audio metadata update */
|
||||||
|
broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) );
|
||||||
|
|
||||||
|
if( mCurrentCallEvent != null &&
|
||||||
|
mCurrentCallEvent.getFromID() == null ||
|
||||||
|
!mCurrentCallEvent.getFromID().contentEquals( from ) )
|
||||||
|
{
|
||||||
|
mCurrentCallEvent.setFromID( from );
|
||||||
|
broadcast( mCurrentCallEvent );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send audio metadata update */
|
|
||||||
broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mComplete = true;
|
mComplete = true;
|
||||||
|
dispatch( new P25Message( mMessage.copy(), duid, mAliasList ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ public class P25_C4FMDecoder extends P25Decoder implements IRealBufferListener
|
|||||||
/* Filter demodulated sample buffers */
|
/* Filter demodulated sample buffers */
|
||||||
float[] filter = FilterFactory.getLowPass( 48000, 2500, 4000, 80, WindowType.HANNING, true );
|
float[] filter = FilterFactory.getLowPass( 48000, 2500, 4000, 80, WindowType.HANNING, true );
|
||||||
|
|
||||||
mLog.debug( "Demod Filter tap count:" + filter.length + " coefficients:" + Arrays.toString( filter ) );
|
|
||||||
mC4FMPreFilter = new RealFIRFilter_RB_RB( filter, 1.0f );
|
mC4FMPreFilter = new RealFIRFilter_RB_RB( filter, 1.0f );
|
||||||
|
|
||||||
/* Issue tuned frequency correction commands, remotely controlled by the
|
/* Issue tuned frequency correction commands, remotely controlled by the
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ public class P25Message extends Message
|
|||||||
sb.append( " " );
|
sb.append( " " );
|
||||||
sb.append( getDUID().getLabel() );
|
sb.append( getDUID().getLabel() );
|
||||||
|
|
||||||
|
sb.append( " DATA UNIT ID:" + mMessage.getInt( DUID ) );
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package module.decode.p25.message.pdu;
|
package module.decode.p25.message.pdu;
|
||||||
|
|
||||||
|
import module.decode.p25.message.pdu.confirmed.PacketData;
|
||||||
import module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended;
|
import module.decode.p25.message.pdu.osp.control.AdjacentStatusBroadcastExtended;
|
||||||
import module.decode.p25.message.pdu.osp.control.CallAlertExtended;
|
import module.decode.p25.message.pdu.osp.control.CallAlertExtended;
|
||||||
import module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended;
|
import module.decode.p25.message.pdu.osp.control.GroupAffiliationQueryExtended;
|
||||||
@@ -121,7 +122,8 @@ public class PDUMessageFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new PDUMessage( message, duid, aliasList );
|
return new PDUMessage( message, duid, aliasList );
|
||||||
|
case PACKET_DATA:
|
||||||
|
return new PacketData( message, aliasList );
|
||||||
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
|
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
|
||||||
return new PDUMessage( message, duid, aliasList );
|
return new PDUMessage( message, duid, aliasList );
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -48,19 +48,27 @@ public class PDUConfirmedMessageFactory
|
|||||||
case UNENCRYPTED_USER_DATA:
|
case UNENCRYPTED_USER_DATA:
|
||||||
switch( message.getPDUType() )
|
switch( message.getPDUType() )
|
||||||
{
|
{
|
||||||
case SN_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||||
return new SNDCPActivateTDSContextAccept( message );
|
return new SNDCPActivateTDSContextAccept( message );
|
||||||
case SN_ACTIVATE_TDS_CONTEXT_REJECT:
|
case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT:
|
||||||
return new SNDCPActivateTDSContextReject( message );
|
return new SNDCPActivateTDSContextReject( message );
|
||||||
case SN_ACTIVATE_TDS_CONTEXT_REQUEST:
|
case SN_ACTIVATE_TDS_CONTEXT_REQUEST:
|
||||||
return new SNDCPActivateTDSContextRequest( message );
|
return new SNDCPActivateTDSContextRequest( message );
|
||||||
case SN_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT:
|
||||||
case SN_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST:
|
||||||
return new SNDCPDeactivateTDSContext( message );
|
return new SNDCPDeactivateTDSContext( message );
|
||||||
case SN_RF_CONFIRMED_DATA:
|
case SNDCP_RF_CONFIRMED_DATA:
|
||||||
return new SNDCPUserData( message );
|
return new SNDCPUserData( message );
|
||||||
case SN_RF_UNCONFIRMED_DATA:
|
case SNDCP_RF_UNCONFIRMED_DATA:
|
||||||
break;
|
break;
|
||||||
|
case PDU_TYPE_6:
|
||||||
|
case PDU_TYPE_7:
|
||||||
|
case PDU_TYPE_8:
|
||||||
|
case PDU_TYPE_9:
|
||||||
|
case PDU_TYPE_10:
|
||||||
|
case PDU_TYPE_11:
|
||||||
|
return new PDUTypeUnknown( message );
|
||||||
|
case UNKNOWN:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package module.decode.p25.message.pdu.confirmed;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class PDUTypeUnknown extends PDUConfirmedMessage
|
||||||
|
{
|
||||||
|
public final static Logger mLog =
|
||||||
|
LoggerFactory.getLogger( PDUTypeUnknown.class );
|
||||||
|
|
||||||
|
public PDUTypeUnknown( PDUConfirmedMessage message )
|
||||||
|
{
|
||||||
|
super( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "NAC:" );
|
||||||
|
sb.append( getNAC() );
|
||||||
|
sb.append( " LLID:" );
|
||||||
|
sb.append( getLogicalLinkID() );
|
||||||
|
sb.append( " PACKET DATA UNIT CONFIRMED - " );
|
||||||
|
sb.append( getPDUType().getLabel() );
|
||||||
|
sb.append( " CRC[" );
|
||||||
|
sb.append( getErrorStatus() );
|
||||||
|
sb.append( "]" );
|
||||||
|
|
||||||
|
sb.append( " PACKET #" );
|
||||||
|
sb.append( getPacketSequenceNumber() );
|
||||||
|
|
||||||
|
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
|
||||||
|
{
|
||||||
|
sb.append( ".C" );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append( "." );
|
||||||
|
sb.append( getFragmentSequenceNumber() );
|
||||||
|
|
||||||
|
if( isFinalFragment() )
|
||||||
|
{
|
||||||
|
sb.append( "C" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append( " " );
|
||||||
|
|
||||||
|
sb.append( mMessage.toString() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/module/decode/p25/message/pdu/confirmed/PacketData.java
Normal file
66
src/module/decode/p25/message/pdu/confirmed/PacketData.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package module.decode.p25.message.pdu.confirmed;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
|
|
||||||
|
public class PacketData extends PDUConfirmedMessage
|
||||||
|
{
|
||||||
|
public final static Logger mLog =
|
||||||
|
LoggerFactory.getLogger( PacketData.class );
|
||||||
|
|
||||||
|
public static final int DATA_BLOCK_START = 176;
|
||||||
|
|
||||||
|
public PacketData( BinaryMessage message, AliasList aliasList )
|
||||||
|
{
|
||||||
|
super( message, aliasList );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "NAC:" );
|
||||||
|
sb.append( getNAC() );
|
||||||
|
sb.append( " PDUC LLID:" );
|
||||||
|
sb.append( getLogicalLinkID() );
|
||||||
|
|
||||||
|
|
||||||
|
sb.append( " PACKET DATA" );
|
||||||
|
sb.append( " CRC[" );
|
||||||
|
sb.append( getErrorStatus() );
|
||||||
|
sb.append( "]" );
|
||||||
|
|
||||||
|
if( !mMessage.get( FINAL_FRAGMENT_FLAG ) )
|
||||||
|
{
|
||||||
|
sb.append( " RESENDING" );
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append( " PACKET #" );
|
||||||
|
sb.append( getPacketSequenceNumber() );
|
||||||
|
|
||||||
|
if( isFinalFragment() && getFragmentSequenceNumber() == 0 )
|
||||||
|
{
|
||||||
|
sb.append( ".C" );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append( "." );
|
||||||
|
sb.append( getFragmentSequenceNumber() );
|
||||||
|
|
||||||
|
if( isFinalFragment() )
|
||||||
|
{
|
||||||
|
sb.append( "C" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append( " " );
|
||||||
|
|
||||||
|
sb.append( mMessage.toString() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ public class SNDCPDeactivateTDSContext extends PDUConfirmedMessage
|
|||||||
|
|
||||||
public String getDeactivationType()
|
public String getDeactivationType()
|
||||||
{
|
{
|
||||||
if( getPDUType() == PDUType.SN_DEACTIVATE_TDS_CONTEXT_REQUEST )
|
if( getPDUType() == PDUType.SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST )
|
||||||
{
|
{
|
||||||
return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ?
|
return mMessage.getInt( DEACTIVATION_TYPE ) == 0 ?
|
||||||
"ALL NSAPIS" : "THIS NSAPI";
|
"ALL NSAPIS" : "THIS NSAPI";
|
||||||
|
|||||||
@@ -18,4 +18,19 @@ public class TDUMessage extends P25Message
|
|||||||
mCRC = new CRC[ 1 ];
|
mCRC = new CRC[ 1 ];
|
||||||
mCRC[ 0 ] = CRC.PASSED;
|
mCRC[ 0 ] = CRC.PASSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "NAC:" );
|
||||||
|
sb.append( getNAC() );
|
||||||
|
sb.append( " " );
|
||||||
|
sb.append( getDUID().getLabel() );
|
||||||
|
|
||||||
|
sb.append( " TERMINATOR DATA UNIT" );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package module.decode.p25.message.tsbk;
|
package module.decode.p25.message.tsbk;
|
||||||
|
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
import module.decode.p25.message.tsbk.motorola.ControlChannelBaseStationIdentification;
|
import module.decode.p25.message.tsbk.motorola.ControlChannelBaseStationIdentification;
|
||||||
import module.decode.p25.message.tsbk.motorola.MotorolaOpcode;
|
import module.decode.p25.message.tsbk.motorola.MotorolaOpcode;
|
||||||
import module.decode.p25.message.tsbk.motorola.MotorolaTSBKMessage;
|
import module.decode.p25.message.tsbk.motorola.MotorolaTSBKMessage;
|
||||||
@@ -19,6 +21,7 @@ import module.decode.p25.message.tsbk.osp.control.ExtendedFunctionCommand;
|
|||||||
import module.decode.p25.message.tsbk.osp.control.GroupAffiliationQuery;
|
import module.decode.p25.message.tsbk.osp.control.GroupAffiliationQuery;
|
||||||
import module.decode.p25.message.tsbk.osp.control.GroupAffiliationResponse;
|
import module.decode.p25.message.tsbk.osp.control.GroupAffiliationResponse;
|
||||||
import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateNonVUHF;
|
import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateNonVUHF;
|
||||||
|
import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateTDMA;
|
||||||
import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateVUHF;
|
import module.decode.p25.message.tsbk.osp.control.IdentifierUpdateVUHF;
|
||||||
import module.decode.p25.message.tsbk.osp.control.LocationRegistrationResponse;
|
import module.decode.p25.message.tsbk.osp.control.LocationRegistrationResponse;
|
||||||
import module.decode.p25.message.tsbk.osp.control.MessageUpdate;
|
import module.decode.p25.message.tsbk.osp.control.MessageUpdate;
|
||||||
@@ -56,8 +59,6 @@ import module.decode.p25.message.tsbk.osp.voice.UnitToUnitVoiceChannelGrantUpdat
|
|||||||
import module.decode.p25.reference.DataUnitID;
|
import module.decode.p25.reference.DataUnitID;
|
||||||
import module.decode.p25.reference.Opcode;
|
import module.decode.p25.reference.Opcode;
|
||||||
import module.decode.p25.reference.Vendor;
|
import module.decode.p25.reference.Vendor;
|
||||||
import alias.AliasList;
|
|
||||||
import bits.BinaryMessage;
|
|
||||||
|
|
||||||
public class TSBKMessageFactory
|
public class TSBKMessageFactory
|
||||||
{
|
{
|
||||||
@@ -119,6 +120,8 @@ public class TSBKMessageFactory
|
|||||||
case IDENTIFIER_UPDATE_NON_VUHF:
|
case IDENTIFIER_UPDATE_NON_VUHF:
|
||||||
return new IdentifierUpdateNonVUHF( message, duid,
|
return new IdentifierUpdateNonVUHF( message, duid,
|
||||||
aliasList );
|
aliasList );
|
||||||
|
case IDENTIFIER_UPDATE_TDMA:
|
||||||
|
return new IdentifierUpdateTDMA( message, duid, aliasList );
|
||||||
case IDENTIFIER_UPDATE_VHF_UHF_BANDS:
|
case IDENTIFIER_UPDATE_VHF_UHF_BANDS:
|
||||||
return new IdentifierUpdateVUHF( message, duid,
|
return new IdentifierUpdateVUHF( message, duid,
|
||||||
aliasList );
|
aliasList );
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package module.decode.p25.message.tsbk.osp.control;
|
||||||
|
|
||||||
|
import module.decode.p25.reference.DataUnitID;
|
||||||
|
import module.decode.p25.reference.Opcode;
|
||||||
|
import alias.AliasList;
|
||||||
|
import bits.BinaryMessage;
|
||||||
|
|
||||||
|
public class IdentifierUpdateTDMA extends IdentifierUpdate
|
||||||
|
{
|
||||||
|
public static final int[] CHANNEL_TYPE = { 84,85,86,87 };
|
||||||
|
public static final int TRANSMIT_OFFSET_SIGN = 88;
|
||||||
|
public static final int[] TRANSMIT_OFFSET = { 89,90,91,92,93,94,95,96,97,98,
|
||||||
|
99,100,101 };
|
||||||
|
|
||||||
|
public IdentifierUpdateTDMA( BinaryMessage message,
|
||||||
|
DataUnitID duid,
|
||||||
|
AliasList aliasList )
|
||||||
|
{
|
||||||
|
super( message, duid, aliasList );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventType()
|
||||||
|
{
|
||||||
|
return Opcode.IDENTIFIER_UPDATE_TDMA.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( getMessageStub() );
|
||||||
|
|
||||||
|
sb.append( " IDEN:" + getIdentifier() );
|
||||||
|
sb.append( " BASE:" + getBaseFrequency() );
|
||||||
|
sb.append( " BW:" + getBandwidth() );
|
||||||
|
sb.append( " SPACING:" + getChannelSpacing() );
|
||||||
|
sb.append( " OFFSET:" + getTransmitOffset() );
|
||||||
|
sb.append( " TYPE:" + getChannelType().toString() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelType getChannelType()
|
||||||
|
{
|
||||||
|
return ChannelType.fromValue( mMessage.getInt( CHANNEL_TYPE) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel bandwidth in hertz
|
||||||
|
*/
|
||||||
|
public int getBandwidth()
|
||||||
|
{
|
||||||
|
return getChannelType().getBandwidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmit offset in hertz
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long getTransmitOffset()
|
||||||
|
{
|
||||||
|
long offset = mMessage.getLong( TRANSMIT_OFFSET ) *
|
||||||
|
getChannelSpacing();
|
||||||
|
|
||||||
|
if( mMessage.get( TRANSMIT_OFFSET_SIGN ) )
|
||||||
|
{
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ChannelType
|
||||||
|
{
|
||||||
|
TYPE_0( AccessType.FDMA, 12500, 1, Vocoder.HALF_RATE ),
|
||||||
|
TYPE_1( AccessType.FDMA, 12500, 1, Vocoder.FULL_RATE ),
|
||||||
|
TYPE_2( AccessType.FDMA, 6250, 1, Vocoder.HALF_RATE ),
|
||||||
|
TYPE_3( AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE ),
|
||||||
|
TYPE_4( AccessType.TDMA, 25000, 4, Vocoder.HALF_RATE ),
|
||||||
|
TYPE_5( AccessType.TDMA, 12500, 2, Vocoder.HALF_RATE ), //HD8PSK simulcast
|
||||||
|
UNKNOWN( AccessType.UNKNOWN, 0, 0, Vocoder.HALF_RATE );
|
||||||
|
|
||||||
|
private AccessType mAccessType;
|
||||||
|
private int mBandwidth;
|
||||||
|
private int mSlotsPerCarrier;
|
||||||
|
private Vocoder mVocoder;
|
||||||
|
|
||||||
|
private ChannelType( AccessType accessType,
|
||||||
|
int bandwidth,
|
||||||
|
int slots,
|
||||||
|
Vocoder vocoder )
|
||||||
|
{
|
||||||
|
mAccessType = accessType;
|
||||||
|
mBandwidth = bandwidth;
|
||||||
|
mSlotsPerCarrier = slots;
|
||||||
|
mVocoder = vocoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if( this.equals( TYPE_5 ) )
|
||||||
|
{
|
||||||
|
sb.append("H-D8PSK SIMULCAST " );
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append( mAccessType.name() );
|
||||||
|
sb.append( " BW:" + mBandwidth );
|
||||||
|
sb.append( " SLOT/CARRIER:" + mSlotsPerCarrier );
|
||||||
|
sb.append( " VOCODER:" + mVocoder.name() );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChannelType fromValue( int value )
|
||||||
|
{
|
||||||
|
if( 0 <= value && value <= 5 )
|
||||||
|
{
|
||||||
|
return ChannelType.values()[ value ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChannelType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessType getAccessType()
|
||||||
|
{
|
||||||
|
return mAccessType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBandwidth()
|
||||||
|
{
|
||||||
|
return mBandwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSlotsPerCarrier()
|
||||||
|
{
|
||||||
|
return mSlotsPerCarrier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vocoder getVocoder()
|
||||||
|
{
|
||||||
|
return mVocoder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AccessType
|
||||||
|
{
|
||||||
|
FDMA,TDMA,UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Vocoder
|
||||||
|
{
|
||||||
|
HALF_RATE, FULL_RATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package module.decode.p25.message.tsbk.osp.control;
|
package module.decode.p25.message.tsbk.osp.control;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.SimpleTimeZone;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import module.decode.p25.message.tsbk.TSBKMessage;
|
import module.decode.p25.message.tsbk.TSBKMessage;
|
||||||
@@ -12,20 +14,21 @@ import alias.AliasList;
|
|||||||
import bits.BinaryMessage;
|
import bits.BinaryMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync Broadcast - used to broadcast FDMA-TDMA synchronization timestamp so
|
* Sync Broadcast - used to broadcast FDMA-TDMA timing synchronization
|
||||||
* that a subscriber unit that is time synchronized on an FDMA control channel
|
* information so that a subscriber unit that is time synchronized on an FDMA
|
||||||
* is also synchronized with a TDMA traffic channel.
|
* control channel is also time synchronized with all TDMA traffic channels at
|
||||||
|
* the site.
|
||||||
*/
|
*/
|
||||||
public class SyncBroadcast extends TSBKMessage
|
public class SyncBroadcast extends TSBKMessage
|
||||||
{
|
{
|
||||||
public static final int US = 92;
|
public static final int[] RESERVED = { 80,81,82,83,84,85,86,87,88,89,90,91,92 };
|
||||||
public static final int IST = 93;
|
public static final int SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE = 93;
|
||||||
public static final int MMU = 94;
|
public static final int MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED = 94;
|
||||||
public static final int[] MC = { 95,96 };
|
public static final int[] LEAP_SECOND_CORRECTION = { 95,96 };
|
||||||
public static final int VL = 97;
|
public static final int LOCAL_TIME_OFFSET_VALID = 97;
|
||||||
public static final int LOCAL_TIME_OFFSET_SIGN = 98;
|
public static final int LOCAL_TIME_OFFSET_SIGN = 98;
|
||||||
public static final int[] LOCAL_TIME_OFFSET = { 99,100,101,102 };
|
public static final int[] LOCAL_TIME_OFFSET_HOURS = { 99,100,101,102 };
|
||||||
public static final int LOCAL_TIME_OFFSET_HALF = 103;
|
public static final int LOCAL_TIME_OFFSET_HALF_HOUR = 103;
|
||||||
|
|
||||||
public static final int[] YEAR = { 104,105,106,107,108,109,110 };
|
public static final int[] YEAR = { 104,105,106,107,108,109,110 };
|
||||||
public static final int[] MONTH = { 111,112,113,114 };
|
public static final int[] MONTH = { 111,112,113,114 };
|
||||||
@@ -36,7 +39,10 @@ public class SyncBroadcast extends TSBKMessage
|
|||||||
139,140,141,142,143 };
|
139,140,141,142,143 };
|
||||||
public static final int[] TSBK_CRC = { 144,145,146,147,148,149,150,151,152,
|
public static final int[] TSBK_CRC = { 144,145,146,147,148,149,150,151,152,
|
||||||
153,154,155,156,157,158,159 };
|
153,154,155,156,157,158,159 };
|
||||||
|
|
||||||
|
private static final DateFormat TIME_FORMATTER = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS Z" );
|
||||||
|
private static final TimeZone NO_TIME_ZONE = new SimpleTimeZone( 0, "NONE" );
|
||||||
|
|
||||||
public SyncBroadcast( BinaryMessage message,
|
public SyncBroadcast( BinaryMessage message,
|
||||||
DataUnitID duid,
|
DataUnitID duid,
|
||||||
AliasList aliasList )
|
AliasList aliasList )
|
||||||
@@ -56,24 +62,41 @@ public class SyncBroadcast extends TSBKMessage
|
|||||||
|
|
||||||
sb.append( getMessageStub() );
|
sb.append( getMessageStub() );
|
||||||
|
|
||||||
sb.append( " " + new Date( getTime() ).toString() );
|
sb.append( " SYSTEM TIME" );
|
||||||
sb.append( " TIME ZONE OFFSET:" + getTimeZone().toUpperCase() );
|
|
||||||
sb.append( " US:" + isUS() );
|
if( isSystemTimeNotLockedToExternalReference() )
|
||||||
sb.append( " IST:" + isIST() );
|
{
|
||||||
sb.append( " MMU:" + isMMU() );
|
sb.append( " UNLOCKED" );
|
||||||
sb.append( " VL:" + isVL() );
|
}
|
||||||
|
|
||||||
|
sb.append( ":" );
|
||||||
|
|
||||||
|
TIME_FORMATTER.setTimeZone( getTimeZone() );
|
||||||
|
sb.append( " " + TIME_FORMATTER.format( new Date( getSystemTime() ) ) );
|
||||||
|
|
||||||
|
sb.append( " LEAP-SECOND CORRECTION:" + getLeapSecondCorrection() + "mS" );
|
||||||
|
|
||||||
|
if( isMicroslotsLockedToMinuteRollover() )
|
||||||
|
{
|
||||||
|
sb.append( " MICROSLOT-MINUTE ROLLOVER:LOCKED" );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append( " MICROSLOT-MINUTE ROLLOVER:UNLOCKED" );
|
||||||
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the time sync in java time (ie milliseconds since epoch )
|
* System Time (UTC) in milliseconds since java epoch
|
||||||
*/
|
*/
|
||||||
public long getTime()
|
public long getSystemTime()
|
||||||
{
|
{
|
||||||
Calendar cal = new GregorianCalendar();
|
Calendar cal = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||||
|
|
||||||
cal.clear();
|
cal.clear();
|
||||||
cal.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
|
|
||||||
cal.set( Calendar.YEAR, getYear() );
|
cal.set( Calendar.YEAR, getYear() );
|
||||||
cal.set( Calendar.MONTH, getMonth() - 1 );
|
cal.set( Calendar.MONTH, getMonth() - 1 );
|
||||||
cal.set( Calendar.DAY_OF_MONTH, getDay() );
|
cal.set( Calendar.DAY_OF_MONTH, getDay() );
|
||||||
@@ -84,102 +107,165 @@ public class SyncBroadcast extends TSBKMessage
|
|||||||
return cal.getTimeInMillis();
|
return cal.getTimeInMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUS()
|
/**
|
||||||
|
* System Time not locked to external time reference indicator
|
||||||
|
*/
|
||||||
|
public boolean isSystemTimeNotLockedToExternalReference()
|
||||||
{
|
{
|
||||||
return mMessage.get( US );
|
return mMessage.get( SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Daylight Savings Time
|
* Micro-slot-to-Minute rollover boundary lock indicator.
|
||||||
|
*
|
||||||
|
* Note: we invert the message value so that true indicates that the micro
|
||||||
|
* slots and minute rollovers are locked, whereas the ICD uses false to
|
||||||
|
* indicate a lock.
|
||||||
|
*
|
||||||
|
* @return true if the microslots rollover at each minute interval is locked
|
||||||
|
* to the system time minute rollover.
|
||||||
|
*
|
||||||
|
* @return false if the microslots and minutes rollover are not locked. This
|
||||||
|
* indicates that the microslots minute rollover is free rolling.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public boolean isIST()
|
public boolean isMicroslotsLockedToMinuteRollover()
|
||||||
{
|
{
|
||||||
return mMessage.get( IST );
|
return !mMessage.get( MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No idea ...
|
* Leap second correction value that should be applied to the system time.
|
||||||
|
*
|
||||||
|
* The leap-second correction field indicates the number of 2.5 millisecond
|
||||||
|
* units that must be added to the system time to account for the insertion
|
||||||
|
* of leap seconds when the system time is represented as UTC universal time.
|
||||||
|
*
|
||||||
|
* @return leap-second correction value in milliseconds 0.0-10.0
|
||||||
*/
|
*/
|
||||||
public boolean isMMU()
|
public double getLeapSecondCorrection()
|
||||||
{
|
{
|
||||||
return mMessage.get( MMU );
|
return (double)mMessage.getInt( LEAP_SECOND_CORRECTION ) * 2.5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the local time offset fields contain valid information.
|
||||||
|
*/
|
||||||
|
public boolean isValidLocalTimeOffset()
|
||||||
|
{
|
||||||
|
return !mMessage.get( LOCAL_TIME_OFFSET_VALID );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Millenium ...?
|
* Local time zone offset from UTC universal time (zulu) when the VALID
|
||||||
|
* LOCAL TIME OFFSET flag indicates a valid offset. Otherwise, this method
|
||||||
|
* returns a static +00:00 indicating no local time offset.
|
||||||
*/
|
*/
|
||||||
public int getMC()
|
public TimeZone getTimeZone()
|
||||||
{
|
{
|
||||||
return mMessage.getInt( MC );
|
if( isValidLocalTimeOffset() )
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
offset += mMessage.getInt( LOCAL_TIME_OFFSET_HOURS ) * 3600000;
|
||||||
|
offset += mMessage.get( LOCAL_TIME_OFFSET_HALF_HOUR ) ? 1800000 : 0;
|
||||||
|
offset = mMessage.get( LOCAL_TIME_OFFSET_SIGN ) ? -offset : offset;
|
||||||
|
|
||||||
|
return new SimpleTimeZone( offset, "LOCAL" );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NO_TIME_ZONE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No idea ....
|
* System time - year.
|
||||||
|
*
|
||||||
|
* @return year in range 2000-2127
|
||||||
*/
|
*/
|
||||||
public boolean isVL()
|
|
||||||
{
|
|
||||||
return mMessage.get( VL );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the time zone from the local time offset field.
|
|
||||||
*/
|
|
||||||
public String getTimeZone()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.append( mMessage.get( LOCAL_TIME_OFFSET_SIGN ) ? "-" : "+" );
|
|
||||||
|
|
||||||
int hours = mMessage.getInt( LOCAL_TIME_OFFSET );
|
|
||||||
sb.append( ( hours < 10 ? "0" : "" ) + String.valueOf( hours ) );
|
|
||||||
|
|
||||||
sb.append( ":" );
|
|
||||||
|
|
||||||
sb.append( mMessage.get( LOCAL_TIME_OFFSET_HALF ) ? "30" : "00" );
|
|
||||||
|
|
||||||
if( mMessage.get( IST ) )
|
|
||||||
{
|
|
||||||
sb.append( " DST" );
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getYear()
|
public int getYear()
|
||||||
{
|
{
|
||||||
return 2000 + mMessage.getInt( YEAR );
|
return 2000 + mMessage.getInt( YEAR );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System time - month
|
||||||
|
*
|
||||||
|
* @return month 1-12 where 1=January and 12=December
|
||||||
|
*/
|
||||||
public int getMonth()
|
public int getMonth()
|
||||||
{
|
{
|
||||||
return mMessage.getInt( MONTH );
|
return mMessage.getInt( MONTH );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System time - day of month
|
||||||
|
*
|
||||||
|
* @return day of month in range 1-31
|
||||||
|
*/
|
||||||
public int getDay()
|
public int getDay()
|
||||||
{
|
{
|
||||||
return mMessage.getInt( DAY );
|
return mMessage.getInt( DAY );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System time - hours
|
||||||
|
*
|
||||||
|
* @return hours in range 0 - 23
|
||||||
|
*/
|
||||||
public int getHours()
|
public int getHours()
|
||||||
{
|
{
|
||||||
return mMessage.getInt( HOURS );
|
return mMessage.getInt( HOURS );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System time - minutes
|
||||||
|
*
|
||||||
|
* @return minutes in range 0 - 59
|
||||||
|
*/
|
||||||
public int getMinutes()
|
public int getMinutes()
|
||||||
{
|
{
|
||||||
return mMessage.getInt( MINUTES );
|
return mMessage.getInt( MINUTES );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System time - milli-seconds
|
||||||
|
*
|
||||||
|
* Note: sub-millisecond values are rounded to nearest millisecond unit to
|
||||||
|
* conform to java Date internal milli-second precision level.
|
||||||
|
*
|
||||||
|
* @return milli-seconds in range 0 - 59999;
|
||||||
|
*/
|
||||||
public int getMilliSeconds()
|
public int getMilliSeconds()
|
||||||
{
|
{
|
||||||
return (int)( (double)getMicroSlots() * 7.5 );
|
return (int)( (double)getMicroSlots() * 7.5 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Micro Slots - each uslot = 7.5 milliseconds. The value ranges 0 - 7999
|
* TDMA Micro Slots.
|
||||||
* and represents 60,000 milliseconds ( 8000 x 7.5 ms). Superframes occur
|
*
|
||||||
* every 360 ms (48 micro-slots) on FDMA and Ultraframes occur every 4
|
* Number of 7.5 millisecond micro slots since the last minute rollover, or
|
||||||
* super frames on TDMA.
|
* since the last micro-slot counter rollover when the the micro-slot to
|
||||||
|
* minute rollover is unlocked.
|
||||||
|
*
|
||||||
|
* @see isMicroslotsLockedToMinuteRollover() method.
|
||||||
|
*
|
||||||
|
* This value supports mobile subscriber timing alignment with a TDMA traffic
|
||||||
|
* channel when a channel is granted from the control channels, so that when
|
||||||
|
* the mobile changes from the control channel to the traffic channel, it is
|
||||||
|
* already aligned with the traffic channel TDMA timing.
|
||||||
|
*
|
||||||
|
* Super frames occur every 360 ms (48 micro-slots) on FDMA and Ultra frames
|
||||||
|
* occur every 4 super frames on TDMA.
|
||||||
|
*
|
||||||
|
* Valid micro-slot values range 0-7999 and represent 60,000
|
||||||
|
* milliseconds ( 8000 x 7.5 ms).
|
||||||
|
*
|
||||||
|
* @return number of 7.5 mS micro-slots since last minute or micro-slot
|
||||||
|
* rollover
|
||||||
*/
|
*/
|
||||||
public int getMicroSlots()
|
public int getMicroSlots()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ public enum DataUnitID
|
|||||||
HDU ( 0, 712, false, "HDU ", "Header Data Unit" ),
|
HDU ( 0, 712, false, "HDU ", "Header Data Unit" ),
|
||||||
TDU ( 3, 64, false, "TDU ", "Simple Terminator Data Unit" ),
|
TDU ( 3, 64, false, "TDU ", "Simple Terminator Data Unit" ),
|
||||||
LDU1 ( 5, 1632, true, "LDU1 ", "Logical Link Data Unit 1" ),
|
LDU1 ( 5, 1632, true, "LDU1 ", "Logical Link Data Unit 1" ),
|
||||||
|
VSELP1( 6, 1680, false, "VSEL1", "Motorola VSELP Unit 1" ),
|
||||||
TSBK1( 7, 260, false, "TSBK1", "Trunking Signaling Block" ),
|
TSBK1( 7, 260, false, "TSBK1", "Trunking Signaling Block" ),
|
||||||
TSBK2( 7, 260, false, "TSBK2", "Trunking Signaling Block" ),
|
TSBK2( 7, 260, false, "TSBK2", "Trunking Signaling Block" ),
|
||||||
TSBK3( 7, 260, false, "TSBK3", "Trunking Signaling Block" ),
|
TSBK3( 7, 260, false, "TSBK3", "Trunking Signaling Block" ),
|
||||||
|
VSELP2( 9, 1680, false, "VSEL1", "Motorola VSELP Unit 1" ),
|
||||||
LDU2 ( 10, 1632, true, "LDU2 ", "Logical Link Data Unit 2" ),
|
LDU2 ( 10, 1632, true, "LDU2 ", "Logical Link Data Unit 2" ),
|
||||||
PDU0 ( 12, 260, false, "PDU0 ", "Packet Header Data Unit" ),
|
PDU0 ( 12, 260, false, "PDU0 ", "Packet Header Data Unit" ),
|
||||||
PDU1 ( 12, 356, false, "PDU1 ", "Packet Data Unit-Unconfirmed" ),
|
PDU1 ( 12, 356, false, "PDU1 ", "Packet Data Unit-Unconfirmed" ),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public enum Opcode
|
|||||||
TDMA_SYNC_BROADCAST( "TDMA_SYNC_BCST ", "FDMA-TDMA Sync Broadcast", 48 ),
|
TDMA_SYNC_BROADCAST( "TDMA_SYNC_BCST ", "FDMA-TDMA Sync Broadcast", 48 ),
|
||||||
RESERVED_31( "RESERVED_31 ", "Reserved", 49 ),
|
RESERVED_31( "RESERVED_31 ", "Reserved", 49 ),
|
||||||
RESERVED_32( "RESERVED_32 ", "Reserved", 50 ),
|
RESERVED_32( "RESERVED_32 ", "Reserved", 50 ),
|
||||||
RESERVED_33( "RESERVED_33 ", "Reserved", 51 ),
|
IDENTIFIER_UPDATE_TDMA( "IDEN_UPDATE_TDMA", "Identifier Update for TDMA", 51 ),
|
||||||
IDENTIFIER_UPDATE_VHF_UHF_BANDS( "IDEN_UPDATE_VUHF", "Identifier Update for VHF/UHF Bands", 52 ),
|
IDENTIFIER_UPDATE_VHF_UHF_BANDS( "IDEN_UPDATE_VUHF", "Identifier Update for VHF/UHF Bands", 52 ),
|
||||||
TIME_DATE_ANNOUNCEMENT( "TIME_DATE_ANNOUN", "Time and Date Announcement", 53 ),
|
TIME_DATE_ANNOUNCEMENT( "TIME_DATE_ANNOUN", "Time and Date Announcement", 53 ),
|
||||||
ROAMING_ADDRESS_COMMAND( "ROAM_ADDR_CMD ", "Roaming Address Command", 54 ),
|
ROAMING_ADDRESS_COMMAND( "ROAM_ADDR_CMD ", "Roaming Address Command", 54 ),
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ package module.decode.p25.reference;
|
|||||||
public enum PDUType
|
public enum PDUType
|
||||||
{
|
{
|
||||||
/* Outbound */
|
/* Outbound */
|
||||||
SN_ACTIVATE_TDS_CONTEXT_ACCEPT( "SN-ACTIVATE TDS CONTEXT ACCEPT", 0 ),
|
SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-ACTIVATE TDS CONTEXT ACCEPT", 0 ),
|
||||||
SN_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SN-DEACTIVATE TDS CONTEXT ACCEPT", 1 ),
|
SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT( "SNDCP-DEACTIVATE TDS CONTEXT ACCEPT", 1 ),
|
||||||
SN_DEACTIVATE_TDS_CONTEXT_REQUEST( "SN-DEACTIVATE TDS CONTEXT REQUEST", 2 ),
|
SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST( "SNDCP-DEACTIVATE TDS CONTEXT REQUEST", 2 ),
|
||||||
SN_ACTIVATE_TDS_CONTEXT_REJECT( "SN-ACTIVATE TDS CONTEXT REJECT", 3 ),
|
SNDCP_ACTIVATE_TDS_CONTEXT_REJECT( "SNDCP-ACTIVATE TDS CONTEXT REJECT", 3 ),
|
||||||
SN_RF_UNCONFIRMED_DATA( "SN-RF UNCONFIRMED DATA", 4 ),
|
SNDCP_RF_UNCONFIRMED_DATA( "SNDCP-RF UNCONFIRMED DATA", 4 ),
|
||||||
SN_RF_CONFIRMED_DATA( "SN-RF CONFIRMED DATA", 5 ),
|
SNDCP_RF_CONFIRMED_DATA( "SNDCP-RF CONFIRMED DATA", 5 ),
|
||||||
|
PDU_TYPE_6( "PDU TYPE 6 UNKNOWN", 6 ),
|
||||||
|
PDU_TYPE_7( "PDU TYPE 7 UNKNOWN", 7 ),
|
||||||
|
PDU_TYPE_8( "PDU TYPE 8 UNKNOWN", 8 ),
|
||||||
|
PDU_TYPE_9( "PDU TYPE 9 UNKNOWN", 9 ),
|
||||||
|
PDU_TYPE_10( "PDU TYPE 10 UNKNOWN", 10 ),
|
||||||
|
PDU_TYPE_11( "PDU TYPE 11 UNKNOWN", 11 ),
|
||||||
|
|
||||||
/* Inbound */
|
/* Inbound */
|
||||||
SN_ACTIVATE_TDS_CONTEXT_REQUEST( "SN-ACTIVATE TDS CONTEXT REQUEST", 0 ),
|
SN_ACTIVATE_TDS_CONTEXT_REQUEST( "SN-ACTIVATE TDS CONTEXT REQUEST", 0 ),
|
||||||
@@ -36,7 +42,7 @@ public enum PDUType
|
|||||||
|
|
||||||
public static PDUType fromValue( int value, boolean outbound )
|
public static PDUType fromValue( int value, boolean outbound )
|
||||||
{
|
{
|
||||||
if( outbound && 0 <= value && value <= 5 )
|
if( outbound && 0 <= value && value <= 11 )
|
||||||
{
|
{
|
||||||
return values()[ value ];
|
return values()[ value ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package module.decode.p25.reference;
|
|||||||
|
|
||||||
public enum ServiceAccessPoint
|
public enum ServiceAccessPoint
|
||||||
{
|
{
|
||||||
UNENCRYPTED_USER_DATA( "USER DATA", 0 ),
|
UNENCRYPTED_USER_DATA( "UNENCRYPTED USER DATA", 0 ),
|
||||||
ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ),
|
ENCRYPTED_USER_DATA( "ENCRYPTED USER DATA", 1 ),
|
||||||
CIRCUIT_DATA( "CIRCUIT DATA", 2 ),
|
CIRCUIT_DATA( "CIRCUIT DATA", 2 ),
|
||||||
CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ),
|
CIRCUIT_DATA_CONTROL( "CIRCUIT DATA CONTROL", 3 ),
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ public class PassportDecoder extends Decoder
|
|||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
|
mPassportMessageFramer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
29
src/module/decode/state/ChangeChannelTimeoutEvent.java
Normal file
29
src/module/decode/state/ChangeChannelTimeoutEvent.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package module.decode.state;
|
||||||
|
|
||||||
|
import controller.channel.Channel.ChannelType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows decoders to change the call timeout setting in the channel state
|
||||||
|
*/
|
||||||
|
public class ChangeChannelTimeoutEvent extends DecoderStateEvent
|
||||||
|
{
|
||||||
|
private ChannelType mChannelType;
|
||||||
|
private long mCallTimeout;
|
||||||
|
|
||||||
|
public ChangeChannelTimeoutEvent( Object source, ChannelType channelType, long timeout )
|
||||||
|
{
|
||||||
|
super( source, Event.CHANGE_CALL_TIMEOUT, State.IDLE );
|
||||||
|
mChannelType = channelType;
|
||||||
|
mCallTimeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelType getChannelType()
|
||||||
|
{
|
||||||
|
return mChannelType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCallTimeout()
|
||||||
|
{
|
||||||
|
return mCallTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,8 @@ import java.awt.Component;
|
|||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.util.HashMap;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.swing.JMenu;
|
import javax.swing.JMenu;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
@@ -53,8 +54,8 @@ public class ChannelList extends JPanel implements ChannelEventListener
|
|||||||
|
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( ChannelList.class );
|
private final static Logger mLog = LoggerFactory.getLogger( ChannelList.class );
|
||||||
|
|
||||||
private HashMap<Channel,ChannelCollectionPanel> mDisplayedPanels =
|
private Map<Channel,ChannelCollectionPanel> mDisplayedPanels =
|
||||||
new HashMap<Channel,ChannelCollectionPanel>();
|
new ConcurrentHashMap<Channel,ChannelCollectionPanel>();
|
||||||
|
|
||||||
private PlaylistManager mPlaylistManager;
|
private PlaylistManager mPlaylistManager;
|
||||||
private SettingsManager mSettingsManager;
|
private SettingsManager mSettingsManager;
|
||||||
@@ -128,10 +129,13 @@ public class ChannelList extends JPanel implements ChannelEventListener
|
|||||||
|
|
||||||
public void setSelectedChannel( Channel channel )
|
public void setSelectedChannel( Channel channel )
|
||||||
{
|
{
|
||||||
/* Send channel selection to each channel panel */
|
synchronized( mDisplayedPanels )
|
||||||
for( ChannelCollectionPanel panel: mDisplayedPanels.values() )
|
|
||||||
{
|
{
|
||||||
panel.setSelectedChannel( channel );
|
/* Send channel selection to each channel panel */
|
||||||
|
for( ChannelCollectionPanel panel: mDisplayedPanels.values() )
|
||||||
|
{
|
||||||
|
panel.setSelectedChannel( channel );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,20 +254,6 @@ public class ChannelList extends JPanel implements ChannelEventListener
|
|||||||
idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
|
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
|
||||||
|
|
||||||
JMenu noTunerMenu = new JMenu( "No Tuner" );
|
|
||||||
channelsMenu.add( noTunerMenu );
|
|
||||||
|
|
||||||
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
|
|
||||||
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
|
|
||||||
noTunerMenu.add( new JSeparator() );
|
|
||||||
|
|
||||||
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
|
|
||||||
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
|
|
||||||
|
|
||||||
JMenu labelMenu = new JMenu( "Labels" );
|
JMenu labelMenu = new JMenu( "Labels" );
|
||||||
colorMenu.add( labelMenu );
|
colorMenu.add( labelMenu );
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,20 +17,14 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package module.decode.state;
|
package module.decode.state;
|
||||||
|
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import module.Module;
|
|
||||||
import module.decode.config.DecodeConfiguration;
|
|
||||||
import module.decode.event.CallEvent;
|
|
||||||
import module.decode.event.ICallEventProvider;
|
|
||||||
import module.decode.state.DecoderStateEvent.Event;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sample.Listener;
|
|
||||||
import audio.metadata.IMetadataProvider;
|
import audio.metadata.IMetadataProvider;
|
||||||
import audio.metadata.Metadata;
|
import audio.metadata.Metadata;
|
||||||
import audio.metadata.MetadataReset;
|
import audio.metadata.MetadataReset;
|
||||||
@@ -40,38 +34,13 @@ import audio.squelch.SquelchState;
|
|||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
import controller.ThreadPoolManager.ThreadType;
|
import controller.ThreadPoolManager.ThreadType;
|
||||||
import controller.channel.Channel.ChannelType;
|
import controller.channel.Channel.ChannelType;
|
||||||
|
import module.Module;
|
||||||
|
import module.decode.config.DecodeConfiguration;
|
||||||
|
import module.decode.event.CallEvent;
|
||||||
|
import module.decode.event.ICallEventProvider;
|
||||||
|
import module.decode.state.DecoderStateEvent.Event;
|
||||||
|
import sample.Listener;
|
||||||
|
|
||||||
/**
|
|
||||||
* Channel state tracks the overall state of all processing modules and decoders
|
|
||||||
* configured for the channel and provides squelch control events.
|
|
||||||
*
|
|
||||||
* Primary channel state provides a state machine for tracking decode events
|
|
||||||
* during a voice or data call through it's life cycle of CALL/DATA, FADE, END,
|
|
||||||
* and then back to an IDLE state. This class also provides squelch control
|
|
||||||
* to support any audio processing.
|
|
||||||
*
|
|
||||||
* State Descriptions:
|
|
||||||
* IDLE No voice or data call activity
|
|
||||||
* CALL/DATA A voice or data call has started, or is continuing
|
|
||||||
* CONTROL Control channel
|
|
||||||
* 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
|
|
||||||
* END The phase after the voice or data call FADE, when the call is
|
|
||||||
* now over, and the gui should be reset to IDLE.
|
|
||||||
*
|
|
||||||
* When a call is started, invoke mTimerService.startFadeTimer() to start the
|
|
||||||
* call fade monitoring timer process that will automatically signal a call end
|
|
||||||
* once no more call signalling has been detected after the fade timeout has
|
|
||||||
* occurred, by invoking the fade() method.
|
|
||||||
*
|
|
||||||
* When a call ends, invoke the mTimerService.stopFadeTimer() and then invoke
|
|
||||||
* the mTimerService.startResetTimer(). Once the reset timeout occurs,
|
|
||||||
* the reset() method will be called, allowing the channel state to perform
|
|
||||||
* any reset actions, like clearing status and id labels on any view panels.
|
|
||||||
*/
|
|
||||||
public class ChannelState extends Module implements ICallEventProvider,
|
public class ChannelState extends Module implements ICallEventProvider,
|
||||||
IChangedAttributeProvider, IDecoderStateEventListener,
|
IChangedAttributeProvider, IDecoderStateEventListener,
|
||||||
IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider
|
IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider
|
||||||
@@ -83,8 +52,6 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
public static final long RESET_TIMEOUT_DELAY = 2000;
|
public static final long RESET_TIMEOUT_DELAY = 2000;
|
||||||
|
|
||||||
private State mState = State.IDLE;
|
private State mState = State.IDLE;
|
||||||
private long mTrafficChannelTimeout =
|
|
||||||
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS;
|
|
||||||
|
|
||||||
private Listener<CallEvent> mCallEventListener;
|
private Listener<CallEvent> mCallEventListener;
|
||||||
private Listener<ChangedAttribute> mChangedAttributeListener;
|
private Listener<ChangedAttribute> mChangedAttributeListener;
|
||||||
@@ -97,12 +64,15 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
private boolean mSquelchLocked = false;
|
private boolean mSquelchLocked = false;
|
||||||
private boolean mSelected = false;
|
private boolean mSelected = false;
|
||||||
|
|
||||||
|
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
|
||||||
|
private long mTrafficChannelFadeTimeout =
|
||||||
|
DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
|
||||||
private long mFadeTimeout;
|
private long mFadeTimeout;
|
||||||
private long mResetTimeout;
|
private long mEndTimeout;
|
||||||
|
|
||||||
private StateMonitor mStateMonitor = new StateMonitor();
|
private StateMonitor mStateMonitor = new StateMonitor();
|
||||||
private AtomicBoolean mMonitoring = new AtomicBoolean();
|
private AtomicBoolean mMonitoring = new AtomicBoolean();
|
||||||
private ScheduledFuture<?> mRunningMonitor;
|
private ScheduledFuture<?> mStateMonitorFuture;
|
||||||
|
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
private ChannelType mChannelType;
|
private ChannelType mChannelType;
|
||||||
@@ -110,32 +80,97 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
private TrafficChannelStatusListener mTrafficChannelStatusListener;
|
private TrafficChannelStatusListener mTrafficChannelStatusListener;
|
||||||
private CallEvent mTrafficChannelCallEvent;
|
private CallEvent mTrafficChannelCallEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
public ChannelState( ThreadPoolManager manager, ChannelType channelType )
|
public ChannelState( ThreadPoolManager manager, ChannelType channelType )
|
||||||
{
|
{
|
||||||
mThreadPoolManager = manager;
|
mThreadPoolManager = manager;
|
||||||
mChannelType = channelType;
|
mChannelType = channelType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets this channel state and prepares it for reuse.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
|
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
|
||||||
|
broadcast( new MetadataReset() );
|
||||||
|
|
||||||
|
mState = State.IDLE;
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
|
//Start a monitoring task to check for FADE and END event timeouts
|
||||||
|
if( mMonitoring.compareAndSet( false, true ) )
|
||||||
|
{
|
||||||
|
if( mStateMonitorFuture == null && mStateMonitor != null )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mStateMonitorFuture = mThreadPoolManager.scheduleFixedRate(
|
||||||
|
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
|
||||||
|
}
|
||||||
|
catch( RejectedExecutionException ree )
|
||||||
|
{
|
||||||
|
mLog.error( "state monitor scheduled task rejected", ree );
|
||||||
|
mMonitoring.set( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
mSquelchLocked = false;
|
mSquelchLocked = false;
|
||||||
|
|
||||||
|
//Shutdown the state montoring task
|
||||||
|
if( mMonitoring.compareAndSet( true, false ) )
|
||||||
|
{
|
||||||
|
if( mStateMonitorFuture != null )
|
||||||
|
{
|
||||||
|
boolean success = mThreadPoolManager.cancel( mStateMonitorFuture );
|
||||||
|
|
||||||
|
if( !success )
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't stop monitoring scheduled future" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mStateMonitorFuture = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
stopMonitor();
|
|
||||||
|
|
||||||
mCallEventListener = null;
|
mCallEventListener = null;
|
||||||
mChangedAttributeListener = null;
|
mChangedAttributeListener = null;
|
||||||
mDecoderStateListener = null;
|
mDecoderStateListener = null;
|
||||||
@@ -144,9 +179,34 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
mStateMonitor = null;
|
mStateMonitor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isStandardChannel()
|
||||||
|
{
|
||||||
|
return mChannelType == ChannelType.STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTrafficChannel()
|
||||||
|
{
|
||||||
|
return mChannelType == ChannelType.TRAFFIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStandardChannelTimeout( long milliseconds )
|
||||||
|
{
|
||||||
|
mStandardChannelFadeTimeout = milliseconds;
|
||||||
|
|
||||||
|
if( mChannelType == ChannelType.STANDARD )
|
||||||
|
{
|
||||||
|
mFadeTimeout = mStandardChannelFadeTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setTrafficChannelTimeout( long milliseconds )
|
public void setTrafficChannelTimeout( long milliseconds )
|
||||||
{
|
{
|
||||||
mTrafficChannelTimeout = milliseconds;
|
mTrafficChannelFadeTimeout = milliseconds;
|
||||||
|
|
||||||
|
if( mChannelType == ChannelType.TRAFFIC )
|
||||||
|
{
|
||||||
|
mFadeTimeout = mTrafficChannelFadeTimeout;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelected( boolean selected )
|
public void setSelected( boolean selected )
|
||||||
@@ -164,71 +224,18 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
return mState;
|
return mState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isIdleOrEndingState()
|
|
||||||
{
|
|
||||||
return mState == State.FADE ||
|
|
||||||
mState == State.END ||
|
|
||||||
mState == State.IDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the state monitor running when we're not in IDLE state. Calls to
|
|
||||||
* start an already started monitor are ignored.
|
|
||||||
*/
|
|
||||||
private void startMonitor()
|
|
||||||
{
|
|
||||||
if( mMonitoring.compareAndSet( false, true ) )
|
|
||||||
{
|
|
||||||
if( mRunningMonitor == null && mStateMonitor != null )
|
|
||||||
{
|
|
||||||
mRunningMonitor = mThreadPoolManager.scheduleFixedRate(
|
|
||||||
ThreadType.DECODER, mStateMonitor, 20, TimeUnit.MILLISECONDS );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new RuntimeException( "Channel state monitor's scheduled "
|
|
||||||
+ "future pointer was not null" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the state monitor when we're in an IDLE state. Calls to stop an
|
|
||||||
* already stopped monitor are ignored.
|
|
||||||
*/
|
|
||||||
private void stopMonitor()
|
|
||||||
{
|
|
||||||
if( mMonitoring.compareAndSet( true, false ) )
|
|
||||||
{
|
|
||||||
if( mRunningMonitor != null )
|
|
||||||
{
|
|
||||||
mThreadPoolManager.cancel( mRunningMonitor );
|
|
||||||
}
|
|
||||||
|
|
||||||
mRunningMonitor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the fade timeout threshold to the current time plus delay
|
* Updates the fade timeout threshold to the current time plus delay
|
||||||
*/
|
*/
|
||||||
private void updateFadeTimeout()
|
private void updateFadeTimeout()
|
||||||
{
|
{
|
||||||
/* Standard channel uses the FADE_TIMEOUT_DELAY and traffic channels
|
if( isTrafficChannel() )
|
||||||
* use the TRAFFIC_FADE_TIMEOUT_DELAY. Some traffic channels (e.g. MPT)
|
|
||||||
* don't have signaling on the traffic channel that can be used to
|
|
||||||
* update the fade timeout, so we set the timeout value larger to ensure
|
|
||||||
* the channel doesn't fade before the end of the call. */
|
|
||||||
long current = System.currentTimeMillis();
|
|
||||||
|
|
||||||
long timeout = current +
|
|
||||||
( mChannelType == ChannelType.STANDARD ? FADE_TIMEOUT_DELAY :
|
|
||||||
mTrafficChannelTimeout );
|
|
||||||
|
|
||||||
if( timeout > mFadeTimeout )
|
|
||||||
{
|
{
|
||||||
mFadeTimeout = timeout;
|
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,13 +244,13 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
*/
|
*/
|
||||||
private void updateResetTimeout()
|
private void updateResetTimeout()
|
||||||
{
|
{
|
||||||
if( mChannelType == ChannelType.TRAFFIC )
|
if( isTrafficChannel() )
|
||||||
{
|
{
|
||||||
mResetTimeout = System.currentTimeMillis();
|
mEndTimeout = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
|
mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,120 +292,122 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
*/
|
*/
|
||||||
protected void setState( State state )
|
protected void setState( State state )
|
||||||
{
|
{
|
||||||
if( mState == state )
|
synchronized( mState )
|
||||||
{
|
{
|
||||||
switch( state )
|
if( state == mState )
|
||||||
{
|
{
|
||||||
case CALL:
|
if( State.CALL_STATES.contains( state ) )
|
||||||
case CONTROL:
|
|
||||||
case DATA:
|
|
||||||
case ENCRYPTED:
|
|
||||||
updateFadeTimeout();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( mState.canChangeTo( state ) )
|
|
||||||
{
|
|
||||||
switch( state )
|
|
||||||
{
|
|
||||||
case CONTROL:
|
|
||||||
//Don't allow traffic channels to be control channels,
|
|
||||||
//otherwise they won't get call tear down
|
|
||||||
if( mChannelType != ChannelType.TRAFFIC )
|
|
||||||
{
|
|
||||||
broadcast( SquelchState.SQUELCH );
|
|
||||||
updateFadeTimeout();
|
|
||||||
startMonitor();
|
|
||||||
mState = state;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DATA:
|
|
||||||
case ENCRYPTED:
|
|
||||||
broadcast( SquelchState.SQUELCH );
|
|
||||||
updateFadeTimeout();
|
|
||||||
startMonitor();
|
|
||||||
mState = state;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
|
||||||
break;
|
|
||||||
case CALL:
|
|
||||||
broadcast( SquelchState.UNSQUELCH );
|
|
||||||
updateFadeTimeout();
|
|
||||||
startMonitor();
|
|
||||||
mState = state;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
|
||||||
break;
|
|
||||||
case FADE:
|
|
||||||
broadcast( SquelchState.SQUELCH );
|
|
||||||
updateResetTimeout();
|
|
||||||
startMonitor();
|
|
||||||
mState = state;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
|
||||||
break;
|
|
||||||
case END:
|
|
||||||
broadcast( SquelchState.SQUELCH );
|
|
||||||
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
|
|
||||||
broadcast( new MetadataReset() );
|
|
||||||
mState = state;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
|
||||||
break;
|
|
||||||
case IDLE:
|
|
||||||
broadcast( SquelchState.SQUELCH );
|
|
||||||
stopMonitor();
|
|
||||||
|
|
||||||
if( mChannelType == ChannelType.TRAFFIC )
|
|
||||||
{
|
|
||||||
if( mTrafficChannelStatusListener != null )
|
|
||||||
{
|
|
||||||
mTrafficChannelStatusListener.callEnd();
|
|
||||||
mTrafficChannelStatusListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mTrafficChannelCallEvent != null )
|
|
||||||
{
|
|
||||||
mTrafficChannelCallEvent.end();
|
|
||||||
|
|
||||||
broadcast( mTrafficChannelCallEvent );
|
|
||||||
mTrafficChannelCallEvent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mState = state;
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StateMonitor implements Runnable
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
if( getState() == State.FADE )
|
|
||||||
{
|
|
||||||
if( mResetTimeout <= System.currentTimeMillis() )
|
|
||||||
{
|
{
|
||||||
setState( State.END );
|
updateFadeTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if( getState() == State.END )
|
else if( state != mState && mState.canChangeTo( state ) )
|
||||||
{
|
{
|
||||||
setState( State.IDLE );
|
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;
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DATA:
|
||||||
|
case ENCRYPTED:
|
||||||
|
broadcast( SquelchState.SQUELCH );
|
||||||
|
updateFadeTimeout();
|
||||||
|
mState = state;
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
|
break;
|
||||||
|
case CALL:
|
||||||
|
broadcast( SquelchState.UNSQUELCH );
|
||||||
|
updateFadeTimeout();
|
||||||
|
mState = state;
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
|
break;
|
||||||
|
case FADE:
|
||||||
|
processFadeState();
|
||||||
|
break;
|
||||||
|
case IDLE:
|
||||||
|
processIdleState();
|
||||||
|
break;
|
||||||
|
case TEARDOWN:
|
||||||
|
processTeardownState();
|
||||||
|
break;
|
||||||
|
case RESET:
|
||||||
|
mState = State.IDLE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if( mFadeTimeout <= System.currentTimeMillis() )
|
// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
|
||||||
{
|
|
||||||
setState( State.FADE );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state and processes related actions
|
||||||
|
*/
|
||||||
|
private void processFadeState()
|
||||||
|
{
|
||||||
|
updateResetTimeout();
|
||||||
|
mState = State.FADE;
|
||||||
|
|
||||||
|
broadcast( SquelchState.SQUELCH );
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processIdleState()
|
||||||
|
{
|
||||||
|
broadcast( SquelchState.SQUELCH );
|
||||||
|
|
||||||
|
if( mState == State.FADE )
|
||||||
|
{
|
||||||
|
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
|
||||||
|
broadcast( new MetadataReset() );
|
||||||
|
}
|
||||||
|
|
||||||
|
mState = State.IDLE;
|
||||||
|
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTeardownState()
|
||||||
|
{
|
||||||
|
broadcast( SquelchState.SQUELCH );
|
||||||
|
|
||||||
|
broadcast( new MetadataReset() );
|
||||||
|
|
||||||
|
mState = State.TEARDOWN;
|
||||||
|
|
||||||
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
|
|
||||||
|
if( mTrafficChannelCallEvent != null )
|
||||||
|
{
|
||||||
|
mTrafficChannelCallEvent.end();
|
||||||
|
|
||||||
|
broadcast( mTrafficChannelCallEvent );
|
||||||
|
mTrafficChannelCallEvent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mTrafficChannelStatusListener != null )
|
||||||
|
{
|
||||||
|
mTrafficChannelStatusListener.callEnd();
|
||||||
|
mTrafficChannelStatusListener = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error( "Traffic channel status listener was null - couldn't "
|
||||||
|
+ "teardown the channel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcasts the call event to the registered listener
|
* Broadcasts the call event to the registered listener
|
||||||
@@ -590,35 +599,95 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
broadcast( SquelchState.UNSQUELCH );
|
broadcast( SquelchState.UNSQUELCH );
|
||||||
mSquelchLocked = true;
|
mSquelchLocked = true;
|
||||||
break;
|
break;
|
||||||
|
case CHANGE_CALL_TIMEOUT:
|
||||||
|
if( event instanceof ChangeChannelTimeoutEvent )
|
||||||
|
{
|
||||||
|
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
|
||||||
|
|
||||||
|
if( timeout.getChannelType() == ChannelType.STANDARD )
|
||||||
|
{
|
||||||
|
setStandardChannelTimeout( timeout.getCallTimeout() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setTrafficChannelTimeout( timeout.getCallTimeout() );
|
||||||
|
}
|
||||||
|
}
|
||||||
case CONTINUATION:
|
case CONTINUATION:
|
||||||
if( isIdleOrEndingState() )
|
|
||||||
{
|
|
||||||
setState( event.getState() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
updateFadeTimeout();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DECODE:
|
case DECODE:
|
||||||
if( getState() == State.IDLE )
|
case START:
|
||||||
|
if( State.CALL_STATES.contains( event.getState() ) )
|
||||||
{
|
{
|
||||||
setState( event.getState() );
|
setState( event.getState() );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case END:
|
case END:
|
||||||
setState( State.FADE );
|
if( isTrafficChannel() )
|
||||||
|
{
|
||||||
|
setState( State.TEARDOWN );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setState( State.FADE );
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case RESET:
|
case RESET:
|
||||||
/* Channel State does not respond to reset events */
|
/* Channel State does not respond to reset events */
|
||||||
break;
|
break;
|
||||||
case START:
|
|
||||||
setState( event.getState() );
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
synchronized( mState )
|
||||||
|
{
|
||||||
|
if( State.CALL_STATES.contains( mState ) &&
|
||||||
|
mFadeTimeout <= System.currentTimeMillis() )
|
||||||
|
{
|
||||||
|
mState = State.FADE;
|
||||||
|
processFadeState();
|
||||||
|
}
|
||||||
|
else if( mState == State.FADE &&
|
||||||
|
mEndTimeout <= System.currentTimeMillis() )
|
||||||
|
{
|
||||||
|
if( isTrafficChannel() )
|
||||||
|
{
|
||||||
|
processTeardownState();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processIdleState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
mLog.error( "Exception thrown while state monitor is running " +
|
||||||
|
"- state [" + getState() +
|
||||||
|
"] current [" + System.currentTimeMillis() +
|
||||||
|
"] mResetTimeout [" + mEndTimeout +
|
||||||
|
"] mFadeTimeout [" + mFadeTimeout +
|
||||||
|
"]", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ import javax.swing.JLabel;
|
|||||||
import javax.swing.JMenu;
|
import javax.swing.JMenu;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
import module.decode.DecoderFactory;
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import controller.channel.Channel;
|
||||||
|
import controller.channel.ChannelUtils;
|
||||||
|
import module.decode.DecoderFactory;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
import playlist.PlaylistManager;
|
import playlist.PlaylistManager;
|
||||||
import record.RecorderType;
|
import record.RecorderType;
|
||||||
import record.config.RecordConfiguration;
|
import record.config.RecordConfiguration;
|
||||||
@@ -29,8 +30,6 @@ import settings.ColorSetting.ColorSettingName;
|
|||||||
import settings.Setting;
|
import settings.Setting;
|
||||||
import settings.SettingChangeListener;
|
import settings.SettingChangeListener;
|
||||||
import settings.SettingsManager;
|
import settings.SettingsManager;
|
||||||
import controller.channel.Channel;
|
|
||||||
import controller.channel.ChannelUtils;
|
|
||||||
|
|
||||||
public class ChannelStatePanel extends JPanel
|
public class ChannelStatePanel extends JPanel
|
||||||
implements Listener<ChangedAttribute>, SettingChangeListener
|
implements Listener<ChangedAttribute>, SettingChangeListener
|
||||||
@@ -236,26 +235,37 @@ public class ChannelStatePanel extends JPanel
|
|||||||
Graphics2D g2 = (Graphics2D)g.create();
|
Graphics2D g2 = (Graphics2D)g.create();
|
||||||
|
|
||||||
Paint p = null;
|
Paint p = null;
|
||||||
|
|
||||||
|
final Channel channel = mChannel;
|
||||||
|
|
||||||
switch( mChannel.getChannelState().getState() )
|
final ChannelState channelState = ( channel == null ? null : channel.getChannelState() );
|
||||||
|
|
||||||
|
if( channelState != null )
|
||||||
{
|
{
|
||||||
case CALL:
|
switch( channelState.getState() )
|
||||||
p = getGradient( mColorTopCall, mColorMiddleCall );
|
{
|
||||||
break;
|
case CALL:
|
||||||
case CONTROL:
|
p = getGradient( mColorTopCall, mColorMiddleCall );
|
||||||
p = getGradient( mColorTopControl, mColorMiddleControl );
|
break;
|
||||||
break;
|
case CONTROL:
|
||||||
case DATA:
|
p = getGradient( mColorTopControl, mColorMiddleControl );
|
||||||
p = getGradient( mColorTopData, mColorMiddleData );
|
break;
|
||||||
break;
|
case DATA:
|
||||||
case FADE:
|
p = getGradient( mColorTopData, mColorMiddleData );
|
||||||
p = getGradient( mColorTopFade, mColorMiddleFade );
|
break;
|
||||||
break;
|
case FADE:
|
||||||
case END:
|
p = getGradient( mColorTopFade, mColorMiddleFade );
|
||||||
case IDLE:
|
break;
|
||||||
default:
|
case IDLE:
|
||||||
p = getGradient( mColorTopIdle, mColorMiddleIdle );
|
default:
|
||||||
break;
|
p = getGradient( mColorTopIdle, mColorMiddleIdle );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( p == null )
|
||||||
|
{
|
||||||
|
p = getGradient( mColorTopIdle, mColorMiddleIdle );
|
||||||
}
|
}
|
||||||
|
|
||||||
g2.setPaint( p );
|
g2.setPaint( p );
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public class DecoderStateEvent
|
|||||||
public enum Event
|
public enum Event
|
||||||
{
|
{
|
||||||
ALWAYS_UNSQUELCH,
|
ALWAYS_UNSQUELCH,
|
||||||
|
CHANGE_CALL_TIMEOUT,
|
||||||
CONTINUATION,
|
CONTINUATION,
|
||||||
DECODE,
|
DECODE,
|
||||||
END,
|
END,
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package module.decode.state;
|
package module.decode.state;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details the set of states for a channel and the allowable transition states
|
||||||
|
*/
|
||||||
public enum State
|
public enum State
|
||||||
{
|
{
|
||||||
IDLE( "IDLE" )
|
IDLE( "IDLE" )
|
||||||
@@ -7,7 +12,8 @@ public enum State
|
|||||||
@Override
|
@Override
|
||||||
public boolean canChangeTo( State state )
|
public boolean canChangeTo( State state )
|
||||||
{
|
{
|
||||||
return true;
|
return state != TEARDOWN &&
|
||||||
|
state != RESET;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CALL( "CALL" )
|
CALL( "CALL" )
|
||||||
@@ -15,11 +21,11 @@ public enum State
|
|||||||
@Override
|
@Override
|
||||||
public boolean canChangeTo( State state )
|
public boolean canChangeTo( State state )
|
||||||
{
|
{
|
||||||
return state == CALL ||
|
return state == CONTROL ||
|
||||||
state == CONTROL ||
|
|
||||||
state == DATA ||
|
state == DATA ||
|
||||||
state == ENCRYPTED ||
|
state == ENCRYPTED ||
|
||||||
state == FADE;
|
state == FADE ||
|
||||||
|
state == TEARDOWN;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DATA( "DATA" )
|
DATA( "DATA" )
|
||||||
@@ -29,9 +35,9 @@ public enum State
|
|||||||
{
|
{
|
||||||
return state == CALL ||
|
return state == CALL ||
|
||||||
state == CONTROL ||
|
state == CONTROL ||
|
||||||
state == DATA ||
|
state == ENCRYPTED ||
|
||||||
state == State.ENCRYPTED ||
|
state == FADE ||
|
||||||
state == FADE;
|
state == TEARDOWN;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ENCRYPTED( "ENCRYPTED" )
|
ENCRYPTED( "ENCRYPTED" )
|
||||||
@@ -39,7 +45,7 @@ public enum State
|
|||||||
@Override
|
@Override
|
||||||
public boolean canChangeTo( State state )
|
public boolean canChangeTo( State state )
|
||||||
{
|
{
|
||||||
return state == FADE;
|
return state == FADE || state == TEARDOWN;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CONTROL( "CONTROL" )
|
CONTROL( "CONTROL" )
|
||||||
@@ -47,7 +53,8 @@ public enum State
|
|||||||
@Override
|
@Override
|
||||||
public boolean canChangeTo( State state )
|
public boolean canChangeTo( State state )
|
||||||
{
|
{
|
||||||
return true;
|
return state == IDLE ||
|
||||||
|
state == FADE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FADE( "FADE" )
|
FADE( "FADE" )
|
||||||
@@ -55,20 +62,36 @@ public enum State
|
|||||||
@Override
|
@Override
|
||||||
public boolean canChangeTo( State state )
|
public boolean canChangeTo( State state )
|
||||||
{
|
{
|
||||||
return state != FADE; //All states except fade allowed
|
return state != FADE &&
|
||||||
}
|
state != RESET;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
END( "END" )
|
RESET( "RESET" )
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public boolean canChangeTo( State state )
|
public boolean canChangeTo(State state)
|
||||||
{
|
{
|
||||||
return state != FADE; //Fade is only disallowed state
|
return state == IDLE;
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
|
||||||
|
TEARDOWN( "TEARDOWN" )
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean canChangeTo(State state)
|
||||||
|
{
|
||||||
|
return state == RESET;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private String mDisplayValue;
|
private String mDisplayValue;
|
||||||
|
|
||||||
|
public static final EnumSet<State> CALL_STATES =
|
||||||
|
EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED );
|
||||||
|
|
||||||
|
public static final EnumSet<State> IDLE_STATES =
|
||||||
|
EnumSet.of( IDLE, FADE );
|
||||||
|
|
||||||
private State( String displayValue )
|
private State( String displayValue )
|
||||||
{
|
{
|
||||||
mDisplayValue = displayValue;
|
mDisplayValue = displayValue;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import record.config.RecordConfiguration;
|
import record.config.RecordConfiguration;
|
||||||
|
import sample.Broadcaster;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import source.config.SourceConfigTuner;
|
import source.config.SourceConfigTuner;
|
||||||
import source.tuner.TunerChannel;
|
import source.tuner.TunerChannel;
|
||||||
@@ -90,16 +91,36 @@ public class TrafficChannelManager extends Module
|
|||||||
@Override
|
@Override
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
|
mLog.debug( "dispose() on traffic channel manager" );
|
||||||
|
for( Channel trafficChannel: mTrafficChannelPool )
|
||||||
|
{
|
||||||
|
trafficChannel.setEnabled( false );
|
||||||
|
trafficChannel.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mTrafficChannelPool.clear();
|
||||||
|
|
||||||
mCallEventListener = null;
|
mCallEventListener = null;
|
||||||
mResourceManager = null;
|
mResourceManager = null;
|
||||||
mDecodeConfiguration = null;
|
mDecodeConfiguration = null;
|
||||||
mPreviousDoNotMonitorCallEvent = null;
|
mPreviousDoNotMonitorCallEvent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a channel by either reusing an existing channel or constructing
|
||||||
|
* a new one, limited by the total number of constructed channels allowed.
|
||||||
|
*
|
||||||
|
* Note: you must enforce thread safety on the mTrafficChannelsInUse
|
||||||
|
* external to this method.
|
||||||
|
*
|
||||||
|
* @param channelNumber
|
||||||
|
* @param tunerChannel
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private Channel getChannel( String channelNumber, TunerChannel tunerChannel )
|
private Channel getChannel( String channelNumber, TunerChannel tunerChannel )
|
||||||
{
|
{
|
||||||
Channel channel = null;
|
Channel channel = null;
|
||||||
|
|
||||||
if( !mTrafficChannelsInUse.containsKey( channelNumber ) )
|
if( !mTrafficChannelsInUse.containsKey( channelNumber ) )
|
||||||
{
|
{
|
||||||
for( Channel configuredChannel: mTrafficChannelPool )
|
for( Channel configuredChannel: mTrafficChannelPool )
|
||||||
@@ -154,7 +175,7 @@ public class TrafficChannelManager extends Module
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,72 +185,79 @@ public class TrafficChannelManager extends Module
|
|||||||
*/
|
*/
|
||||||
private void process( TrafficChannelAllocationEvent event )
|
private void process( TrafficChannelAllocationEvent event )
|
||||||
{
|
{
|
||||||
if( mCallEventListener != null )
|
CallEvent callEvent = event.getCallEvent();
|
||||||
{
|
|
||||||
synchronized( mCallEventListener )
|
|
||||||
{
|
|
||||||
CallEvent callEvent = event.getCallEvent();
|
|
||||||
|
|
||||||
/* Check for duplicate events and suppress */
|
/* Check for duplicate events and suppress */
|
||||||
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) )
|
synchronized( mTrafficChannelsInUse )
|
||||||
|
{
|
||||||
|
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
long frequency = callEvent.getFrequency();
|
|
||||||
|
|
||||||
/* Check the from/to aliases for do not monitor priority */
|
|
||||||
if( isDoNotMonitor( callEvent ) )
|
|
||||||
{
|
{
|
||||||
callEvent.setCallEventType( CallEventType.CALL_DO_NOT_MONITOR );
|
mPreviousDoNotMonitorCallEvent = callEvent;
|
||||||
|
|
||||||
if( isSameCallEvent( mPreviousDoNotMonitorCallEvent, callEvent ) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mPreviousDoNotMonitorCallEvent = callEvent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if( frequency > 0 )
|
}
|
||||||
{
|
else if( frequency > 0 )
|
||||||
Channel channel = getChannel( callEvent.getChannel(),
|
{
|
||||||
new TunerChannel( Type.TRAFFIC, frequency,
|
Channel channel = getChannel( callEvent.getChannel(),
|
||||||
mDecodeConfiguration.getDecoderType()
|
new TunerChannel( Type.TRAFFIC, frequency,
|
||||||
.getChannelBandwidth() ) );
|
mDecodeConfiguration.getDecoderType()
|
||||||
|
.getChannelBandwidth() ) );
|
||||||
|
|
||||||
if( channel != null )
|
if( channel != null )
|
||||||
|
{
|
||||||
|
if( channel.isProcessing() )
|
||||||
{
|
{
|
||||||
if( channel.isProcessing() )
|
callEvent.setDetails( "TRAFFIC CHANNEL ALLOCATED" );
|
||||||
{
|
|
||||||
ChannelState state = channel.getChannelState();
|
ChannelState state = channel.getChannelState();
|
||||||
|
|
||||||
/* Register as a listener to be notified when the call
|
/* Register as a listener to be notified when the call
|
||||||
* is completed */
|
* is completed */
|
||||||
state.configureAsTrafficChannel(
|
state.configureAsTrafficChannel(
|
||||||
new TrafficChannelStatusListener( this,
|
new TrafficChannelStatusListener( this,
|
||||||
callEvent.getChannel() ), event );
|
callEvent.getChannel() ), event );
|
||||||
|
|
||||||
/* Set state to call so that audio squelch state is correct */
|
/* Set state to call so that audio squelch state is correct */
|
||||||
state.setState( State.CALL );
|
state.setState( State.CALL );
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||||
|
callEvent.setDetails( "CHANNEL PROCESSING ERROR" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||||
|
callEvent.setDetails( "NO TUNER AVAILABLE" );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mCallEventListener.receive( callEvent );
|
else
|
||||||
|
{
|
||||||
|
callEvent.setCallEventType( CallEventType.CALL_DETECT );
|
||||||
|
callEvent.setDetails( "UNKNOWN FREQUENCY" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final Listener<CallEvent> listener = mCallEventListener;
|
||||||
|
|
||||||
|
if( listener != null )
|
||||||
|
{
|
||||||
|
listener.receive( callEvent );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,14 +398,18 @@ public class TrafficChannelManager extends Module
|
|||||||
*/
|
*/
|
||||||
public void callEnd( String channelNumber )
|
public void callEnd( String channelNumber )
|
||||||
{
|
{
|
||||||
if( channelNumber != null && mTrafficChannelsInUse.containsKey( channelNumber ) )
|
synchronized( mTrafficChannelsInUse )
|
||||||
{
|
{
|
||||||
Channel channel = mTrafficChannelsInUse.get( channelNumber );
|
if( channelNumber != null &&
|
||||||
|
mTrafficChannelsInUse.containsKey( channelNumber ) )
|
||||||
mTrafficChannelsInUse.remove( channelNumber );
|
{
|
||||||
|
Channel channel = mTrafficChannelsInUse.get( channelNumber );
|
||||||
|
|
||||||
|
mTrafficChannelsInUse.remove( channelNumber );
|
||||||
|
|
||||||
/* Disable the channel and broadcast a notification */
|
/* Disable the channel and broadcast a notification */
|
||||||
channel.setEnabled( false, true );
|
channel.setEnabled( false, true );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,9 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.beans.DesignMode;
|
import java.beans.DesignMode;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||||
@@ -126,13 +125,13 @@ public class JXMapViewer extends JPanel implements DesignMode
|
|||||||
// make a dummy loading image
|
// make a dummy loading image
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
URL url = JXMapViewer.class.getResource("mapviewer/resources/loading.png");
|
ImageIcon imageIcon = new ImageIcon( "images/loading.png" );
|
||||||
this.setLoadingImage(ImageIO.read(url));
|
this.setLoadingImage( imageIcon.getImage() );
|
||||||
}
|
}
|
||||||
catch (Throwable ex)
|
catch (Throwable ex)
|
||||||
{
|
{
|
||||||
|
|
||||||
mLog.error( "JXMapViewer could not load 'loading.png'" );
|
mLog.error( "could not load 'loading.png'" );
|
||||||
|
|
||||||
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D g2 = img.createGraphics();
|
Graphics2D g2 = img.createGraphics();
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class PlaylistManager
|
|||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
mLog.error( "PlaylistManager - couldn't create file to save "
|
mLog.error( "couldn't create file to save "
|
||||||
+ "playlist [" + filePath.toString() + "]" );
|
+ "playlist [" + filePath.toString() + "]" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,14 +122,12 @@ public class PlaylistManager
|
|||||||
}
|
}
|
||||||
catch ( JAXBException e )
|
catch ( JAXBException e )
|
||||||
{
|
{
|
||||||
mLog.error( "PlaylistManager - jaxb exception while saving " +
|
mLog.error( "jaxb exception while saving playlist: ", e );
|
||||||
"playlist: ", e );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( Exception e )
|
catch ( Exception e )
|
||||||
{
|
{
|
||||||
mLog.error( "PlaylistManager - coulcn't open outputstream to " +
|
mLog.error( "coulcn't open outputstream to save playlist [" + filePath.toString() + "]", e );
|
||||||
"save playlist [" + filePath.toString() + "]", e );
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -155,8 +153,7 @@ public class PlaylistManager
|
|||||||
{
|
{
|
||||||
if( Files.exists( playlistPath ) )
|
if( Files.exists( playlistPath ) )
|
||||||
{
|
{
|
||||||
mLog.info( "PlaylistManager - loading playlist file [" +
|
mLog.info( "loading playlist file [" + playlistPath.toString() + "]" );
|
||||||
playlistPath.toString() + "]" );
|
|
||||||
|
|
||||||
JAXBContext context = null;
|
JAXBContext context = null;
|
||||||
|
|
||||||
@@ -176,14 +173,12 @@ public class PlaylistManager
|
|||||||
}
|
}
|
||||||
catch ( JAXBException e )
|
catch ( JAXBException e )
|
||||||
{
|
{
|
||||||
mLog.error( "PlaylistManager - jaxb exception while loading " +
|
mLog.error( "jaxb exception while loading playlist: ", e );
|
||||||
"playlist: ", e );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( Exception e )
|
catch ( Exception e )
|
||||||
{
|
{
|
||||||
mLog.error( "PlaylistManager - coulcn't open inputstream to " +
|
mLog.error( "coulcn't open inputstream to load playlist [" + playlistPath.toString() + "]", e );
|
||||||
"load playlist [" + playlistPath.toString() + "]", e );
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -195,7 +190,7 @@ public class PlaylistManager
|
|||||||
}
|
}
|
||||||
catch ( IOException e )
|
catch ( IOException e )
|
||||||
{
|
{
|
||||||
mLog.error( "PlaylistManager - exception while closing " +
|
mLog.error( "exception while closing " +
|
||||||
"the playlist file inputstream reader", e );
|
"the playlist file inputstream reader", e );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,8 +198,7 @@ public class PlaylistManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.info( "PlaylistManager - playlist does not exist [" +
|
mLog.info( "playlist does not exist [" + playlistPath.toString() + "]" );
|
||||||
playlistPath.toString() + "]" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mPlaylist == null )
|
if( mPlaylist == null )
|
||||||
|
|||||||
@@ -82,18 +82,13 @@ public class SystemProperties
|
|||||||
{
|
{
|
||||||
out = new FileOutputStream( propsPath.toString() );
|
out = new FileOutputStream( propsPath.toString() );
|
||||||
|
|
||||||
String comments =
|
String comments = "SDRTrunk - SDR Trunking Decoder Application Settings";
|
||||||
"SDRTrunk - SDR Trunking Decoder Application Settings";
|
|
||||||
|
|
||||||
mProperties.store( out, comments );
|
mProperties.store( out, comments );
|
||||||
|
|
||||||
mLog.info( "SystemProperties - saved current properties [" +
|
|
||||||
propsPath.toString() + "]" );
|
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
mLog.error( "SystemProperties - exception while saving " +
|
mLog.error( "exception while saving application properties", e );
|
||||||
"application properties", e );
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -146,8 +141,7 @@ public class SystemProperties
|
|||||||
}
|
}
|
||||||
catch ( IOException e )
|
catch ( IOException e )
|
||||||
{
|
{
|
||||||
mLog.error( "SystemProperties - exception while creating " +
|
mLog.error( "exception while creating app folder [" + folder + "]", e );
|
||||||
"app folder [" + folder + "]", e );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,11 +152,11 @@ public class SystemProperties
|
|||||||
{
|
{
|
||||||
if( mPropertiesPath == null )
|
if( mPropertiesPath == null )
|
||||||
{
|
{
|
||||||
mLog.info( "SystemProperties - no properties file loaded - using defaults" );
|
mLog.info( "no properties file loaded - using defaults" );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.info( "SystemProperties - application properties loaded [" + mPropertiesPath.toString() + "]" );
|
mLog.info( "application properties loaded [" + mPropertiesPath.toString() + "]" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +178,7 @@ public class SystemProperties
|
|||||||
}
|
}
|
||||||
catch ( FileNotFoundException e )
|
catch ( FileNotFoundException e )
|
||||||
{
|
{
|
||||||
mLog.error( "SDRTrunk - exception while opening inputstream on " +
|
mLog.error( "exception while opening inputstream on " +
|
||||||
"application properties file", e );
|
"application properties file", e );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +190,7 @@ public class SystemProperties
|
|||||||
}
|
}
|
||||||
catch ( IOException e )
|
catch ( IOException e )
|
||||||
{
|
{
|
||||||
mLog.error( "SDRTrunk - exception while loading properties " +
|
mLog.error( "exception while loading properties " +
|
||||||
"inputstream into SystemProperties", e );
|
"inputstream into SystemProperties", e );
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -212,8 +206,7 @@ public class SystemProperties
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mLog.info( "SystemProperties - loaded [" +
|
mLog.info( "loaded [" + propertiesPath.toString() + "]" );
|
||||||
propertiesPath.toString() + "]" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ public class ColorSetting extends Setting
|
|||||||
"Control Gradient Middle", "Channel Control State Gradient Middle" ),
|
"Control Gradient Middle", "Channel Control State Gradient Middle" ),
|
||||||
CHANNEL_STATE_GRADIENT_TOP_DATA( Color.BLACK,
|
CHANNEL_STATE_GRADIENT_TOP_DATA( Color.BLACK,
|
||||||
"Data Gradient Top", "Channel Data State Gradient Top" ),
|
"Data Gradient Top", "Channel Data State Gradient Top" ),
|
||||||
CHANNEL_STATE_GRADIENT_MIDDLE_DATA( Color.BLUE,
|
CHANNEL_STATE_GRADIENT_MIDDLE_DATA( new Color( 0xCC00CC ),
|
||||||
"Data Gradient Middle", "Channel Data State Gradient Middle" ),
|
"Data Gradient Middle", "Channel Data State Gradient Middle" ),
|
||||||
CHANNEL_STATE_GRADIENT_TOP_FADE( Color.BLACK,
|
CHANNEL_STATE_GRADIENT_TOP_FADE( Color.BLACK,
|
||||||
"Fade Gradient Top", "Channel Fade State Gradient Top" ),
|
"Fade Gradient Top", "Channel Fade State Gradient Top" ),
|
||||||
|
|||||||
@@ -431,8 +431,7 @@ public class SettingsManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Something happened ... the above should always return an icon */
|
/* Something happened ... the above should always return an icon */
|
||||||
mLog.error( "SettingsManager - couldn't return an icon named [" +
|
mLog.error( "couldn't return an icon named [" + name + "] of heigh [" + height + "]" );
|
||||||
name + "] of heigh [" + height + "]" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -630,8 +629,7 @@ public class SettingsManager
|
|||||||
|
|
||||||
return r820TConfig;
|
return r820TConfig;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException( "TunerConfiguration"
|
throw new IllegalArgumentException( "tuner type is unrecognized [" +
|
||||||
+ "Directory - tuner type is unrecognized [" +
|
|
||||||
type.toString() + "]" );
|
type.toString() + "]" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,8 +703,7 @@ public class SettingsManager
|
|||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
mLog.error( "SettingsManager - couldn't create file to save "
|
mLog.error( "couldn't create file to save settings [" + filePath.toString() + "]", e );
|
||||||
+ "settings [" + filePath.toString() + "]", e );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputStream out = null;
|
OutputStream out = null;
|
||||||
@@ -727,14 +724,12 @@ public class SettingsManager
|
|||||||
}
|
}
|
||||||
catch ( JAXBException e )
|
catch ( JAXBException e )
|
||||||
{
|
{
|
||||||
mLog.error( "SettingsManager - jaxb exception while saving " +
|
mLog.error( "jaxb exception while saving settings", e );
|
||||||
"settings", e );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( Exception e )
|
catch ( Exception e )
|
||||||
{
|
{
|
||||||
mLog.error( "SettingsManager - coulcn't open outputstream to " +
|
mLog.error( "couldn't open outputstream to save settings [" + filePath.toString() + "]" );
|
||||||
"save settings [" + filePath.toString() + "]" );
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -760,8 +755,7 @@ public class SettingsManager
|
|||||||
{
|
{
|
||||||
if( Files.exists( settingsPath ) )
|
if( Files.exists( settingsPath ) )
|
||||||
{
|
{
|
||||||
mLog.info( "SettingsManager - loading settings file [" +
|
mLog.info( "loading settings file [" + settingsPath.toString() + "]" );
|
||||||
settingsPath.toString() + "]" );
|
|
||||||
|
|
||||||
JAXBContext context = null;
|
JAXBContext context = null;
|
||||||
|
|
||||||
@@ -781,13 +775,13 @@ public class SettingsManager
|
|||||||
}
|
}
|
||||||
catch ( JAXBException e )
|
catch ( JAXBException e )
|
||||||
{
|
{
|
||||||
mLog.error( "SettingsManager - jaxb exception while loading " +
|
mLog.error( "jaxb exception while loading " +
|
||||||
"settings", e );
|
"settings", e );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( Exception e )
|
catch ( Exception e )
|
||||||
{
|
{
|
||||||
mLog.error( "SettingsManager - coulcn't open inputstream to " +
|
mLog.error( "coulcn't open inputstream to " +
|
||||||
"load settings [" + settingsPath.toString() + "]", e );
|
"load settings [" + settingsPath.toString() + "]", e );
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -800,7 +794,7 @@ public class SettingsManager
|
|||||||
}
|
}
|
||||||
catch ( IOException e )
|
catch ( IOException e )
|
||||||
{
|
{
|
||||||
mLog.error( "SettingsManager - exception while closing " +
|
mLog.error( "exception while closing " +
|
||||||
"the settings file inputstream reader", e );
|
"the settings file inputstream reader", e );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -808,8 +802,7 @@ public class SettingsManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.info( "SettingsManager - settings does not exist [" +
|
mLog.info( "settings does not exist [" + settingsPath.toString() + "]" );
|
||||||
settingsPath.toString() + "]" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mSettings == null )
|
if( mSettings == null )
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ public class MixerChannelConfiguration
|
|||||||
return mMixerChannel;
|
return mMixerChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean matches( String mixer, String channels )
|
||||||
|
{
|
||||||
|
return mixer != null &&
|
||||||
|
channels != null &&
|
||||||
|
mMixer.getMixerInfo().getName().contentEquals( mixer ) &&
|
||||||
|
mMixerChannel.name().contentEquals( channels );
|
||||||
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@@ -63,9 +63,8 @@ public class Recording implements Comparable<Recording>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException,
|
throws RejectedExecutionException, SourceException
|
||||||
SourceException
|
|
||||||
{
|
{
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -71,9 +71,7 @@ public class RecordingSourceManager
|
|||||||
|
|
||||||
if( recording != null )
|
if( recording != null )
|
||||||
{
|
{
|
||||||
retVal = recording.getChannel(
|
retVal = recording.getChannel( tunerChannel );
|
||||||
mResourceManager.getThreadPoolManager(),
|
|
||||||
tunerChannel );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -143,8 +141,7 @@ public class RecordingSourceManager
|
|||||||
ArrayList<RecordingConfiguration> recordingConfigurations =
|
ArrayList<RecordingConfiguration> recordingConfigurations =
|
||||||
settingsManager.getRecordingConfigurations();
|
settingsManager.getRecordingConfigurations();
|
||||||
|
|
||||||
mLog.info( "RecordingSourceManager - discovered [" +
|
mLog.info( "discovered [" + recordingConfigurations.size() + "] recording configurations" );
|
||||||
recordingConfigurations.size() + "] recording configurations" );
|
|
||||||
|
|
||||||
for( RecordingConfiguration config: recordingConfigurations )
|
for( RecordingConfiguration config: recordingConfigurations )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,10 +30,9 @@ import sample.complex.ComplexBuffer;
|
|||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
import source.tuner.frequency.FrequencyChangeBroadcaster;
|
import source.tuner.frequency.FrequencyChangeBroadcaster;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import source.tuner.frequency.FrequencyChangeListener;
|
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||||
|
import source.tuner.frequency.FrequencyChangeListener;
|
||||||
import controller.ResourceManager;
|
import controller.ResourceManager;
|
||||||
import controller.ThreadPoolManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tuner - provides tuner channel sources, representing a channel frequency
|
* Tuner - provides tuner channel sources, representing a channel frequency
|
||||||
@@ -147,8 +146,8 @@ public abstract class Tuner implements FrequencyChangeBroadcaster,
|
|||||||
*
|
*
|
||||||
* @return - source for 48k sample rate
|
* @return - source for 48k sample rate
|
||||||
*/
|
*/
|
||||||
public abstract TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public abstract TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException;
|
throws RejectedExecutionException, SourceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the tuned channel resources
|
* Releases the tuned channel resources
|
||||||
|
|||||||
@@ -97,9 +97,13 @@ public class TunerChannel implements Comparable<TunerChannel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo( TunerChannel otherLock )
|
public int compareTo( TunerChannel otherTunerChannel )
|
||||||
{
|
{
|
||||||
if( mFrequency < otherLock.getFrequency() )
|
if( this.equals( otherTunerChannel ) )
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if( mFrequency < otherTunerChannel.getFrequency() )
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ public interface TunerChannelProvider
|
|||||||
*
|
*
|
||||||
* @return - source for 48k sample rate
|
* @return - source for 48k sample rate
|
||||||
*/
|
*/
|
||||||
public abstract TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public abstract TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException;
|
throws RejectedExecutionException, SourceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the tuned channel resources
|
* Releases the tuned channel resources
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@@ -37,8 +38,6 @@ import source.SourceException;
|
|||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||||
import source.tuner.frequency.FrequencyChangeListener;
|
import source.tuner.frequency.FrequencyChangeListener;
|
||||||
import controller.ThreadPoolManager;
|
|
||||||
import controller.ThreadPoolManager.ThreadType;
|
|
||||||
import dsp.filter.FilterFactory;
|
import dsp.filter.FilterFactory;
|
||||||
import dsp.filter.Window.WindowType;
|
import dsp.filter.Window.WindowType;
|
||||||
import dsp.filter.cic.ComplexPrimeCICDecimate;
|
import dsp.filter.cic.ComplexPrimeCICDecimate;
|
||||||
@@ -62,7 +61,6 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||||
private Listener<ComplexBuffer> mListener;
|
private Listener<ComplexBuffer> mListener;
|
||||||
private FrequencyChangeListener mFrequencyChangeListener;
|
private FrequencyChangeListener mFrequencyChangeListener;
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
|
||||||
private ScheduledFuture<?> mTaskHandle;
|
private ScheduledFuture<?> mTaskHandle;
|
||||||
|
|
||||||
private long mTunerFrequency = 0;
|
private long mTunerFrequency = 0;
|
||||||
@@ -71,15 +69,11 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
|
|
||||||
private DecimationProcessor mDecimationProcessor = new DecimationProcessor();
|
private DecimationProcessor mDecimationProcessor = new DecimationProcessor();
|
||||||
|
|
||||||
public TunerChannelSource( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource( Tuner tuner, TunerChannel tunerChannel )
|
||||||
Tuner tuner,
|
throws RejectedExecutionException, SourceException
|
||||||
TunerChannel tunerChannel )
|
|
||||||
throws RejectedExecutionException, SourceException
|
|
||||||
{
|
{
|
||||||
super( "Tuner Channel Source" );
|
super( "Tuner Channel Source" );
|
||||||
|
|
||||||
mThreadPoolManager = threadPoolManager;
|
|
||||||
|
|
||||||
mTuner = tuner;
|
mTuner = tuner;
|
||||||
mTuner.addListener( (FrequencyChangeListener)this );
|
mTuner.addListener( (FrequencyChangeListener)this );
|
||||||
|
|
||||||
@@ -95,14 +89,17 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
/* Fire a sample rate change event to setup the decimation chain */
|
/* Fire a sample rate change event to setup the decimation chain */
|
||||||
frequencyChanged( new FrequencyChangeEvent(
|
frequencyChanged( new FrequencyChangeEvent(
|
||||||
Event.SAMPLE_RATE_CHANGE_NOTIFICATION, mTuner.getSampleRate() ) );
|
Event.SAMPLE_RATE_CHANGE_NOTIFICATION, mTuner.getSampleRate() ) );
|
||||||
|
}
|
||||||
/* Schedule the decimation task to run every 20 ms (50 iterations/second) */
|
|
||||||
mTaskHandle = mThreadPoolManager.scheduleFixedRate( ThreadType.DECIMATION,
|
public void start( ScheduledExecutorService scheduledExecutorService )
|
||||||
mDecimationProcessor, 20, TimeUnit.MILLISECONDS );
|
{
|
||||||
|
/* Schedule the decimation task to run every 10 ms (100 iterations/second) */
|
||||||
|
mTaskHandle = scheduledExecutorService.scheduleAtFixedRate(
|
||||||
|
mDecimationProcessor, 0, 10, TimeUnit.MILLISECONDS );
|
||||||
|
|
||||||
/* Finally, register to receive samples from the tuner */
|
/* Finally, register to receive samples from the tuner */
|
||||||
mTuner.addListener( (Listener<ComplexBuffer>)this );
|
mTuner.addListener( (Listener<ComplexBuffer>)this );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose()
|
public void dispose()
|
||||||
@@ -129,14 +126,15 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
{
|
{
|
||||||
if( mTaskHandle != null )
|
if( mTaskHandle != null )
|
||||||
{
|
{
|
||||||
mThreadPoolManager.cancel( mTaskHandle );
|
mTaskHandle.cancel( true );
|
||||||
mThreadPoolManager = null;
|
|
||||||
mTaskHandle = null;
|
mTaskHandle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized( mBuffer )
|
||||||
mBuffer.clear();
|
{
|
||||||
mBuffer = null;
|
mBuffer.clear();
|
||||||
|
mBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
mFrequencyChangeListener = null;
|
mFrequencyChangeListener = null;
|
||||||
mListener = null;
|
mListener = null;
|
||||||
@@ -160,7 +158,13 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
@Override
|
@Override
|
||||||
public void receive( ComplexBuffer buffer )
|
public void receive( ComplexBuffer buffer )
|
||||||
{
|
{
|
||||||
mBuffer.add( buffer );
|
synchronized( mBuffer )
|
||||||
|
{
|
||||||
|
if( mBuffer != null )
|
||||||
|
{
|
||||||
|
mBuffer.add( buffer );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFrequencyChangeListener( FrequencyChangeListener listener )
|
public void setFrequencyChangeListener( FrequencyChangeListener listener )
|
||||||
@@ -290,7 +294,7 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
{
|
{
|
||||||
if( mBuffer != null )
|
if( mBuffer != null )
|
||||||
{
|
{
|
||||||
mBuffer.drainTo( mSampleBuffers, 4 );
|
mBuffer.drainTo( mSampleBuffers, 8 );
|
||||||
|
|
||||||
for( Buffer buffer: mSampleBuffers )
|
for( Buffer buffer: mSampleBuffers )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package source.tuner;
|
package source.tuner;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.SortedSet;
|
||||||
import java.util.Collections;
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -36,8 +36,7 @@ public abstract class TunerController implements Tunable
|
|||||||
LoggerFactory.getLogger( TunerController.class );
|
LoggerFactory.getLogger( TunerController.class );
|
||||||
|
|
||||||
/* List of currently tuned channels being served to demod channels */
|
/* List of currently tuned channels being served to demod channels */
|
||||||
protected ArrayList<TunerChannel> mTunedChannels =
|
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
|
||||||
new ArrayList<TunerChannel>();
|
|
||||||
protected FrequencyController mFrequencyController;
|
protected FrequencyController mFrequencyController;
|
||||||
private int mMiddleUnusable;
|
private int mMiddleUnusable;
|
||||||
private double mUsableBandwidthPercentage;
|
private double mUsableBandwidthPercentage;
|
||||||
@@ -147,13 +146,9 @@ public abstract class TunerController implements Tunable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//Sort the existing locks and get the min/max locked frequencies
|
|
||||||
Collections.sort( mTunedChannels );
|
|
||||||
|
|
||||||
int usableBandwidth = getUsableBandwidth();
|
int usableBandwidth = getUsableBandwidth();
|
||||||
long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency();
|
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
|
||||||
long maxLockedFrequency = mTunedChannels
|
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
|
||||||
.get( mTunedChannels.size() - 1 ).getMaxFrequency();
|
|
||||||
|
|
||||||
//Requested channel is within current locked channel frequency range
|
//Requested channel is within current locked channel frequency range
|
||||||
if( minLockedFrequency <= channel.getMinFrequency() &&
|
if( minLockedFrequency <= channel.getMinFrequency() &&
|
||||||
@@ -199,8 +194,8 @@ public abstract class TunerController implements Tunable
|
|||||||
*
|
*
|
||||||
* @throws RejectedExecutionException if the decimation processor has an error
|
* @throws RejectedExecutionException if the decimation processor has an error
|
||||||
*/
|
*/
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( Tuner tuner, TunerChannel channel )
|
||||||
Tuner tuner, TunerChannel channel ) throws RejectedExecutionException
|
throws RejectedExecutionException
|
||||||
{
|
{
|
||||||
TunerChannelSource source = null;
|
TunerChannelSource source = null;
|
||||||
|
|
||||||
@@ -215,8 +210,7 @@ public abstract class TunerController implements Tunable
|
|||||||
updateLOFrequency();
|
updateLOFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
source = new TunerChannelSource( threadPoolManager,
|
source = new TunerChannelSource( tuner, channel );
|
||||||
tuner, channel );
|
|
||||||
}
|
}
|
||||||
catch( SourceException se )
|
catch( SourceException se )
|
||||||
{
|
{
|
||||||
@@ -251,10 +245,6 @@ public abstract class TunerController implements Tunable
|
|||||||
{
|
{
|
||||||
mTunedChannels.remove( tunerChannelSource.getTunerChannel() );
|
mTunedChannels.remove( tunerChannelSource.getTunerChannel() );
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
mLog.error( "Request to release channel that is not in current tuned channels set" );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,8 +272,6 @@ public abstract class TunerController implements Tunable
|
|||||||
*/
|
*/
|
||||||
private void updateLOFrequency() throws SourceException
|
private void updateLOFrequency() throws SourceException
|
||||||
{
|
{
|
||||||
Collections.sort( mTunedChannels );
|
|
||||||
|
|
||||||
long frequency = getFrequency();
|
long frequency = getFrequency();
|
||||||
|
|
||||||
boolean frequencyValid = true;
|
boolean frequencyValid = true;
|
||||||
@@ -291,13 +279,12 @@ public abstract class TunerController implements Tunable
|
|||||||
//If there is only 1 channel, position it to the right of center
|
//If there is only 1 channel, position it to the right of center
|
||||||
if( mTunedChannels.size() == 1 )
|
if( mTunedChannels.size() == 1 )
|
||||||
{
|
{
|
||||||
frequency = mTunedChannels.get( 0 ).getMinFrequency() - mMiddleUnusable;
|
frequency = mTunedChannels.first().getMinFrequency() - mMiddleUnusable;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency();
|
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
|
||||||
long maxLockedFrequency = mTunedChannels
|
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
|
||||||
.get( mTunedChannels.size() - 1 ).getMaxFrequency();
|
|
||||||
|
|
||||||
//Start by placing the highest frequency channel at the high end of
|
//Start by placing the highest frequency channel at the high end of
|
||||||
//the spectrum
|
//the spectrum
|
||||||
|
|||||||
@@ -100,8 +100,7 @@ public class TunerManager
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
retVal = tuner.getChannel(
|
retVal = tuner.getChannel( tunerChannel );
|
||||||
mResourceManager.getThreadPoolManager(), tunerChannel );
|
|
||||||
}
|
}
|
||||||
catch ( RejectedExecutionException ree )
|
catch ( RejectedExecutionException ree )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ public class AirspyTuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
throws RejectedExecutionException, SourceException
|
||||||
{
|
{
|
||||||
//TODO: this channel has a decimated sample rate of:
|
//TODO: this channel has a decimated sample rate of:
|
||||||
// 10.0 MSps = 10,000,000 / 208 = 48076.923
|
// 10.0 MSps = 10,000,000 / 208 = 48076.923
|
||||||
@@ -138,7 +138,7 @@ public class AirspyTuner extends Tuner
|
|||||||
//Consider implementing a fractional resampler to get a correct 48 kHz
|
//Consider implementing a fractional resampler to get a correct 48 kHz
|
||||||
//output sample rate
|
//output sample rate
|
||||||
|
|
||||||
return mController.getChannel( threadPoolManager, this, channel );
|
return mController.getChannel( this, channel );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ public class B100Tuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
throws RejectedExecutionException, SourceException
|
||||||
{
|
{
|
||||||
//TODO: pass this call to the tuner controller
|
//TODO: pass this call to the tuner controller
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -112,11 +112,10 @@ public class FCDTuner extends MixerTuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel tunerChannel )
|
||||||
TunerChannel tunerChannel ) throws RejectedExecutionException,
|
throws RejectedExecutionException, SourceException
|
||||||
SourceException
|
|
||||||
{
|
{
|
||||||
return mController.getChannel( threadPoolManager, this, tunerChannel );
|
return mController.getChannel( this, tunerChannel );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ public class HackRFTuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
throws RejectedExecutionException, SourceException
|
||||||
{
|
{
|
||||||
return mController.getChannel( threadPoolManager, this, channel );
|
return mController.getChannel( this, channel );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import source.tuner.TunerType;
|
|||||||
import source.tuner.frequency.FrequencyChangeListener;
|
import source.tuner.frequency.FrequencyChangeListener;
|
||||||
import source.tuner.rtl.RTL2832TunerController.SampleRate;
|
import source.tuner.rtl.RTL2832TunerController.SampleRate;
|
||||||
import controller.ResourceManager;
|
import controller.ResourceManager;
|
||||||
import controller.ThreadPoolManager;
|
|
||||||
|
|
||||||
public class RTL2832Tuner extends Tuner
|
public class RTL2832Tuner extends Tuner
|
||||||
{
|
{
|
||||||
@@ -148,16 +147,14 @@ public class RTL2832Tuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel )
|
throws RejectedExecutionException, SourceException
|
||||||
throws RejectedExecutionException,
|
|
||||||
SourceException
|
|
||||||
{
|
{
|
||||||
TunerChannelSource source = null;
|
TunerChannelSource source = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
source = mController.getChannel( threadPoolManager, this, channel );
|
source = mController.getChannel( this, channel );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.jtransforms.fft.FloatFFT_1D;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import properties.SystemProperties;
|
||||||
import sample.Buffer;
|
import sample.Buffer;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import sample.SampleType;
|
import sample.SampleType;
|
||||||
@@ -61,6 +62,8 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
|||||||
private ScheduledExecutorService mScheduler = Executors
|
private ScheduledExecutorService mScheduler = Executors
|
||||||
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
|
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
|
||||||
|
|
||||||
|
public static final String FRAME_RATE_PROPERTY = "spectral.display.frame.rate";
|
||||||
|
|
||||||
private DFTSize mDFTSize = DFTSize.FFT04096;
|
private DFTSize mDFTSize = DFTSize.FFT04096;
|
||||||
private DFTSize mNewDFTSize = DFTSize.FFT04096;
|
private DFTSize mNewDFTSize = DFTSize.FFT04096;
|
||||||
|
|
||||||
@@ -89,7 +92,16 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
|||||||
public DFTProcessor( SampleType sampleType )
|
public DFTProcessor( SampleType sampleType )
|
||||||
{
|
{
|
||||||
setSampleType( sampleType );
|
setSampleType( sampleType );
|
||||||
setFrameRate( 20 );
|
|
||||||
|
loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSettings()
|
||||||
|
{
|
||||||
|
int frameRate = SystemProperties.getInstance()
|
||||||
|
.get( DFTProcessor.FRAME_RATE_PROPERTY, 20 );
|
||||||
|
|
||||||
|
setFrameRate( frameRate );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
@@ -171,6 +183,10 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
|||||||
|
|
||||||
mFrameRate = framesPerSecond;
|
mFrameRate = framesPerSecond;
|
||||||
|
|
||||||
|
SystemProperties properties = SystemProperties.getInstance();
|
||||||
|
properties.set( FRAME_RATE_PROPERTY, String.valueOf( mFrameRate ) );
|
||||||
|
properties.save();
|
||||||
|
|
||||||
calculateConsumptionRate();
|
calculateConsumptionRate();
|
||||||
|
|
||||||
restart();
|
restart();
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import playlist.PlaylistManager;
|
import playlist.PlaylistManager;
|
||||||
|
import properties.SystemProperties;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import sample.SampleType;
|
import sample.SampleType;
|
||||||
import sample.complex.ComplexBuffer;
|
import sample.complex.ComplexBuffer;
|
||||||
@@ -99,8 +100,10 @@ public class SpectralDisplayPanel extends JPanel
|
|||||||
|
|
||||||
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" );
|
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" );
|
||||||
|
|
||||||
|
public static final String FFT_SIZE_PROPERTY = "spectral.display.dft.size";
|
||||||
public static final int NO_ZOOM = 0;
|
public static final int NO_ZOOM = 0;
|
||||||
public static final int MAX_ZOOM = 6;
|
public static final int MAX_ZOOM = 6;
|
||||||
|
|
||||||
|
|
||||||
private DFTSize mDFTSize = DFTSize.FFT04096;
|
private DFTSize mDFTSize = DFTSize.FFT04096;
|
||||||
private int mZoom = 0;
|
private int mZoom = 0;
|
||||||
@@ -150,6 +153,36 @@ public class SpectralDisplayPanel extends JPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSettings()
|
||||||
|
{
|
||||||
|
SystemProperties properties = SystemProperties.getInstance();
|
||||||
|
|
||||||
|
String rawSize = properties.get( FFT_SIZE_PROPERTY, DFTSize.FFT04096.name() );
|
||||||
|
|
||||||
|
DFTSize size = null;
|
||||||
|
|
||||||
|
if( rawSize != null )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
size = DFTSize.valueOf( rawSize );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
//Do nothing, we couldn't parse the stored value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( size == null )
|
||||||
|
{
|
||||||
|
size = DFTSize.FFT04096;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDFTSize( size );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
@@ -194,6 +227,10 @@ public class SpectralDisplayPanel extends JPanel
|
|||||||
mDFTProcessor.setDFTSize( size );
|
mDFTProcessor.setDFTSize( size );
|
||||||
mOverlayPanel.setDFTSize( size );
|
mOverlayPanel.setDFTSize( size );
|
||||||
mDFTSize = size;
|
mDFTSize = size;
|
||||||
|
|
||||||
|
SystemProperties properties = SystemProperties.getInstance();
|
||||||
|
properties.set( FFT_SIZE_PROPERTY, size.name() );
|
||||||
|
properties.save();
|
||||||
|
|
||||||
setZoom( 0, 0, 0 );
|
setZoom( 0, 0, 0 );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
|
|||||||
mMemoryImageSource.setAnimated( true );
|
mMemoryImageSource.setAnimated( true );
|
||||||
|
|
||||||
mWaterfallImage = createImage( mMemoryImageSource );
|
mWaterfallImage = createImage( mMemoryImageSource );
|
||||||
|
|
||||||
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user