mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#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:
@@ -81,6 +81,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
{
|
||||
mAudioSegment.completeProperty().set(true);
|
||||
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
|
||||
mAudioSegment.decrementConsumerCount();
|
||||
mAudioSegment = null;
|
||||
}
|
||||
}
|
||||
@@ -103,6 +104,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
if(mAudioSegment == null)
|
||||
{
|
||||
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
|
||||
mAudioSegment.incrementConsumerCount();
|
||||
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
|
||||
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
|
||||
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
|
||||
@@ -74,6 +75,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
private AtomicInteger mConsumerCount = new AtomicInteger();
|
||||
private AliasList mAliasList;
|
||||
private long mStartTimestamp = System.currentTimeMillis();
|
||||
private long mSampleCount = 0;
|
||||
private boolean mDisposing = false;
|
||||
private AudioSegment mLinkedAudioSegment;
|
||||
private int mTimeslot;
|
||||
@@ -107,6 +109,24 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
return mStartTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* End timestamp as calculated from start timestamp and current sample count
|
||||
*/
|
||||
public long getEndTimestamp()
|
||||
{
|
||||
return mStartTimestamp + getDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current duration of this audio segment. Note: this is a dynamic value until the complete property is set to true.
|
||||
*
|
||||
* @return duration in milliseconds
|
||||
*/
|
||||
public long getDuration()
|
||||
{
|
||||
return (mSampleCount / 8); //8 kHz audio generates 8 samples per millisecond
|
||||
}
|
||||
|
||||
/**
|
||||
* The complete property is used by the audio segment producer to signal that the segment is complete and no
|
||||
* additional audio or identifiers will be added to the segment.
|
||||
@@ -118,6 +138,15 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
||||
* an audio segment is a duplicate.
|
||||
*/
|
||||
public BooleanProperty duplicateProperty()
|
||||
{
|
||||
return mDuplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* An observable list of broadcast channels for this audio segment. Broadcast channels are added to this segment
|
||||
* across the life-cycle of the segment. As each new alias identifier is added to the segment, any broadcast
|
||||
@@ -347,6 +376,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
}
|
||||
|
||||
mAudioBuffers.add(audioBuffer);
|
||||
mSampleCount += audioBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,6 +426,23 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio segment has been flagged as a duplicate audio call
|
||||
*/
|
||||
public boolean isDuplicate()
|
||||
{
|
||||
return mDuplicate.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duplicate audio call flag for this audio segment
|
||||
* @param duplicate true if this is a duplicate audio segment
|
||||
*/
|
||||
public void setDuplicate(boolean duplicate)
|
||||
{
|
||||
mDuplicate.set(duplicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes identifier update notifications and updates audio segment properties using the associated aliases
|
||||
* from the alias list.
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.audio;
|
||||
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method.
|
||||
*
|
||||
* Note: system in this context refers to the system name value that is used in channel configurations. All decoder
|
||||
* channels must share the same system name for call duplication detection.
|
||||
*/
|
||||
public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
||||
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
|
||||
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
||||
|
||||
public DuplicateCallDetector(UserPreferences userPreferences)
|
||||
{
|
||||
mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(AudioSegment audioSegment)
|
||||
{
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled())
|
||||
{
|
||||
Identifier identifier = audioSegment.getIdentifierCollection()
|
||||
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||
|
||||
if(identifier instanceof SystemConfigurationIdentifier)
|
||||
{
|
||||
String system = ((SystemConfigurationIdentifier)identifier).getValue();
|
||||
|
||||
synchronized(mDetectorMap)
|
||||
{
|
||||
SystemDuplicateCallDetector detector = mDetectorMap.get(system);
|
||||
|
||||
if(detector == null)
|
||||
{
|
||||
detector = new SystemDuplicateCallDetector();
|
||||
mDetectorMap.put(system, detector);
|
||||
}
|
||||
|
||||
detector.add(audioSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SystemDuplicateCallDetector
|
||||
{
|
||||
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||
private AtomicBoolean mMonitoring = new AtomicBoolean();
|
||||
private ScheduledFuture<?> mProcessorFuture;
|
||||
|
||||
public SystemDuplicateCallDetector()
|
||||
{
|
||||
}
|
||||
|
||||
public void add(AudioSegment audioSegment)
|
||||
{
|
||||
//Block on audio segment queue so that we don't interfere with monitoring shutdown
|
||||
synchronized(mAudioSegmentQueue)
|
||||
{
|
||||
mAudioSegmentQueue.add(audioSegment);
|
||||
startMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
private void startMonitoring()
|
||||
{
|
||||
if(mMonitoring.compareAndSet(false, true))
|
||||
{
|
||||
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> process(),
|
||||
0, 25, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopMonitoring()
|
||||
{
|
||||
if(mMonitoring.compareAndSet(true, false))
|
||||
{
|
||||
if(mProcessorFuture != null)
|
||||
{
|
||||
mProcessorFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if either the TO or FROM integer identifiers for the audio segments are the same
|
||||
* @param segment1 to compare
|
||||
* @param segment2 to compare
|
||||
* @return true if integer identifiers are the same
|
||||
*/
|
||||
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
||||
{
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
||||
{
|
||||
//Step 1 check for duplicate TO values
|
||||
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
List<Identifier> to2 = segment2.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
|
||||
if(isDuplicate(to1, to2))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByRadioEnabled())
|
||||
{
|
||||
//Step 2 check for duplicate FROM values
|
||||
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||
List<Identifier> from2 = segment2.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||
|
||||
return isDuplicate(from1, from2);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks both lists of identifiers to determine if there are talkgroups or radio identifiers that are
|
||||
* the same in both lists. Note: talkgroup check compares both talkgroups and patch groups.
|
||||
*
|
||||
* @param identifiers1
|
||||
* @param identifiers2
|
||||
* @return
|
||||
*/
|
||||
private boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
|
||||
{
|
||||
for(Identifier identifier1: identifiers1)
|
||||
{
|
||||
if(identifier1 instanceof TalkgroupIdentifier)
|
||||
{
|
||||
int talkgroup1 = ((TalkgroupIdentifier)identifier1).getValue();
|
||||
|
||||
for(Identifier identifier2: identifiers2)
|
||||
{
|
||||
if(identifier2 instanceof TalkgroupIdentifier &&
|
||||
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(identifier2 instanceof PatchGroupIdentifier &&
|
||||
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(identifier1 instanceof PatchGroupIdentifier)
|
||||
{
|
||||
int talkgroup1 = ((PatchGroupIdentifier)identifier1).getValue().getPatchGroup().getValue();
|
||||
|
||||
for(Identifier identifier2: identifiers2)
|
||||
{
|
||||
if(identifier2 instanceof TalkgroupIdentifier &&
|
||||
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(identifier2 instanceof PatchGroupIdentifier &&
|
||||
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(identifier1 instanceof RadioIdentifier)
|
||||
{
|
||||
int radio1 = ((RadioIdentifier)identifier1).getValue();
|
||||
|
||||
for(Identifier identifier2: identifiers2)
|
||||
{
|
||||
if(identifier2 instanceof RadioIdentifier && ((RadioIdentifier)identifier2).getValue() == radio1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes audio segments to detect duplicates
|
||||
*/
|
||||
private void process()
|
||||
{
|
||||
//Transfer in newly arrived audio segments
|
||||
mAudioSegmentQueue.drainTo(mAudioSegments);
|
||||
|
||||
//Remove any completed audio segments.
|
||||
mAudioSegments.removeIf(audioSegment -> {
|
||||
boolean complete = audioSegment.completeProperty().get();
|
||||
|
||||
if(complete)
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
|
||||
return complete;
|
||||
});
|
||||
|
||||
//Only check for duplicates if there is more than one call
|
||||
if(mAudioSegments.size() > 1)
|
||||
{
|
||||
List<AudioSegment> duplicates = new ArrayList<>();
|
||||
|
||||
int currentIndex = 0;
|
||||
while(currentIndex < mAudioSegments.size() - 1)
|
||||
{
|
||||
AudioSegment current = mAudioSegments.get(currentIndex);
|
||||
|
||||
if(!current.isDuplicate())
|
||||
{
|
||||
int checkIndex = currentIndex + 1;
|
||||
|
||||
while(checkIndex < mAudioSegments.size())
|
||||
{
|
||||
AudioSegment toCheck = mAudioSegments.get(checkIndex);
|
||||
|
||||
if(!toCheck.isDuplicate())
|
||||
{
|
||||
if(isDuplicate(current, toCheck))
|
||||
{
|
||||
toCheck.setDuplicate(true);
|
||||
toCheck.decrementConsumerCount();
|
||||
duplicates.add(toCheck);
|
||||
}
|
||||
}
|
||||
|
||||
checkIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
mAudioSegments.removeAll(duplicates);
|
||||
}
|
||||
|
||||
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
|
||||
if(mAudioSegments.isEmpty())
|
||||
{
|
||||
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
|
||||
//allow the add(segment) to restart monitoring as soon as needed.
|
||||
synchronized(mAudioSegmentQueue)
|
||||
{
|
||||
if(mAudioSegmentQueue.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
stopMonitoring();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Unexpected error during duplicate audio segment monitoring shutdown", e);
|
||||
//Do nothing, we got interrupted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,12 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
{
|
||||
audioSegment = it.next();
|
||||
|
||||
if(audioSegment.completeProperty().get())
|
||||
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled())
|
||||
{
|
||||
it.remove();
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
else if(audioSegment.completeProperty().get())
|
||||
{
|
||||
it.remove();
|
||||
|
||||
|
||||
@@ -381,8 +381,9 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
|
||||
try
|
||||
{
|
||||
//Remove an assigned audio segment that's subsequently flagged as do not monitor before playback
|
||||
if(mNextAudioSegment.isDoNotMonitor())
|
||||
//Remove an assigned segment that's subsequently flagged as duplicate or do not monitor before playback
|
||||
if(mNextAudioSegment.isDoNotMonitor() || (mNextAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
{
|
||||
mNextAudioSegment = null;
|
||||
|
||||
@@ -456,9 +457,11 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
|
||||
if(mCurrentAudioSegment != null)
|
||||
{
|
||||
//Check for completed audio segment or a segment flagged as Do Not Monitor
|
||||
if(mCurrentAudioSegment.isDoNotMonitor() || (mCurrentAudioSegment.completeProperty().get() &&
|
||||
mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||
//Check for completed audio segment or a segment flagged as duplicate or Do Not Monitor
|
||||
if(mCurrentAudioSegment.isDoNotMonitor() ||
|
||||
(mCurrentAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()) ||
|
||||
(mCurrentAudioSegment.completeProperty().get() && mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||
{
|
||||
if(mCurrentAudioSegment.isDoNotMonitor())
|
||||
{
|
||||
@@ -490,7 +493,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
//segment is a linked segment, ignore it so that we can close out the current segment.
|
||||
while(mCurrentAudioSegment != null && (mNextAudioSegment == null || mNextAudioSegment.isLinked()) &&
|
||||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount() &&
|
||||
!mCurrentAudioSegment.isDoNotMonitor())
|
||||
!mCurrentAudioSegment.isDoNotMonitor() && !(mCurrentAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
{
|
||||
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
||||
|
||||
|
||||
@@ -27,20 +27,15 @@ import io.github.dsheirer.audio.IAudioController;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
||||
import io.github.dsheirer.source.mixer.MixerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -123,7 +118,12 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
|
||||
while(newSegment != null)
|
||||
{
|
||||
if(newSegment.hasAudio())
|
||||
if(newSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
|
||||
{
|
||||
newSegment.decrementConsumerCount();
|
||||
}
|
||||
else if(newSegment.hasAudio())
|
||||
{
|
||||
mAudioSegments.add(newSegment);
|
||||
}
|
||||
@@ -146,7 +146,13 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
{
|
||||
audioSegment = it.next();
|
||||
|
||||
if(audioSegment.hasAudio())
|
||||
if(audioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
|
||||
{
|
||||
it.remove();
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
else if(audioSegment.hasAudio())
|
||||
{
|
||||
//Queue it up for replay
|
||||
it.remove();
|
||||
@@ -174,7 +180,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
{
|
||||
audioSegment = it.next();
|
||||
|
||||
if(audioSegment.isDoNotMonitor())
|
||||
if(audioSegment.isDoNotMonitor() || (audioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
{
|
||||
it.remove();
|
||||
audioSegment.decrementConsumerCount();
|
||||
@@ -235,7 +242,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
{
|
||||
audioSegment = it.next();
|
||||
|
||||
if(audioSegment.completeProperty().get())
|
||||
if(audioSegment.completeProperty().get() || (audioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
{
|
||||
it.remove();
|
||||
audioSegment.decrementConsumerCount();
|
||||
|
||||
@@ -49,6 +49,8 @@ import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.SourceManager;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import javafx.application.Platform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -180,7 +182,23 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + "] - " + ce.getMessage());
|
||||
if(channel.getSourceConfiguration() instanceof SourceConfigTuner)
|
||||
{
|
||||
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequency +
|
||||
"] - " + ce.getMessage());
|
||||
}
|
||||
else if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency)
|
||||
{
|
||||
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel
|
||||
.getSourceConfiguration()).getFrequencies();
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequencies +
|
||||
"] - " + ce.getMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + "] - " + ce.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public enum PreferenceType
|
||||
{
|
||||
DECODE_EVENT,
|
||||
DIRECTORY,
|
||||
DUPLICATE_CALL_DETECTION,
|
||||
JMBE_LIBRARY,
|
||||
MULTI_FREQUENCY,
|
||||
PLAYLIST,
|
||||
|
||||
@@ -22,6 +22,7 @@ package io.github.dsheirer.preference;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.preference.decoder.JmbeLibraryPreference;
|
||||
import io.github.dsheirer.preference.directory.DirectoryPreference;
|
||||
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
|
||||
import io.github.dsheirer.preference.event.DecodeEventPreference;
|
||||
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
|
||||
import io.github.dsheirer.preference.javafx.JavaFxPreferences;
|
||||
@@ -52,16 +53,18 @@ import io.github.dsheirer.sample.Listener;
|
||||
*/
|
||||
public class UserPreferences implements Listener<PreferenceType>
|
||||
{
|
||||
private JmbeLibraryPreference mJmbeLibraryPreference;
|
||||
private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
|
||||
private DecodeEventPreference mDecodeEventPreference;
|
||||
private DirectoryPreference mDirectoryPreference;
|
||||
private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
|
||||
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
|
||||
private JmbeLibraryPreference mJmbeLibraryPreference;
|
||||
private PlaybackPreference mPlaybackPreference;
|
||||
private PlaylistPreference mPlaylistPreference;
|
||||
private RadioReferencePreference mRadioReferencePreference;
|
||||
private RecordPreference mRecordPreference;
|
||||
private TalkgroupFormatPreference mTalkgroupFormatPreference;
|
||||
private TunerPreference mTunerPreference;
|
||||
|
||||
private SwingPreference mSwingPreference = new SwingPreference();
|
||||
private JavaFxPreferences mJavaFxPreferences = new JavaFxPreferences();
|
||||
|
||||
@@ -170,15 +173,24 @@ public class UserPreferences implements Listener<PreferenceType>
|
||||
return mSwingPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate call detection preferences
|
||||
*/
|
||||
public DuplicateCallDetectionPreference getDuplicateCallDetectionPreference()
|
||||
{
|
||||
return mDuplicateCallDetectionPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the managed preferences
|
||||
*/
|
||||
private void loadPreferenceTypes()
|
||||
{
|
||||
mDecodeEventPreference = new DecodeEventPreference(this::receive);
|
||||
mJmbeLibraryPreference = new JmbeLibraryPreference(this::receive);
|
||||
mDirectoryPreference = new DirectoryPreference(this::receive);
|
||||
mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this::receive);
|
||||
mDecodeEventPreference = new DecodeEventPreference(this::receive);
|
||||
mDirectoryPreference = new DirectoryPreference(this::receive);
|
||||
mDuplicateCallDetectionPreference = new DuplicateCallDetectionPreference(this::receive);
|
||||
mJmbeLibraryPreference = new JmbeLibraryPreference(this::receive);
|
||||
mPlaybackPreference = new PlaybackPreference(this::receive);
|
||||
mPlaylistPreference = new PlaylistPreference(this::receive, mDirectoryPreference);
|
||||
mRadioReferencePreference = new RadioReferencePreference(this::receive);
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.preference.duplicate;
|
||||
|
||||
import io.github.dsheirer.preference.Preference;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* User preferences for duplicate call detection
|
||||
*/
|
||||
public class DuplicateCallDetectionPreference extends Preference
|
||||
{
|
||||
private static final String PREFERENCE_KEY_DETECT_DUPLICATE_TALKGROUP = "duplicate.call.detect.talkgroup";
|
||||
private static final String PREFERENCE_KEY_DETECT_DUPLICATE_RADIO = "duplicate.call.detect.radio";
|
||||
private static final String PREFERENCE_KEY_SUPPRESS_DUPLICATE_PLAYBACK = "suppress.duplicate.audio.playback";
|
||||
private static final String PREFERENCE_KEY_SUPPRESS_DUPLICATE_RECORDING = "suppress.duplicate.audio.recording";
|
||||
private static final String PREFERENCE_KEY_SUPPRESS_DUPLICATE_STREAMING = "suppress.duplicate.audio.streaming";
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetectionPreference.class);
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(DuplicateCallDetectionPreference.class);
|
||||
private Boolean mDuplicateCallDetectionByTalkgroupEnabled;
|
||||
private Boolean mDuplicateCallDetectionByRadioEnabled;
|
||||
private Boolean mDuplicatePlaybackSuppressionEnabled;
|
||||
private Boolean mDuplicateRecordingSuppressionEnabled;
|
||||
private Boolean mDuplicateStreamingSuppressionEnabled;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param updateListener to receive notifications that a preference has been updated
|
||||
*/
|
||||
public DuplicateCallDetectionPreference(Listener<PreferenceType> updateListener)
|
||||
{
|
||||
super(updateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceType getPreferenceType()
|
||||
{
|
||||
return PreferenceType.DUPLICATE_CALL_DETECTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if duplicate call detection is enabled for either talkgroups or radio identifiers.
|
||||
*/
|
||||
public boolean isDuplicateCallDetectionEnabled()
|
||||
{
|
||||
return isDuplicateCallDetectionByTalkgroupEnabled() || isDuplicateCallDetectionByRadioEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preference for detecting duplicate calls by talkgroup or patchgroup
|
||||
*/
|
||||
public boolean isDuplicateCallDetectionByTalkgroupEnabled()
|
||||
{
|
||||
if(mDuplicateCallDetectionByTalkgroupEnabled == null)
|
||||
{
|
||||
mDuplicateCallDetectionByTalkgroupEnabled =
|
||||
mPreferences.getBoolean(PREFERENCE_KEY_DETECT_DUPLICATE_TALKGROUP, true);
|
||||
}
|
||||
|
||||
return mDuplicateCallDetectionByTalkgroupEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference for detecting duplicate calls by talkgroup
|
||||
*/
|
||||
public void setDuplicateCallDetectionByTalkgroupEnabled(boolean enabled)
|
||||
{
|
||||
mPreferences.putBoolean(PREFERENCE_KEY_DETECT_DUPLICATE_TALKGROUP, enabled);
|
||||
mDuplicateCallDetectionByTalkgroupEnabled = enabled;
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preference for detecting duplicate calls by radio identifier
|
||||
*/
|
||||
public boolean isDuplicateCallDetectionByRadioEnabled()
|
||||
{
|
||||
if(mDuplicateCallDetectionByRadioEnabled == null)
|
||||
{
|
||||
mDuplicateCallDetectionByRadioEnabled = mPreferences.getBoolean(PREFERENCE_KEY_DETECT_DUPLICATE_RADIO, false);
|
||||
}
|
||||
|
||||
return mDuplicateCallDetectionByRadioEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference for detecting duplicate calls by radio identifier
|
||||
*/
|
||||
public void setDuplicateCallDetectionByRadioEnabled(boolean enabled)
|
||||
{
|
||||
mPreferences.putBoolean(PREFERENCE_KEY_DETECT_DUPLICATE_RADIO, enabled);
|
||||
mDuplicateCallDetectionByRadioEnabled = enabled;
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preference for suppressig playback of duplicate calls.
|
||||
*/
|
||||
public boolean isDuplicatePlaybackSuppressionEnabled()
|
||||
{
|
||||
if(mDuplicatePlaybackSuppressionEnabled == null)
|
||||
{
|
||||
mDuplicatePlaybackSuppressionEnabled = mPreferences.getBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_PLAYBACK, true);
|
||||
}
|
||||
|
||||
return mDuplicatePlaybackSuppressionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference for suppressing playback of duplicate calls.
|
||||
*/
|
||||
public void setDuplicatePlaybackSuppressionEnabled(boolean enabled)
|
||||
{
|
||||
mPreferences.putBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_PLAYBACK, enabled);
|
||||
mDuplicatePlaybackSuppressionEnabled = enabled;
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preference for suppressing recording of duplicate calls.
|
||||
*/
|
||||
public boolean isDuplicateRecordingSuppressionEnabled()
|
||||
{
|
||||
if(mDuplicateRecordingSuppressionEnabled == null)
|
||||
{
|
||||
mDuplicateRecordingSuppressionEnabled = mPreferences.getBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_RECORDING, true);
|
||||
}
|
||||
|
||||
return mDuplicateRecordingSuppressionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference for suppressing recording of duplicate calls.
|
||||
*/
|
||||
public void setDuplicateRecordingSuppressionEnabled(boolean enabled)
|
||||
{
|
||||
mPreferences.putBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_RECORDING, enabled);
|
||||
mDuplicateRecordingSuppressionEnabled = enabled;
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preference for suppressing streaming of duplicate calls
|
||||
*/
|
||||
public boolean isDuplicateStreamingSuppressionEnabled()
|
||||
{
|
||||
if(mDuplicateStreamingSuppressionEnabled == null)
|
||||
{
|
||||
mDuplicateStreamingSuppressionEnabled = mPreferences.getBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_STREAMING, true);
|
||||
}
|
||||
|
||||
return mDuplicateStreamingSuppressionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference for suppressing streaming of duplicate calls
|
||||
*/
|
||||
public void setDuplicateStreamingSuppressionEnabled(boolean enabled)
|
||||
{
|
||||
mPreferences.putBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_STREAMING, enabled);
|
||||
mDuplicateStreamingSuppressionEnabled = enabled;
|
||||
notifyPreferenceUpdated();;
|
||||
}
|
||||
}
|
||||
@@ -139,21 +139,28 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
||||
|
||||
while(audioSegment != null)
|
||||
{
|
||||
Path path = getAudioRecordingPath(audioSegment.getIdentifierCollection(), recordFormat);
|
||||
|
||||
try
|
||||
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateRecordingSuppressionEnabled())
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, recordFormat);
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
else
|
||||
{
|
||||
mLog.error("Error recording audio segment to [" + path.toString() + "]");
|
||||
Path path = getAudioRecordingPath(audioSegment.getIdentifierCollection(), recordFormat);
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, recordFormat);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error recording audio segment to [" + path.toString() + "]");
|
||||
}
|
||||
|
||||
audioSegment.decrementConsumerCount();
|
||||
|
||||
//Grab the next one to record
|
||||
audioSegment = mCompletedAudioSegmentQueue.poll();
|
||||
}
|
||||
|
||||
audioSegment.decrementConsumerCount();
|
||||
|
||||
//Grab the next one to record
|
||||
audioSegment = mCompletedAudioSegmentQueue.poll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user