Skip to content

Commit 91e5bc8

Browse files
DSheirerDennis Sheirer
andauthored
#1973 JUnit test for patched talkgroup audio call streaming that tests streaming the audio as the patched group or as the individual patched talkgroups according to the user preferences setting in call management. (#2015)
Co-authored-by: Dennis Sheirer <[email protected]>
1 parent 97afdfa commit 91e5bc8

File tree

8 files changed

+243
-21
lines changed

8 files changed

+243
-21
lines changed

src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
6262
*/
6363
public DuplicateCallDetector(UserPreferences userPreferences)
6464
{
65-
this(userPreferences.getDuplicateCallDetectionPreference());
65+
this(userPreferences.getCallManagementPreference());
6666
}
6767

6868
/**

src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* *****************************************************************************
3-
* Copyright (C) 2014-2023 Dennis Sheirer
3+
* Copyright (C) 2014-2024 Dennis Sheirer
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -136,7 +136,7 @@ private void processAudioSegments()
136136
{
137137
audioSegment = it.next();
138138

139-
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled())
139+
if(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled())
140140
{
141141
it.remove();
142142
audioSegment.decrementConsumerCount();
@@ -152,7 +152,7 @@ else if(audioSegment.completeProperty().get())
152152

153153
if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier)
154154
{
155-
if(mUserPreferences.getDuplicateCallDetectionPreference()
155+
if(mUserPreferences.getCallManagementPreference()
156156
.getPatchGroupStreamingOption() == PatchGroupStreamingOption.TALKGROUPS)
157157
{
158158
//Decompose the patch group into the individual (patched) talkgroups and process the audio

src/main/java/io/github/dsheirer/audio/playback/AudioOutput.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public AudioOutput(Mixer mixer, MixerChannel mixerChannel, AudioFormat audioForm
110110
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
111111
"sdrtrunk audio output " + mixerChannel.name()));
112112
mUserPreferences = userPreferences;
113-
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
113+
mDropDuplicates = mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled();
114114
mAudioFormat = audioFormat;
115115
mLineInfo = lineInfo;
116116
mRequestedBufferSize = requestedBufferSize;
@@ -238,7 +238,7 @@ public void preferenceUpdated(PreferenceType preferenceType)
238238
}
239239
else if(preferenceType == PreferenceType.DUPLICATE_CALL_DETECTION)
240240
{
241-
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
241+
mDropDuplicates = mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled();
242242
}
243243
}
244244

src/main/java/io/github/dsheirer/audio/playback/AudioPlaybackManager.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* *****************************************************************************
3-
* Copyright (C) 2014-2023 Dennis Sheirer
3+
* Copyright (C) 2014-2024 Dennis Sheirer
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -120,7 +120,7 @@ private void processAudioSegments()
120120
while(newSegment != null)
121121
{
122122
if(newSegment.isDuplicate() &&
123-
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
123+
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled())
124124
{
125125
newSegment.decrementConsumerCount();
126126
}
@@ -148,7 +148,7 @@ else if(newSegment.hasAudio())
148148
audioSegment = it.next();
149149

150150
if(audioSegment.isDuplicate() &&
151-
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
151+
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled())
152152
{
153153
it.remove();
154154
audioSegment.decrementConsumerCount();
@@ -182,7 +182,7 @@ else if(audioSegment.completeProperty().get())
182182
audioSegment = it.next();
183183

184184
if(audioSegment.isDoNotMonitor() || (audioSegment.isDuplicate() &&
185-
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
185+
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled()))
186186
{
187187
it.remove();
188188
audioSegment.decrementConsumerCount();
@@ -228,7 +228,7 @@ else if(audioSegment.isLinked())
228228
audioSegment = it.next();
229229

230230
if(audioSegment.completeProperty().get() || (audioSegment.isDuplicate() &&
231-
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
231+
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled()))
232232
{
233233
it.remove();
234234
audioSegment.decrementConsumerCount();

src/main/java/io/github/dsheirer/gui/preference/call/CallManagementPreferenceEditor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* *****************************************************************************
3-
* Copyright (C) 2014-2023 Dennis Sheirer
3+
* Copyright (C) 2014-2024 Dennis Sheirer
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -56,7 +56,7 @@ public class CallManagementPreferenceEditor extends HBox
5656
*/
5757
public CallManagementPreferenceEditor(UserPreferences userPreferences)
5858
{
59-
mPreference = userPreferences.getDuplicateCallDetectionPreference();
59+
mPreference = userPreferences.getCallManagementPreference();
6060

6161
HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
6262
getChildren().add(getEditorPane());

src/main/java/io/github/dsheirer/preference/UserPreferences.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* *****************************************************************************
3-
* Copyright (C) 2014-2023 Dennis Sheirer
3+
* Copyright (C) 2014-2024 Dennis Sheirer
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -60,7 +60,7 @@ public class UserPreferences implements Listener<PreferenceType>
6060
private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
6161
private DecodeEventPreference mDecodeEventPreference;
6262
private DirectoryPreference mDirectoryPreference;
63-
private CallManagementPreference mDuplicateCallDetectionPreference;
63+
private CallManagementPreference mCallManagementPreference;
6464
private JmbeLibraryPreference mJmbeLibraryPreference;
6565
private MP3Preference mMP3Preference;
6666
private PlaybackPreference mPlaybackPreference;
@@ -206,11 +206,11 @@ public SwingPreference getSwingPreference()
206206
}
207207

