mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 17:47:32 -05:00
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,6 +27,7 @@ import io.github.dsheirer.identifier.Identifier;
|
|||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
|
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -68,6 +69,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
|
||||||
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
|
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
|
||||||
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
|
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
|
||||||
|
private BooleanProperty mEncrypted = new SimpleBooleanProperty(false);
|
||||||
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
|
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
|
||||||
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||||
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
|
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
|
||||||
@@ -129,6 +131,25 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
return (mSampleCount / 8); //8 kHz audio generates 8 samples per millisecond
|
return (mSampleCount / 8); //8 kHz audio generates 8 samples per millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio segment contains encrypted audio.
|
||||||
|
*
|
||||||
|
* @return encrypted property
|
||||||
|
*/
|
||||||
|
public BooleanProperty encryptedProperty()
|
||||||
|
{
|
||||||
|
return mEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this audio segment is encrypted
|
||||||
|
* @return true if encrypted.
|
||||||
|
*/
|
||||||
|
public boolean isEncrypted()
|
||||||
|
{
|
||||||
|
return mEncrypted.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The complete property is used by the audio segment producer to signal that the segment is complete and no
|
* 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.
|
* additional audio or identifiers will be added to the segment.
|
||||||
@@ -386,6 +407,11 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
throw new IllegalStateException("Can't add audio to an audio segment that is being disposed");
|
throw new IllegalStateException("Can't add audio to an audio segment that is being disposed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mAudioBuffers.isEmpty())
|
||||||
|
{
|
||||||
|
mStartTimestamp = System.currentTimeMillis() - 20;
|
||||||
|
}
|
||||||
|
|
||||||
mAudioBuffers.add(audioBuffer);
|
mAudioBuffers.add(audioBuffer);
|
||||||
mSampleCount += audioBuffer.length;
|
mSampleCount += audioBuffer.length;
|
||||||
}
|
}
|
||||||
@@ -415,6 +441,14 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
{
|
{
|
||||||
mIdentifierCollection.update(identifier);
|
mIdentifierCollection.update(identifier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we have a late-add encryption key, set the encrypted flag to true.
|
||||||
|
*/
|
||||||
|
if(identifier instanceof EncryptionKeyIdentifier eki)
|
||||||
|
{
|
||||||
|
mEncrypted.set(eki.isEncrypted());
|
||||||
|
}
|
||||||
|
|
||||||
List<Alias> aliases = mAliasList.getAliases(identifier);
|
List<Alias> aliases = mAliasList.getAliases(identifier);
|
||||||
|
|
||||||
for(Alias alias: aliases)
|
for(Alias alias: aliases)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,7 +28,7 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.duplicate.CallManagementPreference;
|
import io.github.dsheirer.preference.duplicate.ICallManagementProvider;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -38,7 +38,8 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -51,18 +52,41 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class DuplicateCallDetector implements Listener<AudioSegment>
|
public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
||||||
private CallManagementPreference mCallManagementPreference;
|
private ICallManagementProvider mCallManagementProvider;
|
||||||
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
||||||
|
protected Listener<AudioSegment> mDuplicateCallDetectionListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param userPreferences to access the duplicate call detection preferences.
|
||||||
|
*/
|
||||||
public DuplicateCallDetector(UserPreferences userPreferences)
|
public DuplicateCallDetector(UserPreferences userPreferences)
|
||||||
{
|
{
|
||||||
mCallManagementPreference = userPreferences.getDuplicateCallDetectionPreference();
|
this(userPreferences.getDuplicateCallDetectionPreference());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param callManagementProvider to provide call management preferences.
|
||||||
|
*/
|
||||||
|
public DuplicateCallDetector(ICallManagementProvider callManagementProvider)
|
||||||
|
{
|
||||||
|
mCallManagementProvider = callManagementProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional listener to be notified each time an audio segment is flagged as duplicate.
|
||||||
|
* @param listener to register
|
||||||
|
*/
|
||||||
|
public void setDuplicateCallDetectionListener(Listener<AudioSegment> listener)
|
||||||
|
{
|
||||||
|
mDuplicateCallDetectionListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive(AudioSegment audioSegment)
|
public void receive(AudioSegment audioSegment)
|
||||||
{
|
{
|
||||||
if(mCallManagementPreference.isDuplicateCallDetectionEnabled())
|
if(mCallManagementProvider.isDuplicateCallDetectionEnabled())
|
||||||
{
|
{
|
||||||
Identifier identifier = audioSegment.getIdentifierCollection()
|
Identifier identifier = audioSegment.getIdentifierCollection()
|
||||||
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||||
@@ -77,7 +101,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
|
|
||||||
if(detector == null)
|
if(detector == null)
|
||||||
{
|
{
|
||||||
detector = new SystemDuplicateCallDetector();
|
detector = new SystemDuplicateCallDetector(mCallManagementProvider, system);
|
||||||
mDetectorMap.put(system, detector);
|
mDetectorMap.put(system, detector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,45 +111,99 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System level duplicate call detector. Uses a scheduled executor to run every 25 ms to compare all ongoing call
|
||||||
|
* audio segments to detect duplicates.
|
||||||
|
*
|
||||||
|
* All audio segments remain in the queue until they are flagged as complete. While in the queue, each call is
|
||||||
|
* compared against the others to detect duplicates. Once all calls are either flagged as complete or flagged as
|
||||||
|
* duplicate and removed, the queue is empty and the monitoring is shutdown until a new audio segment arrives and
|
||||||
|
* then the monitoring starts again.
|
||||||
|
*/
|
||||||
public class SystemDuplicateCallDetector
|
public class SystemDuplicateCallDetector
|
||||||
{
|
{
|
||||||
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
private final LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
private final List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||||
private AtomicBoolean mMonitoring = new AtomicBoolean();
|
|
||||||
private ScheduledFuture<?> mProcessorFuture;
|
private ScheduledFuture<?> mProcessorFuture;
|
||||||
|
private Lock mLock = new ReentrantLock();
|
||||||
|
private boolean mMonitoring = false;
|
||||||
|
private final ICallManagementProvider mCallManagementProvider;
|
||||||
|
private String mSystem;
|
||||||
|
|
||||||
public SystemDuplicateCallDetector()
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param callManagementProvider to check for duplicate monitoring preferences
|
||||||
|
*/
|
||||||
|
public SystemDuplicateCallDetector(ICallManagementProvider callManagementProvider, String system)
|
||||||
{
|
{
|
||||||
|
mCallManagementProvider = callManagementProvider;
|
||||||
|
mSystem = system;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the audio segment to the monitoring queue.
|
||||||
|
* @param audioSegment to add
|
||||||
|
*/
|
||||||
public void add(AudioSegment audioSegment)
|
public void add(AudioSegment audioSegment)
|
||||||
{
|
{
|
||||||
//Block on audio segment queue so that we don't interfere with monitoring shutdown
|
mLock.lock();
|
||||||
synchronized(mAudioSegmentQueue)
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
mAudioSegmentQueue.add(audioSegment);
|
mAudioSegmentQueue.add(audioSegment);
|
||||||
startMonitoring();
|
startMonitoring();
|
||||||
}
|
}
|
||||||
}
|
finally
|
||||||
|
|
||||||
private void startMonitoring()
|
|
||||||
{
|
|
||||||
if(mMonitoring.compareAndSet(false, true))
|
|
||||||
{
|
{
|
||||||
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> process(),
|
mLock.unlock();
|
||||||
0, 25, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the call monitoring thread if it's not already running.
|
||||||
|
*
|
||||||
|
* Note: this method should only be called from a thread with the lock acquired.
|
||||||
|
*/
|
||||||
|
private void startMonitoring()
|
||||||
|
{
|
||||||
|
if(!mMonitoring)
|
||||||
|
{
|
||||||
|
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::process,
|
||||||
|
0, 25, TimeUnit.MILLISECONDS);
|
||||||
|
mMonitoring = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the call monitoring thread if the audio segements queue is empty.
|
||||||
|
*
|
||||||
|
* Note: this should only be called from a separate thread and not from within the scheduled monitoring thread
|
||||||
|
* because cancelling the scheduled timer from within the process() method would kill the thread without
|
||||||
|
* releasing the lock, causing a deadlock.
|
||||||
|
*/
|
||||||
private void stopMonitoring()
|
private void stopMonitoring()
|
||||||
{
|
{
|
||||||
if(mMonitoring.compareAndSet(true, false))
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(mProcessorFuture != null)
|
//Recheck the audio segments queue to make sure we didn't slip in another audio segment before we can
|
||||||
|
//shut down the scheduled monitoring thread.
|
||||||
|
if(mMonitoring && mAudioSegments.isEmpty())
|
||||||
{
|
{
|
||||||
mProcessorFuture.cancel(true);
|
if(mProcessorFuture != null)
|
||||||
|
{
|
||||||
|
mProcessorFuture.cancel(true);
|
||||||
|
mProcessorFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mMonitoring = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,7 +214,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
*/
|
*/
|
||||||
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
||||||
{
|
{
|
||||||
if(mCallManagementPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
if(mCallManagementProvider.isDuplicateCallDetectionByTalkgroupEnabled())
|
||||||
{
|
{
|
||||||
//Step 1 check for duplicate TO values
|
//Step 1 check for duplicate TO values
|
||||||
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||||
@@ -148,7 +226,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mCallManagementPreference.isDuplicateCallDetectionByRadioEnabled())
|
if(mCallManagementProvider.isDuplicateCallDetectionByRadioEnabled())
|
||||||
{
|
{
|
||||||
//Step 2 check for duplicate FROM values
|
//Step 2 check for duplicate FROM values
|
||||||
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||||
@@ -168,53 +246,51 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
* @param identifiers2
|
* @param identifiers2
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
|
public static boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
|
||||||
{
|
{
|
||||||
for(Identifier identifier1: identifiers1)
|
for(Identifier identifier1: identifiers1)
|
||||||
{
|
{
|
||||||
if(identifier1 instanceof TalkgroupIdentifier)
|
if(identifier1 instanceof TalkgroupIdentifier tgId1)
|
||||||
{
|
{
|
||||||
int talkgroup1 = ((TalkgroupIdentifier)identifier1).getValue();
|
int tg1 = tgId1.getValue();
|
||||||
|
|
||||||
for(Identifier identifier2: identifiers2)
|
for(Identifier identifier2: identifiers2)
|
||||||
{
|
{
|
||||||
if(identifier2 instanceof TalkgroupIdentifier &&
|
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == tg1)
|
||||||
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if(identifier2 instanceof PatchGroupIdentifier &&
|
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
|
||||||
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
pgId2.getValue().getPatchGroup().getValue() == tg1)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(identifier1 instanceof PatchGroupIdentifier)
|
else if(identifier1 instanceof PatchGroupIdentifier pgId1)
|
||||||
{
|
{
|
||||||
int talkgroup1 = ((PatchGroupIdentifier)identifier1).getValue().getPatchGroup().getValue();
|
int talkgroup1 = pgId1.getValue().getPatchGroup().getValue();
|
||||||
|
|
||||||
for(Identifier identifier2: identifiers2)
|
for(Identifier identifier2: identifiers2)
|
||||||
{
|
{
|
||||||
if(identifier2 instanceof TalkgroupIdentifier &&
|
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == talkgroup1)
|
||||||
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if(identifier2 instanceof PatchGroupIdentifier &&
|
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
|
||||||
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
pgId2.getValue().getPatchGroup().getValue() == talkgroup1)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(identifier1 instanceof RadioIdentifier)
|
else if(identifier1 instanceof RadioIdentifier raId1)
|
||||||
{
|
{
|
||||||
int radio1 = ((RadioIdentifier)identifier1).getValue();
|
int radio1 = raId1.getValue();
|
||||||
|
|
||||||
for(Identifier identifier2: identifiers2)
|
for(Identifier identifier2: identifiers2)
|
||||||
{
|
{
|
||||||
if(identifier2 instanceof RadioIdentifier && ((RadioIdentifier)identifier2).getValue() == radio1)
|
if(identifier2 instanceof RadioIdentifier raId2 && raId2.getValue() == radio1)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -230,6 +306,8 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
*/
|
*/
|
||||||
private void process()
|
private void process()
|
||||||
{
|
{
|
||||||
|
mLock.lock();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Transfer in newly arrived audio segments
|
//Transfer in newly arrived audio segments
|
||||||
@@ -247,6 +325,19 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
return complete;
|
return complete;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Remove any encrypted audio segments.
|
||||||
|
mAudioSegments.removeIf(audioSegment -> {
|
||||||
|
boolean encrypted = audioSegment.isEncrypted();
|
||||||
|
|
||||||
|
if(encrypted)
|
||||||
|
{
|
||||||
|
audioSegment.decrementConsumerCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return encrypted;
|
||||||
|
});
|
||||||
|
|
||||||
//Only check for duplicates if there is more than one call
|
//Only check for duplicates if there is more than one call
|
||||||
if(mAudioSegments.size() > 1)
|
if(mAudioSegments.size() > 1)
|
||||||
{
|
{
|
||||||
@@ -257,7 +348,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
{
|
{
|
||||||
AudioSegment current = mAudioSegments.get(currentIndex);
|
AudioSegment current = mAudioSegments.get(currentIndex);
|
||||||
|
|
||||||
if(!current.isDuplicate())
|
if(current.hasAudio() && !current.isDuplicate())
|
||||||
{
|
{
|
||||||
int checkIndex = currentIndex + 1;
|
int checkIndex = currentIndex + 1;
|
||||||
|
|
||||||
@@ -272,6 +363,12 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
toCheck.setDuplicate(true);
|
toCheck.setDuplicate(true);
|
||||||
toCheck.decrementConsumerCount();
|
toCheck.decrementConsumerCount();
|
||||||
duplicates.add(toCheck);
|
duplicates.add(toCheck);
|
||||||
|
|
||||||
|
//Notify optional listener that we flagged the call as duplicate.
|
||||||
|
if(mDuplicateCallDetectionListener != null)
|
||||||
|
{
|
||||||
|
mDuplicateCallDetectionListener.receive(toCheck);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,26 +382,12 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
mAudioSegments.removeAll(duplicates);
|
mAudioSegments.removeAll(duplicates);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
|
//Finally, if the audio segment queue is now empty, shutdown monitoring until a new segment arrives.
|
||||||
|
//The monitor shutdown method has to be called on a separate thread so that we don't kill our current
|
||||||
|
// thread and fail to release the lock.
|
||||||
if(mAudioSegments.isEmpty())
|
if(mAudioSegments.isEmpty())
|
||||||
{
|
{
|
||||||
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
|
ThreadPool.CACHED.submit(this::stopMonitoring);
|
||||||
//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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Throwable t)
|
catch(Throwable t)
|
||||||
@@ -312,6 +395,10 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
mLog.error("Unknown error while processing audio segments for duplicate call detection. Please report " +
|
mLog.error("Unknown error while processing audio segments for duplicate call detection. Please report " +
|
||||||
"this to the developer.", t);
|
"this to the developer.", t);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* 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.module.decode.p25.identifier.encryption;
|
package io.github.dsheirer.module.decode.p25.identifier.encryption;
|
||||||
|
|
||||||
@@ -75,4 +72,12 @@ public class APCO25EncryptionKey extends EncryptionKey
|
|||||||
{
|
{
|
||||||
return new APCO25EncryptionKey(algorithm, keyId);
|
return new APCO25EncryptionKey(algorithm, keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new APCO-25 encryption algorithm
|
||||||
|
*/
|
||||||
|
public static APCO25EncryptionKey create(Encryption encryption, int keyId)
|
||||||
|
{
|
||||||
|
return create(encryption.getValue(), keyId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,20 @@ public enum Encryption
|
|||||||
return mLabel;
|
return mLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption type value.
|
||||||
|
* @return value.
|
||||||
|
*/
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to lookup the encryption type from the value.
|
||||||
|
* @param value of the encryption type.
|
||||||
|
* @return enumeration entry or UNKNOWN.
|
||||||
|
*/
|
||||||
public static Encryption fromValue(int value)
|
public static Encryption fromValue(int value)
|
||||||
{
|
{
|
||||||
switch(value)
|
switch(value)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
/**
|
/**
|
||||||
* User preferences for call management
|
* User preferences for call management
|
||||||
*/
|
*/
|
||||||
public class CallManagementPreference extends Preference
|
public class CallManagementPreference extends Preference implements ICallManagementProvider
|
||||||
{
|
{
|
||||||
private static final String PREFERENCE_KEY_DETECT_DUPLICATE_TALKGROUP = "duplicate.call.detect.talkgroup";
|
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_DETECT_DUPLICATE_RADIO = "duplicate.call.detect.radio";
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a call management value provider.
|
||||||
|
*/
|
||||||
|
public interface ICallManagementProvider
|
||||||
|
{
|
||||||
|
boolean isDuplicateCallDetectionEnabled();
|
||||||
|
boolean isDuplicateCallDetectionByTalkgroupEnabled();
|
||||||
|
boolean isDuplicateCallDetectionByRadioEnabled();
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test implementation of duplicate call detection preferences.
|
||||||
|
*/
|
||||||
|
public class TestCallManagementProvider implements ICallManagementProvider
|
||||||
|
{
|
||||||
|
private final boolean mByTalkgroup;
|
||||||
|
private final boolean mByRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param byTalkgroup to enable duplicate detection by talkgroup
|
||||||
|
* @param byRadio to enable duplicate detection by radio
|
||||||
|
*/
|
||||||
|
public TestCallManagementProvider(boolean byTalkgroup, boolean byRadio)
|
||||||
|
{
|
||||||
|
mByTalkgroup = byTalkgroup;
|
||||||
|
mByRadio = byRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDuplicateCallDetectionEnabled()
|
||||||
|
{
|
||||||
|
return mByTalkgroup || mByRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDuplicateCallDetectionByTalkgroupEnabled()
|
||||||
|
{
|
||||||
|
return mByTalkgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDuplicateCallDetectionByRadioEnabled()
|
||||||
|
{
|
||||||
|
return mByRadio;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.alias.AliasList;
|
||||||
|
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey;
|
||||||
|
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.reference.Encryption;
|
||||||
|
import io.github.dsheirer.preference.duplicate.ICallManagementProvider;
|
||||||
|
import io.github.dsheirer.preference.duplicate.TestCallManagementProvider;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit tests for the duplicate call detector.
|
||||||
|
*/
|
||||||
|
public class DuplicateCallDetectionTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test: same source radio to two different talkgroups on the same site where one call is encrytped and the other
|
||||||
|
* call is not.
|
||||||
|
*
|
||||||
|
* Success Criteria: neither call gets flagged as duplicate.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void sameCallOnDifferentSites()
|
||||||
|
{
|
||||||
|
AliasList aliasList = new AliasList("test");
|
||||||
|
AudioSegment audioSegment1 = new AudioSegment(aliasList, 1);
|
||||||
|
audioSegment1.addIdentifier(SystemConfigurationIdentifier.create("Test System"));
|
||||||
|
audioSegment1.addIdentifier(SiteConfigurationIdentifier.create("Test Site 1"));
|
||||||
|
audioSegment1.addIdentifier(APCO25Talkgroup.create(1));
|
||||||
|
audioSegment1.addIdentifier(APCO25RadioIdentifier.createFrom(2));
|
||||||
|
audioSegment1.addAudio(new float[2]);
|
||||||
|
|
||||||
|
AudioSegment audioSegment2 = new AudioSegment(aliasList, 2);
|
||||||
|
audioSegment2.addIdentifier(SystemConfigurationIdentifier.create("Test System"));
|
||||||
|
audioSegment2.addIdentifier(SiteConfigurationIdentifier.create("Test Site 2"));
|
||||||
|
audioSegment2.addIdentifier(APCO25Talkgroup.create(1));
|
||||||
|
audioSegment2.addIdentifier(APCO25RadioIdentifier.createFrom(2));
|
||||||
|
audioSegment1.addAudio(new float[2]);
|
||||||
|
|
||||||
|
boolean testByTalkgroup = true;
|
||||||
|
boolean testByRadio = false;
|
||||||
|
|
||||||
|
ICallManagementProvider provider = new TestCallManagementProvider(testByTalkgroup, testByRadio);
|
||||||
|
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
|
Listener<AudioSegment> callback = audioSegment -> countDownLatch.countDown();
|
||||||
|
|
||||||
|
DuplicateCallDetector duplicateCallDetector = new DuplicateCallDetector(provider);
|
||||||
|
duplicateCallDetector.setDuplicateCallDetectionListener(callback);
|
||||||
|
|
||||||
|
duplicateCallDetector.receive(audioSegment1);
|
||||||
|
duplicateCallDetector.receive(audioSegment2);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Wait up to 100 ms, but the duplicate detector should fire within 25 ms.
|
||||||
|
countDownLatch.await(100, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
catch(InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
audioSegment1.completeProperty().set(true);
|
||||||
|
audioSegment2.completeProperty().set(true);
|
||||||
|
|
||||||
|
//Test that at least one of the audio segments was marked as duplicate.
|
||||||
|
assertTrue(audioSegment1.isDuplicate() || audioSegment2.isDuplicate(),
|
||||||
|
"At least one audio segment should have been flagged as duplicate");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: same call, same site, same source radio, simulcasting to two different talkgroups.
|
||||||
|
*
|
||||||
|
* Success Criteria: one audio segment is flagged as duplicate.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void sameCallSameSiteSameRadioSimulcastToDifferentTalkgroups()
|
||||||
|
{
|
||||||
|
AliasList aliasList = new AliasList("test");
|
||||||
|
|
||||||
|
AudioSegment audioSegment1 = new AudioSegment(aliasList, 1);
|
||||||
|
audioSegment1.addIdentifier(SystemConfigurationIdentifier.create("Test System"));
|
||||||
|
audioSegment1.addIdentifier(SiteConfigurationIdentifier.create("Test Site 1"));
|
||||||
|
audioSegment1.addIdentifier(APCO25Talkgroup.create(1));
|
||||||
|
audioSegment1.addIdentifier(APCO25RadioIdentifier.createFrom(2));
|
||||||
|
audioSegment1.addAudio(new float[2]);
|
||||||
|
|
||||||
|
AudioSegment audioSegment2 = new AudioSegment(aliasList, 2);
|
||||||
|
audioSegment2.addIdentifier(SystemConfigurationIdentifier.create("Test System"));
|
||||||
|
audioSegment2.addIdentifier(SiteConfigurationIdentifier.create("Test Site 1"));
|
||||||
|
audioSegment2.addIdentifier(APCO25Talkgroup.create(2));
|
||||||
|
audioSegment2.addIdentifier(APCO25RadioIdentifier.createFrom(2));
|
||||||
|
audioSegment1.addAudio(new float[2]);
|
||||||
|
|
||||||
|
boolean testByTalkgroup = true;
|
||||||
|
boolean testByRadio = true;
|
||||||
|
ICallManagementProvider provider = new TestCallManagementProvider(testByTalkgroup, testByRadio);
|
||||||
|
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
|
Listener<AudioSegment> callback = audioSegment -> countDownLatch.countDown();
|
||||||
|
|
||||||
|
DuplicateCallDetector duplicateCallDetector = new DuplicateCallDetector(provider);
|
||||||
|
duplicateCallDetector.setDuplicateCallDetectionListener(callback);
|
||||||
|
|
||||||
|
duplicateCallDetector.receive(audioSegment1);
|
||||||
|
duplicateCallDetector.receive(audioSegment2);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Wait up to 100 ms, but the duplicate detector should fire within 25 ms.
|
||||||
|
countDownLatch.await(100, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
catch(InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
audioSegment1.completeProperty().set(true);
|
||||||
|
audioSegment2.completeProperty().set(true);
|
||||||
|
|
||||||
|
//Test that at least one of the audio segments was marked as duplicate.
|
||||||
|
assertTrue(audioSegment1.isDuplicate() || audioSegment2.isDuplicate(),
|
||||||
|
"At least one audio segment should have been flagged as duplicate");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test: same source radio to two different talkgroups on the same site where one call is encrypted and the other
|
||||||
|
* call is not.
|
||||||
|
*
|
||||||
|
* Success Criteria: neither call gets flagged as duplicate.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void sameCallSameSiteSameRadioSimulcastToDifferentTalkgroupsOneIsEncrypted()
|
||||||
|
{
|
||||||
|
AliasList aliasList = new AliasList("test");
|
||||||
|
|
||||||
|
AudioSegment audioSegment1 = new AudioSegment(aliasList, 1);
|
||||||
|
audioSegment1.addIdentifier(SystemConfigurationIdentifier.create("Test System"));
|
||||||
|
audioSegment1.addIdentifier(SiteConfigurationIdentifier.create("Test Site 1"));
|
||||||
|
audioSegment1.addIdentifier(APCO25Talkgroup.create(1));
|
||||||
|
audioSegment1.addIdentifier(APCO25RadioIdentifier.createFrom(2));
|
||||||
|
EncryptionKeyIdentifier eki1 = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(Encryption.AES_256, 1));
|
||||||
|
audioSegment1.addIdentifier(eki1);
|
||||||
|
audioSegment1.addAudio(new float[2]);
|
||||||
|
|
||||||
|
AudioSegment audioSegment2 = new AudioSegment(aliasList, 2);
|
||||||
|
audioSegment2.addIdentifier(SystemConfigurationIdentifier.create("Test System"));
|
||||||
|
audioSegment2.addIdentifier(SiteConfigurationIdentifier.create("Test Site 1"));
|
||||||
|
audioSegment2.addIdentifier(APCO25Talkgroup.create(2));
|
||||||
|
audioSegment2.addIdentifier(APCO25RadioIdentifier.createFrom(2));
|
||||||
|
EncryptionKeyIdentifier eki2 = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(Encryption.UNENCRYPTED, 1));
|
||||||
|
audioSegment2.addIdentifier(eki2);
|
||||||
|
audioSegment1.addAudio(new float[2]);
|
||||||
|
|
||||||
|
boolean testByTalkgroup = true;
|
||||||
|
boolean testByRadio = true;
|
||||||
|
ICallManagementProvider provider = new TestCallManagementProvider(testByTalkgroup, testByRadio);
|
||||||
|
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
|
Listener<AudioSegment> callback = audioSegment -> countDownLatch.countDown();
|
||||||
|
|
||||||
|
DuplicateCallDetector duplicateCallDetector = new DuplicateCallDetector(provider);
|
||||||
|
duplicateCallDetector.setDuplicateCallDetectionListener(callback);
|
||||||
|
|
||||||
|
duplicateCallDetector.receive(audioSegment1);
|
||||||
|
duplicateCallDetector.receive(audioSegment2);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Wait up to 100 ms, but the duplicate detector should fire within 25 ms.
|
||||||
|
countDownLatch.await(100, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
catch(InterruptedException e)
|
||||||
|
{
|
||||||
|
//Don't log the exception ... it's expected
|
||||||
|
}
|
||||||
|
|
||||||
|
audioSegment1.completeProperty().set(true);
|
||||||
|
audioSegment2.completeProperty().set(true);
|
||||||
|
|
||||||
|
//Test that neither audio segment was flagged as duplicate and audio segment 1 is flagged as encrypted.
|
||||||
|
assertTrue(audioSegment1.isEncrypted(), "Audio segment 1 should be flagged as encrypted");
|
||||||
|
assertFalse(audioSegment2.isEncrypted(), "Audio segment 1 should be flagged as unencrypted");
|
||||||
|
assertFalse(audioSegment1.isDuplicate(), "Audio segment should not be flagged as duplicate.");
|
||||||
|
assertFalse(audioSegment2.isDuplicate(), "Audio segment should not be flagged as duplicate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user