Compare commits

...

14 Commits

Author SHA1 Message Date
Denny Sheirer
2e37f1e687 Updates gradle wrapper to 6.3. Updates embeded java to JDK 14 for building platform releases. (#729)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-04 09:22:13 -04:00
Denny Sheirer
5e8f2d0523 Updates build to 0.4.0 final and updates CHANGELOG revision history. (#727)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-04 08:19:29 -04:00
crsawyer
9fb6c33319 Fix HackRF-JawBreaker and HackRF sample conversion (#725) 2020-04-04 08:02:17 -04:00
Denny Sheirer
fdecac4230 Resovles #721 Suppress RID values of 0. (#723)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-01 05:05:28 -04:00
Denny Sheirer
85c27a6cca Resolves #719 P25 Phase 2 display channel number (#722)
Co-authored-by: Denny <denny@denny-desktop>
2020-04-01 03:28:26 -04:00
Denny Sheirer
57f56d31a2 #713 Update Version 0.4.0 Beta 8 (#714)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 12:06:23 -04:00
Denny Sheirer
df25675d3f Resolves #707 separate DTMF tones of the same value get merged together (#712)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 12:03:47 -04:00
Denny Sheirer
277a84702a Resolves #580 channel map selection and editing (#711)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 11:52:39 -04:00
Denny Sheirer
c651296378 Resolves #709 MPT1327 no audio on traffic channels. (#710)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 11:33:15 -04:00
Denny Sheirer
866d25e86a #705 Updates release version 0.4.0 beta 7 (#706)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 06:17:06 -04:00
Denny Sheirer
5e62f1b889 Resolves #677 Streaming Audio Metadata UNKNOWN/UNKNOWN (#704)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 05:58:21 -04:00
Denny Sheirer
e9d524ad14 Resolves #699 Channel Multi-Frequency rotation delay user preference does not persist. (#703)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-28 05:53:58 -04:00
Denny Sheirer
158c8b4b8a #701 P25 Phase 2 Tone Metadata and Channel State (#702)
* Resolves #701 Updates P25P2 Tone metadata handling and P25P2 channel state management.
2020-03-28 05:51:11 -04:00
Denny Sheirer
41df207bb9 Resolves #694. Address null pointer when no system audio devices are available. (#695)
Co-authored-by: Denny <denny@denny-desktop>
2020-03-23 04:45:06 -04:00
43 changed files with 794 additions and 718 deletions

2
.idea/misc.xml generated
View File

@@ -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>

View File

@@ -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

View File

@@ -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)

Binary file not shown.

View File

@@ -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
View File

@@ -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
View File

@@ -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

View File

@@ -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()

View File

@@ -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;

View File

@@ -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();
}
} }

View File

@@ -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)

View File

@@ -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);
} }
} }

View File

@@ -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:

View File

@@ -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());
} }

View File

@@ -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;

View File

@@ -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:

View File

@@ -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;
} }

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
} }
} }

View File

@@ -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());
} }
} }

View File

@@ -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()
{ {

View File

@@ -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

View File

@@ -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));
} }
} }

View File

@@ -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
} }
} }
} }
} }

View File

@@ -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("/-----");
} }

View File

@@ -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;

View File

@@ -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;
}
} }
} }

View File

@@ -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();
} }
} }

View File

@@ -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()
{ {

View File

@@ -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");
} }
} }

View File

@@ -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

View File

@@ -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());

View File

@@ -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

View File

@@ -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:

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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