208208
/**
209-
* Duplicate call detection preferences
209+
* Call management and duplicate call detection preferences
210210
*/
211-
public CallManagementPreference getDuplicateCallDetectionPreference()
211+
public CallManagementPreference getCallManagementPreference()
212212
{
213-
return mDuplicateCallDetectionPreference;
213+
return mCallManagementPreference;
214214
}
215215

216216
/**
@@ -222,10 +222,9 @@ private void loadPreferenceTypes()
222222
mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this::receive);
223223
mDecodeEventPreference = new DecodeEventPreference(this::receive);
224224
mDirectoryPreference = new DirectoryPreference(this::receive);
225-
mDuplicateCallDetectionPreference = new CallManagementPreference(this::receive);
225+
mCallManagementPreference = new CallManagementPreference(this::receive);
226226
mJmbeLibraryPreference = new JmbeLibraryPreference(this::receive);
227227
mMP3Preference = new MP3Preference(this::receive);
228-
mPlaybackPreference = new PlaybackPreference(this::receive);
229228
mPlaylistPreference = new PlaylistPreference(this::receive, mDirectoryPreference);
230229
mRadioReferencePreference = new RadioReferencePreference(this::receive);
231230
mRecordPreference = new RecordPreference(this::receive);

src/main/java/io/github/dsheirer/record/AudioRecordingManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ private void processAudioSegments()
140140

141141
while(audioSegment != null)
142142
{
143-
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateRecordingSuppressionEnabled())
143+
if(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateRecordingSuppressionEnabled())
144144
{
145145
audioSegment.decrementConsumerCount();
146146
}
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* *****************************************************************************
3+
* Copyright (C) 2014-2024 Dennis Sheirer
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>
17+
* ****************************************************************************
18+
*/
19+
20+
package io.github.dsheirer.audio.broadcast;
21+
22+
import io.github.dsheirer.alias.Alias;
23+
import io.github.dsheirer.alias.AliasList;
24+
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
25+
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
26+
import io.github.dsheirer.audio.AudioSegment;
27+
import io.github.dsheirer.dsp.oscillator.ScalarRealOscillator;
28+
import io.github.dsheirer.identifier.patch.PatchGroup;
29+
import io.github.dsheirer.identifier.radio.RadioIdentifier;
30+
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
31+
import io.github.dsheirer.message.TimeslotMessage;
32+
import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup;
33+
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
34+
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
35+
import io.github.dsheirer.preference.UserPreferences;
36+
import io.github.dsheirer.protocol.Protocol;
37+
import io.github.dsheirer.sample.Listener;
38+
import java.io.IOException;
39+
import java.nio.file.Files;
40+
import java.nio.file.Path;
41+
import java.util.concurrent.CountDownLatch;
42+
import java.util.concurrent.TimeUnit;
43+
import java.util.stream.Stream;
44+
import org.junit.jupiter.api.Test;
45+
46+
import static org.junit.jupiter.api.Assertions.assertTrue;
47+
48+
/**
49+
* Automated testing for the AudioStreamingManager that includes for testing streaming a patch group audio segment as
50+
* an individual stream aliased against the patch group, or broken up into the set of patched talkgroups and streamed
51+
* according to the aliases for each patched talkgroup.
52+
*/
53+
public class AudioStreamingManagerTest
54+
{
55+
private static final int TALKGROUP_1 = 100;
56+
private static final int TALKGROUP_2 = 200;
57+
private static final int TALKGROUP_3 = 300;
58+
private static final int RADIO_1 = 9999;
59+
60+
@Test
61+
public void testPatchGroupStreamingAsPatchGroup()
62+
{
63+
int expectedRecordingsCount = 1;
64+
65+
//We use a countdown latch to count the number of expected audio recordings produced.
66+
CountDownLatch latch = new CountDownLatch(expectedRecordingsCount);
67+
Listener<AudioRecording> listener = audioRecording -> {
68+
latch.countDown();
69+
};
70+
71+
UserPreferences userPreferences = new UserPreferences();
72+
userPreferences.getCallManagementPreference().setPatchGroupStreamingOption(PatchGroupStreamingOption.PATCH_GROUP);
73+
AudioStreamingManager manager = new AudioStreamingManager(listener, BroadcastFormat.MP3, userPreferences);
74+
manager.start();
75+
manager.receive(getAudioSegment());
76+
77+
boolean success = false;
78+
79+
try
80+
{
81+
success = latch.await(5, TimeUnit.SECONDS);
82+
}
83+
catch(InterruptedException e)
84+
{
85+
throw new RuntimeException(e);
86+
}
87+
88+
cleanupStreamingDirectory(userPreferences.getDirectoryPreference().getDirectoryStreaming());
89+
90+
assertTrue(success, "Stream patch group audio as PATCHED GROUP failed to produce [" +
91+
latch.getCount() + "/" + expectedRecordingsCount + "] streaming recordings");
92+
}
93+
94+
@Test
95+
public void testPatchGroupStreamingAsIndividualGroups()
96+
{
97+
int expectedRecordingsCount = 2;
98+
99+
//We use a countdown latch to count the number of expected audio recordings produced. In this case, we expect
100+
//two audio recordings, one for stream B and one for stream C associated with the two patched talkgroups.
101+
CountDownLatch latch = new CountDownLatch(expectedRecordingsCount);
102+
Listener<AudioRecording> listener = audioRecording -> {
103+
latch.countDown();
104+
};
105+
106+
UserPreferences userPreferences = new UserPreferences();
107+
userPreferences.getCallManagementPreference().setPatchGroupStreamingOption(PatchGroupStreamingOption.TALKGROUPS);
108+
AudioStreamingManager manager = new AudioStreamingManager(listener, BroadcastFormat.MP3, userPreferences);
109+
manager.start();
110+
manager.receive(getAudioSegment());
111+
112+
boolean success = false;
113+
114+
try
115+
{
116+
success = latch.await(5, TimeUnit.SECONDS);
117+
}
118+
catch(InterruptedException e)
119+
{
120+
throw new RuntimeException(e);
121+
}
122+
123+
cleanupStreamingDirectory(userPreferences.getDirectoryPreference().getDirectoryStreaming());
124+
125+
assertTrue(success, "Stream patch group audio as INDIVIDUAL TALKGROUPS failed to produce [" +
126+
latch.getCount() + "/" + expectedRecordingsCount + "] streaming recordings");
127+
}
128+
129+
/**
130+
* Cleanup any generated streaming recordings.
131+
* @param streamingDirectory
132+
*/
133+
private void cleanupStreamingDirectory(Path streamingDirectory)
134+
{
135+
if(Files.exists(streamingDirectory))
136+
{
137+
try(Stream<Path> fileStream = Files.list(streamingDirectory))
138+
{
139+
fileStream.forEach(path -> {
140+
try
141+
{
142+
Files.delete(path);
143+
}
144+
catch(IOException e)
145+
{
146+
e.printStackTrace();
147+
}
148+
});
149+
}
150+
catch(IOException e)
151+
{
152+
e.printStackTrace();
153+
}
154+
}
155+
}
156+
157+
/**
158+
* Creates an audio segment with audio using the supplied alias list.
159+
* @return audio segment
160+
*/
161+
private static AudioSegment getAudioSegment()
162+
{
163+
AliasList aliasList = getAliasList();
164+
165+
AudioSegment audioSegment = new AudioSegment(aliasList, TimeslotMessage.TIMESLOT_0);
166+
ScalarRealOscillator oscillator = new ScalarRealOscillator(1000, 8000);
167+
for(int x = 0; x < 100; x++)
168+
{
169+
audioSegment.addAudio(oscillator.generate(500));
170+
}
171+
audioSegment.addIdentifier(getPatchGroup());
172+
audioSegment.addIdentifier(getRadio());
173+
audioSegment.completeProperty().set(true);
174+
return audioSegment;
175+
}
176+
177+
private static AliasList getAliasList()
178+
{
179+
AliasList aliasList = new AliasList("test");
180+
181+
Alias patchAlias = new Alias("patch");
182+
patchAlias.addAliasID(new Talkgroup(Protocol.APCO25, 100));
183+
patchAlias.addAliasID(new BroadcastChannel("Stream A"));
184+
aliasList.addAlias(patchAlias);
185+
186+
Alias talkgroupAlias1 = new Alias("talkgroup1");
187+
talkgroupAlias1.addAliasID(new Talkgroup(Protocol.APCO25, 200));
188+
talkgroupAlias1.addAliasID(new BroadcastChannel("Stream B"));
189+
aliasList.addAlias(talkgroupAlias1);
190+
191+
Alias talkgroupAlias2 = new Alias("talkgroup2");
192+
talkgroupAlias2.addAliasID(new Talkgroup(Protocol.APCO25, 300));
193+
talkgroupAlias2.addAliasID(new BroadcastChannel("Stream C"));
194+
aliasList.addAlias(talkgroupAlias2);
195+
196+
return aliasList;
197+
}
198+
199+
/**
200+
* Creates a patch group
201+
* @return p25 patch group
202+
*/
203+
private static APCO25PatchGroup getPatchGroup()
204+
{
205+
TalkgroupIdentifier talkgroup1 = APCO25Talkgroup.create(TALKGROUP_1);
206+
TalkgroupIdentifier talkgroup2 = APCO25Talkgroup.create(TALKGROUP_2);
207+
TalkgroupIdentifier talkgroup3 = APCO25Talkgroup.create(TALKGROUP_3);
208+
209+
PatchGroup pg = new PatchGroup(talkgroup1);
210+
pg.addPatchedTalkgroup(talkgroup2);
211+
pg.addPatchedTalkgroup(talkgroup3);
212+
return APCO25PatchGroup.create(pg);
213+
}
214+
215+
/**
216+
* Creates a source radio identifier.
217+
* @return radio
218+
*/
219+
private static RadioIdentifier getRadio()
220+
{
221+
return APCO25RadioIdentifier.createFrom(RADIO_1);
222+
}
223+
}

0 commit comments

Comments
 (0)