mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
14 Commits
v0.4.0-bet
...
v0.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e37f1e687 | ||
|
|
5e8f2d0523 | ||
|
|
9fb6c33319 | ||
|
|
fdecac4230 | ||
|
|
85c27a6cca | ||
|
|
57f56d31a2 | ||
|
|
df25675d3f | ||
|
|
277a84702a | ||
|
|
c651296378 | ||
|
|
866d25e86a | ||
|
|
5e62f1b889 | ||
|
|
e9d524ad14 | ||
|
|
158c8b4b8a | ||
|
|
41df207bb9 |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="true" project-jdk-name="13" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="14" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
15
CHANGELOG
15
CHANGELOG
@@ -1,3 +1,18 @@
|
|||||||
|
0.4.0 20200404
|
||||||
|
- Release includes embedded Java 14 & JavaFX 13 runtime
|
||||||
|
- Adds Polyphase Channelizer with 2x channel oversampling and redesigned source management
|
||||||
|
- Adds P25 Phase 2 decoder
|
||||||
|
- Adds P25 radio identifier aliasing
|
||||||
|
- JMBE library 1.0.4 with support for AMBE tone metadata
|
||||||
|
- New JavaFX User Preferences dialog
|
||||||
|
- HackRF tuner enhanced (sample rates and saturation issues)
|
||||||
|
- Adds support for Airspy mini tuner
|
||||||
|
- Redesigned audio sub-system that uses AudioSegments
|
||||||
|
- USB tuner bus saturation management/monitoring
|
||||||
|
- Bitstream (.bits) recording
|
||||||
|
- MP3 Audio Recording
|
||||||
|
- Numerous bug fixes
|
||||||
|
|
||||||
0.3.4 20180418
|
0.3.4 20180418
|
||||||
- New P25 C4FM decoder that uses Differential QPSK decoder instead of FM demodulation with 4FSK.
|
- New P25 C4FM decoder that uses Differential QPSK decoder instead of FM demodulation with 4FSK.
|
||||||
- Adds channel auto-start and ordering capability with startup dialog allowing user to invoke or bypass channel
|
- Adds channel auto-start and ordering capability with startup dialog allowing user to invoke or bypass channel
|
||||||
|
|||||||
14
build.gradle
14
build.gradle
@@ -33,7 +33,7 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.4.0-beta.6'
|
version = '0.4.0'
|
||||||
sourceCompatibility = '13'
|
sourceCompatibility = '13'
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -114,7 +114,7 @@ jar {
|
|||||||
attributes (
|
attributes (
|
||||||
'Implementation-Title' : 'sdrtrunk project',
|
'Implementation-Title' : 'sdrtrunk project',
|
||||||
'Implementation-Version' : archiveVersion,
|
'Implementation-Version' : archiveVersion,
|
||||||
'Class-Path' : 'jmbe-1.0.1.jar',
|
'Class-Path' : 'jmbe-1.0.4.jar',
|
||||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||||
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||||
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
||||||
@@ -129,11 +129,11 @@ jar {
|
|||||||
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
|
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
|
||||||
* host OS using the installed JDK.
|
* host OS using the installed JDK.
|
||||||
*/
|
*/
|
||||||
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-13.0.1'
|
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-14'
|
||||||
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-13.0.1'
|
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-14'
|
||||||
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-13.0.1.jdk'
|
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-14.jdk'
|
||||||
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-13.0.1'
|
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-14'
|
||||||
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-13.0.1'
|
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-14'
|
||||||
|
|
||||||
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
|
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
|
||||||
def file_jdk_linux_x86_32 = new File(jdk_linux_x86_32)
|
def file_jdk_linux_x86_32 = new File(jdk_linux_x86_32)
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
24
gradle/wrapper/gradle-wrapper.properties
vendored
24
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,27 +1,5 @@
|
|||||||
#
|
|
||||||
# /*
|
|
||||||
# * ******************************************************************************
|
|
||||||
# * Copyright (C) 2014-2019 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/>
|
|
||||||
# * *****************************************************************************
|
|
||||||
# */
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
51
gradlew
vendored
51
gradlew
vendored
@@ -1,5 +1,21 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
##
|
||||||
## Gradle start up script for UN*X
|
## Gradle start up script for UN*X
|
||||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
@@ -109,8 +125,8 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
@@ -138,19 +154,19 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -159,14 +175,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ public class Radio extends AliasID
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TalkgroupFormat talkgroupFormat = TalkgroupFormat.get(mProtocol);
|
RadioFormat radioFormat = RadioFormat.get(mProtocol);
|
||||||
return talkgroupFormat.getMinimumValidValue() <= mValue && mValue <= talkgroupFormat.getMaximumValidValue();
|
return radioFormat.getMinimumValidValue() <= mValue && mValue <= radioFormat.getMaximumValidValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
|||||||
private static final int MAX_SEGMENT_AUDIO_SAMPLE_LENGTH = 8000 * 60 * 1; //8 kHz - 1 minute
|
private static final int MAX_SEGMENT_AUDIO_SAMPLE_LENGTH = 8000 * 60 * 1; //8 kHz - 1 minute
|
||||||
|
|
||||||
private Listener<AudioSegment> mAudioSegmentListener;
|
private Listener<AudioSegment> mAudioSegmentListener;
|
||||||
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
protected MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||||
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
||||||
private AliasList mAliasList;
|
private AliasList mAliasList;
|
||||||
private AudioSegment mAudioSegment;
|
private AudioSegment mAudioSegment;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -408,11 +409,28 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||||
{
|
{
|
||||||
//Only process add updates that match this timeslot
|
//Only process add updates that match this timeslot
|
||||||
if(identifierUpdateNotification.getTimeslot() == getTimeslot() &&
|
if(identifierUpdateNotification.getTimeslot() == getTimeslot())
|
||||||
(identifierUpdateNotification.isAdd() || identifierUpdateNotification.isSilentAdd()))
|
|
||||||
{
|
{
|
||||||
addIdentifier(identifierUpdateNotification.getIdentifier());
|
if(identifierUpdateNotification.isAdd() || identifierUpdateNotification.isSilentAdd())
|
||||||
|
{
|
||||||
|
addIdentifier(identifierUpdateNotification.getIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
mIdentifierUpdateNotificationBroadcaster.broadcast(identifierUpdateNotification);
|
mIdentifierUpdateNotificationBroadcaster.broadcast(identifierUpdateNotification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Audio Segment\n Timeslot:").append(getTimeslot()).append("\n");
|
||||||
|
sb.append(" Start Time:").append(new Date(getStartTimestamp()));
|
||||||
|
sb.append(" Linked:").append(isLinked()).append("\n");
|
||||||
|
sb.append(" Recordable:").append(recordAudioProperty().get()).append("\n");
|
||||||
|
sb.append("Do Not Monitor:").append(isDoNotMonitor()).append("\n");
|
||||||
|
sb.append(" Stream To:").append(getBroadcastChannels()).append("\n");
|
||||||
|
sb.append(" Identifiers:\n").append(getIdentifierCollection()).append("\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
import io.github.dsheirer.audio.AudioSegment;
|
import io.github.dsheirer.audio.AudioSegment;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||||
import io.github.dsheirer.record.RecordFormat;
|
import io.github.dsheirer.record.RecordFormat;
|
||||||
@@ -145,8 +146,11 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
|
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
|
||||||
|
IdentifierCollection identifierCollectionCopy =
|
||||||
|
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||||
|
|
||||||
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
|
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
|
||||||
audioSegment.getIdentifierCollection(), audioSegment.getStartTimestamp(), length);
|
identifierCollectionCopy, audioSegment.getStartTimestamp(), length);
|
||||||
mAudioRecordingListener.receive(audioRecording);
|
mAudioRecordingListener.receive(audioRecording);
|
||||||
}
|
}
|
||||||
catch(IOException ioe)
|
catch(IOException ioe)
|
||||||
|
|||||||
@@ -81,15 +81,23 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
MyEventBus.getEventBus().register(this);
|
MyEventBus.getEventBus().register(this);
|
||||||
|
|
||||||
MixerChannelConfiguration configuration = mUserPreferences.getPlaybackPreference().getMixerChannelConfiguration();
|
MixerChannelConfiguration configuration = mUserPreferences.getPlaybackPreference().getMixerChannelConfiguration();
|
||||||
try
|
|
||||||
|
if(configuration != null)
|
||||||
{
|
{
|
||||||
setMixerChannelConfiguration(configuration);
|
try
|
||||||
|
{
|
||||||
|
setMixerChannelConfiguration(configuration);
|
||||||
|
}
|
||||||
|
catch(AudioException ae)
|
||||||
|
{
|
||||||
|
mLog.error("Error during setup of audio playback configuration. Attempted to use audio mixer [" +
|
||||||
|
(configuration != null ? configuration.getMixer().toString() : "null") + "] and channel [" +
|
||||||
|
(configuration != null ? configuration.getMixerChannel().name() : "null") + "]", ae);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(AudioException ae)
|
else
|
||||||
{
|
{
|
||||||
mLog.error("Error during setup of audio playback configuration. Attempted to use audio mixer [" +
|
mLog.warn("No audio output devices available");
|
||||||
(configuration != null ? configuration.getMixer().toString() : "null") + "] and channel [" +
|
|
||||||
(configuration != null ? configuration.getMixerChannel().name() : "null") + "]", ae);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
//Set the proxy as a listener so that echo'd updates are broadcast externally
|
//Set the proxy as a listener so that echo'd updates are broadcast externally
|
||||||
mutableIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
|
mutableIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
|
||||||
|
|
||||||
StateMachine stateMachine = new StateMachine(timeslot);
|
StateMachine stateMachine = new StateMachine(timeslot, State.MULTI_CHANNEL_ACTIVE_STATES);
|
||||||
mStateMachineMap.put(timeslot, stateMachine);
|
mStateMachineMap.put(timeslot, stateMachine);
|
||||||
stateMachine.addListener(this);
|
stateMachine.addListener(this);
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
public void stateChanged(State state, int timeslot)
|
public void stateChanged(State state, int timeslot)
|
||||||
{
|
{
|
||||||
//Broadcast current channel state so that channel rotation monitor can track
|
//Broadcast current channel state so that channel rotation monitor can track
|
||||||
if(state.isActiveState())
|
if(State.MULTI_CHANNEL_ACTIVE_STATES.contains(state))
|
||||||
{
|
{
|
||||||
broadcast(DecoderStateEvent.activeState(timeslot));
|
broadcast(DecoderStateEvent.activeState(timeslot));
|
||||||
}
|
}
|
||||||
@@ -148,9 +148,6 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
|
|
||||||
switch(state)
|
switch(state)
|
||||||
{
|
{
|
||||||
case IDLE:
|
|
||||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
|
||||||
break;
|
|
||||||
case RESET:
|
case RESET:
|
||||||
reset(timeslot);
|
reset(timeslot);
|
||||||
mStateMachineMap.get(timeslot).setState(State.IDLE);
|
mStateMachineMap.get(timeslot).setState(State.IDLE);
|
||||||
@@ -173,19 +170,49 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
*/
|
*/
|
||||||
private void checkTeardown()
|
private void checkTeardown()
|
||||||
{
|
{
|
||||||
boolean teardown = true;
|
boolean teardown = false;
|
||||||
|
boolean active = false;
|
||||||
|
|
||||||
for(StateMachine stateMachine: mStateMachineMap.values())
|
for(StateMachine stateMachine: mStateMachineMap.values())
|
||||||
{
|
{
|
||||||
if(stateMachine.getState().isActiveState())
|
State state = stateMachine.getState();
|
||||||
|
|
||||||
|
//If we have an active state in either timeslot, don't teardown. IDLE is a special state that is active
|
||||||
|
//but doesn't prevent a teardown when the other timeslot is in TEARDOWN
|
||||||
|
if(State.MULTI_CHANNEL_ACTIVE_STATES.contains(state) && state != State.IDLE)
|
||||||
{
|
{
|
||||||
teardown = false;
|
active = true;
|
||||||
|
}
|
||||||
|
else if(state == State.TEARDOWN)
|
||||||
|
{
|
||||||
|
teardown = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(teardown)
|
if(teardown && !active)
|
||||||
{
|
{
|
||||||
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
|
if(mChannel.isTrafficChannel())
|
||||||
|
{
|
||||||
|
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(StateMachine stateMachine: mStateMachineMap.values())
|
||||||
|
{
|
||||||
|
stateMachine.setState(State.RESET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//If one timeslot is teardown but the other is still active, reset the teardown timeslot to IDLE
|
||||||
|
else if(teardown && active)
|
||||||
|
{
|
||||||
|
for(StateMachine stateMachine: mStateMachineMap.values())
|
||||||
|
{
|
||||||
|
if(stateMachine.getState() == State.TEARDOWN)
|
||||||
|
{
|
||||||
|
stateMachine.setState(State.RESET);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,11 +367,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||||
{
|
{
|
||||||
mIdentifierCollectionMap.get(timeslot).broadcastIdentifiers();
|
mIdentifierCollectionMap.get(timeslot).broadcastIdentifiers();
|
||||||
|
mStateMachineMap.get(timeslot).setState(State.RESET);
|
||||||
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
|
|
||||||
{
|
|
||||||
mStateMachineMap.get(timeslot).setState(State.ACTIVE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +499,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
//in an active (ie sync locked) state.
|
//in an active (ie sync locked) state.
|
||||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||||
{
|
{
|
||||||
if(mStateMachineMap.get(timeslot).getState().isActiveState())
|
if(State.MULTI_CHANNEL_ACTIVE_STATES.contains(mStateMachineMap.get(timeslot).getState()))
|
||||||
{
|
{
|
||||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
||||||
mChannel.getChannelType().name()));
|
mChannel.getChannelType().name()));
|
||||||
@@ -511,7 +534,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
case CONTINUATION:
|
case CONTINUATION:
|
||||||
case DECODE:
|
case DECODE:
|
||||||
case START:
|
case START:
|
||||||
if(State.CALL_STATES.contains(event.getState()))
|
if(State.MULTI_CHANNEL_ACTIVE_STATES.contains(event.getState()))
|
||||||
{
|
{
|
||||||
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||||
}
|
}
|
||||||
@@ -519,11 +542,11 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
|||||||
case END:
|
case END:
|
||||||
if(mChannel.isTrafficChannel())
|
if(mChannel.isTrafficChannel())
|
||||||
{
|
{
|
||||||
mStateMachineMap.get(event.getTimeslot()).setState(State.TEARDOWN);
|
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mStateMachineMap.get(event.getTimeslot()).setState(State.FADE);
|
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RESET:
|
case RESET:
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
|||||||
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
|
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
|
||||||
private SourceEventListener mInternalSourceEventListener;
|
private SourceEventListener mInternalSourceEventListener;
|
||||||
private ChannelMetadata mChannelMetadata;
|
private ChannelMetadata mChannelMetadata;
|
||||||
private StateMachine mStateMachine = new StateMachine(0);
|
private StateMachine mStateMachine = new StateMachine(0, State.SINGLE_CHANNEL_ACTIVE_STATES);
|
||||||
private StateMonitoringSquelchController mSquelchController = new StateMonitoringSquelchController(0);
|
private StateMonitoringSquelchController mSquelchController = new StateMonitoringSquelchController(0);
|
||||||
|
|
||||||
public SingleChannelState(Channel channel, AliasModel aliasModel)
|
public SingleChannelState(Channel channel, AliasModel aliasModel)
|
||||||
@@ -123,7 +123,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
|||||||
public void stateChanged(State state, int timeslot)
|
public void stateChanged(State state, int timeslot)
|
||||||
{
|
{
|
||||||
//Broadcast current channel state so that channel rotation monitor can track
|
//Broadcast current channel state so that channel rotation monitor can track
|
||||||
if(state.isActiveState())
|
if(State.SINGLE_CHANNEL_ACTIVE_STATES.contains(state))
|
||||||
{
|
{
|
||||||
broadcast(DecoderStateEvent.activeState(timeslot));
|
broadcast(DecoderStateEvent.activeState(timeslot));
|
||||||
}
|
}
|
||||||
@@ -377,7 +377,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
|||||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||||
//Rebroadcast frequency error measurements to external listener if we're currently
|
//Rebroadcast frequency error measurements to external listener if we're currently
|
||||||
//in an active (ie sync locked) state.
|
//in an active (ie sync locked) state.
|
||||||
if(mStateMachine.getState().isActiveState())
|
if(State.SINGLE_CHANNEL_ACTIVE_STATES.contains(mStateMachine.getState()))
|
||||||
{
|
{
|
||||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
||||||
mChannel.getChannelType().name()));
|
mChannel.getChannelType().name()));
|
||||||
@@ -411,7 +411,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
|||||||
case CONTINUATION:
|
case CONTINUATION:
|
||||||
case DECODE:
|
case DECODE:
|
||||||
case START:
|
case START:
|
||||||
if(State.CALL_STATES.contains(event.getState()))
|
if(State.SINGLE_CHANNEL_ACTIVE_STATES.contains(event.getState()))
|
||||||
{
|
{
|
||||||
mStateMachine.setState(event.getState());
|
mStateMachine.setState(event.getState());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public enum State
|
|||||||
state == DATA ||
|
state == DATA ||
|
||||||
state == ENCRYPTED ||
|
state == ENCRYPTED ||
|
||||||
state == FADE ||
|
state == FADE ||
|
||||||
|
state == IDLE ||
|
||||||
state == TEARDOWN ||
|
state == TEARDOWN ||
|
||||||
state == RESET;
|
state == RESET;
|
||||||
}
|
}
|
||||||
@@ -60,6 +61,7 @@ public enum State
|
|||||||
state == DATA ||
|
state == DATA ||
|
||||||
state == ENCRYPTED ||
|
state == ENCRYPTED ||
|
||||||
state == FADE ||
|
state == FADE ||
|
||||||
|
state == IDLE ||
|
||||||
state == TEARDOWN ||
|
state == TEARDOWN ||
|
||||||
state == RESET;
|
state == RESET;
|
||||||
}
|
}
|
||||||
@@ -121,7 +123,8 @@ public enum State
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Indicates the channel is idle and not actively decoding any messages
|
* Indicates the channel is idle and not actively decoding any messages or in a multiple timeslot decoder this
|
||||||
|
* state reflects active decoding that indicates the timeslot is idle.
|
||||||
*/
|
*/
|
||||||
IDLE("IDLE")
|
IDLE("IDLE")
|
||||||
{
|
{
|
||||||
@@ -159,8 +162,8 @@ public enum State
|
|||||||
|
|
||||||
private String mDisplayValue;
|
private String mDisplayValue;
|
||||||
|
|
||||||
public static final EnumSet<State> CALL_STATES = EnumSet.of(ACTIVE, CALL, CONTROL, DATA, ENCRYPTED);
|
public static final EnumSet<State> SINGLE_CHANNEL_ACTIVE_STATES = EnumSet.of(ACTIVE, CALL, CONTROL, DATA, ENCRYPTED);
|
||||||
public static final EnumSet<State> IDLE_STATES = EnumSet.of(IDLE, FADE);
|
public static final EnumSet<State> MULTI_CHANNEL_ACTIVE_STATES = EnumSet.of(ACTIVE, CALL, CONTROL, DATA, ENCRYPTED, IDLE);
|
||||||
|
|
||||||
private State(String displayValue)
|
private State(String displayValue)
|
||||||
{
|
{
|
||||||
@@ -169,17 +172,6 @@ public enum State
|
|||||||
|
|
||||||
public abstract boolean canChangeTo(State state);
|
public abstract boolean canChangeTo(State state);
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that this state is an active (ie decoding) state and is one of the states
|
|
||||||
* enumerated in the CALL_STATES enumeration set.
|
|
||||||
*
|
|
||||||
* @return true if this state is an active state
|
|
||||||
*/
|
|
||||||
public boolean isActiveState()
|
|
||||||
{
|
|
||||||
return CALL_STATES.contains(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayValue()
|
public String getDisplayValue()
|
||||||
{
|
{
|
||||||
return mDisplayValue;
|
return mDisplayValue;
|
||||||
|
|||||||
@@ -30,8 +30,12 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State machine for tracking a channel state.
|
||||||
|
*/
|
||||||
public class StateMachine
|
public class StateMachine
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(StateMachine.class);
|
private final static Logger mLog = LoggerFactory.getLogger(StateMachine.class);
|
||||||
@@ -42,13 +46,21 @@ public class StateMachine
|
|||||||
protected long mEndTimeout;
|
protected long mEndTimeout;
|
||||||
protected long mEndTimeoutBuffer = 0;
|
protected long mEndTimeoutBuffer = 0;
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
|
private EnumSet<State> mActiveStates;
|
||||||
private Channel.ChannelType mChannelType = Channel.ChannelType.STANDARD;
|
private Channel.ChannelType mChannelType = Channel.ChannelType.STANDARD;
|
||||||
private List<IStateMachineListener> mStateMachineListeners = new ArrayList<>();
|
private List<IStateMachineListener> mStateMachineListeners = new ArrayList<>();
|
||||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateListener;
|
private Listener<IdentifierUpdateNotification> mIdentifierUpdateListener;
|
||||||
|
|
||||||
public StateMachine(int timeslot)
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param timeslot for this state machine
|
||||||
|
* @param activeStates set of states considered active for updating the fade timeout
|
||||||
|
*/
|
||||||
|
public StateMachine(int timeslot, EnumSet<State> activeStates)
|
||||||
{
|
{
|
||||||
mTimeslot = timeslot;
|
mTimeslot = timeslot;
|
||||||
|
mActiveStates = activeStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(IStateMachineListener listener)
|
public void addListener(IStateMachineListener listener)
|
||||||
@@ -73,7 +85,7 @@ public class StateMachine
|
|||||||
|
|
||||||
public void checkState()
|
public void checkState()
|
||||||
{
|
{
|
||||||
if(mState.isActiveState() && mFadeTimeout <= System.currentTimeMillis())
|
if(mActiveStates.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
|
||||||
{
|
{
|
||||||
setState(State.FADE);
|
setState(State.FADE);
|
||||||
}
|
}
|
||||||
@@ -92,18 +104,22 @@ public class StateMachine
|
|||||||
{
|
{
|
||||||
if(state == mState)
|
if(state == mState)
|
||||||
{
|
{
|
||||||
if(State.CALL_STATES.contains(state))
|
if(mActiveStates.contains(state))
|
||||||
{
|
{
|
||||||
updateFadeTimeout();
|
updateFadeTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(mState.canChangeTo(state))
|
else if(mState.canChangeTo(state))
|
||||||
{
|
{
|
||||||
|
if(mActiveStates.contains(state))
|
||||||
|
{
|
||||||
|
updateFadeTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
switch(state)
|
switch(state)
|
||||||
{
|
{
|
||||||
case ACTIVE:
|
case ACTIVE:
|
||||||
mState = state;
|
mState = state;
|
||||||
updateFadeTimeout();
|
|
||||||
broadcast(ChannelStateIdentifier.ACTIVE);
|
broadcast(ChannelStateIdentifier.ACTIVE);
|
||||||
break;
|
break;
|
||||||
case CONTROL:
|
case CONTROL:
|
||||||
@@ -111,23 +127,19 @@ public class StateMachine
|
|||||||
if(mChannelType == Channel.ChannelType.STANDARD)
|
if(mChannelType == Channel.ChannelType.STANDARD)
|
||||||
{
|
{
|
||||||
mState = state;
|
mState = state;
|
||||||
updateFadeTimeout();
|
|
||||||
broadcast(ChannelStateIdentifier.CONTROL);
|
broadcast(ChannelStateIdentifier.CONTROL);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DATA:
|
case DATA:
|
||||||
mState = state;
|
mState = state;
|
||||||
updateFadeTimeout();
|
|
||||||
broadcast(ChannelStateIdentifier.DATA);
|
broadcast(ChannelStateIdentifier.DATA);
|
||||||
break;
|
break;
|
||||||
case ENCRYPTED:
|
case ENCRYPTED:
|
||||||
mState = state;
|
mState = state;
|
||||||
updateFadeTimeout();
|
|
||||||
broadcast(ChannelStateIdentifier.ENCRYPTED);
|
broadcast(ChannelStateIdentifier.ENCRYPTED);
|
||||||
break;
|
break;
|
||||||
case CALL:
|
case CALL:
|
||||||
mState = state;
|
mState = state;
|
||||||
updateFadeTimeout();
|
|
||||||
broadcast(ChannelStateIdentifier.CALL);
|
broadcast(ChannelStateIdentifier.CALL);
|
||||||
break;
|
break;
|
||||||
case FADE:
|
case FADE:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.jtransforms.fft.FloatFFT_1D;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -62,7 +63,7 @@ import java.util.List;
|
|||||||
public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChannelizer
|
public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChannelizer
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
|
||||||
|
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
||||||
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
|
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
|
||||||
|
|
||||||
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
|
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
|
||||||
@@ -154,7 +155,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
channels--;
|
channels--;
|
||||||
}
|
}
|
||||||
|
|
||||||
mLog.debug("Sample Rate [" + sampleRate + "] providing [" + channels + "] channels at [" + (sampleRate / (double)channels) + "] Hz each");
|
mLog.info("Sample Rate [" + DECIMAL_FORMAT.format(sampleRate) + "] providing [" + channels +
|
||||||
|
"] channels at [" + DECIMAL_FORMAT.format(sampleRate / (double)channels) + "] Hz each");
|
||||||
|
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 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 io.github.dsheirer.identifier.tone;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import io.github.dsheirer.identifier.Form;
|
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
|
||||||
import io.github.dsheirer.identifier.Role;
|
|
||||||
import io.github.dsheirer.identifier.string.StringListIdentifier;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call Progress (ie dial tone, busy tone) tones identifier
|
|
||||||
*/
|
|
||||||
public abstract class CallProgressIdentifier extends StringListIdentifier
|
|
||||||
{
|
|
||||||
public CallProgressIdentifier(List<String> values)
|
|
||||||
{
|
|
||||||
super(values, IdentifierClass.USER, Form.CALL_PROGRESS_TONE, Role.TO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "CALL:" + Joiner.on("").join(getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 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 io.github.dsheirer.identifier.tone;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import io.github.dsheirer.identifier.Form;
|
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
|
||||||
import io.github.dsheirer.identifier.Role;
|
|
||||||
import io.github.dsheirer.identifier.string.StringListIdentifier;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dual-Tone Multi-Frequency (DTMF) tones identifier
|
|
||||||
*/
|
|
||||||
public abstract class DtmfIdentifier extends StringListIdentifier
|
|
||||||
{
|
|
||||||
public DtmfIdentifier(List<String> values)
|
|
||||||
{
|
|
||||||
super(values, IdentifierClass.USER, Form.DTMF, Role.TO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "DTMF:" + Joiner.on("").join(getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 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 io.github.dsheirer.identifier.tone;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import io.github.dsheirer.identifier.Form;
|
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
|
||||||
import io.github.dsheirer.identifier.Role;
|
|
||||||
import io.github.dsheirer.identifier.string.StringListIdentifier;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Knox tones identifier
|
|
||||||
*/
|
|
||||||
public abstract class KnoxIdentifier extends StringListIdentifier
|
|
||||||
{
|
|
||||||
public KnoxIdentifier(List<String> values)
|
|
||||||
{
|
|
||||||
super(values, IdentifierClass.USER, Form.KNOX_TONE, Role.TO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "KNOX:" + Joiner.on("").join(getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 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 io.github.dsheirer.identifier.tone;
|
|
||||||
|
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class P25CallProgressIdentifier extends CallProgressIdentifier
|
|
||||||
{
|
|
||||||
public P25CallProgressIdentifier(List<String> values)
|
|
||||||
{
|
|
||||||
super(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Protocol getProtocol()
|
|
||||||
{
|
|
||||||
return Protocol.APCO25;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static P25CallProgressIdentifier create(List<String> values)
|
|
||||||
{
|
|
||||||
return new P25CallProgressIdentifier(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 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 io.github.dsheirer.identifier.tone;
|
|
||||||
|
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class P25DtmfIdentifier extends DtmfIdentifier
|
|
||||||
{
|
|
||||||
public P25DtmfIdentifier(List<String> values)
|
|
||||||
{
|
|
||||||
super(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Protocol getProtocol()
|
|
||||||
{
|
|
||||||
return Protocol.APCO25;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static P25DtmfIdentifier create(List<String> values)
|
|
||||||
{
|
|
||||||
return new P25DtmfIdentifier(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 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 io.github.dsheirer.identifier.tone;
|
|
||||||
|
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class P25KnoxIdentifier extends KnoxIdentifier
|
|
||||||
{
|
|
||||||
public P25KnoxIdentifier(List<String> values)
|
|
||||||
{
|
|
||||||
super(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Protocol getProtocol()
|
|
||||||
{
|
|
||||||
return Protocol.APCO25;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static P25KnoxIdentifier create(List<String> values)
|
|
||||||
{
|
|
||||||
return new P25KnoxIdentifier(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,11 +26,14 @@ import io.github.dsheirer.protocol.Protocol;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P25 Phase II AMBE audio codec tone metadata
|
||||||
|
*/
|
||||||
public class P25ToneIdentifier extends ToneIdentifier
|
public class P25ToneIdentifier extends ToneIdentifier
|
||||||
{
|
{
|
||||||
public P25ToneIdentifier(List<String> values)
|
public P25ToneIdentifier(String value)
|
||||||
{
|
{
|
||||||
super(values);
|
super(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,8 +42,8 @@ public class P25ToneIdentifier extends ToneIdentifier
|
|||||||
return Protocol.APCO25;
|
return Protocol.APCO25;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static P25ToneIdentifier create(List<String> values)
|
public static P25ToneIdentifier create(String value)
|
||||||
{
|
{
|
||||||
return new P25ToneIdentifier(values);
|
return new P25ToneIdentifier(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,27 +22,18 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.identifier.tone;
|
package io.github.dsheirer.identifier.tone;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import io.github.dsheirer.identifier.Form;
|
import io.github.dsheirer.identifier.Form;
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
import io.github.dsheirer.identifier.string.StringListIdentifier;
|
import io.github.dsheirer.identifier.string.StringIdentifier;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass-band audio tones identifier
|
* Pass-band audio tones identifier
|
||||||
*/
|
*/
|
||||||
public abstract class ToneIdentifier extends StringListIdentifier
|
public abstract class ToneIdentifier extends StringIdentifier
|
||||||
{
|
{
|
||||||
public ToneIdentifier(List<String> values)
|
public ToneIdentifier(String value)
|
||||||
{
|
{
|
||||||
super(values, IdentifierClass.USER, Form.TONE, Role.TO);
|
super(value, IdentifierClass.USER, Form.TONE, Role.FROM);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "TONES:" + Joiner.on("").join(getValue());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import io.github.dsheirer.gui.editor.EditorValidationException;
|
|||||||
import io.github.dsheirer.gui.editor.ValidatingEditor;
|
import io.github.dsheirer.gui.editor.ValidatingEditor;
|
||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.DefaultComboBoxModel;
|
import javax.swing.DefaultComboBoxModel;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
@@ -41,10 +43,13 @@ import javax.swing.event.ChangeListener;
|
|||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MPT1327DecoderEditor extends ValidatingEditor<Channel>
|
public class MPT1327DecoderEditor extends ValidatingEditor<Channel>
|
||||||
{
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(MPT1327DecoderEditor.class);
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private JComboBox<ChannelMap> mComboChannelMaps;
|
private JComboBox<ChannelMap> mComboChannelMaps;
|
||||||
@@ -65,12 +70,17 @@ public class MPT1327DecoderEditor extends ValidatingEditor<Channel>
|
|||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0,wrap 4", "[right][grow,fill][right][grow,fill]", ""));
|
setLayout(new MigLayout("insets 0 0 0 0,wrap 4", "[right][grow,fill][right][grow,fill]", ""));
|
||||||
|
|
||||||
List<ChannelMap> maps = mChannelMapModel.getChannelMaps();
|
|
||||||
ChannelMap[] mapArray = maps.toArray(new ChannelMap[maps.size()]);
|
|
||||||
|
|
||||||
mComboChannelMaps = new JComboBox<ChannelMap>();
|
mComboChannelMaps = new JComboBox<ChannelMap>();
|
||||||
mComboChannelMaps.setModel(new DefaultComboBoxModel<ChannelMap>(mapArray));
|
refreshChannelMaps();
|
||||||
mComboChannelMaps.setEnabled(false);
|
mComboChannelMaps.setEnabled(false);
|
||||||
|
mComboChannelMaps.addActionListener(new ActionListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
setModified(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
add(new JLabel("Channel Map:"));
|
add(new JLabel("Channel Map:"));
|
||||||
add(mComboChannelMaps);
|
add(mComboChannelMaps);
|
||||||
|
|
||||||
@@ -80,8 +90,17 @@ public class MPT1327DecoderEditor extends ValidatingEditor<Channel>
|
|||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e)
|
public void actionPerformed(ActionEvent e)
|
||||||
{
|
{
|
||||||
final ChannelMapManagerFrame manager =
|
final ChannelMapManagerFrame manager = new ChannelMapManagerFrame(mChannelMapModel);
|
||||||
new ChannelMapManagerFrame(mChannelMapModel);
|
manager.addWindowListener(new WindowAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void windowClosed(WindowEvent e)
|
||||||
|
{
|
||||||
|
super.windowClosed(e);
|
||||||
|
mLog.debug("Refreshing channel maps combo box");
|
||||||
|
refreshChannelMaps();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
EventQueue.invokeLater(new Runnable()
|
EventQueue.invokeLater(new Runnable()
|
||||||
{
|
{
|
||||||
@@ -139,6 +158,22 @@ public class MPT1327DecoderEditor extends ValidatingEditor<Channel>
|
|||||||
add(mCallTimeout);
|
add(mCallTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshChannelMaps()
|
||||||
|
{
|
||||||
|
List<ChannelMap> maps = mChannelMapModel.getChannelMaps();
|
||||||
|
|
||||||
|
ChannelMap[] mapArray = new ChannelMap[maps.size() + 1];
|
||||||
|
|
||||||
|
int index = 1;
|
||||||
|
|
||||||
|
for(ChannelMap channelMap: maps)
|
||||||
|
{
|
||||||
|
mapArray[index++] = channelMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
mComboChannelMaps.setModel(new DefaultComboBoxModel<ChannelMap>(mapArray));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save()
|
public void save()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -280,7 +280,13 @@ public class MPT1327DecoderState extends DecoderState
|
|||||||
@Override
|
@Override
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
|
//Send call start event for traffic channels to unsquelch the audio. Decoded return to channel message
|
||||||
|
//or fade timeout expire will end the call event.
|
||||||
|
if(mChannelType == ChannelType.TRAFFIC)
|
||||||
|
{
|
||||||
|
//Broadcast a start call event so the squelch kicks on and the audio path opens
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.START, State.CALL));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -572,7 +572,7 @@ public class P25TrafficChannelManager extends Module implements IDecodeEventProv
|
|||||||
|
|
||||||
if(type == null)
|
if(type == null)
|
||||||
{
|
{
|
||||||
mLog.debug("Unrecognized opcode for determining decode event type: " + opcode.name());
|
mLog.error("Unrecognized opcode for determining decode event type: " + opcode.name());
|
||||||
type = DecodeEventType.CALL;
|
type = DecodeEventType.CALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,7 +697,7 @@ public class P25TrafficChannelManager extends Module implements IDecodeEventProv
|
|||||||
//Issue a disable request for each traffic channel
|
//Issue a disable request for each traffic channel
|
||||||
for(Channel channel: channels)
|
for(Channel channel: channels)
|
||||||
{
|
{
|
||||||
mLog.debug("Stopping traffic channel: " + channel);
|
mLog.info("Stopping traffic channel: " + channel);
|
||||||
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
|
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.audio;
|
package io.github.dsheirer.module.decode.p25.audio;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.audio.codec.mbe.AmbeAudioModule;
|
import io.github.dsheirer.audio.codec.mbe.AmbeAudioModule;
|
||||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||||
@@ -27,9 +28,7 @@ import io.github.dsheirer.bits.BinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
|
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
|
||||||
import io.github.dsheirer.identifier.tone.P25CallProgressIdentifier;
|
import io.github.dsheirer.identifier.Role;
|
||||||
import io.github.dsheirer.identifier.tone.P25DtmfIdentifier;
|
|
||||||
import io.github.dsheirer.identifier.tone.P25KnoxIdentifier;
|
|
||||||
import io.github.dsheirer.identifier.tone.P25ToneIdentifier;
|
import io.github.dsheirer.identifier.tone.P25ToneIdentifier;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
||||||
@@ -51,17 +50,9 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(P25P2AudioModule.class);
|
private final static Logger mLog = LoggerFactory.getLogger(P25P2AudioModule.class);
|
||||||
|
|
||||||
private static final String METADATA_TYPE_DTMF = "DTMF";
|
|
||||||
private static final String METADATA_TYPE_KNOX = "KNOX";
|
|
||||||
private static final String METADATA_TYPE_TONE = "TONE";
|
|
||||||
private static final String METADATA_TYPE_CALL_PROGRESS = "CALL PROGRESS";
|
|
||||||
|
|
||||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
|
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
|
||||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||||
private MetadataProcessor mDtmfMetadataProcessor;
|
private ToneMetadataProcessor mToneMetadataProcessor = new ToneMetadataProcessor();
|
||||||
private MetadataProcessor mKnoxMetadataProcessor;
|
|
||||||
private MetadataProcessor mToneMetadataProcessor;
|
|
||||||
private MetadataProcessor mCallProcessMetadataProcessor;
|
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
private Queue<AbstractVoiceTimeslot> mQueuedAudioTimeslots = new ArrayDeque<>();
|
private Queue<AbstractVoiceTimeslot> mQueuedAudioTimeslots = new ArrayDeque<>();
|
||||||
private boolean mEncryptedCallStateEstablished = false;
|
private boolean mEncryptedCallStateEstablished = false;
|
||||||
@@ -92,10 +83,10 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
mCallProcessMetadataProcessor = null;
|
//Explicitly clear FROM identifiers to ensure previous call TONE identifiers are cleared.
|
||||||
mDtmfMetadataProcessor = null;
|
mIdentifierCollection.remove(Role.FROM);
|
||||||
mKnoxMetadataProcessor = null;
|
|
||||||
mToneMetadataProcessor = null;
|
mToneMetadataProcessor.reset();
|
||||||
mQueuedAudioTimeslots.clear();
|
mQueuedAudioTimeslots.clear();
|
||||||
|
|
||||||
//Reset encrypted call handling flags
|
//Reset encrypted call handling flags
|
||||||
@@ -179,107 +170,49 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
{
|
{
|
||||||
byte[] voiceFrameBytes = voiceFrame.getBytes();
|
byte[] voiceFrameBytes = voiceFrame.getBytes();
|
||||||
|
|
||||||
IAudioWithMetadata audioWithMetadata = getAudioCodec().getAudioWithMetadata(voiceFrameBytes);
|
try
|
||||||
processMetadata(audioWithMetadata);
|
{
|
||||||
addAudio(audioWithMetadata.getAudio());
|
IAudioWithMetadata audioWithMetadata = getAudioCodec().getAudioWithMetadata(voiceFrameBytes);
|
||||||
|
addAudio(audioWithMetadata.getAudio());
|
||||||
|
processMetadata(audioWithMetadata);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error synthesizing AMBE audio - continuing [" + e.getLocalizedMessage() + "]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes optional metadata that can be included with decoded audio (ie dtmf, tones, knox, etc.)
|
* Processes optional metadata that can be included with decoded audio (ie dtmf, tones, knox, etc.) so that the
|
||||||
|
* tone metadata can be converted into a FROM identifier and included with any call segment.
|
||||||
*/
|
*/
|
||||||
private void processMetadata(IAudioWithMetadata audioWithMetadata)
|
private void processMetadata(IAudioWithMetadata audioWithMetadata)
|
||||||
{
|
{
|
||||||
if(mIdentifierUpdateNotificationListener != null)
|
if(audioWithMetadata.hasMetadata())
|
||||||
{
|
{
|
||||||
if(audioWithMetadata.hasMetadata())
|
//JMBE only places 1 entry in the map, but for consistency we'll process the map entry set
|
||||||
|
for(Map.Entry<String,String> entry: audioWithMetadata.getMetadata().entrySet())
|
||||||
{
|
{
|
||||||
Map<String,String> metadata = audioWithMetadata.getMetadata();
|
//Each metadata map entry contains a tone-type (key) and tone (value)
|
||||||
|
Identifier metadataIdentifier = mToneMetadataProcessor.process(entry.getKey(), entry.getValue());
|
||||||
|
|
||||||
if(metadata.containsKey(METADATA_TYPE_DTMF))
|
if(metadataIdentifier != null)
|
||||||
{
|
{
|
||||||
String dtmf = metadata.get(METADATA_TYPE_DTMF);
|
broadcast(metadataIdentifier);
|
||||||
|
|
||||||
if(dtmf != null)
|
|
||||||
{
|
|
||||||
if(mDtmfMetadataProcessor == null)
|
|
||||||
{
|
|
||||||
mDtmfMetadataProcessor = new MetadataProcessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> dtmfTones = mDtmfMetadataProcessor.process(dtmf);
|
|
||||||
broadcast(P25DtmfIdentifier.create(dtmfTones));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(metadata.containsKey(METADATA_TYPE_KNOX))
|
|
||||||
{
|
|
||||||
String knox = metadata.get(METADATA_TYPE_KNOX);
|
|
||||||
|
|
||||||
if(knox != null)
|
|
||||||
{
|
|
||||||
if(mKnoxMetadataProcessor == null)
|
|
||||||
{
|
|
||||||
mKnoxMetadataProcessor = new MetadataProcessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> knoxTones = mKnoxMetadataProcessor.process(knox);
|
|
||||||
broadcast(P25KnoxIdentifier.create(knoxTones));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(metadata.containsKey(METADATA_TYPE_TONE))
|
|
||||||
{
|
|
||||||
String tone = metadata.get(METADATA_TYPE_TONE);
|
|
||||||
|
|
||||||
if(tone != null)
|
|
||||||
{
|
|
||||||
if(mToneMetadataProcessor == null)
|
|
||||||
{
|
|
||||||
mToneMetadataProcessor = new MetadataProcessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> tones = mToneMetadataProcessor.process(tone);
|
|
||||||
broadcast(P25ToneIdentifier.create(tones));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(metadata.containsKey(METADATA_TYPE_CALL_PROGRESS))
|
|
||||||
{
|
|
||||||
String callProgressTone = metadata.get(METADATA_TYPE_CALL_PROGRESS);
|
|
||||||
|
|
||||||
if(callProgressTone != null)
|
|
||||||
{
|
|
||||||
if(mCallProcessMetadataProcessor == null)
|
|
||||||
{
|
|
||||||
mCallProcessMetadataProcessor = new MetadataProcessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> tones = mCallProcessMetadataProcessor.process(callProgressTone);
|
|
||||||
broadcast(P25CallProgressIdentifier.create(tones));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(mDtmfMetadataProcessor != null)
|
|
||||||
{
|
|
||||||
mDtmfMetadataProcessor.noMetadata();
|
|
||||||
}
|
|
||||||
if(mKnoxMetadataProcessor != null)
|
|
||||||
{
|
|
||||||
mKnoxMetadataProcessor.noMetadata();
|
|
||||||
}
|
|
||||||
if(mCallProcessMetadataProcessor != null)
|
|
||||||
{
|
|
||||||
mCallProcessMetadataProcessor.noMetadata();
|
|
||||||
}
|
|
||||||
if(mToneMetadataProcessor != null)
|
|
||||||
{
|
|
||||||
mToneMetadataProcessor.noMetadata();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mToneMetadataProcessor.closeMetadata();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts the identifier to a registered listener
|
||||||
|
*/
|
||||||
private void broadcast(Identifier identifier)
|
private void broadcast(Identifier identifier)
|
||||||
{
|
{
|
||||||
if(mIdentifierUpdateNotificationListener != null)
|
if(mIdentifierUpdateNotificationListener != null)
|
||||||
@@ -289,12 +222,18 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive identifier updates
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
|
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
|
||||||
{
|
{
|
||||||
mIdentifierUpdateNotificationListener = listener;
|
mIdentifierUpdateNotificationListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a listener from receiving identifier updates
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void removeIdentifierUpdateListener()
|
public void removeIdentifierUpdateListener()
|
||||||
{
|
{
|
||||||
@@ -302,40 +241,134 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes metadata string values to provide a de-duplicated list of metadata values.
|
* Process AMBE audio frame tone metadata. Tracks the count of sequential frames containing tone metadata to
|
||||||
*
|
* provide a list of each unique tone and a time duration (milliseconds) for the tone. Tones are concatenated into
|
||||||
* This is necessary for metadata such as DTMF where a tone can span multiple 20ms voice frames but is intended
|
* a comma separated list and included as call segment metadata.
|
||||||
* to be represented as a single tone. An empty (no-metadata) frame causes the repeat checker to stop and allow
|
|
||||||
* the next metadata value to be added to the list.
|
|
||||||
*/
|
*/
|
||||||
public class MetadataProcessor
|
public class ToneMetadataProcessor
|
||||||
{
|
{
|
||||||
private List<String> mValues = new ArrayList<>();
|
private List<ToneMetadata> mToneMetadata = new ArrayList<>();
|
||||||
private String mLastInput;
|
private ToneMetadata mCurrentToneMetadata;
|
||||||
|
|
||||||
public List<String> process(String value)
|
/**
|
||||||
|
* Resets or clears any accumulated call tone metadata to prepare for the next call.
|
||||||
|
*/
|
||||||
|
public void reset()
|
||||||
{
|
{
|
||||||
if(value != null)
|
mToneMetadata.clear();
|
||||||
{
|
|
||||||
if(mLastInput != null && mLastInput.equalsIgnoreCase(value))
|
|
||||||
{
|
|
||||||
//Suppress the repeat and return the current value
|
|
||||||
return mValues;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLastInput = value;
|
|
||||||
mValues.add(value);
|
|
||||||
return mValues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mValues;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void noMetadata()
|
/**
|
||||||
|
* Process the tone metadata
|
||||||
|
* @param type of tone
|
||||||
|
* @param value of tone
|
||||||
|
* @return an identifier with the accumulated tone metadata set
|
||||||
|
*/
|
||||||
|
public Identifier process(String type, String value)
|
||||||
{
|
{
|
||||||
mLastInput = null;
|
if(type == null || value == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mCurrentToneMetadata != null && mCurrentToneMetadata.matches(type, value))
|
||||||
|
{
|
||||||
|
mCurrentToneMetadata.incrementCount();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mCurrentToneMetadata = new ToneMetadata(type, value);
|
||||||
|
mToneMetadata.add(mCurrentToneMetadata);
|
||||||
|
mCurrentToneMetadata.incrementCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return P25ToneIdentifier.create(Joiner.on(",").join(mToneMetadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes current tone metadata when there is no metadata for the current audio frame.
|
||||||
|
*/
|
||||||
|
public void closeMetadata()
|
||||||
|
{
|
||||||
|
mCurrentToneMetadata = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata about the tone type being transmitted and the duration.
|
||||||
|
*
|
||||||
|
* Note: each AMBE frame is 20 milliseconds in duration, so total duration is the count of 20 ms tone frames
|
||||||
|
* of the same metadata type.
|
||||||
|
*/
|
||||||
|
public class ToneMetadata
|
||||||
|
{
|
||||||
|
private String mType;
|
||||||
|
private String mValue;
|
||||||
|
private int mCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param type of tone
|
||||||
|
* @param value of the tone
|
||||||
|
*/
|
||||||
|
public ToneMetadata(String type, String value)
|
||||||
|
{
|
||||||
|
mType = type;
|
||||||
|
mValue = value;
|
||||||
|
mCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this tone metadata matches the tone metadata represented by the arguments
|
||||||
|
* @param type of tone
|
||||||
|
* @param value of tone
|
||||||
|
* @return true if they match
|
||||||
|
*/
|
||||||
|
public boolean matches(String type, String value)
|
||||||
|
{
|
||||||
|
return type != null && value != null && type.matches(mType) && value.matches(mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of tone
|
||||||
|
*/
|
||||||
|
public String getType()
|
||||||
|
{
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of tone
|
||||||
|
*/
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of times this tone has occurred sequentially
|
||||||
|
*/
|
||||||
|
public int getCount()
|
||||||
|
{
|
||||||
|
return mCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the count of the number of sequential frames that contained the tone
|
||||||
|
*/
|
||||||
|
public void incrementCount()
|
||||||
|
{
|
||||||
|
mCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(mType);
|
||||||
|
sb.append(" ").append(mValue);
|
||||||
|
sb.append(" (").append(mCount * 20).append("ms)");
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,5 +392,4 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,25 +189,40 @@ public class P25Channel implements IChannelDescriptor
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
int downlinkChannelNumber = getDownlinkChannelNumber();
|
||||||
|
|
||||||
|
if(isTDMAChannel())
|
||||||
|
{
|
||||||
|
downlinkChannelNumber -= downlinkChannelNumber % getTimeslotCount();
|
||||||
|
}
|
||||||
|
|
||||||
if(getDownlinkBandIdentifier() == getUplinkBandIdentifier() && getDownlinkChannelNumber() == getUplinkChannelNumber())
|
if(getDownlinkBandIdentifier() == getUplinkBandIdentifier() && getDownlinkChannelNumber() == getUplinkChannelNumber())
|
||||||
{
|
{
|
||||||
sb.append(getDownlinkBandIdentifier()).append("-").append(getDownlinkLogicalChannelNumber());
|
sb.append(getDownlinkBandIdentifier()).append("-").append(downlinkChannelNumber);
|
||||||
}
|
}
|
||||||
else if(hasUplinkChannel())
|
else if(hasUplinkChannel())
|
||||||
{
|
{
|
||||||
|
|
||||||
|
int uplinkChannelNumber = getUplinkChannelNumber();
|
||||||
|
|
||||||
|
if(isTDMAChannel())
|
||||||
|
{
|
||||||
|
uplinkChannelNumber -= uplinkChannelNumber % getTimeslotCount();
|
||||||
|
}
|
||||||
|
|
||||||
sb.append(getDownlinkBandIdentifier())
|
sb.append(getDownlinkBandIdentifier())
|
||||||
.append("-")
|
.append("-")
|
||||||
.append(getDownlinkLogicalChannelNumber())
|
.append(downlinkChannelNumber)
|
||||||
.append("/")
|
.append("/")
|
||||||
.append(getUplinkBandIdentifier())
|
.append(getUplinkBandIdentifier())
|
||||||
.append("-")
|
.append("-")
|
||||||
.append(getUplinkLogicalChannelNumber());
|
.append(uplinkChannelNumber);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.append(getDownlinkBandIdentifier())
|
sb.append(getDownlinkBandIdentifier())
|
||||||
.append("-")
|
.append("-")
|
||||||
.append(getDownlinkLogicalChannelNumber())
|
.append(downlinkChannelNumber)
|
||||||
.append("/-----");
|
.append("/-----");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -94,7 +94,7 @@ public class LCMotorolaTalkComplete extends LinkControlWord
|
|||||||
{
|
{
|
||||||
if(mAddress == null)
|
if(mAddress == null)
|
||||||
{
|
{
|
||||||
mAddress = APCO25Talkgroup.create(getMessage().getInt(ADDRESS));
|
mAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(ADDRESS));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mAddress;
|
return mAddress;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import io.github.dsheirer.module.decode.event.DecodeEventType;
|
|||||||
import io.github.dsheirer.module.decode.p25.P25DecodeEvent;
|
import io.github.dsheirer.module.decode.p25.P25DecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
|
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacPduType;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacPduType;
|
||||||
@@ -102,6 +103,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
private PatchGroupManager mPatchGroupManager = new PatchGroupManager();
|
private PatchGroupManager mPatchGroupManager = new PatchGroupManager();
|
||||||
private P25P2NetworkConfigurationMonitor mNetworkConfigurationMonitor = new P25P2NetworkConfigurationMonitor();
|
private P25P2NetworkConfigurationMonitor mNetworkConfigurationMonitor = new P25P2NetworkConfigurationMonitor();
|
||||||
private DecodeEvent mCurrentCallEvent;
|
private DecodeEvent mCurrentCallEvent;
|
||||||
|
private int mEndPttOnFacchCounter = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an APCO-25 decoder state for a traffic channel.
|
* Constructs an APCO-25 decoder state for a traffic channel.
|
||||||
@@ -138,7 +140,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
protected void resetState()
|
protected void resetState()
|
||||||
{
|
{
|
||||||
super.resetState();
|
super.resetState();
|
||||||
closeCurrentCallEvent(System.currentTimeMillis(), true);
|
closeCurrentCallEvent(System.currentTimeMillis(), true, MacPduType.MAC_3_IDLE);
|
||||||
|
mEndPttOnFacchCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,28 +154,68 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
{
|
{
|
||||||
if(message instanceof MacMessage)
|
if(message instanceof MacMessage)
|
||||||
{
|
{
|
||||||
processMacMessage((MacMessage)message);
|
MacMessage macMessage = (MacMessage)message;
|
||||||
|
processMacMessage(macMessage);
|
||||||
|
|
||||||
|
MacPduType macPduType = macMessage.getMacPduType();
|
||||||
|
|
||||||
|
//Ignore End PTT - this is handled in the processMacMessage() method
|
||||||
|
if(macPduType != MacPduType.MAC_2_END_PTT)
|
||||||
|
{
|
||||||
|
mEndPttOnFacchCounter = 0;
|
||||||
|
continueState(getStateFromPduType(macPduType));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(macPduType == MacPduType.MAC_3_IDLE)
|
||||||
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp(), true, macPduType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(message instanceof AbstractVoiceTimeslot)
|
else if(message instanceof AbstractVoiceTimeslot)
|
||||||
{
|
{
|
||||||
if(isEncrypted())
|
if(mCurrentCallEvent != null)
|
||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED, getTimeslot()));
|
if(isEncrypted())
|
||||||
}
|
{
|
||||||
else
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED, getTimeslot()));
|
||||||
{
|
}
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
else
|
||||||
|
{
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCurrentCall(null, null, message.getTimestamp());
|
updateCurrentCall(null, null, message.getTimestamp());
|
||||||
}
|
}
|
||||||
else if(message instanceof EncryptionSynchronizationSequence)
|
else if(message instanceof EncryptionSynchronizationSequence)
|
||||||
{
|
{
|
||||||
|
//We don't send any state events for this message since it can only occur in conjunction with
|
||||||
|
//an audio frame that already sends the call state event
|
||||||
getIdentifierCollection().update(message.getIdentifiers());
|
getIdentifierCollection().update(message.getIdentifiers());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the channel state according to the PDU type
|
||||||
|
*/
|
||||||
|
private static State getStateFromPduType(MacPduType macPduType)
|
||||||
|
{
|
||||||
|
switch(macPduType)
|
||||||
|
{
|
||||||
|
case MAC_1_PTT:
|
||||||
|
return State.CALL;
|
||||||
|
case MAC_2_END_PTT:
|
||||||
|
return State.TEARDOWN;
|
||||||
|
case MAC_4_ACTIVE:
|
||||||
|
case MAC_6_HANGTIME:
|
||||||
|
return State.ACTIVE;
|
||||||
|
case MAC_3_IDLE:
|
||||||
|
default:
|
||||||
|
return State.IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the current channel to the local identifier collection which will cause it to be broadcast to all of the
|
* Adds the current channel to the local identifier collection which will cause it to be broadcast to all of the
|
||||||
* other listeners and will allow both timeslots on this channel to receive it and update accordingly.
|
* other listeners and will allow both timeslots on this channel to receive it and update accordingly.
|
||||||
@@ -215,30 +258,67 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
{
|
{
|
||||||
//Add the set of identifiers before we close out the call event to ensure they're captured in
|
//Add the set of identifiers before we close out the call event to ensure they're captured in
|
||||||
//the closing event.
|
//the closing event.
|
||||||
for(Identifier identifier : mac.getIdentifiers())
|
if(mCurrentCallEvent != null)
|
||||||
{
|
{
|
||||||
if(identifier.getRole() == Role.FROM && identifier instanceof APCO25RadioIdentifier)
|
for(Identifier identifier : mac.getIdentifiers())
|
||||||
{
|
{
|
||||||
int value = ((APCO25RadioIdentifier)identifier).getValue();
|
if(identifier.getRole() == Role.FROM)
|
||||||
|
|
||||||
if(value == SYSTEM_CONTROLLER)
|
|
||||||
{
|
{
|
||||||
//ignore so we don't overwrite the real from radio identifier
|
if(identifier instanceof APCO25RadioIdentifier)
|
||||||
|
{
|
||||||
|
int value = ((APCO25RadioIdentifier)identifier).getValue();
|
||||||
|
|
||||||
|
//Group call End PTT uses a FROM value of 0xFFFFFF - don't overwrite the correct id
|
||||||
|
if(value != SYSTEM_CONTROLLER)
|
||||||
|
{
|
||||||
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(identifier.getRole() == Role.TO)
|
||||||
|
{
|
||||||
|
if(identifier instanceof APCO25Talkgroup)
|
||||||
|
{
|
||||||
|
int value = ((APCO25Talkgroup)identifier).getValue();
|
||||||
|
|
||||||
|
//Individual call End PTT uses a TO value of 0 - don't overwrite the correct id
|
||||||
|
if(value != 0)
|
||||||
|
{
|
||||||
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//Add to the identifier collection after filtering through the patch group manager
|
|
||||||
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
//Add to the identifier collection after filtering through the patch group manager
|
|
||||||
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeCurrentCallEvent(message.getTimestamp(), true);
|
if(message.getDataUnitID().isFACCH())
|
||||||
|
{
|
||||||
|
mEndPttOnFacchCounter++;
|
||||||
|
|
||||||
|
//FNE sending 2 or more End PTT in FACCH timeslots indicates a channel teardown event.
|
||||||
|
if(mEndPttOnFacchCounter > 1)
|
||||||
|
{
|
||||||
|
if(mChannelType == ChannelType.TRAFFIC)
|
||||||
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_2_END_PTT);
|
||||||
|
//Don't send a continue state here ... the close() method will send the TEARDOWN state
|
||||||
|
}
|
||||||
|
//Otherwise the channel state is set to ACTIVE in anticipation of further call activity
|
||||||
|
else
|
||||||
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_4_ACTIVE);
|
||||||
|
continueState(State.RESET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_4_ACTIVE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TDMA_0_NULL_INFORMATION_MESSAGE:
|
case TDMA_0_NULL_INFORMATION_MESSAGE:
|
||||||
@@ -246,23 +326,18 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
|
|
||||||
if(type == MacPduType.MAC_3_IDLE || type == MacPduType.MAC_6_HANGTIME)
|
if(type == MacPduType.MAC_3_IDLE || type == MacPduType.MAC_6_HANGTIME)
|
||||||
{
|
{
|
||||||
closeCurrentCallEvent(message.getTimestamp(), true);
|
closeCurrentCallEvent(message.getTimestamp(), true, type);
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED:
|
case TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED:
|
||||||
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
||||||
{
|
{
|
||||||
closeCurrentCallEvent(message.getTimestamp(), false);
|
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
|
||||||
|
|
||||||
for(Identifier identifier : mac.getIdentifiers())
|
for(Identifier identifier : mac.getIdentifiers())
|
||||||
{
|
{
|
||||||
if(!(identifier.getForm() == Form.TALKGROUP && identifier.getRole() == Role.FROM))
|
//Add to the identifier collection after filtering through the patch group manager
|
||||||
{
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
//Add to the identifier collection after filtering through the patch group manager
|
|
||||||
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -291,16 +366,12 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
case TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED:
|
case TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED:
|
||||||
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
||||||
{
|
{
|
||||||
closeCurrentCallEvent(message.getTimestamp(), false);
|
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
|
||||||
|
|
||||||
for(Identifier identifier : mac.getIdentifiers())
|
for(Identifier identifier : mac.getIdentifiers())
|
||||||
{
|
{
|
||||||
if(!(identifier.getForm() == Form.TALKGROUP && identifier.getRole() == Role.FROM))
|
//Add to the identifier collection after filtering through the patch group manager
|
||||||
{
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
//Add to the identifier collection after filtering through the patch group manager
|
|
||||||
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -329,16 +400,12 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
case TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER:
|
case TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER:
|
||||||
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
||||||
{
|
{
|
||||||
closeCurrentCallEvent(message.getTimestamp(), false);
|
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
|
||||||
|
|
||||||
for(Identifier identifier : mac.getIdentifiers())
|
for(Identifier identifier : mac.getIdentifiers())
|
||||||
{
|
{
|
||||||
if(!(identifier.getForm() == Form.TALKGROUP && identifier.getRole() == Role.FROM))
|
//Add to the identifier collection after filtering through the patch group manager
|
||||||
{
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
//Add to the identifier collection after filtering through the patch group manager
|
|
||||||
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -367,16 +434,12 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
case TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
|
case TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
|
||||||
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
||||||
{
|
{
|
||||||
closeCurrentCallEvent(message.getTimestamp(), false);
|
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
|
||||||
|
|
||||||
for(Identifier identifier : mac.getIdentifiers())
|
for(Identifier identifier : mac.getIdentifiers())
|
||||||
{
|
{
|
||||||
if(!(identifier.getForm() == Form.TALKGROUP && identifier.getRole() == Role.FROM))
|
//Add to the identifier collection after filtering through the patch group manager
|
||||||
{
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
//Add to the identifier collection after filtering through the patch group manager
|
|
||||||
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -405,16 +468,12 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
case TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
|
case TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
|
||||||
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
|
||||||
{
|
{
|
||||||
closeCurrentCallEvent(message.getTimestamp(), false);
|
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
|
||||||
|
|
||||||
for(Identifier identifier : mac.getIdentifiers())
|
for(Identifier identifier : mac.getIdentifiers())
|
||||||
{
|
{
|
||||||
if(!(identifier.getForm() == Form.TALKGROUP && identifier.getRole() == Role.FROM))
|
//Add to the identifier collection after filtering through the patch group manager
|
||||||
{
|
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
||||||
//Add to the identifier collection after filtering through the patch group manager
|
|
||||||
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -439,7 +498,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE:
|
case TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE:
|
||||||
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultiple)
|
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultiple)
|
||||||
@@ -461,7 +519,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcastCurrentChannel(gvcgum.getChannelC());
|
broadcastCurrentChannel(gvcgum.getChannelC());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT:
|
case TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT:
|
||||||
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultipleExplicit)
|
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultipleExplicit)
|
||||||
@@ -478,7 +535,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcastCurrentChannel(gvcgume.getChannelB());
|
broadcastCurrentChannel(gvcgume.getChannelB());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED:
|
case PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED:
|
||||||
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantAbbreviated)
|
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantAbbreviated)
|
||||||
@@ -490,7 +546,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcastCurrentChannel(gvcga.getChannel());
|
broadcastCurrentChannel(gvcga.getChannel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE:
|
case PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE:
|
||||||
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdate)
|
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdate)
|
||||||
@@ -507,7 +562,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcastCurrentChannel(gvcgu.getChannelB());
|
broadcastCurrentChannel(gvcgu.getChannelB());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED:
|
case PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED:
|
||||||
if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantAbbreviated)
|
if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantAbbreviated)
|
||||||
@@ -519,7 +573,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcastCurrentChannel(uuvcga.getChannel());
|
broadcastCurrentChannel(uuvcga.getChannel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT:
|
case PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT:
|
||||||
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateExplicit)
|
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateExplicit)
|
||||||
@@ -531,7 +584,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcastCurrentChannel(gvcgue.getChannel());
|
broadcastCurrentChannel(gvcgue.getChannel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED:
|
case PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED:
|
||||||
if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantUpdateExtended)
|
if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantUpdateExtended)
|
||||||
@@ -543,7 +595,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcastCurrentChannel(uuvcgue.getChannel());
|
broadcastCurrentChannel(uuvcgue.getChannel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case TDMA_17_INDIRECT_GROUP_PAGING:
|
case TDMA_17_INDIRECT_GROUP_PAGING:
|
||||||
MutableIdentifierCollection icGroupPaging = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection icGroupPaging = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
@@ -556,7 +607,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.details("GROUP PAGE")
|
.details("GROUP PAGE")
|
||||||
.identifiers(icGroupPaging)
|
.identifiers(icGroupPaging)
|
||||||
.build());
|
.build());
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY:
|
case TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY:
|
||||||
if(mac instanceof IndividualPagingMessage)
|
if(mac instanceof IndividualPagingMessage)
|
||||||
@@ -575,7 +625,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icIndividualPaging)
|
.identifiers(icIndividualPaging)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case TDMA_48_POWER_CONTROL_SIGNAL_QUALITY:
|
case TDMA_48_POWER_CONTROL_SIGNAL_QUALITY:
|
||||||
if(mac instanceof PowerControlSignalQuality)
|
if(mac instanceof PowerControlSignalQuality)
|
||||||
@@ -593,7 +642,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icPowerControl)
|
.identifiers(icPowerControl)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case TDMA_49_MAC_RELEASE:
|
case TDMA_49_MAC_RELEASE:
|
||||||
if(mac instanceof MacRelease)
|
if(mac instanceof MacRelease)
|
||||||
@@ -604,7 +652,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
icMacRelease.remove(IdentifierClass.USER);
|
icMacRelease.remove(IdentifierClass.USER);
|
||||||
icMacRelease.update(mac.getIdentifiers());
|
icMacRelease.update(mac.getIdentifiers());
|
||||||
|
|
||||||
closeCurrentCallEvent(message.getTimestamp(), true);
|
closeCurrentCallEvent(message.getTimestamp(), true, message.getMacPduType());
|
||||||
|
|
||||||
broadcast(P25DecodeEvent.builder(message.getTimestamp())
|
broadcast(P25DecodeEvent.builder(message.getTimestamp())
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
@@ -630,7 +678,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icMacRelease)
|
.identifiers(icMacRelease)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED:
|
case PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED:
|
||||||
if(mac instanceof UnitToUnitVoiceChannelGrantAbbreviated)
|
if(mac instanceof UnitToUnitVoiceChannelGrantAbbreviated)
|
||||||
@@ -647,7 +694,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icGrant)
|
.identifiers(icGrant)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED:
|
case PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED:
|
||||||
if(mac instanceof UnitToUnitAnswerRequestAbbreviated)
|
if(mac instanceof UnitToUnitAnswerRequestAbbreviated)
|
||||||
@@ -664,7 +710,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icRequest)
|
.identifiers(icRequest)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
|
case PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
|
||||||
if(mac instanceof TelephoneInterconnectAnswerRequest)
|
if(mac instanceof TelephoneInterconnectAnswerRequest)
|
||||||
@@ -681,7 +726,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icRequest)
|
.identifiers(icRequest)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED:
|
case PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED:
|
||||||
if(mac instanceof RadioUnitMonitorCommand)
|
if(mac instanceof RadioUnitMonitorCommand)
|
||||||
@@ -699,7 +743,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icRequest)
|
.identifiers(icRequest)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_84_SNDCP_DATA_CHANNEL_GRANT:
|
case PHASE1_84_SNDCP_DATA_CHANNEL_GRANT:
|
||||||
if(mac instanceof SNDCPDataChannelGrant)
|
if(mac instanceof SNDCPDataChannelGrant)
|
||||||
@@ -716,7 +759,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icGrant)
|
.identifiers(icGrant)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_85_SNDCP_DATA_PAGE_REQUEST:
|
case PHASE1_85_SNDCP_DATA_PAGE_REQUEST:
|
||||||
if(mac instanceof SNDCPDataPageRequest)
|
if(mac instanceof SNDCPDataPageRequest)
|
||||||
@@ -733,7 +775,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icPage)
|
.identifiers(icPage)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_88_STATUS_UPDATE_ABBREVIATED:
|
case PHASE1_88_STATUS_UPDATE_ABBREVIATED:
|
||||||
if(mac instanceof StatusUpdateAbbreviated)
|
if(mac instanceof StatusUpdateAbbreviated)
|
||||||
@@ -750,7 +791,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icStatusUpdate)
|
.identifiers(icStatusUpdate)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_90_STATUS_QUERY_ABBREVIATED:
|
case PHASE1_90_STATUS_QUERY_ABBREVIATED:
|
||||||
if(mac instanceof StatusQueryAbbreviated)
|
if(mac instanceof StatusQueryAbbreviated)
|
||||||
@@ -766,7 +806,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icStatusUpdate)
|
.identifiers(icStatusUpdate)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_92_MESSAGE_UPDATE_ABBREVIATED:
|
case PHASE1_92_MESSAGE_UPDATE_ABBREVIATED:
|
||||||
if(mac instanceof MessageUpdateAbbreviated)
|
if(mac instanceof MessageUpdateAbbreviated)
|
||||||
@@ -783,7 +822,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icStatusUpdate)
|
.identifiers(icStatusUpdate)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED:
|
case PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED:
|
||||||
if(mac instanceof RadioUnitMonitorCommandEnhanced)
|
if(mac instanceof RadioUnitMonitorCommandEnhanced)
|
||||||
@@ -802,7 +840,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icRequest)
|
.identifiers(icRequest)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_95_CALL_ALERT_ABBREVIATED:
|
case PHASE1_95_CALL_ALERT_ABBREVIATED:
|
||||||
MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
@@ -814,7 +851,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.eventDescription(DecodeEventType.CALL_ALERT.toString())
|
.eventDescription(DecodeEventType.CALL_ALERT.toString())
|
||||||
.identifiers(icCallAlert)
|
.identifiers(icCallAlert)
|
||||||
.build());
|
.build());
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_96_ACK_RESPONSE:
|
case PHASE1_96_ACK_RESPONSE:
|
||||||
if(mac instanceof AcknowledgeResponse)
|
if(mac instanceof AcknowledgeResponse)
|
||||||
@@ -831,7 +867,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icAckResponse)
|
.identifiers(icAckResponse)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_97_QUEUED_RESPONSE:
|
case PHASE1_97_QUEUED_RESPONSE:
|
||||||
if(mac instanceof QueuedResponse)
|
if(mac instanceof QueuedResponse)
|
||||||
@@ -850,7 +885,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icQueuedResponse)
|
.identifiers(icQueuedResponse)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED:
|
case PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED:
|
||||||
if(mac instanceof ExtendedFunctionCommand)
|
if(mac instanceof ExtendedFunctionCommand)
|
||||||
@@ -868,7 +902,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icExtendedFunction)
|
.identifiers(icExtendedFunction)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_103_DENY_RESPONSE:
|
case PHASE1_103_DENY_RESPONSE:
|
||||||
if(mac instanceof DenyResponse)
|
if(mac instanceof DenyResponse)
|
||||||
@@ -885,7 +918,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icDenyResponse)
|
.identifiers(icDenyResponse)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED:
|
case PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED:
|
||||||
MutableIdentifierCollection icGroupAffiliationQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection icGroupAffiliationQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
@@ -898,7 +930,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.details("GROUP AFFILIATION")
|
.details("GROUP AFFILIATION")
|
||||||
.identifiers(icGroupAffiliationQuery)
|
.identifiers(icGroupAffiliationQuery)
|
||||||
.build());
|
.build());
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED:
|
case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED:
|
||||||
MutableIdentifierCollection icUnitRegisterCommand = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection icUnitRegisterCommand = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
@@ -911,7 +942,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.details("UNIT REGISTRATION")
|
.details("UNIT REGISTRATION")
|
||||||
.identifiers(icUnitRegisterCommand)
|
.identifiers(icUnitRegisterCommand)
|
||||||
.build());
|
.build());
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED:
|
case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED:
|
||||||
if(mac instanceof UnitToUnitAnswerRequestExtended)
|
if(mac instanceof UnitToUnitAnswerRequestExtended)
|
||||||
@@ -930,7 +960,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icUnitAnswerRequestExtended)
|
.identifiers(icUnitAnswerRequestExtended)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED:
|
case PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED:
|
||||||
if(mac instanceof RadioUnitMonitorCommandExtended)
|
if(mac instanceof RadioUnitMonitorCommandExtended)
|
||||||
@@ -948,7 +977,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icRequest)
|
.identifiers(icRequest)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_216_STATUS_UPDATE_EXTENDED:
|
case PHASE1_216_STATUS_UPDATE_EXTENDED:
|
||||||
if(mac instanceof StatusUpdateExtended)
|
if(mac instanceof StatusUpdateExtended)
|
||||||
@@ -965,7 +993,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icStatusUpdate)
|
.identifiers(icStatusUpdate)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_218_STATUS_QUERY_EXTENDED:
|
case PHASE1_218_STATUS_QUERY_EXTENDED:
|
||||||
MutableIdentifierCollection icStatusQueryExtended = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection icStatusQueryExtended = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
@@ -978,7 +1005,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.details("STATUS QUERY")
|
.details("STATUS QUERY")
|
||||||
.identifiers(icStatusQueryExtended)
|
.identifiers(icStatusQueryExtended)
|
||||||
.build());
|
.build());
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_220_MESSAGE_UPDATE_EXTENDED:
|
case PHASE1_220_MESSAGE_UPDATE_EXTENDED:
|
||||||
if(mac instanceof MessageUpdateExtended)
|
if(mac instanceof MessageUpdateExtended)
|
||||||
@@ -995,7 +1021,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icStatusUpdate)
|
.identifiers(icStatusUpdate)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_223_CALL_ALERT_EXTENDED:
|
case PHASE1_223_CALL_ALERT_EXTENDED:
|
||||||
if(mac instanceof CallAlertExtended)
|
if(mac instanceof CallAlertExtended)
|
||||||
@@ -1010,7 +1035,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icCallAlertExtended)
|
.identifiers(icCallAlertExtended)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED:
|
case PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED:
|
||||||
if(mac instanceof ExtendedFunctionCommandExtended)
|
if(mac instanceof ExtendedFunctionCommandExtended)
|
||||||
@@ -1028,7 +1052,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icExtendedFunction)
|
.identifiers(icExtendedFunction)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
case PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED:
|
case PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED:
|
||||||
if(mac instanceof GroupAffiliationQueryExtended)
|
if(mac instanceof GroupAffiliationQueryExtended)
|
||||||
@@ -1045,7 +1068,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.identifiers(icGroupAffiliationQueryExtended)
|
.identifiers(icGroupAffiliationQueryExtended)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
continueState(State.ACTIVE);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PHASE1_115_IDENTIFIER_UPDATE_TDMA:
|
case PHASE1_115_IDENTIFIER_UPDATE_TDMA:
|
||||||
@@ -1071,8 +1093,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
case PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED:
|
case PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED:
|
||||||
case UNKNOWN:
|
case UNKNOWN:
|
||||||
default:
|
default:
|
||||||
continueState(State.ACTIVE);
|
|
||||||
//ignore
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1135,7 +1155,15 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(mCurrentCallEvent);
|
broadcast(mCurrentCallEvent);
|
||||||
broadcast(new DecoderStateEvent(this, Event.START, State.CALL, getTimeslot()));
|
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.START, State.ENCRYPTED, getTimeslot()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.START, State.CALL, getTimeslot()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1152,7 +1180,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
|
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||||
mCurrentCallEvent.end(timestamp);
|
mCurrentCallEvent.end(timestamp);
|
||||||
broadcast(mCurrentCallEvent);
|
broadcast(mCurrentCallEvent);
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1160,8 +1187,10 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
* Ends/closes the current call event.
|
* Ends/closes the current call event.
|
||||||
*
|
*
|
||||||
* @param timestamp of the message that indicates the event has ended.
|
* @param timestamp of the message that indicates the event has ended.
|
||||||
|
* @param resetIdentifiers to reset the FROM/TO identifiers
|
||||||
|
* @param pduType of the message that caused the close call event - to determine channel state after call
|
||||||
*/
|
*/
|
||||||
private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers)
|
private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers, MacPduType pduType)
|
||||||
{
|
{
|
||||||
if(mCurrentCallEvent != null)
|
if(mCurrentCallEvent != null)
|
||||||
{
|
{
|
||||||
@@ -1169,21 +1198,21 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
broadcast(mCurrentCallEvent);
|
broadcast(mCurrentCallEvent);
|
||||||
mCurrentCallEvent = null;
|
mCurrentCallEvent = null;
|
||||||
|
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.END, getStateFromPduType(pduType), getTimeslot()));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
continueState(State.ACTIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(resetIdentifiers)
|
if(resetIdentifiers)
|
||||||
{
|
{
|
||||||
getIdentifierCollection().remove(IdentifierClass.USER);
|
getIdentifierCollection().remove(IdentifierClass.USER);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Only clear the from identifier at this point ... the channel may still be allocated to the TO talkgroup
|
||||||
|
getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else if(pduType == MacPduType.MAC_2_END_PTT)
|
||||||
{
|
{
|
||||||
//Only clear the from identifier at this point ... the channel may still be allocated to the TO talkgroup
|
broadcast(new DecoderStateEvent(this, Event.END, getStateFromPduType(pduType), getTimeslot()));
|
||||||
getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1215,14 +1244,17 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
|||||||
@Override
|
@Override
|
||||||
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
||||||
{
|
{
|
||||||
switch(event.getEvent())
|
if(event.getTimeslot() == getTimeslot())
|
||||||
{
|
{
|
||||||
case RESET:
|
switch(event.getEvent())
|
||||||
resetState();
|
{
|
||||||
mNetworkConfigurationMonitor.reset();
|
case RESET:
|
||||||
break;
|
resetState();
|
||||||
default:
|
mNetworkConfigurationMonitor.reset();
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -224,25 +224,30 @@ public class P25P2MessageFramer implements Listener<Dibit>
|
|||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
UserPreferences userPreferences = new UserPreferences();
|
UserPreferences userPreferences = new UserPreferences();
|
||||||
userPreferences.getJmbeLibraryPreference().setPathJmbeLibrary(Path.of("/home/denny/JMBE/jmbe-1.0.2.jar"));
|
userPreferences.getJmbeLibraryPreference().setPathJmbeLibrary(Path.of("/home/denny/JMBE/jmbe-1.0.3.jar"));
|
||||||
ApplicationLog applicationLog = new ApplicationLog(userPreferences);
|
ApplicationLog applicationLog = new ApplicationLog(userPreferences);
|
||||||
applicationLog.start();
|
applicationLog.start();
|
||||||
|
|
||||||
|
// Path directory = Paths.get("/home/denny/SDRTrunk/recordings");
|
||||||
Path directory = Paths.get("/home/denny/temp/Harris P25-2 Logs/bits");
|
Path directory = Paths.get("/home/denny/temp/Harris P25-2 Logs/bits");
|
||||||
// Path directory = Paths.get("/media/denny/500G1EXT4/PBITRecordings");
|
// Path directory = Paths.get("/media/denny/500G1EXT4/PBITRecordings");
|
||||||
|
|
||||||
|
// ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 1186, 1189); //Nugent
|
||||||
ScrambleParameters scrambleParameters = new ScrambleParameters(123654, 813, 10); //Hills
|
ScrambleParameters scrambleParameters = new ScrambleParameters(123654, 813, 10); //Hills
|
||||||
// ScrambleParameters scrambleParameters = new ScrambleParameters(1, 972, 972));
|
// ScrambleParameters scrambleParameters = new ScrambleParameters(1, 972, 972));
|
||||||
// ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 686, 677)); //CNYICC - Rome
|
// ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 686, 677); //CNYICC - Rome
|
||||||
|
|
||||||
|
// Channel channel = new Channel("Phase 2 Test", Channel.ChannelType.STANDARD);
|
||||||
|
Channel channel = new Channel("Phase 2 Test", Channel.ChannelType.TRAFFIC);
|
||||||
|
|
||||||
Channel channel = new Channel("Phase 2 Test");
|
|
||||||
channel.setDecodeConfiguration(new DecodeConfigP25Phase2());
|
channel.setDecodeConfiguration(new DecodeConfigP25Phase2());
|
||||||
AliasList aliasList = new AliasList("Test Alias List");
|
AliasList aliasList = new AliasList("Test Alias List");
|
||||||
Alias alias = new Alias("TG Range 1-65535");
|
// Alias alias = new Alias("TG Range 1-65535");
|
||||||
alias.addAliasID(new Record());
|
// alias.addAliasID(new Record());
|
||||||
// alias.addAliasID(new TalkgroupRange(Protocol.APCO25, 1, 65535));
|
// alias.addAliasID(new TalkgroupRange(Protocol.APCO25, 1, 65535));
|
||||||
alias.addAliasID(new Talkgroup(Protocol.APCO25, 11185));
|
// alias.addAliasID(new Talkgroup(Protocol.APCO25, 11857));
|
||||||
aliasList.addAlias(alias);
|
// alias.addAliasID(new Talkgroup(Protocol.APCO25, 12601));
|
||||||
|
// aliasList.addAlias(alias);
|
||||||
AudioRecordingManager recordingManager = new AudioRecordingManager(userPreferences);
|
AudioRecordingManager recordingManager = new AudioRecordingManager(userPreferences);
|
||||||
recordingManager.start();
|
recordingManager.start();
|
||||||
ProcessingChain processingChain = new ProcessingChain(channel, new AliasModel());
|
ProcessingChain processingChain = new ProcessingChain(channel, new AliasModel());
|
||||||
@@ -254,20 +259,21 @@ public class P25P2MessageFramer implements Listener<Dibit>
|
|||||||
processingChain.addModule(new P25P2AudioModule(userPreferences, 1, aliasList));
|
processingChain.addModule(new P25P2AudioModule(userPreferences, 1, aliasList));
|
||||||
MessageProviderModule messageProviderModule = new MessageProviderModule();
|
MessageProviderModule messageProviderModule = new MessageProviderModule();
|
||||||
processingChain.addModule(messageProviderModule);
|
processingChain.addModule(messageProviderModule);
|
||||||
processingChain.start();
|
|
||||||
|
|
||||||
try(OutputStream logOutput = Files.newOutputStream(directory.resolve("log.txt")))
|
try(OutputStream logOutput = Files.newOutputStream(directory.resolve("log.txt")))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*PHASE2_Hills*TRAFFIC.bits");
|
// DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*042102*TRAFFIC.bits");
|
||||||
|
// DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*042102*.bits");
|
||||||
|
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*214601*Hills*TRAFFIC.bits");
|
||||||
|
|
||||||
stream.forEach(new Consumer<Path>()
|
stream.forEach(new Consumer<Path>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void accept(Path path)
|
public void accept(Path path)
|
||||||
{
|
{
|
||||||
mLog.debug("Processing:" + path.toString());
|
mLog.debug("\n\nProcessing:" + path.toString() + "\n\n");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logOutput.write(("Processing:" + path.toString() + "\n").getBytes());
|
logOutput.write(("Processing:" + path.toString() + "\n").getBytes());
|
||||||
@@ -277,6 +283,8 @@ public class P25P2MessageFramer implements Listener<Dibit>
|
|||||||
mLog.error("Error", ioe);
|
mLog.error("Error", ioe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processingChain.start();
|
||||||
|
|
||||||
P25P2MessageFramer messageFramer = new P25P2MessageFramer(null, DecoderType.P25_PHASE2.getProtocol().getBitRate());
|
P25P2MessageFramer messageFramer = new P25P2MessageFramer(null, DecoderType.P25_PHASE2.getProtocol().getBitRate());
|
||||||
messageFramer.setScrambleParameters(scrambleParameters);
|
messageFramer.setScrambleParameters(scrambleParameters);
|
||||||
P25P2MessageProcessor messageProcessor = new P25P2MessageProcessor();
|
P25P2MessageProcessor messageProcessor = new P25P2MessageProcessor();
|
||||||
@@ -313,6 +321,12 @@ public class P25P2MessageFramer implements Listener<Dibit>
|
|||||||
{
|
{
|
||||||
ioe.printStackTrace();
|
ioe.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.debug("**STOPPING PROCESSING CHAIN**");
|
||||||
|
|
||||||
|
processingChain.stop();
|
||||||
|
|
||||||
|
mLog.debug("\n============================================================================================================================\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -326,5 +340,7 @@ public class P25P2MessageFramer implements Listener<Dibit>
|
|||||||
{
|
{
|
||||||
mLog.error("Error", ioe);
|
mLog.error("Error", ioe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordingManager.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,22 @@ public enum DataUnitID
|
|||||||
return mValueWithParity;
|
return mValueWithParity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this DUID is a SACCH, either scrambled or unscrambled
|
||||||
|
*/
|
||||||
|
public boolean isSACCH()
|
||||||
|
{
|
||||||
|
return this == SCRAMBLED_SACCH || this == UNSCRAMBLED_SACCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this DUID is a FACCH, either scrambled or unscrambled
|
||||||
|
*/
|
||||||
|
public boolean isFACCH()
|
||||||
|
{
|
||||||
|
return this == SCRAMBLED_FACCH || this == UNSCRAMBLED_FACCH;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -350,19 +350,26 @@ public class PlaybackPreference extends Preference
|
|||||||
{
|
{
|
||||||
MixerChannelConfiguration defaultConfig = MixerManager.getDefaultOutputMixer();
|
MixerChannelConfiguration defaultConfig = MixerManager.getDefaultOutputMixer();
|
||||||
|
|
||||||
String configName = mPreferences.get(PREFERENCE_KEY_MIXER_CHANNEL_CONFIG, defaultConfig.toString());
|
if(defaultConfig != null)
|
||||||
|
|
||||||
for(MixerChannelConfiguration config: MixerManager.getOutputMixers())
|
|
||||||
{
|
{
|
||||||
if(config.toString().contentEquals(configName))
|
String configName = mPreferences.get(PREFERENCE_KEY_MIXER_CHANNEL_CONFIG, defaultConfig.toString());
|
||||||
|
|
||||||
|
for(MixerChannelConfiguration config: MixerManager.getOutputMixers())
|
||||||
{
|
{
|
||||||
mMixerChannelConfiguration = config;
|
if(config.toString().contentEquals(configName))
|
||||||
|
{
|
||||||
|
mMixerChannelConfiguration = config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mMixerChannelConfiguration == null)
|
||||||
|
{
|
||||||
|
mMixerChannelConfiguration = defaultConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if(mMixerChannelConfiguration == null)
|
|
||||||
{
|
{
|
||||||
mMixerChannelConfiguration = defaultConfig;
|
mLog.error("Error - no system audio devices available");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class ChannelMultiFrequencyPreference extends Preference
|
|||||||
private Preferences mPreferences = Preferences.userNodeForPackage(ChannelMultiFrequencyPreference.class);
|
private Preferences mPreferences = Preferences.userNodeForPackage(ChannelMultiFrequencyPreference.class);
|
||||||
private static final String PREFERENCE_KEY_ROTATION_DELAY = "rotation.delay";
|
private static final String PREFERENCE_KEY_ROTATION_DELAY = "rotation.delay";
|
||||||
private static final long DEFAULT_ROTATION_DELAY = 5000; //5 seconds
|
private static final long DEFAULT_ROTATION_DELAY = 5000; //5 seconds
|
||||||
private Long mRotationDelay = DEFAULT_ROTATION_DELAY;
|
private Long mRotationDelay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a tuner preference with the update listener
|
* Constructs a tuner preference with the update listener
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
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.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
@@ -55,6 +54,8 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
|||||||
private ScheduledFuture<?> mQueueProcessorHandle;
|
private ScheduledFuture<?> mQueueProcessorHandle;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private int mUnknownAudioRecordingIndex = 1;
|
private int mUnknownAudioRecordingIndex = 1;
|
||||||
|
private int mDuplicateAudioRecordingSuffix = 1;
|
||||||
|
private String mPreviousRecordingPath = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -105,6 +106,19 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
|||||||
*/
|
*/
|
||||||
public void processCompletedAudioSegment(AudioSegment audioSegment)
|
public void processCompletedAudioSegment(AudioSegment audioSegment)
|
||||||
{
|
{
|
||||||
|
//Debug
|
||||||
|
if(audioSegment.getAudioBufferCount() == 0)
|
||||||
|
{
|
||||||
|
mLog.debug("Audio Segment detected with 0 audio buffers");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Identifier> toIdentifiers = audioSegment.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||||
|
|
||||||
|
if(toIdentifiers.isEmpty())
|
||||||
|
{
|
||||||
|
mLog.debug("Audio Segment detected with NO TO identifiers");
|
||||||
|
}
|
||||||
|
|
||||||
if(audioSegment.recordAudioProperty().get())
|
if(audioSegment.recordAudioProperty().get())
|
||||||
{
|
{
|
||||||
mCompletedAudioSegmentQueue.add(audioSegment);
|
mCompletedAudioSegmentQueue.add(audioSegment);
|
||||||
@@ -120,28 +134,26 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
|||||||
*/
|
*/
|
||||||
private void processAudioSegments()
|
private void processAudioSegments()
|
||||||
{
|
{
|
||||||
List<AudioSegment> audioSegments = new ArrayList<>();
|
RecordFormat recordFormat = mUserPreferences.getRecordPreference().getAudioRecordFormat();
|
||||||
mCompletedAudioSegmentQueue.drainTo(audioSegments);
|
AudioSegment audioSegment = mCompletedAudioSegmentQueue.poll();
|
||||||
|
|
||||||
if(!audioSegments.isEmpty())
|
while(audioSegment != null)
|
||||||
{
|
{
|
||||||
RecordFormat recordFormat = mUserPreferences.getRecordPreference().getAudioRecordFormat();
|
Path path = getAudioRecordingPath(audioSegment.getIdentifierCollection(), recordFormat);
|
||||||
|
|
||||||
for(AudioSegment audioSegment: audioSegments)
|
try
|
||||||
{
|
{
|
||||||
Path path = getAudioRecordingPath(audioSegment.getIdentifierCollection(), recordFormat);
|
AudioSegmentRecorder.record(audioSegment, path, recordFormat);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AudioSegmentRecorder.record(audioSegment, path, recordFormat);
|
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
|
||||||
mLog.error("Error recording audio segment to [" + path.toString() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
audioSegment.decrementConsumerCount();
|
|
||||||
}
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error recording audio segment to [" + path.toString() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
audioSegment.decrementConsumerCount();
|
||||||
|
|
||||||
|
//Grab the next one to record
|
||||||
|
audioSegment = mCompletedAudioSegmentQueue.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +209,7 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
|||||||
|
|
||||||
if(!toIdentifiers.isEmpty())
|
if(!toIdentifiers.isEmpty())
|
||||||
{
|
{
|
||||||
sb.append("_TO_").append(toIdentifiers.get(0)).toString();
|
sb.append("_TO_").append(toIdentifiers.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +226,14 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
|||||||
|
|
||||||
if(!fromIdentifiers.isEmpty())
|
if(!fromIdentifiers.isEmpty())
|
||||||
{
|
{
|
||||||
sb.append("_FROM_").append(fromIdentifiers.get(0)).toString();
|
for(Identifier identifier: fromIdentifiers)
|
||||||
|
{
|
||||||
|
if(identifier.getForm() != Form.TONE)
|
||||||
|
{
|
||||||
|
sb.append("_FROM_").append(identifier);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,6 +252,26 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
|||||||
|
|
||||||
//Remove any illegal filename characters
|
//Remove any illegal filename characters
|
||||||
sbFinal.append(StringUtils.replaceIllegalCharacters(sb.toString()));
|
sbFinal.append(StringUtils.replaceIllegalCharacters(sb.toString()));
|
||||||
|
|
||||||
|
if(mPreviousRecordingPath != null && mPreviousRecordingPath.contentEquals(sbFinal.toString()))
|
||||||
|
{
|
||||||
|
sbFinal.append("_V").append(mDuplicateAudioRecordingSuffix++);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mDuplicateAudioRecordingSuffix = 2;
|
||||||
|
mPreviousRecordingPath = sbFinal.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Identifier identifier: identifierCollection.getIdentifiers(Role.FROM))
|
||||||
|
{
|
||||||
|
if(identifier.getForm() == Form.TONE)
|
||||||
|
{
|
||||||
|
sbFinal.append("_WITH_TONES");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sbFinal.append(recordFormat.getExtension());
|
sbFinal.append(recordFormat.getExtension());
|
||||||
|
|
||||||
return getRecordingBasePath().resolve(sbFinal.toString());
|
return getRecordingBasePath().resolve(sbFinal.toString());
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public enum TunerClass
|
|||||||
GIGABYTE_GTU7300( TunerType.FITIPOWER_FC0012, "1B80", "D393", "Gigabyte", "GT-U7300" ),
|
GIGABYTE_GTU7300( TunerType.FITIPOWER_FC0012, "1B80", "D393", "Gigabyte", "GT-U7300" ),
|
||||||
GTEK_T803( TunerType.FITIPOWER_FC0012, "1F4D", "B803", "GTek", "T803" ),
|
GTEK_T803( TunerType.FITIPOWER_FC0012, "1F4D", "B803", "GTek", "T803" ),
|
||||||
HACKRF_ONE( TunerType.HACKRF, "1D50", "6089", "Great Scott Gadgets", "HackRF One" ),
|
HACKRF_ONE( TunerType.HACKRF, "1D50", "6089", "Great Scott Gadgets", "HackRF One" ),
|
||||||
|
HACKRF_JAWBREAKER( TunerType.HACKRF, "1D50", "604B", "Great Scott Gadgets", "HackRF Jawbreaker" ),
|
||||||
RAD1O( TunerType.HACKRF, "1D50", "CC15", "Munich hackerspace", "Rad1o" ),
|
RAD1O( TunerType.HACKRF, "1D50", "CC15", "Munich hackerspace", "Rad1o" ),
|
||||||
LIFEVIEW_LV5T_DELUXE( TunerType.FITIPOWER_FC0012, "1F4D", "C803", "Liveview", "LV5T Deluxe" ),
|
LIFEVIEW_LV5T_DELUXE( TunerType.FITIPOWER_FC0012, "1F4D", "C803", "Liveview", "LV5T Deluxe" ),
|
||||||
MYGICA_TD312( TunerType.FITIPOWER_FC0012, "1F4D", "D286", "MyGica", "TD312" ),
|
MYGICA_TD312( TunerType.FITIPOWER_FC0012, "1F4D", "D286", "MyGica", "TD312" ),
|
||||||
@@ -196,6 +197,10 @@ public enum TunerClass
|
|||||||
{
|
{
|
||||||
retVal = HACKRF_ONE;
|
retVal = HACKRF_ONE;
|
||||||
}
|
}
|
||||||
|
else if( productID == 24651 ) //604B
|
||||||
|
{
|
||||||
|
retVal = HACKRF_JAWBREAKER;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 8013: //1F4D
|
case 8013: //1F4D
|
||||||
if( productID == 47107 ) //B803
|
if( productID == 47107 ) //B803
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ public class TunerManager
|
|||||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
case FUNCUBE_DONGLE_PRO_PLUS:
|
||||||
return initFuncubeProPlusTuner(device, descriptor);
|
return initFuncubeProPlusTuner(device, descriptor);
|
||||||
case HACKRF_ONE:
|
case HACKRF_ONE:
|
||||||
|
case HACKRF_JAWBREAKER:
|
||||||
case RAD1O:
|
case RAD1O:
|
||||||
return initHackRFTuner(device, descriptor);
|
return initHackRFTuner(device, descriptor);
|
||||||
case COMPRO_VIDEOMATE_U620F:
|
case COMPRO_VIDEOMATE_U620F:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import io.github.dsheirer.source.SourceException;
|
|||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.usb.USBTransferProcessor;
|
import io.github.dsheirer.source.tuner.usb.USBTransferProcessor;
|
||||||
import io.github.dsheirer.source.tuner.usb.USBTunerController;
|
import io.github.dsheirer.source.tuner.usb.USBTunerController;
|
||||||
import io.github.dsheirer.source.tuner.usb.converter.ByteSampleConverter;
|
import io.github.dsheirer.source.tuner.usb.converter.SignedByteSampleConverter;
|
||||||
import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter;
|
import io.github.dsheirer.source.tuner.usb.converter.NativeBufferConverter;
|
||||||
import org.apache.commons.io.EndianUtils;
|
import org.apache.commons.io.EndianUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -60,7 +60,7 @@ public class HackRFTunerController extends USBTunerController
|
|||||||
public static final double USABLE_BANDWIDTH_PERCENT = 0.75;
|
public static final double USABLE_BANDWIDTH_PERCENT = 0.75;
|
||||||
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
|
public static final int DC_SPIKE_AVOID_BUFFER = 5000;
|
||||||
|
|
||||||
private NativeBufferConverter mNativeBufferConverter = new ByteSampleConverter();
|
private NativeBufferConverter mNativeBufferConverter = new SignedByteSampleConverter();
|
||||||
private USBTransferProcessor mUSBTransferProcessor;
|
private USBTransferProcessor mUSBTransferProcessor;
|
||||||
|
|
||||||
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
|
private HackRFSampleRate mSampleRate = HackRFSampleRate.RATE2_016MHZ;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class USBMasterProcessor
|
|||||||
{
|
{
|
||||||
if(mRunning.compareAndSet(false, true))
|
if(mRunning.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
mLog.warn("Starting USB master processor thread");
|
mLog.info("Starting USB master processor thread");
|
||||||
if(mProcessorThread == null)
|
if(mProcessorThread == null)
|
||||||
{
|
{
|
||||||
mProcessor.reset();
|
mProcessor.reset();
|
||||||
@@ -110,7 +110,7 @@ public class USBMasterProcessor
|
|||||||
{
|
{
|
||||||
if(mRunning.compareAndSet(true, false))
|
if(mRunning.compareAndSet(true, false))
|
||||||
{
|
{
|
||||||
mLog.warn("Stopping USB master processor thread");
|
mLog.info("Stopping USB master processor thread");
|
||||||
if(mProcessorThread != null)
|
if(mProcessorThread != null)
|
||||||
{
|
{
|
||||||
mProcessor.stop();
|
mProcessor.stop();
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdr-trunk
|
||||||
|
* Copyright (C) 2014-2018 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 io.github.dsheirer.source.tuner.usb.converter;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
|
public class SignedByteSampleConverter extends NativeBufferConverter
|
||||||
|
{
|
||||||
|
private final static float[] LOOKUP_VALUES;
|
||||||
|
|
||||||
|
//Creates a static lookup table that converts the signed 8-bit valued range from -128 to 127 into scaled float values
|
||||||
|
//of -1.0 to 0 to 1.0
|
||||||
|
static
|
||||||
|
{
|
||||||
|
LOOKUP_VALUES = new float[256];
|
||||||
|
|
||||||
|
for(int x = 0; x < 256; x++)
|
||||||
|
{
|
||||||
|
LOOKUP_VALUES[x] = (float)((byte)x) / 128.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FloatBuffer mFloatBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts native byte buffers containing signed 8-bit complex samples into complex float samples loaded into a tracked,
|
||||||
|
* reusable complex sample buffer. Internally tracks the reusable buffer until all downstream consumers have finished
|
||||||
|
* processing the buffer contents and then reuses the buffer (and memory) for subsequent samples.
|
||||||
|
*/
|
||||||
|
public SignedByteSampleConverter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the signed 8-bit complex samples contained in the native buffer into floats that are loaded into a float
|
||||||
|
* buffer and subsequently transferred to a reusable complex buffer by the parent class.
|
||||||
|
*
|
||||||
|
* @param nativeBuffer containing signed 8-bit complex samples
|
||||||
|
* @param length of bytes to read from the native buffer
|
||||||
|
* @return float buffer loaded with converted samples
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected FloatBuffer convertSamples(ByteBuffer nativeBuffer, int length)
|
||||||
|
{
|
||||||
|
nativeBuffer.rewind();
|
||||||
|
|
||||||
|
if(mFloatBuffer == null || mFloatBuffer.capacity() != nativeBuffer.capacity())
|
||||||
|
{
|
||||||
|
mFloatBuffer = FloatBuffer.allocate(nativeBuffer.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
mFloatBuffer.rewind();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while(nativeBuffer.hasRemaining() && count < length)
|
||||||
|
{
|
||||||
|
byte sample = nativeBuffer.get();
|
||||||
|
count++;
|
||||||
|
|
||||||
|
mFloatBuffer.put(LOOKUP_VALUES[(sample & 0xFF)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFloatBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ package io.github.dsheirer.util;
|
|||||||
public class StringUtils
|
public class StringUtils
|
||||||
{
|
{
|
||||||
private static final String[] ILLEGAL_FILENAME_CHARACTERS = {"#", "%", "&", "{", "}", "\\", "<", ">",
|
private static final String[] ILLEGAL_FILENAME_CHARACTERS = {"#", "%", "&", "{", "}", "\\", "<", ">",
|
||||||
"*", "?", "/", " ", "$", "!", "'", "\"", ":", "@", "+", "`", "|", "="};
|
"*", "?", "/", " ", "$", "!", "'", "\"", ":", "@", "+", "`", "|", "=", ","};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares string a to b and returns true if a is non-null and non-empty, b is non-null and non-empty, and
|
* Compares string a to b and returns true if a is non-null and non-empty, b is non-null and non-empty, and
|
||||||
|
|||||||
Reference in New Issue
Block a user