#438 Adds feature for duplicate call audio detection and suppression for playback, recording and streaming. (#835)

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-05-27 06:08:53 -04:00
committed by GitHub
parent ad194d1bab
commit a1e8c761ef
16 changed files with 858 additions and 33 deletions

View File

@@ -21,6 +21,7 @@ package io.github.dsheirer.gui;
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.DuplicateCallDetector;
import io.github.dsheirer.audio.broadcast.AudioStreamingManager;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastStatusPanel;
@@ -180,6 +181,9 @@ public class SDRTrunk implements Listener<TunerEvent>
mUserPreferences);
mAudioStreamingManager.start();
DuplicateCallDetector duplicateCallDetector = new DuplicateCallDetector(mUserPreferences);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(duplicateCallDetector);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(audioPlaybackManager);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioRecordingManager);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioStreamingManager);

View File

@@ -21,6 +21,7 @@ package io.github.dsheirer.gui.preference;
import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor;
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
import io.github.dsheirer.gui.preference.duplicate.DuplicateCallPreferenceEditor;
import io.github.dsheirer.gui.preference.playback.PlaybackPreferenceEditor;
import io.github.dsheirer.gui.preference.record.RecordPreferenceEditor;
import io.github.dsheirer.gui.preference.tuner.ChannelMultipleFrequencyPreferenceEditor;
@@ -37,6 +38,8 @@ public class PreferenceEditorFactory
{
switch(preferenceEditorType)
{
case AUDIO_DUPLICATE_CALL_DETECTION:
return new DuplicateCallPreferenceEditor(userPreferences);
case AUDIO_OUTPUT:
return new PlaybackPreferenceEditor(userPreferences);
case AUDIO_RECORD:

View File

@@ -29,6 +29,7 @@ public enum PreferenceEditorType
JMBE_LIBRARY("JMBE Audio Library"),
AUDIO_RECORD("Record"),
AUDIO_OUTPUT("Output/Tones"),
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
SOURCE_CHANNEL_MULTIPLE_FREQUENCY("Channel - Multiple Frequency"),
SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"),
TALKGROUP_FORMAT("Talkgroup & Radio ID"),

View File

@@ -167,6 +167,7 @@ public class UserPreferencesEditor extends BorderPane
TreeItem<String> treeRoot = new TreeItem<>("Root node");
TreeItem<String> audioItem = new TreeItem<>("Audio");
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_CALL_DETECTION));
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_OUTPUT));
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_RECORD));
treeRoot.getChildren().add(audioItem);

View File

