diff --git a/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java b/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java index 5997dce6..9d3dac9f 100644 --- a/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java +++ b/src/main/java/io/github/dsheirer/audio/DuplicateCallDetector.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -28,12 +28,9 @@ 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.preference.duplicate.CallManagementPreference; 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; @@ -42,6 +39,8 @@ import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method. @@ -52,18 +51,18 @@ import java.util.concurrent.atomic.AtomicBoolean; public class DuplicateCallDetector implements Listener { private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class); - private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference; + private CallManagementPreference mCallManagementPreference; private Map mDetectorMap = new HashMap(); public DuplicateCallDetector(UserPreferences userPreferences) { - mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference(); + mCallManagementPreference = userPreferences.getDuplicateCallDetectionPreference(); } @Override public void receive(AudioSegment audioSegment) { - if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled()) + if(mCallManagementPreference.isDuplicateCallDetectionEnabled()) { Identifier identifier = audioSegment.getIdentifierCollection() .getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY); @@ -137,7 +136,7 @@ public class DuplicateCallDetector implements Listener */ private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2) { - if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled()) + if(mCallManagementPreference.isDuplicateCallDetectionByTalkgroupEnabled()) { //Step 1 check for duplicate TO values List to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO); @@ -149,7 +148,7 @@ public class DuplicateCallDetector implements Listener } } - if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByRadioEnabled()) + if(mCallManagementPreference.isDuplicateCallDetectionByRadioEnabled()) { //Step 2 check for duplicate FROM values List from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java index 864d30e9..ea6d4d15 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -19,25 +19,34 @@ package io.github.dsheirer.audio.broadcast; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; import io.github.dsheirer.audio.AudioSegment; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.AudioSegmentRecorder; import io.github.dsheirer.record.RecordFormat; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on @@ -138,29 +147,60 @@ public class AudioStreamingManager implements Listener if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels()) { - Path path = getTemporaryRecordingPath(); - long length = 0; - - for(float[] audioBuffer: audioSegment.getAudioBuffers()) - { - length += audioBuffer.length; - } - - length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond. - - try - { - AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences); - IdentifierCollection identifierCollectionCopy = + IdentifierCollection identifiers = new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers()); - AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(), - identifierCollectionCopy, audioSegment.getStartTimestamp(), length); - mAudioRecordingListener.receive(audioRecording); - } - catch(IOException ioe) + if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier) { - mLog.error("Error recording temporary stream MP3"); + if(mUserPreferences.getDuplicateCallDetectionPreference() + .getPatchGroupStreamingOption() == PatchGroupStreamingOption.TALKGROUPS) + { + //Decompose the patch group into the individual (patched) talkgroups and process the audio + //segment for each patched talkgroup. + PatchGroup patchGroup = patchGroupIdentifier.getValue(); + + List ids = new ArrayList<>(); + ids.addAll(patchGroup.getPatchedTalkgroupIdentifiers()); + ids.addAll(patchGroup.getPatchedRadioIdentifiers()); + + //If there are no patched radios/talkgroups, override user preference and stream as a patch group + if(ids.isEmpty() || audioSegment.getAliasList() == null) + { + processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels()); + } + else + { + AliasList aliasList = audioSegment.getAliasList(); + + for(Identifier identifier: ids) + { + List aliases = aliasList.getAliases(identifier); + Set broadcastChannels = new HashSet<>(); + for(Alias alias: aliases) + { + broadcastChannels.addAll(alias.getBroadcastChannels()); + } + + if(!broadcastChannels.isEmpty()) + { + MutableIdentifierCollection decomposedIdentifiers = + new MutableIdentifierCollection(identifiers.getIdentifiers()); + //Remove patch group TO identifier & replace with the patched talkgroup/radio + decomposedIdentifiers.remove(Role.TO); + decomposedIdentifiers.update(identifier); + processAudioSegment(audioSegment, decomposedIdentifiers, broadcastChannels); + } + } + } + } + else + { + processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels()); + } + } + else + { + processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels()); } } @@ -169,6 +209,40 @@ public class AudioStreamingManager implements Listener } } + /** + * Processes an audio segment for streaming by creating a temporary MP3 recording and submitting the recording + * to the specific broadcast channel(s). + * @param audioSegment to process for streaming + * @param identifierCollection to use for the streamed audio recording. + * @param broadcastChannels to receive the audio recording + */ + private void processAudioSegment(AudioSegment audioSegment, IdentifierCollection identifierCollection, + Set broadcastChannels) + { + Path path = getTemporaryRecordingPath(); + long length = 0; + + for(float[] audioBuffer: audioSegment.getAudioBuffers()) + { + length += audioBuffer.length; + } + + length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond. + + try + { + AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences, identifierCollection); + + AudioRecording audioRecording = new AudioRecording(path, broadcastChannels, identifierCollection, + audioSegment.getStartTimestamp(), length); + mAudioRecordingListener.receive(audioRecording); + } + catch(IOException ioe) + { + mLog.error("Error recording temporary stream MP3"); + } + } + /** * Creates a temporary streaming recording file path */ diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/PatchGroupStreamingOption.java b/src/main/java/io/github/dsheirer/audio/broadcast/PatchGroupStreamingOption.java new file mode 100644 index 00000000..1d1e6839 --- /dev/null +++ b/src/main/java/io/github/dsheirer/audio/broadcast/PatchGroupStreamingOption.java @@ -0,0 +1,48 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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 + * **************************************************************************** + */ + +package io.github.dsheirer.audio.broadcast; + +/** + * Options for streaming management of patch groups. These options are used when streaming an audio call that is + * tagged to a patch group. The audio will either be streamed once and identified as the patch group, or it will + * be streamed multiple times, once for each individual patched talkgroup. + */ +public enum PatchGroupStreamingOption +{ + PATCH_GROUP("Patch Group"), + TALKGROUPS("Individual Talkgroups"); + + private String mLabel; + + /** + * Constructs an instance + * @param label to display + */ + PatchGroupStreamingOption(String label) + { + mLabel = label; + } + + @Override + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceConverter.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceConverter.java index e99fc779..497c7b65 100644 --- a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceConverter.java +++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequenceConverter.java @@ -125,7 +125,7 @@ public class MBECallSequenceConverter try { - AudioSegmentRecorder.recordWAVE(audioSegment, outputPath); + AudioSegmentRecorder.recordWAVE(audioSegment, outputPath, audioSegment.getIdentifierCollection()); } catch(IOException ioe) { diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java index 6414411b..e0f3e13c 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java @@ -21,9 +21,9 @@ package io.github.dsheirer.gui.preference; import io.github.dsheirer.gui.preference.application.ApplicationPreferenceEditor; import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor; +import io.github.dsheirer.gui.preference.call.CallManagementPreferenceEditor; 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.mp3.MP3PreferenceEditor; import io.github.dsheirer.gui.preference.playback.PlaybackPreferenceEditor; import io.github.dsheirer.gui.preference.record.RecordPreferenceEditor; @@ -42,8 +42,8 @@ public class PreferenceEditorFactory { case APPLICATION: return new ApplicationPreferenceEditor(userPreferences); - case AUDIO_DUPLICATE_CALL_DETECTION: - return new DuplicateCallPreferenceEditor(userPreferences); + case AUDIO_CALL_MANAGEMENT: + return new CallManagementPreferenceEditor(userPreferences); case AUDIO_MP3: return new MP3PreferenceEditor(userPreferences); case AUDIO_OUTPUT: diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java index 4adfdd2e..2967d584 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java +++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java @@ -31,7 +31,7 @@ public enum PreferenceEditorType AUDIO_MP3("MP3"), AUDIO_RECORD("Record"), AUDIO_OUTPUT("Output/Tones"), - AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"), + AUDIO_CALL_MANAGEMENT("Call Management"), SOURCE_TUNERS("Tuners"), TALKGROUP_FORMAT("Talkgroup & Radio ID"), VECTOR_CALIBRATION("Vector Calibration"), diff --git a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java index e98aafe7..0b7c5f5e 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java @@ -171,7 +171,7 @@ public class UserPreferencesEditor extends BorderPane applicationItem.setExpanded(true); TreeItem audioItem = new TreeItem<>("Audio"); - audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_CALL_DETECTION)); + audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_CALL_MANAGEMENT)); audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_MP3)); audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_OUTPUT)); audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_RECORD)); diff --git a/src/main/java/io/github/dsheirer/gui/preference/duplicate/DuplicateCallPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/call/CallManagementPreferenceEditor.java similarity index 79% rename from src/main/java/io/github/dsheirer/gui/preference/duplicate/DuplicateCallPreferenceEditor.java rename to src/main/java/io/github/dsheirer/gui/preference/call/CallManagementPreferenceEditor.java index f04fe190..37619c9f 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/duplicate/DuplicateCallPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/call/CallManagementPreferenceEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -17,12 +17,16 @@ * **************************************************************************** */ -package io.github.dsheirer.gui.preference.duplicate; +package io.github.dsheirer.gui.preference.call; +import io.github.dsheirer.audio.broadcast.PatchGroupStreamingOption; import io.github.dsheirer.preference.UserPreferences; -import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference; +import io.github.dsheirer.preference.duplicate.CallManagementPreference; import javafx.beans.binding.Bindings; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.geometry.Insets; +import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.layout.GridPane; @@ -33,23 +37,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Preference settings for duplicate call audio handling + * Preference settings for audio call management for duplicate calls and patch group streaming. */ -public class DuplicateCallPreferenceEditor extends HBox +public class CallManagementPreferenceEditor extends HBox { - private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallPreferenceEditor.class); - private DuplicateCallDetectionPreference mPreference; + private final static Logger mLog = LoggerFactory.getLogger(CallManagementPreferenceEditor.class); + private CallManagementPreference mPreference; private GridPane mEditorPane; private ToggleSwitch mDetectDuplicateTalkgroups; private ToggleSwitch mDetectDuplicateRadios; private ToggleSwitch mSuppressDuplicateListening; private ToggleSwitch mSuppressDuplicateRecording; private ToggleSwitch mSuppressDuplicateStreaming; + private ComboBox mPatchGroupStreamingOptionComboBox; /** * Constructs an instance */ - public DuplicateCallPreferenceEditor(UserPreferences userPreferences) + public CallManagementPreferenceEditor(UserPreferences userPreferences) { mPreference = userPreferences.getDuplicateCallDetectionPreference(); @@ -134,6 +139,19 @@ public class DuplicateCallPreferenceEditor extends HBox streamingLabel.setWrapText(true); GridPane.setConstraints(streamingLabel, 1, row); mEditorPane.getChildren().add(streamingLabel); + + Separator separator2 = new Separator(); + GridPane.setHgrow(separator2, Priority.ALWAYS); + GridPane.setConstraints(separator2, 0, ++row, 2, 1); + mEditorPane.getChildren().add(separator2); + + Label patchGroupLabel = new Label("Patch Group Streaming. Stream a patch group call as:"); + patchGroupLabel.setWrapText(true); + GridPane.setConstraints(patchGroupLabel, 0, ++row, 2, 1); + mEditorPane.getChildren().add(patchGroupLabel); + + GridPane.setConstraints(getPatchGroupStreamingOptionComboBox(), 0, ++row, 2, 1); + mEditorPane.getChildren().add(getPatchGroupStreamingOptionComboBox()); } return mEditorPane; @@ -212,4 +230,23 @@ public class DuplicateCallPreferenceEditor extends HBox return mSuppressDuplicateStreaming; } + + /** + * Combo box for presenting the patch group streaming options. + * @return combo box. + */ + private ComboBox getPatchGroupStreamingOptionComboBox() + { + if(mPatchGroupStreamingOptionComboBox == null) + { + ObservableList options = FXCollections.observableArrayList(); + options.addAll(PatchGroupStreamingOption.values()); + mPatchGroupStreamingOptionComboBox = new ComboBox<>(options); + mPatchGroupStreamingOptionComboBox.getSelectionModel().select(mPreference.getPatchGroupStreamingOption()); + mPatchGroupStreamingOptionComboBox.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> mPreference.setPatchGroupStreamingOption(newValue)); + } + + return mPatchGroupStreamingOptionComboBox; + } } diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java index 42b5e26f..a2b628d0 100644 --- a/src/main/java/io/github/dsheirer/preference/UserPreferences.java +++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java @@ -24,7 +24,7 @@ import io.github.dsheirer.preference.application.ApplicationPreference; import io.github.dsheirer.preference.calibration.VectorCalibrationPreference; 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.duplicate.CallManagementPreference; import io.github.dsheirer.preference.event.DecodeEventPreference; import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference; import io.github.dsheirer.preference.javafx.JavaFxPreferences; @@ -60,7 +60,7 @@ public class UserPreferences implements Listener private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference; private DecodeEventPreference mDecodeEventPreference; private DirectoryPreference mDirectoryPreference; - private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference; + private CallManagementPreference mDuplicateCallDetectionPreference; private JmbeLibraryPreference mJmbeLibraryPreference; private MP3Preference mMP3Preference; private PlaybackPreference mPlaybackPreference; @@ -208,7 +208,7 @@ public class UserPreferences implements Listener /** * Duplicate call detection preferences */ - public DuplicateCallDetectionPreference getDuplicateCallDetectionPreference() + public CallManagementPreference getDuplicateCallDetectionPreference() { return mDuplicateCallDetectionPreference; } @@ -222,7 +222,7 @@ public class UserPreferences implements Listener mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this::receive); mDecodeEventPreference = new DecodeEventPreference(this::receive); mDirectoryPreference = new DirectoryPreference(this::receive); - mDuplicateCallDetectionPreference = new DuplicateCallDetectionPreference(this::receive); + mDuplicateCallDetectionPreference = new CallManagementPreference(this::receive); mJmbeLibraryPreference = new JmbeLibraryPreference(this::receive); mMP3Preference = new MP3Preference(this::receive); mPlaybackPreference = new PlaybackPreference(this::receive); diff --git a/src/main/java/io/github/dsheirer/preference/duplicate/DuplicateCallDetectionPreference.java b/src/main/java/io/github/dsheirer/preference/duplicate/CallManagementPreference.java similarity index 76% rename from src/main/java/io/github/dsheirer/preference/duplicate/DuplicateCallDetectionPreference.java rename to src/main/java/io/github/dsheirer/preference/duplicate/CallManagementPreference.java index 74749fe3..7d5b90cb 100644 --- a/src/main/java/io/github/dsheirer/preference/duplicate/DuplicateCallDetectionPreference.java +++ b/src/main/java/io/github/dsheirer/preference/duplicate/CallManagementPreference.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -19,38 +19,40 @@ package io.github.dsheirer.preference.duplicate; +import io.github.dsheirer.audio.broadcast.PatchGroupStreamingOption; import io.github.dsheirer.preference.Preference; import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.sample.Listener; +import java.util.prefs.Preferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.prefs.Preferences; - /** - * User preferences for duplicate call detection + * User preferences for call management */ -public class DuplicateCallDetectionPreference extends Preference +public class CallManagementPreference 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 static final String PREFERENCE_KEY_PATCHGROUP_STREAMING = "patchgroup.streaming"; - private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetectionPreference.class); - private Preferences mPreferences = Preferences.userNodeForPackage(DuplicateCallDetectionPreference.class); + private final static Logger mLog = LoggerFactory.getLogger(CallManagementPreference.class); + private Preferences mPreferences = Preferences.userNodeForPackage(CallManagementPreference.class); private Boolean mDuplicateCallDetectionByTalkgroupEnabled; private Boolean mDuplicateCallDetectionByRadioEnabled; private Boolean mDuplicatePlaybackSuppressionEnabled; private Boolean mDuplicateRecordingSuppressionEnabled; private Boolean mDuplicateStreamingSuppressionEnabled; + private PatchGroupStreamingOption mPatchGroupStreamingOption; /** * Constructs an instance * @param updateListener to receive notifications that a preference has been updated */ - public DuplicateCallDetectionPreference(Listener updateListener) + public CallManagementPreference(Listener updateListener) { super(updateListener); } @@ -182,6 +184,51 @@ public class DuplicateCallDetectionPreference extends Preference { mPreferences.putBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_STREAMING, enabled); mDuplicateStreamingSuppressionEnabled = enabled; - notifyPreferenceUpdated();; + notifyPreferenceUpdated(); + } + + /** + * Patch group streaming option preference. + * @return option. + */ + public PatchGroupStreamingOption getPatchGroupStreamingOption() + { + if(mPatchGroupStreamingOption == null) + { + String option = mPreferences.get(PREFERENCE_KEY_PATCHGROUP_STREAMING, PatchGroupStreamingOption.PATCH_GROUP.name()); + + if(option != null) + { + try + { + mPatchGroupStreamingOption = PatchGroupStreamingOption.valueOf(option); + } + catch(Exception e) + { + //Ignore ... we'll use the default value. + } + } + + if(mPatchGroupStreamingOption == null) + { + mPatchGroupStreamingOption = PatchGroupStreamingOption.PATCH_GROUP; + } + } + + return mPatchGroupStreamingOption; + } + + /** + * Sets the patch group streaming option preference. + * @param patchGroupStreamingOption preference + */ + public void setPatchGroupStreamingOption(PatchGroupStreamingOption patchGroupStreamingOption) + { + if(patchGroupStreamingOption != null) + { + mPreferences.put(PREFERENCE_KEY_PATCHGROUP_STREAMING, patchGroupStreamingOption.name()); + mPatchGroupStreamingOption = patchGroupStreamingOption; + notifyPreferenceUpdated(); + } } } diff --git a/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java b/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java index 5139b4dd..a57c8294 100644 --- a/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java +++ b/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -24,6 +24,7 @@ import io.github.dsheirer.audio.AudioSegment; import io.github.dsheirer.audio.convert.InputAudioFormat; import io.github.dsheirer.audio.convert.MP3AudioConverter; import io.github.dsheirer.audio.convert.MP3Setting; +import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.wave.AudioMetadata; import io.github.dsheirer.record.wave.AudioMetadataUtils; @@ -58,14 +59,28 @@ public class AudioSegmentRecorder */ public static void record(AudioSegment audioSegment, Path path, RecordFormat recordFormat, UserPreferences userPreferences) throws IOException + { + record(audioSegment, path, recordFormat, userPreferences, audioSegment.getIdentifierCollection()); + } + + /** + * Records the audio segment to the specified path using the specified recording format + * @param audioSegment to record + * @param path for the recording + * @param recordFormat to use (WAVE, MP3) + * @param identifierCollection to use instead of the audioSegment's embedded collection. + * @throws IOException on any errors + */ + public static void record(AudioSegment audioSegment, Path path, RecordFormat recordFormat, + UserPreferences userPreferences, IdentifierCollection identifierCollection) throws IOException { switch(recordFormat) { case MP3: - recordMP3(audioSegment, path, userPreferences); + recordMP3(audioSegment, path, userPreferences, identifierCollection); break; case WAVE: - recordWAVE(audioSegment, path); + recordWAVE(audioSegment, path, identifierCollection); break; default: throw new IllegalArgumentException("Unrecognized recording format [" + recordFormat.name() + "]"); @@ -76,16 +91,19 @@ public class AudioSegmentRecorder * Records the audio segment as an MP3 file to the specified path. * @param audioSegment to record * @param path for the recording + * @param userPreferences for configuration + * @param identifierCollection to use instead of the collection embedded in the audio segment * @throws IOException on any errors */ - public static void recordMP3(AudioSegment audioSegment, Path path, UserPreferences userPreferences) throws IOException + public static void recordMP3(AudioSegment audioSegment, Path path, UserPreferences userPreferences, + IdentifierCollection identifierCollection) throws IOException { if(audioSegment.hasAudio()) { OutputStream outputStream = new FileOutputStream(path.toFile()); //Write ID3 metadata - Map metadataMap = AudioMetadataUtils.getMetadataMap(audioSegment.getIdentifierCollection(), + Map metadataMap = AudioMetadataUtils.getMetadataMap(identifierCollection, audioSegment.getAliasList()); byte[] id3Bytes = AudioMetadataUtils.getMP3ID3(metadataMap); @@ -123,9 +141,10 @@ public class AudioSegmentRecorder * Records the audio segment as a WAVe file to the specified path. * @param audioSegment to record * @param path for the recording + * @param identifierCollection to use instead of the audioSegment's embedded identifier collection * @throws IOException on any errors */ - public static void recordWAVE(AudioSegment audioSegment, Path path) throws IOException + public static void recordWAVE(AudioSegment audioSegment, Path path, IdentifierCollection identifierCollection) throws IOException { if(audioSegment.hasAudio()) { @@ -136,7 +155,7 @@ public class AudioSegmentRecorder writer.writeData(ConversionUtils.convertToSigned16BitSamples(audioBuffer)); } - Map metadataMap = AudioMetadataUtils.getMetadataMap(audioSegment.getIdentifierCollection(), + Map metadataMap = AudioMetadataUtils.getMetadataMap(identifierCollection, audioSegment.getAliasList()); ByteBuffer listChunk = AudioMetadataUtils.getLISTChunk(metadataMap);