@@ -0,0 +1,215 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.gui.preference.duplicate;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Preference settings for duplicate call audio handling
*/
public class DuplicateCallPreferenceEditor extends HBox
{
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallPreferenceEditor.class);
private DuplicateCallDetectionPreference mPreference;
private GridPane mEditorPane;
private ToggleSwitch mDetectDuplicateTalkgroups;
private ToggleSwitch mDetectDuplicateRadios;
private ToggleSwitch mSuppressDuplicateListening;
private ToggleSwitch mSuppressDuplicateRecording;
private ToggleSwitch mSuppressDuplicateStreaming;
/**
* Constructs an instance
*/
public DuplicateCallPreferenceEditor(UserPreferences userPreferences)
{
mPreference = userPreferences.getDuplicateCallDetectionPreference();
HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
getChildren().add(getEditorPane());
}
private GridPane getEditorPane()
{
if(mEditorPane == null)
{
int row = 0;
mEditorPane = new GridPane();
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
mEditorPane.setHgap(10);
mEditorPane.setVgap(10);
Label detectionLabel = new Label("Duplicate Call Detection. Detect duplicate" +
" calls across channels that share a common System name in each channel configuration.");
detectionLabel.setWrapText(true);
GridPane.setConstraints(detectionLabel, 0, row, 2, 1);
mEditorPane.getChildren().add(detectionLabel);
GridPane.setConstraints(getDetectDuplicateTalkgroups(), 0, ++row);
mEditorPane.getChildren().add(getDetectDuplicateTalkgroups());
Label talkgroupLabel = new Label("Talkgroup. Detect duplicate calls by matching talkgroup or " +
"patchgroup values");
talkgroupLabel.setWrapText(true);
GridPane.setConstraints(talkgroupLabel, 1, row);
mEditorPane.getChildren().add(talkgroupLabel);
GridPane.setConstraints(getDetectDuplicateRadios(), 0, ++row);
mEditorPane.getChildren().add(getDetectDuplicateRadios());
Label radioLabel = new Label("Radio ID. Detect duplicate calls by matching radio identifiers.");
radioLabel.setWrapText(true);
GridPane.setConstraints(radioLabel, 1, row);
mEditorPane.getChildren().add(radioLabel);
Label warningLabel = new Label("Note: be careful when enabling duplicate call detection by Radio ID " +
"because this can produce unintended side-effects. For example, if you have two talkgroups with " +
"talkgroup 1 set to record and talkgroup 2 set to stream and dispatch radio ID 1234 makes a " +
"simultaneous call to both talkgroup 1 and talkgroup 2, there is no way to control which call audio " +
"(talkgroup 1 or 2) gets flagged as the duplicate call and therefore either the audio for talkgroup 1 " +
"doesn't record, or the audio for talkgroup 2 doesn't stream.");
warningLabel.setWrapText(true);
GridPane.setConstraints(warningLabel, 0, ++row, 2, 1);
mEditorPane.getChildren().add(warningLabel);
Separator separator = new Separator();
GridPane.setHgrow(separator, Priority.ALWAYS);
GridPane.setConstraints(separator, 0, ++row, 2, 1);
mEditorPane.getChildren().add(separator);
Label suppressionLabel = new Label("Duplicate Call Suppression. When duplicate call audio is detected, " +
"suppress the audio during:");
suppressionLabel.setWrapText(true);
GridPane.setConstraints(suppressionLabel, 0, ++row, 2, 1);
mEditorPane.getChildren().add(suppressionLabel);
GridPane.setConstraints(getSuppressDuplicateListening(), 0, ++row);
mEditorPane.getChildren().add(getSuppressDuplicateListening());
Label listeningLabel = new Label("Listening");
listeningLabel.setWrapText(true);
GridPane.setConstraints(listeningLabel, 1, row);
mEditorPane.getChildren().add(listeningLabel);
GridPane.setConstraints(getSuppressDuplicateRecording(), 0, ++row);
mEditorPane.getChildren().add(getSuppressDuplicateRecording());
Label recordingLabel = new Label("Recording");
recordingLabel.setWrapText(true);
GridPane.setConstraints(recordingLabel, 1, row);
mEditorPane.getChildren().add(recordingLabel);
GridPane.setConstraints(getSuppressDuplicateStreaming(), 0, ++row);
mEditorPane.getChildren().add(getSuppressDuplicateStreaming());
Label streamingLabel = new Label("Streaming");
streamingLabel.setWrapText(true);
GridPane.setConstraints(streamingLabel, 1, row);
mEditorPane.getChildren().add(streamingLabel);
}
return mEditorPane;
}
private ToggleSwitch getDetectDuplicateTalkgroups()
{
if(mDetectDuplicateTalkgroups == null)
{
mDetectDuplicateTalkgroups = new ToggleSwitch();
mDetectDuplicateTalkgroups.setSelected(mPreference.isDuplicateCallDetectionByTalkgroupEnabled());
mDetectDuplicateTalkgroups.selectedProperty()
.addListener((observable, oldValue, newValue) -> mPreference.setDuplicateCallDetectionByTalkgroupEnabled(newValue));
}
return mDetectDuplicateTalkgroups;
}
private ToggleSwitch getDetectDuplicateRadios()
{
if(mDetectDuplicateRadios == null)
{
mDetectDuplicateRadios = new ToggleSwitch();
mDetectDuplicateRadios.setSelected(mPreference.isDuplicateCallDetectionByRadioEnabled());
mDetectDuplicateRadios.selectedProperty()
.addListener((observable, oldValue, newValue) -> mPreference.setDuplicateCallDetectionByRadioEnabled(newValue));
}
return mDetectDuplicateRadios;
}
private ToggleSwitch getSuppressDuplicateListening()
{
if(mSuppressDuplicateListening == null)
{
mSuppressDuplicateListening = new ToggleSwitch();
mSuppressDuplicateListening.disableProperty()
.bind(Bindings.and(getDetectDuplicateTalkgroups().selectedProperty().not(),
getDetectDuplicateRadios().selectedProperty().not()));
mSuppressDuplicateListening.setSelected(mPreference.isDuplicatePlaybackSuppressionEnabled());
mSuppressDuplicateListening.selectedProperty()
.addListener((observable, oldValue, newValue) -> mPreference.setDuplicatePlaybackSuppressionEnabled(newValue));
}
return mSuppressDuplicateListening;
}
private ToggleSwitch getSuppressDuplicateRecording()
{
if(mSuppressDuplicateRecording == null)
{
mSuppressDuplicateRecording = new ToggleSwitch();
mSuppressDuplicateRecording.disableProperty()
.bind(Bindings.and(getDetectDuplicateTalkgroups().selectedProperty().not(),
getDetectDuplicateRadios().selectedProperty().not()));
mSuppressDuplicateRecording.setSelected(mPreference.isDuplicateRecordingSuppressionEnabled());
mSuppressDuplicateRecording.selectedProperty()
.addListener((observable, oldValue, newValue) -> mPreference.setDuplicateRecordingSuppressionEnabled(newValue));
}
return mSuppressDuplicateRecording;
}
private ToggleSwitch getSuppressDuplicateStreaming()
{
if(mSuppressDuplicateStreaming == null)
{
mSuppressDuplicateStreaming = new ToggleSwitch();
mSuppressDuplicateStreaming.disableProperty()
.bind(Bindings.and(getDetectDuplicateTalkgroups().selectedProperty().not(),
getDetectDuplicateRadios().selectedProperty().not()));
mSuppressDuplicateStreaming.setSelected(mPreference.isDuplicateStreamingSuppressionEnabled());
mSuppressDuplicateStreaming.selectedProperty()
.addListener((observable, oldValue, newValue) -> mPreference.setDuplicateStreamingSuppressionEnabled(newValue));
}
return mSuppressDuplicateStreaming;
}
}