From bc8873de018206d53e4af8e490dd7ae88c74d8e3 Mon Sep 17 00:00:00 2001 From: Denny Date: Tue, 17 Jan 2017 19:24:54 -0500 Subject: [PATCH] #90 [WIP] merged metadata management for channel state, decoder states and audio metadata into a new metadata class. The new metadata class is maintained for each decoding audio channel, or in the future, a decoding TDMA channel. Audio playback, recording and streaming all use the same metadata object for the channel. The list of channel processing gui panels have been deleted and channel processing status will now reside in a table where each table row is a metadata instance from the processing channel. The channel processing manager is responsible for adding/removing the channel's metadata instance to/from the metadata table. This allows the channel processing table to be the controller for the messages, events and channel spectrum views, keyed to the selected table row metadata. I still have to clean up the gui components and wire in the table selection listeners. --- .idea/dictionaries/denny.xml | 1 + src/alias/AliasList.java | 207 ++- src/audio/AudioChannelPanel.java | 70 +- src/audio/AudioManager.java | 988 +++++++------- src/audio/AudioModule.java | 138 +- src/audio/AudioPacket.java | 139 +- src/audio/broadcast/AudioBroadcaster.java | 22 +- src/audio/broadcast/AudioRecording.java | 17 +- src/audio/broadcast/BroadcastModel.java | 51 +- .../broadcast/IBroadcastMetadataUpdater.java | 4 +- src/audio/broadcast/StreamManager.java | 46 +- .../IcecastBroadcastMetadataUpdater.java | 69 +- .../ShoutcastV1BroadcastMetadataUpdater.java | 66 +- .../v2/ShoutcastV2AudioBroadcaster.java | 66 +- src/audio/metadata/AudioMetadata.java | 357 ----- src/audio/metadata/IMetadataListener.java | 8 - src/audio/metadata/IMetadataProvider.java | 9 - src/audio/metadata/Metadata.java | 162 --- src/audio/metadata/MetadataReset.java | 14 - src/audio/metadata/MetadataType.java | 40 - src/audio/output/AudioOutput.java | 805 ++++++----- src/channel/metadata/Attribute.java | 29 +- .../metadata/AttributeChangeRequest.java | 20 + .../metadata/ChannelMetadataModel.java | 2 +- .../metadata/ChannelMetadataViewer.java | 66 +- src/channel/metadata/Metadata.java | 4 +- src/channel/metadata/MutableMetadata.java | 32 +- src/channel/state/ChannelList.java | 317 ----- src/channel/state/ChannelPanel.java | 466 ------- src/channel/state/ChannelState.java | 1199 ++++++++--------- src/channel/state/DecoderPanel.java | 163 --- src/channel/state/DecoderState.java | 4 +- .../state/IChangedAttributeProvider.java | 10 - src/controller/ControllerPanel.java | 139 +- .../channel/ChannelProcessingManager.java | 148 +- src/instrument/gui/DecoderViewFrame.java | 603 +++++---- src/module/ProcessingChain.java | 172 +-- src/module/decode/DecoderFactory.java | 141 +- src/module/decode/am/AMDecoderPanel.java | 84 -- src/module/decode/event/CallEvent.java | 597 ++++---- .../decode/event/MessageActivityPanel.java | 526 ++++---- .../fleetsync2/Fleetsync2DecoderPanel.java | 201 --- .../fleetsync2/Fleetsync2DecoderState.java | 78 +- .../decode/lj1200/LJ1200DecoderPanel.java | 127 -- .../decode/lj1200/LJ1200DecoderState.java | 290 ++-- .../decode/ltrnet/LTRNetDecoderPanel.java | 157 --- .../decode/ltrnet/LTRNetDecoderState.java | 436 +++--- .../ltrstandard/LTRStandardDecoderPanel.java | 164 --- .../ltrstandard/LTRStandardDecoderState.java | 908 +++++++------ .../decode/mdc1200/MDCDecoderPanel.java | 183 --- .../decode/mdc1200/MDCDecoderState.java | 581 ++++---- .../decode/mpt1327/MPT1327DecoderPanel.java | 200 --- .../decode/mpt1327/MPT1327DecoderState.java | 1132 ++++++++-------- .../decode/mpt1327/MPT1327TrafficPanel.java | 179 --- src/module/decode/nbfm/NBFMDecoderPanel.java | 80 -- src/module/decode/p25/P25DecoderPanel.java | 226 ---- src/module/decode/p25/P25DecoderState.java | 124 +- .../decode/p25/audio/P25AudioModule.java | 86 +- .../decode/passport/PassportDecoderPanel.java | 225 ---- .../decode/passport/PassportDecoderState.java | 1143 ++++++++-------- .../decode/tait/Tait1200DecoderPanel.java | 180 --- .../decode/tait/Tait1200DecoderState.java | 500 +++---- src/playlist/version1/AliasListOld.java | 1005 +++++++------- src/record/AudioRecorder.java | 34 +- src/record/RecorderManager.java | 44 +- .../wave/AudioPacketMonoWaveReader.java | 9 +- src/util/StringUtils.java | 41 + 67 files changed, 6207 insertions(+), 10127 deletions(-) delete mode 100644 src/audio/metadata/AudioMetadata.java delete mode 100644 src/audio/metadata/IMetadataListener.java delete mode 100644 src/audio/metadata/IMetadataProvider.java delete mode 100644 src/audio/metadata/Metadata.java delete mode 100644 src/audio/metadata/MetadataReset.java delete mode 100644 src/audio/metadata/MetadataType.java delete mode 100644 src/channel/state/ChannelList.java delete mode 100644 src/channel/state/ChannelPanel.java delete mode 100644 src/channel/state/DecoderPanel.java delete mode 100644 src/channel/state/IChangedAttributeProvider.java delete mode 100644 src/module/decode/am/AMDecoderPanel.java delete mode 100644 src/module/decode/fleetsync2/Fleetsync2DecoderPanel.java delete mode 100644 src/module/decode/lj1200/LJ1200DecoderPanel.java delete mode 100644 src/module/decode/ltrnet/LTRNetDecoderPanel.java delete mode 100644 src/module/decode/ltrstandard/LTRStandardDecoderPanel.java delete mode 100644 src/module/decode/mdc1200/MDCDecoderPanel.java delete mode 100644 src/module/decode/mpt1327/MPT1327DecoderPanel.java delete mode 100644 src/module/decode/mpt1327/MPT1327TrafficPanel.java delete mode 100644 src/module/decode/nbfm/NBFMDecoderPanel.java delete mode 100644 src/module/decode/p25/P25DecoderPanel.java delete mode 100644 src/module/decode/passport/PassportDecoderPanel.java delete mode 100644 src/module/decode/tait/Tait1200DecoderPanel.java create mode 100644 src/util/StringUtils.java diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml index 408050c7..24e59925 100644 --- a/.idea/dictionaries/denny.xml +++ b/.idea/dictionaries/denny.xml @@ -8,6 +8,7 @@ icecast ident polyphase + remez samplerate sdrtrunk sheirer diff --git a/src/alias/AliasList.java b/src/alias/AliasList.java index cc1c4662..10c3e214 100644 --- a/src/alias/AliasList.java +++ b/src/alias/AliasList.java @@ -1,6 +1,6 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer + * sdrtrunk + * Copyright (C) 2014-2017 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 @@ -14,6 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see + * ******************************************************************************/ package alias; @@ -29,7 +30,6 @@ import alias.id.siteID.SiteID; import alias.id.status.StatusID; import alias.id.talkgroup.TalkgroupID; import alias.id.uniqueID.UniqueID; -import audio.metadata.MetadataType; import module.decode.lj1200.LJ1200Message.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,16 +48,16 @@ public class AliasList implements Listener public static final String WILDCARD = "*"; public static final String REGEX_WILDCARD = "."; - private Map mESN = new HashMap<>(); - private Map mFleetsync = new HashMap<>(); - private Map mLoJack = new HashMap<>(); - private Map mMDC1200 = new HashMap<>(); - private Map mMobileID = new HashMap<>(); - private Map mMPT1327 = new HashMap<>(); - private Map mSiteID = new HashMap<>(); - private Map mStatus = new HashMap<>(); - private Map mTalkgroup = new HashMap<>(); - private Map mUniqueID = new HashMap<>(); + private Map mESN = new HashMap<>(); + private Map mFleetsync = new HashMap<>(); + private Map mLoJack = new HashMap<>(); + private Map mMDC1200 = new HashMap<>(); + private Map mMobileID = new HashMap<>(); + private Map mMPT1327 = new HashMap<>(); + private Map mSiteID = new HashMap<>(); + private Map mStatus = new HashMap<>(); + private Map mTalkgroup = new HashMap<>(); + private Map mUniqueID = new HashMap<>(); private List mESNWildcards = new ArrayList<>(); private List mMobileIDWildcards = new ArrayList<>(); @@ -85,9 +85,9 @@ public class AliasList implements Listener */ public void addAlias(Alias alias) { - if (alias != null) + if(alias != null) { - for (AliasID aliasID : alias.getId()) + for(AliasID aliasID : alias.getId()) { addAliasID(aliasID, alias); } @@ -99,18 +99,18 @@ public class AliasList implements Listener */ private void addAliasID(AliasID id, Alias alias) { - if (id.isValid()) + if(id.isValid()) { try { - switch (id.getType()) + switch(id.getType()) { case ESN: - String esn = ((Esn)id).getEsn(); + String esn = ((Esn) id).getEsn(); if(esn != null) { - if (esn.contains(WILDCARD)) + if(esn.contains(WILDCARD)) { mESNWildcards.add(new WildcardID(esn)); Collections.sort(mESNWildcards); @@ -120,11 +120,11 @@ public class AliasList implements Listener } break; case FLEETSYNC: - String fleetsync = ((FleetsyncID)id).getIdent(); + String fleetsync = ((FleetsyncID) id).getIdent(); if(fleetsync != null) { - if (fleetsync.contains(WILDCARD)) + if(fleetsync.contains(WILDCARD)) { mFleetsyncWildcards.add(new WildcardID(fleetsync)); Collections.sort(mFleetsyncWildcards); @@ -137,11 +137,11 @@ public class AliasList implements Listener mLoJack.put((LoJackFunctionAndID) id, alias); break; case MDC1200: - String mdc = ((MDC1200ID)id).getIdent(); + String mdc = ((MDC1200ID) id).getIdent(); if(mdc != null) { - if (mdc.contains(WILDCARD)) + if(mdc.contains(WILDCARD)) { mMDC1200Wildcards.add(new WildcardID(mdc)); Collections.sort(mMDC1200Wildcards); @@ -151,11 +151,11 @@ public class AliasList implements Listener } break; case MPT1327: - String mpt = ((MPT1327ID)id).getIdent(); + String mpt = ((MPT1327ID) id).getIdent(); if(mpt != null) { - if (mpt.contains(WILDCARD)) + if(mpt.contains(WILDCARD)) { mMPT1327Wildcards.add(new WildcardID(mpt)); Collections.sort(mMPT1327Wildcards); @@ -165,11 +165,11 @@ public class AliasList implements Listener } break; case MIN: - String min = ((Min)id).getMin(); + String min = ((Min) id).getMin(); if(min != null) { - if (min.contains(WILDCARD)) + if(min.contains(WILDCARD)) { mMobileIDWildcards.add(new WildcardID(min)); Collections.sort(mMobileIDWildcards); @@ -179,14 +179,14 @@ public class AliasList implements Listener } break; case LTR_NET_UID: - mUniqueID.put(((UniqueID)id).getUid(), alias); + mUniqueID.put(((UniqueID) id).getUid(), alias); break; case SITE: - String siteID = ((SiteID)id).getSite(); + String siteID = ((SiteID) id).getSite(); if(siteID != null) { - if (siteID.contains(WILDCARD)) + if(siteID.contains(WILDCARD)) { mSiteWildcards.add(new WildcardID(siteID)); Collections.sort(mSiteWildcards); @@ -199,11 +199,11 @@ public class AliasList implements Listener mStatus.put(((StatusID) id).getStatus(), alias); break; case TALKGROUP: - String tgid = ((TalkgroupID)id).getTalkgroup(); + String tgid = ((TalkgroupID) id).getTalkgroup(); if(tgid != null) { - if (tgid.contains(WILDCARD)) + if(tgid.contains(WILDCARD)) { mTalkgroupWildcards.add(new WildcardID(tgid)); Collections.sort(mTalkgroupWildcards); @@ -222,7 +222,7 @@ public class AliasList implements Listener break; } } - catch (Exception e) + catch(Exception e) { mLog.error("Couldn't add alias ID " + id + " for alias " + alias); } @@ -234,9 +234,9 @@ public class AliasList implements Listener */ public void removeAlias(Alias alias) { - if (alias != null) + if(alias != null) { - for (AliasID aliasID : alias.getId()) + for(AliasID aliasID : alias.getId()) { removeAliasID(aliasID, alias); } @@ -247,7 +247,7 @@ public class AliasList implements Listener { if(value != null) { - for(WildcardID wildcardID: wildcards) + for(WildcardID wildcardID : wildcards) { if(wildcardID.value().equals(value)) { @@ -262,12 +262,12 @@ public class AliasList implements Listener */ private void removeAliasID(AliasID id, Alias alias) { - if (id.isValid()) + if(id.isValid()) { - switch (id.getType()) + switch(id.getType()) { case ESN: - String esn = ((Esn)id).getEsn(); + String esn = ((Esn) id).getEsn(); if(esn != null) { @@ -280,13 +280,13 @@ public class AliasList implements Listener mESN.remove(esn); break; case FLEETSYNC: - String fleetsync = ((FleetsyncID)id).getIdent(); + String fleetsync = ((FleetsyncID) id).getIdent(); if(fleetsync != null) { if(fleetsync.contains(WILDCARD)) { - removeWildcard(fleetsync,mFleetsyncWildcards); + removeWildcard(fleetsync, mFleetsyncWildcards); } mFleetsync.remove(fleetsync); @@ -296,7 +296,7 @@ public class AliasList implements Listener mLoJack.remove((LoJackFunctionAndID) id); break; case MDC1200: - String mdc = ((MDC1200ID)id).getIdent(); + String mdc = ((MDC1200ID) id).getIdent(); if(mdc != null) { @@ -309,7 +309,7 @@ public class AliasList implements Listener } break; case MPT1327: - String mpt = ((MPT1327ID)id).getIdent(); + String mpt = ((MPT1327ID) id).getIdent(); if(mpt != null) { @@ -322,7 +322,7 @@ public class AliasList implements Listener } break; case MIN: - String min = ((Min)id).getMin(); + String min = ((Min) id).getMin(); if(min != null) { @@ -344,7 +344,7 @@ public class AliasList implements Listener mStatus.remove(((StatusID) id).getStatus()); break; case TALKGROUP: - String tgid = ((TalkgroupID)id).getTalkgroup(); + String tgid = ((TalkgroupID) id).getTalkgroup(); if(tgid != null) { @@ -378,11 +378,11 @@ public class AliasList implements Listener */ private String getWildcardMatch(String id, List wildcards) { - if (id != null) + if(id != null) { - for (WildcardID wildcard : wildcards) + for(WildcardID wildcard : wildcards) { - if (wildcard.matches(id)) + if(wildcard.matches(id)) { return wildcard.value(); } @@ -399,15 +399,15 @@ public class AliasList implements Listener { Alias alias = null; - if (siteID != null) + if(siteID != null) { alias = mSiteID.get(siteID); - if (alias == null) + if(alias == null) { String wildcard = getWildcardMatch(siteID, mSiteWildcards); - if (wildcard != null) + if(wildcard != null) { alias = mSiteID.get(wildcard); } @@ -440,15 +440,15 @@ public class AliasList implements Listener { Alias alias = null; - if (esn != null) + if(esn != null) { alias = mESN.get(esn); - if (alias == null) + if(alias == null) { String wildcard = getWildcardMatch(esn, mESNWildcards); - if (wildcard != null) + if(wildcard != null) { alias = mESN.get(wildcard); } @@ -465,15 +465,15 @@ public class AliasList implements Listener { Alias alias = null; - if (ident != null) + if(ident != null) { alias = mFleetsync.get(ident); - if (alias == null) + if(alias == null) { String wildcard = getWildcardMatch(ident, mFleetsyncWildcards); - if (wildcard != null) + if(wildcard != null) { alias = mFleetsync.get(wildcard); } @@ -488,11 +488,11 @@ public class AliasList implements Listener */ public Alias getLoJackAlias(Function function, String id) { - if (id != null) + if(id != null) { - for (LoJackFunctionAndID lojack : mLoJack.keySet()) + for(LoJackFunctionAndID lojack : mLoJack.keySet()) { - if (lojack.matches(function, id)) + if(lojack.matches(function, id)) { return mLoJack.get(lojack); } @@ -509,15 +509,15 @@ public class AliasList implements Listener { Alias alias = null; - if (ident != null) + if(ident != null) { alias = mMDC1200.get(ident); - if (alias == null) + if(alias == null) { String wildcard = getWildcardMatch(ident, mMDC1200Wildcards); - if (wildcard != null) + if(wildcard != null) { alias = mMDC1200.get(wildcard); } @@ -534,15 +534,15 @@ public class AliasList implements Listener { Alias alias = null; - if (ident != null) + if(ident != null) { alias = mMPT1327.get(ident); - if (alias == null) + if(alias == null) { String wildcard = getWildcardMatch(ident, mMPT1327Wildcards); - if (wildcard != null) + if(wildcard != null) { alias = mMPT1327.get(wildcard); } @@ -559,15 +559,15 @@ public class AliasList implements Listener { Alias alias = null; - if (ident != null) + if(ident != null) { alias = mMobileID.get(ident); - if (alias == null) + if(alias == null) { String wildcard = getWildcardMatch(ident, mMobileIDWildcards); - if (wildcard != null) + if(wildcard != null) { alias = mMobileID.get(wildcard); } @@ -584,15 +584,15 @@ public class AliasList implements Listener { Alias alias = null; - if (tgid != null) + if(tgid != null) { alias = mTalkgroup.get(tgid); - if (alias == null) + if(alias == null) { String wildcard = getWildcardMatch(tgid, mTalkgroupWildcards); - if (wildcard != null) + if(wildcard != null) { alias = mTalkgroup.get(wildcard); } @@ -619,61 +619,6 @@ public class AliasList implements Listener return mName; } - /** - * Lookup an alias by metadata type and string value - */ - public Alias getAlias(String value, MetadataType type) - { - switch (type) - { - case ESN: - return getESNAlias(value); - case FLEETSYNC: - return getFleetsyncAlias(value); - case MDC1200: - return getMDC1200Alias(value); - case MOBILE_ID: - return getMobileIDNumberAlias(value); - case MPT1327: - return getMPT1327Alias(value); - case SITE_ID: - return getSiteID(value); - case STATUS: - if (value != null) - { - try - { - return getStatus(Integer.valueOf(value)); - } - catch (Exception e) - { - //do nothing, we couldn't parse the int value - } - } - break; - case FROM: - case TO: - return getTalkgroupAlias(value); - case UNIQUE_ID: - if (value != null) - { - try - { - return getUniqueID(Integer.valueOf(value)); - } - catch (Exception e) - { - //do nothing, we couldn't parse the int value - } - } - break; - default: - break; - } - - return null; - } - /** * Receive alias change event notifications and modify this list accordingly */ @@ -682,22 +627,22 @@ public class AliasList implements Listener { Alias alias = event.getAlias(); - switch (event.getEvent()) + switch(event.getEvent()) { case ADD: - if (alias.getList() != null && getName().equalsIgnoreCase(alias.getList())) + if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList())) { addAlias(alias); } break; case CHANGE: - if (alias.getList() != null && getName().equalsIgnoreCase(alias.getList())) + if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList())) { addAlias(alias); } break; case DELETE: - if (alias.getList() != null && getName().equalsIgnoreCase(alias.getList())) + if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList())) { removeAlias(alias); } diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java index 72f4a091..5532fdb7 100644 --- a/src/audio/AudioChannelPanel.java +++ b/src/audio/AudioChannelPanel.java @@ -19,10 +19,8 @@ package audio; import alias.Alias; -import audio.metadata.AudioMetadata; -import audio.metadata.Metadata; -import audio.metadata.MetadataType; import audio.output.AudioOutput; +import channel.metadata.Metadata; import icon.IconManager; import net.miginfocom.swing.MigLayout; import sample.Listener; @@ -50,12 +48,12 @@ public class AudioChannelPanel extends JPanel private JLabel mChannelName = new JLabel(" "); private JLabel mToLabel = new JLabel("TO:"); - private JLabel mTo = new JLabel(""); + private JLabel mTo = new JLabel("-----"); private JLabel mToAlias = new JLabel(""); private JLabel mMutedLabel = new JLabel(" "); private JLabel mFromLabel = new JLabel("FROM:"); - private JLabel mFrom = new JLabel(""); + private JLabel mFrom = new JLabel("-----"); private JLabel mFromAlias = new JLabel(""); private boolean mConfigured = false; @@ -71,7 +69,7 @@ public class AudioChannelPanel extends JPanel if(mAudioOutput != null) { mAudioOutput.addAudioEventListener(this); - mAudioOutput.setAudioMetadataListener(new AudioMetadataProcessor()); + mAudioOutput.setMetadataListener(new AudioMetadataProcessor()); } mLabelColor = mSettingsManager.getColorSetting( @@ -173,11 +171,8 @@ public class AudioChannelPanel extends JPanel */ private void resetLabels() { - mFrom.setText(""); - updateAlias(mFromAlias, null); - - mTo.setText(""); - updateAlias(mToAlias, null); + updateLabel(mFromLabel, null, mFromAlias, null); + updateLabel(mToLabel, null, mToAlias, null); mConfigured = false; } @@ -187,27 +182,25 @@ public class AudioChannelPanel extends JPanel * does not occur on the Swing event thread -- wrap any calls to this * method with an event thread call. */ - private void updateAlias(JLabel label, Alias alias) + private void updateLabel(JLabel valueLabel, String value, JLabel aliasLabel, Alias alias) { - if(alias != null) + if(value != null) { - label.setText(alias.getName()); + valueLabel.setText(value); - String icon = alias.getIconName(); - - if(icon != null) + if(alias != null) { - label.setIcon(mIconManager.getIcon(icon, IconManager.DEFAULT_ICON_SIZE)); + aliasLabel.setIcon(mIconManager.getIcon(alias.getIconName(), IconManager.DEFAULT_ICON_SIZE)); } else { - label.setIcon(null); + aliasLabel.setIcon(null); } } else { - label.setText(""); - label.setIcon(null); + valueLabel.setText("-----"); + aliasLabel.setIcon(null); } } @@ -215,45 +208,28 @@ public class AudioChannelPanel extends JPanel /** * Processes audio metadata to update this panel's display values */ - public class AudioMetadataProcessor implements Listener + public class AudioMetadataProcessor implements Listener { @Override - public void receive(AudioMetadata audioMetadata) + public void receive(final Metadata metadata) { - if(!mConfigured || audioMetadata.isUpdated()) + if(metadata.isUpdated() || !mConfigured) { - final Metadata from = audioMetadata.getMetadata(MetadataType.FROM); - - final Metadata to = audioMetadata.getMetadata(MetadataType.TO); - EventQueue.invokeLater(new Runnable() { @Override public void run() { - if(from != null) - { - mFrom.setText(from.getValue()); - updateAlias(mFromAlias, from.getAlias()); - } - else - { - mFrom.setText("-----"); - } + updateLabel(mFromLabel, metadata.getPrimaryAddressFrom().getIdentifier(), + mFromAlias, metadata.getPrimaryAddressFrom().getAlias()); - if(to != null) - { - mTo.setText(to.getValue()); - updateAlias(mToAlias, to.getAlias()); - } - else - { - mTo.setText("-----"); - } + updateLabel(mToLabel, metadata.getPrimaryAddressTo().getIdentifier(), + mToAlias, metadata.getPrimaryAddressTo().getAlias()); + + mConfigured = true; } }); - mConfigured = true; } } } diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java index fbc7e7b1..6be25898 100644 --- a/src/audio/AudioManager.java +++ b/src/audio/AudioManager.java @@ -1,22 +1,40 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 + * sdrtrunk + * Copyright (C) 2014-2017 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 audio; +import audio.AudioEvent.Type; +import audio.output.AudioOutput; +import audio.output.MonoAudioOutput; +import audio.output.StereoAudioOutput; +import controller.ThreadPoolManager; +import controller.ThreadPoolManager.ThreadType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import properties.SystemProperties; +import sample.Broadcaster; +import sample.Listener; +import source.mixer.MixerChannel; +import source.mixer.MixerChannelConfiguration; +import source.mixer.MixerManager; + +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Mixer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -27,523 +45,495 @@ import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.Mixer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import properties.SystemProperties; -import sample.Broadcaster; -import sample.Listener; -import source.mixer.MixerChannel; -import source.mixer.MixerChannelConfiguration; -import source.mixer.MixerManager; -import audio.AudioEvent.Type; -import audio.output.AudioOutput; -import audio.output.MonoAudioOutput; -import audio.output.StereoAudioOutput; -import controller.ThreadPoolManager; -import controller.ThreadPoolManager.ThreadType; - public class AudioManager implements Listener, IAudioController { - private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class ); + private static final Logger mLog = LoggerFactory.getLogger(AudioManager.class); - public static final int AUDIO_TIMEOUT = 1000; //1 second - - public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels"; - public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer"; - - public static final AudioEvent CONFIGURATION_CHANGE_STARTED = - new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null ); - - public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE = - new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null ); + public static final int AUDIO_TIMEOUT = 1000; //1 second - private LinkedTransferQueue mAudioPacketQueue = new LinkedTransferQueue<>(); - private Map mChannelConnectionMap = new HashMap<>(); - private List mAudioOutputConnections = new ArrayList<>(); - private AudioOutputConnection mLowestPriorityConnection; - private int mAvailableConnectionCount; + public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels"; + public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer"; - private Map mAudioOutputMap = new HashMap<>(); - - private Broadcaster mControllerBroadcaster = new Broadcaster<>(); - - private ThreadPoolManager mThreadPoolManager; - private ScheduledFuture mProcessingTask; - private MixerManager mMixerManager; - private MixerChannelConfiguration mMixerChannelConfiguration; - + public static final AudioEvent CONFIGURATION_CHANGE_STARTED = + new AudioEvent(Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null); - /** - * Processes all audio produced by the decoding channels and routes audio - * packets to any combination of outputs based on any alias audio routing - * options specified by the user. - */ - public AudioManager( ThreadPoolManager manager, MixerManager mixerManager ) - { - mThreadPoolManager = manager; - mMixerManager = mixerManager; + public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE = + new AudioEvent(Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null); - loadSettings(); - } - - private void loadSettings() - { - MixerChannelConfiguration configuration = null; - - SystemProperties properties = SystemProperties.getInstance(); - - Mixer defaultMixer = AudioSystem.getMixer( null ); + private LinkedTransferQueue mAudioPacketQueue = new LinkedTransferQueue<>(); + private Map mChannelConnectionMap = new HashMap<>(); + private List mAudioOutputConnections = new ArrayList<>(); + private AudioOutputConnection mLowestPriorityConnection; + private int mAvailableConnectionCount; - String mixer = properties.get( AUDIO_MIXER_PROPERTY, defaultMixer.getMixerInfo().getName() ); - - String channels = properties.get( AUDIO_CHANNELS_PROPERTY, MixerChannel.MONO.name() ); - - MixerChannelConfiguration[] mixerConfigurations = mMixerManager.getOutputMixers(); + private Map mAudioOutputMap = new HashMap<>(); - for( MixerChannelConfiguration mixerConfig: mixerConfigurations ) - { - if( mixerConfig.matches( mixer, channels ) ) - { - configuration = mixerConfig; - } - } - - if( configuration == null ) - { - configuration = getDefaultConfiguration(); - } + private Broadcaster mControllerBroadcaster = new Broadcaster<>(); - try - { - setMixerChannelConfiguration( configuration ); - } - catch( Exception e ) - { - mLog.error( "Couldn't set stored audio mixer/channel configuration" - + " - using default", e ); - - try - { - setMixerChannelConfiguration( getDefaultConfiguration() ); - } - catch( Exception e2 ) - { - mLog.error( "Couldn't set default audio mixer/channel " - + "configuration - no audio will be available", e2 ); - } - } - } - - private MixerChannelConfiguration getDefaultConfiguration() - { - /* Use the system default mixer and mono channel as default startup */ - Mixer defaultMixer = AudioSystem.getMixer( null ); - - return new MixerChannelConfiguration( defaultMixer, MixerChannel.MONO ); - } - - public void dispose() - { - if( mThreadPoolManager != null && mProcessingTask != null ) - { - mThreadPoolManager.cancel( mProcessingTask ); - } - - mAudioPacketQueue.clear(); - - mThreadPoolManager = null; - mProcessingTask = null; + private ThreadPoolManager mThreadPoolManager; + private ScheduledFuture mProcessingTask; + private MixerManager mMixerManager; + private MixerChannelConfiguration mMixerChannelConfiguration; - mChannelConnectionMap.clear(); - - for( AudioOutputConnection connection: mAudioOutputConnections ) - { - connection.dispose(); - } - - mAudioOutputConnections.clear(); - } - @Override - public synchronized void receive( AudioPacket packet ) - { - mAudioPacketQueue.add( packet ); - } - - /** - * Checks each audio channel assignment and disconnects any inactive connections - */ - private void disconnectInactiveChannelAssignments() - { - boolean changed = false; - - for( AudioOutputConnection connection: mAudioOutputConnections ) - { - if( connection.isInactive() && - mChannelConnectionMap.containsKey( connection.getSource() ) ) - { - mChannelConnectionMap.remove( connection.getSource() ); - connection.disconnect(); - changed = true; - } - } - - if( changed ) - { - updateLowestPriorityAssignment(); - } - } + /** + * Processes all audio produced by the decoding channels and routes audio + * packets to any combination of outputs based on any alias audio routing + * options specified by the user. + */ + public AudioManager(ThreadPoolManager manager, MixerManager mixerManager) + { + mThreadPoolManager = manager; + mMixerManager = mixerManager; - /** - * Identifies the lowest priority channel connection - */ - private void updateLowestPriorityAssignment() - { - mLowestPriorityConnection = null; - - for( AudioOutputConnection connection: mAudioOutputConnections ) - { - if( connection.isConnected() && - ( mLowestPriorityConnection == null || - mLowestPriorityConnection.getPriority() > connection.getPriority() ) ) - { - mLowestPriorityConnection = connection; - } - } - } - - @Override - public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException - { - if( entry != null && - ( entry.getMixerChannel() == MixerChannel.MONO || - entry.getMixerChannel() == MixerChannel.STEREO ) ) - { - mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_STARTED ); - - if( mThreadPoolManager != null && mProcessingTask != null ) - { - mThreadPoolManager.cancel( mProcessingTask ); - } - - disposeCurrentConfiguration(); - - switch( entry.getMixerChannel() ) - { - case MONO: - AudioOutput mono = new MonoAudioOutput( mThreadPoolManager, - entry.getMixer() ); - mAudioOutputConnections.add( new AudioOutputConnection( mono ) ); - mAvailableConnectionCount++; - mAudioOutputMap.put( mono.getChannelName(), mono ); - break; - case STEREO: - AudioOutput left = new StereoAudioOutput( mThreadPoolManager, - entry.getMixer(), MixerChannel.LEFT ); - mAudioOutputConnections.add( new AudioOutputConnection( left ) ); - mAvailableConnectionCount++; - mAudioOutputMap.put( left.getChannelName(), left ); - - AudioOutput right = new StereoAudioOutput( mThreadPoolManager, - entry.getMixer(), MixerChannel.RIGHT ); - mAudioOutputConnections.add( new AudioOutputConnection( right ) ); - mAvailableConnectionCount++; - mAudioOutputMap.put( right.getChannelName(), right ); - break; - default: - throw new AudioException( "Unsupported mixer channel " - + "configuration: " + entry.getMixerChannel() ); - } + loadSettings(); + } - mProcessingTask = mThreadPoolManager.scheduleFixedRate( - ThreadType.AUDIO_PROCESSING, new AudioPacketProcessor(), 15, - TimeUnit.MILLISECONDS ); + private void loadSettings() + { + MixerChannelConfiguration configuration = null; - mControllerBroadcaster.broadcast( CONFIGURATION_CHANGE_COMPLETE ); - - SystemProperties properties = SystemProperties.getInstance(); - - properties.set( AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName() ); - properties.set( AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name() ); - properties.save(); - } - } + SystemProperties properties = SystemProperties.getInstance(); - /** - * Clears all channel assignments and terminates all audio outputs in - * preparation for complete shutdown or change to another mixer/channel - * configuration - */ - private void disposeCurrentConfiguration() - { - mChannelConnectionMap.clear(); + Mixer defaultMixer = AudioSystem.getMixer(null); - for( AudioOutputConnection connection : mAudioOutputConnections ) - { - connection.dispose(); - } - - mAvailableConnectionCount = 0; + String mixer = properties.get(AUDIO_MIXER_PROPERTY, defaultMixer.getMixerInfo().getName()); - mAudioOutputConnections.clear(); - - mAudioOutputMap.clear(); - - mLowestPriorityConnection = null; - } + String channels = properties.get(AUDIO_CHANNELS_PROPERTY, MixerChannel.MONO.name()); - @Override - public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException - { - return mMixerChannelConfiguration; - } + MixerChannelConfiguration[] mixerConfigurations = mMixerManager.getOutputMixers(); - @Override - public List getAudioOutputs() - { - List outputs = new ArrayList<>( mAudioOutputMap.values() ); + for(MixerChannelConfiguration mixerConfig : mixerConfigurations) + { + if(mixerConfig.matches(mixer, channels)) + { + configuration = mixerConfig; + } + } - Collections.sort( outputs, new Comparator() - { - @Override - public int compare( AudioOutput first, AudioOutput second ) - { - return first.getChannelName().compareTo( second.getChannelName() ); - } - } ); - - return outputs; - } + if(configuration == null) + { + configuration = getDefaultConfiguration(); + } - @Override - public void addControllerListener( Listener listener ) - { - mControllerBroadcaster.addListener( listener ); - } + try + { + setMixerChannelConfiguration(configuration); + } + catch(Exception e) + { + mLog.error("Couldn't set stored audio mixer/channel configuration" + + " - using default", e); - @Override - public void removeControllerListener( Listener listener ) - { - mControllerBroadcaster.removeListener( listener ); - } - - /** - * Returns an audio output connection for the packet if one is - * available, or overrides an existing lower priority connection. - * Returns null if no connection is available for the audio packet. - * - * @param packet from a source - * @return an audio output connection or null - */ - private AudioOutputConnection getConnection( AudioPacket packet ) - { - int source = packet.getAudioMetadata().getSource(); - - //Use an existing connection - if( mChannelConnectionMap.containsKey( source ) ) - { - return mChannelConnectionMap.get( source ); - } + try + { + setMixerChannelConfiguration(getDefaultConfiguration()); + } + catch(Exception e2) + { + mLog.error("Couldn't set default audio mixer/channel " + + "configuration - no audio will be available", e2); + } + } + } - //Connect to and use an available connection - if( mAvailableConnectionCount > 0 ) - { - for( AudioOutputConnection connection: mAudioOutputConnections ) - { - if( connection.isDisconnected() ) - { - connection.connect( source ); - mChannelConnectionMap.put( source, connection ); - return connection; - } - } - } - //Preempt an existing connection and connect it to this higher priority source - else //Check for a channel priority override - { - int priority = packet.getAudioMetadata().getPriority(); + private MixerChannelConfiguration getDefaultConfiguration() + { + /* Use the system default mixer and mono channel as default startup */ + Mixer defaultMixer = AudioSystem.getMixer(null); - if( mLowestPriorityConnection != null && - priority < mLowestPriorityConnection.getPriority() ) - { - AudioOutputConnection connection = mLowestPriorityConnection; + return new MixerChannelConfiguration(defaultMixer, MixerChannel.MONO); + } - mChannelConnectionMap.remove( connection.getSource() ); - - connection.connect( source ); - - mChannelConnectionMap.put( source, connection ); - - updateLowestPriorityAssignment(); - - return connection; - } - } - - return null; - } - - public class AudioPacketProcessor implements Runnable - { - @Override - public void run() - { - try - { - disconnectInactiveChannelAssignments(); - - if( mAudioPacketQueue != null ) - { - List packets = new ArrayList(); + public void dispose() + { + if(mThreadPoolManager != null && mProcessingTask != null) + { + mThreadPoolManager.cancel(mProcessingTask); + } - mAudioPacketQueue.drainTo( packets ); - - for( AudioPacket packet: packets ) - { + mAudioPacketQueue.clear(); + + mThreadPoolManager = null; + mProcessingTask = null; + + mChannelConnectionMap.clear(); + + for(AudioOutputConnection connection : mAudioOutputConnections) + { + connection.dispose(); + } + + mAudioOutputConnections.clear(); + } + + @Override + public synchronized void receive(AudioPacket packet) + { + mAudioPacketQueue.add(packet); + } + + /** + * Checks each audio channel assignment and disconnects any inactive connections + */ + private void disconnectInactiveChannelAssignments() + { + boolean changed = false; + + for(AudioOutputConnection connection : mAudioOutputConnections) + { + if(connection.isInactive() && + mChannelConnectionMap.containsKey(connection.getChannelMetadataID())) + { + mChannelConnectionMap.remove(connection.getChannelMetadataID()); + connection.disconnect(); + changed = true; + } + } + + if(changed) + { + updateLowestPriorityAssignment(); + } + } + + /** + * Identifies the lowest priority channel connection + */ + private void updateLowestPriorityAssignment() + { + mLowestPriorityConnection = null; + + for(AudioOutputConnection connection : mAudioOutputConnections) + { + if(connection.isConnected() && + (mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority())) + { + mLowestPriorityConnection = connection; + } + } + } + + @Override + public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException + { + if(entry != null && + (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO)) + { + mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_STARTED); + + if(mThreadPoolManager != null && mProcessingTask != null) + { + mThreadPoolManager.cancel(mProcessingTask); + } + + disposeCurrentConfiguration(); + + switch(entry.getMixerChannel()) + { + case MONO: + AudioOutput mono = new MonoAudioOutput(mThreadPoolManager, + entry.getMixer()); + mAudioOutputConnections.add(new AudioOutputConnection(mono)); + mAvailableConnectionCount++; + mAudioOutputMap.put(mono.getChannelName(), mono); + break; + case STEREO: + AudioOutput left = new StereoAudioOutput(mThreadPoolManager, entry.getMixer(), MixerChannel.LEFT); + mAudioOutputConnections.add(new AudioOutputConnection(left)); + mAvailableConnectionCount++; + mAudioOutputMap.put(left.getChannelName(), left); + + AudioOutput right = new StereoAudioOutput(mThreadPoolManager, entry.getMixer(), MixerChannel.RIGHT); + mAudioOutputConnections.add(new AudioOutputConnection(right)); + mAvailableConnectionCount++; + mAudioOutputMap.put(right.getChannelName(), right); + break; + default: + throw new AudioException("Unsupported mixer channel " + + "configuration: " + entry.getMixerChannel()); + } + + mProcessingTask = mThreadPoolManager.scheduleFixedRate( + ThreadType.AUDIO_PROCESSING, new AudioPacketProcessor(), 15, + TimeUnit.MILLISECONDS); + + mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE); + + SystemProperties properties = SystemProperties.getInstance(); + + properties.set(AUDIO_MIXER_PROPERTY, entry.getMixer().getMixerInfo().getName()); + properties.set(AUDIO_CHANNELS_PROPERTY, entry.getMixerChannel().name()); + properties.save(); + } + } + + /** + * Clears all channel assignments and terminates all audio outputs in + * preparation for complete shutdown or change to another mixer/channel + * configuration + */ + private void disposeCurrentConfiguration() + { + mChannelConnectionMap.clear(); + + for(AudioOutputConnection connection : mAudioOutputConnections) + { + connection.dispose(); + } + + mAvailableConnectionCount = 0; + + mAudioOutputConnections.clear(); + + mAudioOutputMap.clear(); + + mLowestPriorityConnection = null; + } + + @Override + public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException + { + return mMixerChannelConfiguration; + } + + @Override + public List getAudioOutputs() + { + List outputs = new ArrayList<>(mAudioOutputMap.values()); + + Collections.sort(outputs, new Comparator() + { + @Override + public int compare(AudioOutput first, AudioOutput second) + { + return first.getChannelName().compareTo(second.getChannelName()); + } + }); + + return outputs; + } + + @Override + public void addControllerListener(Listener listener) + { + mControllerBroadcaster.addListener(listener); + } + + @Override + public void removeControllerListener(Listener listener) + { + mControllerBroadcaster.removeListener(listener); + } + + /** + * Returns an audio output connection for the packet if one is + * available, or overrides an existing lower priority connection. + * Returns null if no connection is available for the audio packet. + * + * @param packet from a source + * @return an audio output connection or null + */ + private AudioOutputConnection getConnection(AudioPacket packet) + { + int channelMetadataID = packet.getMetadata().getMetadataID(); + + //Use an existing connection + if(mChannelConnectionMap.containsKey(channelMetadataID)) + { + return mChannelConnectionMap.get(channelMetadataID); + } + + //Connect to and use an available connection + if(mAvailableConnectionCount > 0) + { + for(AudioOutputConnection connection : mAudioOutputConnections) + { + if(connection.isDisconnected()) + { + connection.connect(channelMetadataID); + mChannelConnectionMap.put(channelMetadataID, connection); + return connection; + } + } + } + //Preempt an existing connection and connect it to this higher priority source + else //Check for a channel priority override + { + int priority = packet.getMetadata().getAudioPriority(); + + if(mLowestPriorityConnection != null && + priority < mLowestPriorityConnection.getPriority()) + { + AudioOutputConnection connection = mLowestPriorityConnection; + + mChannelConnectionMap.remove(connection.getChannelMetadataID()); + + connection.connect(channelMetadataID); + + mChannelConnectionMap.put(channelMetadataID, connection); + + updateLowestPriorityAssignment(); + + return connection; + } + } + + return null; + } + + public class AudioPacketProcessor implements Runnable + { + @Override + public void run() + { + try + { + disconnectInactiveChannelAssignments(); + + if(mAudioPacketQueue != null) + { + List packets = new ArrayList(); + + mAudioPacketQueue.drainTo(packets); + + for(AudioPacket packet : packets) + { /* Don't process any packet's marked as do not monitor */ - if( !packet.getAudioMetadata().isDoNotMonitor() && - packet.getType() == AudioPacket.Type.AUDIO ) - { - AudioOutputConnection connection = getConnection( packet ); + if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO) + { + AudioOutputConnection connection = getConnection(packet); - if( connection != null ) - { - connection.receive( packet ); - } - } - } - } - } - catch( Exception e ) - { - mLog.error( "Encountered error while processing audio packets", e ); - } - } - } - - /** - * Audio output connection manages a connection between a source and an audio - * output and maintains current state information about the audio activity - * received form the source. - */ - public class AudioOutputConnection - { - private static final int DISCONNECTED = -1; - - private AudioOutput mAudioOutput; - private int mPriority = 0; - private int mSource = DISCONNECTED; - - public AudioOutputConnection( AudioOutput audioOutput ) - { - mAudioOutput = audioOutput; - } - - public void receive( AudioPacket packet ) - { - if( packet.hasAudioMetadata() && - packet.getAudioMetadata().getSource() == mSource ) - { - int priority = packet.getAudioMetadata().getPriority(); + if(connection != null) + { + connection.receive(packet); + } + } + } + } + } + catch(Exception e) + { + mLog.error("Encountered error while processing audio packets", e); + } + } + } - if( mPriority != priority ) - { - mPriority = priority; - updateLowestPriorityAssignment(); - } - - if( mAudioOutput != null ) - { - mAudioOutput.receive( packet ); - } - } - else - { - if( packet.hasAudioMetadata() ) - { - mLog.error( "Received audio packet from channel source [" + - packet.getAudioMetadata().getSource() + - "] however this assignment is currently connected " - + "to channel source [" + mSource + "]" ); - } - else - { - mLog.error( "Received audio packet with no metadata - " - + "cannot route audio packet" ); - } - } - } - - /** - * Terminates the audio output and prepares this connection for disposal - */ - public void dispose() - { - mAudioOutput.dispose(); - mAudioOutput = null; - } + /** + * Audio output connection manages a connection between a source and an audio + * output and maintains current state information about the audio activity + * received form the source. + */ + public class AudioOutputConnection + { + private static final int DISCONNECTED = -1; - /** - * Indicates if this assignment is currently disconnected from a channel source - */ - public boolean isDisconnected() - { - return mSource == DISCONNECTED; - } - - /** - * Indicates if this assignment is currently connected to a channel source - */ - public boolean isConnected() - { - return !isDisconnected(); - } + private AudioOutput mAudioOutput; + private int mPriority = 0; + private int mChannelMetadataID = DISCONNECTED; - /** - * Connects this assignment to the indicated source so that audio - * packets from this source can be sent to the audio output - */ - public void connect( int source ) - { - mSource = source; - - mAudioOutput.updateTimestamp(); - } + public AudioOutputConnection(AudioOutput audioOutput) + { + mAudioOutput = audioOutput; + } - /** - * Currently connected source or -1 if disconnected - */ - public int getSource() - { - return mSource; - } + public void receive(AudioPacket packet) + { + if(packet.hasMetadata() && packet.getMetadata().getMetadataID() == mChannelMetadataID) + { + int priority = packet.getMetadata().getAudioPriority(); - /** - * Disconnects this assignment from the source and prevents any audio - * from being routed to the audio output until another source is assigned - */ - public void disconnect() - { - mSource = DISCONNECTED; - mPriority = 0; - } + if(mPriority != priority) + { + mPriority = priority; + updateLowestPriorityAssignment(); + } - /** - * Indicates if audio output is current inactive, meaning that the - * audio output hasn't recently processed any audio packets. - */ - public boolean isInactive() - { - return ( mAudioOutput.getLastActivityTimestamp() + AUDIO_TIMEOUT ) < System.currentTimeMillis(); - } - - public int getPriority() - { - return mPriority; - } - } + if(mAudioOutput != null) + { + mAudioOutput.receive(packet); + } + } + else + { + if(packet.hasMetadata()) + { + mLog.error("Received audio packet from channel metadata [" + packet.getMetadata().getMetadataID() + + "] however this assignment is currently connected to metadata [" + mChannelMetadataID + "]"); + } + else + { + mLog.error("Received audio packet with no metadata - cannot route audio packet"); + } + } + } + + /** + * Terminates the audio output and prepares this connection for disposal + */ + public void dispose() + { + mAudioOutput.dispose(); + mAudioOutput = null; + } + + /** + * Indicates if this assignment is currently disconnected from a channel source + */ + public boolean isDisconnected() + { + return mChannelMetadataID == DISCONNECTED; + } + + /** + * Indicates if this assignment is currently connected to a channel source + */ + public boolean isConnected() + { + return !isDisconnected(); + } + + /** + * Connects this assignment to the indicated source so that audio + * packets from this source can be sent to the audio output + */ + public void connect(int source) + { + mChannelMetadataID = source; + + mAudioOutput.updateTimestamp(); + } + + /** + * Currently connected source or -1 if disconnected + */ + public int getChannelMetadataID() + { + return mChannelMetadataID; + } + + /** + * Disconnects this assignment from the source and prevents any audio + * from being routed to the audio output until another source is assigned + */ + public void disconnect() + { + mChannelMetadataID = DISCONNECTED; + mPriority = 0; + } + + /** + * Indicates if audio output is current inactive, meaning that the + * audio output hasn't recently processed any audio packets. + */ + public boolean isInactive() + { + return (mAudioOutput.getLastActivityTimestamp() + AUDIO_TIMEOUT) < System.currentTimeMillis(); + } + + public int getPriority() + { + return mPriority; + } + } } diff --git a/src/audio/AudioModule.java b/src/audio/AudioModule.java index 4a02298f..58b0be0f 100644 --- a/src/audio/AudioModule.java +++ b/src/audio/AudioModule.java @@ -1,29 +1,38 @@ +/******************************************************************************* + * sdrtrunk + * Copyright (C) 2014-2017 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 audio; -import java.util.Arrays; -import java.util.concurrent.ScheduledExecutorService; - +import audio.squelch.ISquelchStateListener; +import audio.squelch.SquelchState; +import channel.metadata.Metadata; import dsp.filter.design.FilterDesignException; import dsp.filter.fir.FIRFilterSpecification; import dsp.filter.fir.remez.RemezFIRFilterDesigner; import dsp.filter.polyphase.PolyphaseFIRDecimatingFilter_RB; import module.Module; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sample.Listener; import sample.real.IFilteredRealBufferListener; import sample.real.RealBuffer; -import audio.metadata.AudioMetadata; -import audio.metadata.IMetadataListener; -import audio.metadata.Metadata; -import audio.squelch.ISquelchStateListener; -import audio.squelch.SquelchState; -import controller.channel.ChannelEvent; -import controller.channel.IChannelEventListener; -import dsp.filter.Filters; -import dsp.filter.fir.real.RealFIRFilter_R_R; + +import java.util.concurrent.ScheduledExecutorService; /** * Provides packaging of demodulated audio sample buffers into audio packets for @@ -33,12 +42,8 @@ import dsp.filter.fir.real.RealFIRFilter_R_R; * Incorporates audio squelch state listener to control if audio packets are * broadcast or ignored. */ -public class AudioModule extends Module implements IAudioPacketProvider, - IChannelEventListener, - IMetadataListener, - IFilteredRealBufferListener, - ISquelchStateListener, - Listener +public class AudioModule extends Module implements IAudioPacketProvider, IFilteredRealBufferListener, + ISquelchStateListener, Listener { protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class); @@ -48,48 +53,41 @@ public class AudioModule extends Module implements IAudioPacketProvider, { //Band-pass filter to both decimate (48kHz to 8kHz) and block 0 - 300 Hz (LTR signalling). FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() - .sampleRate( 48000 ) - .stopFrequency1( 150 ) - .passFrequencyBegin( 300 ) - .passFrequencyEnd( 3500 ) - .stopFrequency2( 4000 ) - .stopRipple( 0.0004 ) - .passRipple( 0.008) - .build(); + .sampleRate(48000) + .stopFrequency1(150) + .passFrequencyBegin(300) + .passFrequencyEnd(3500) + .stopFrequency2(4000) + .stopRipple(0.0004) + .passRipple(0.008) + .build(); RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); try { - if (!designer.isValid()) + if(!designer.isValid()) { throw new FilterDesignException("Couldn't design the audio decimation filter"); } mDecimationCoefficients = designer.getImpulseResponse(); } - catch (FilterDesignException e) + catch(FilterDesignException e) { mLog.debug("Error designing filter", e); } } - /* Provides a unique identifier for this audio module instance to use as a - * source identifier for all audio packets */ - private static int UNIQUE_ID = 0; - private int mSourceID; - - private AudioMetadata mAudioMetadata; - private ChannelEventListener mChannelEventListener = new ChannelEventListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private SquelchState mSquelchState = SquelchState.SQUELCH; private Listener mAudioPacketListener; private PolyphaseFIRDecimatingFilter_RB mAudioDecimationFilter; + private Metadata mMetadata; - public AudioModule(boolean record) + public AudioModule(Metadata metadata) { - mSourceID = ++UNIQUE_ID; - mAudioMetadata = new AudioMetadata(mSourceID, record); + mMetadata = metadata; mAudioDecimationFilter = new PolyphaseFIRDecimatingFilter_RB(mDecimationCoefficients, 6, 2.0f); mAudioDecimationFilter.setListener(new Listener() @@ -97,9 +95,9 @@ public class AudioModule extends Module implements IAudioPacketProvider, @Override public void receive(RealBuffer realBuffer) { - if (mAudioPacketListener != null) + if(mAudioPacketListener != null) { - AudioPacket packet = new AudioPacket(realBuffer.getSamples(), mAudioMetadata.copyOf()); + AudioPacket packet = new AudioPacket(realBuffer.getSamples(), mMetadata.copyOf()); mAudioPacketListener.receive(packet); } } @@ -116,7 +114,6 @@ public class AudioModule extends Module implements IAudioPacketProvider, @Override public void reset() { - mAudioMetadata.reset(); } @Override @@ -128,11 +125,10 @@ public class AudioModule extends Module implements IAudioPacketProvider, @Override public void stop() { - /* Issue an end audio packet in case a recorder is still rolling */ - if (mAudioPacketListener != null) + /* Issue an end audio packet in case a recorder is still rolling */ + if(mAudioPacketListener != null) { - mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, - mAudioMetadata.copyOf())); + mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf())); } } @@ -143,15 +139,9 @@ public class AudioModule extends Module implements IAudioPacketProvider, @Override public void receive(RealBuffer buffer) { - if (mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH) + if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH) { mAudioDecimationFilter.receive(buffer); - -// /* We make a copy of the samples, so that we don't affect any other -// * processes that might be concurrently processing the same buffer */ -// float[] audio = Arrays.copyOf( buffer.getSamples(), buffer.getSamples().length ); -// -// mAudioFilter.filter( audio ); } } @@ -161,12 +151,6 @@ public class AudioModule extends Module implements IAudioPacketProvider, return this; } - @Override - public Listener getMetadataListener() - { - return mAudioMetadata; - } - @Override public void setAudioPacketListener(Listener listener) { @@ -179,41 +163,12 @@ public class AudioModule extends Module implements IAudioPacketProvider, mAudioPacketListener = null; } - @Override - public Listener getChannelEventListener() - { - return mChannelEventListener; - } - @Override public Listener getSquelchStateListener() { return mSquelchStateListener; } - /** - * Wrapper for channel event listener. Responds to channel state reset - * events to remove/cleanup current audio metadata - */ - public class ChannelEventListener implements Listener - { - @Override - public void receive(ChannelEvent event) - { - switch (event.getEvent()) - { - case NOTIFICATION_STATE_RESET: - mAudioMetadata.reset(); - break; - case NOTIFICATION_SELECTION_CHANGE: - mAudioMetadata.setSelected(event.getChannel().isSelected()); - break; - default: - break; - } - } - } - /** * Wrapper for squelch state listener */ @@ -222,10 +177,9 @@ public class AudioModule extends Module implements IAudioPacketProvider, @Override public void receive(SquelchState state) { - if (state == SquelchState.SQUELCH && mAudioPacketListener != null) + if(state == SquelchState.SQUELCH && mAudioPacketListener != null) { - mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, - mAudioMetadata.copyOf())); + mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf())); } mSquelchState = state; diff --git a/src/audio/AudioPacket.java b/src/audio/AudioPacket.java index 2936557e..aa655975 100644 --- a/src/audio/AudioPacket.java +++ b/src/audio/AudioPacket.java @@ -1,77 +1,78 @@ +/******************************************************************************* + * sdrtrunk + * Copyright (C) 2014-2017 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 audio; +import channel.metadata.Metadata; import sample.real.RealBuffer; -import audio.metadata.AudioMetadata; -/******************************************************************************* - * SDR Trunk - * Copyright (C) 2014,2016 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 - ******************************************************************************/ public class AudioPacket { - private Type mType; - private RealBuffer mAudioData; - private AudioMetadata mAudioMetadata; - - public AudioPacket( Type type, AudioMetadata metadata ) - { - mType = type; - mAudioMetadata = metadata; - } - - public AudioPacket( float[] audio, AudioMetadata metadata ) - { - this( Type.AUDIO, metadata ); + private Type mType; + private RealBuffer mAudioData; + private Metadata mMetadata; - mAudioData = new RealBuffer( audio ); - } - - public boolean hasAudioMetadata() - { - return mAudioMetadata != null; - } - - public AudioMetadata getAudioMetadata() - { - return mAudioMetadata; - } - - public void setMetadata( AudioMetadata metadata ) - { - mAudioMetadata = metadata; - } - - public Type getType() - { - return mType; - } - - public RealBuffer getAudioBuffer() - { - return mAudioData; - } - - public boolean hasAudioBuffer() - { - return mAudioData != null; - } - - public enum Type - { - AUDIO, - END; - } + public AudioPacket(Type type, Metadata metadata) + { + mType = type; + mMetadata = metadata; + } + + public AudioPacket(float[] audio, Metadata metadata) + { + this(Type.AUDIO, metadata); + + mAudioData = new RealBuffer(audio); + } + + public boolean hasMetadata() + { + return mMetadata != null; + } + + public Metadata getMetadata() + { + return mMetadata; + } + + public void setMetadata(Metadata metadata) + { + mMetadata = metadata; + } + + public Type getType() + { + return mType; + } + + public RealBuffer getAudioBuffer() + { + return mAudioData; + } + + public boolean hasAudioBuffer() + { + return mAudioData != null; + } + + public enum Type + { + AUDIO, + END; + } } diff --git a/src/audio/broadcast/AudioBroadcaster.java b/src/audio/broadcast/AudioBroadcaster.java index 7467a2b6..2115480b 100644 --- a/src/audio/broadcast/AudioBroadcaster.java +++ b/src/audio/broadcast/AudioBroadcaster.java @@ -1,6 +1,6 @@ /******************************************************************************* * sdrtrunk - * Copyright (C) 2014-2016 Dennis Sheirer + * Copyright (C) 2014-2017 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 @@ -18,14 +18,11 @@ ******************************************************************************/ package audio.broadcast; -import audio.AudioPacket; -import audio.IAudioPacketListener; import audio.convert.ISilenceGenerator; -import audio.metadata.AudioMetadata; +import channel.metadata.Metadata; import controller.ThreadPoolManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import properties.SystemProperties; import sample.Listener; import java.io.ByteArrayInputStream; @@ -34,14 +31,13 @@ import java.nio.file.Files; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public abstract class AudioBroadcaster implements Listener { - private final static Logger mLog = LoggerFactory.getLogger( AudioBroadcaster.class ); + private final static Logger mLog = LoggerFactory.getLogger(AudioBroadcaster.class); public static final int PROCESSOR_RUN_INTERVAL_MS = 1000; @@ -107,9 +103,10 @@ public abstract class AudioBroadcaster implements Listener /** * Broadcasts the next song's audio metadata prior to streaming the next song. + * * @param metadata for the next recording that will be streamed */ - protected void broadcastMetadata(AudioMetadata metadata) + protected void broadcastMetadata(Metadata metadata) { IBroadcastMetadataUpdater metadataUpdater = getMetadataUpdater(); @@ -136,7 +133,7 @@ public abstract class AudioBroadcaster implements Listener if(mThreadPoolManager != null) { mScheduledTask = mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_PROCESSING, - mRecordingQueueProcessor, PROCESSOR_RUN_INTERVAL_MS, TimeUnit.MILLISECONDS ); + mRecordingQueueProcessor, PROCESSOR_RUN_INTERVAL_MS, TimeUnit.MILLISECONDS); } } } @@ -160,6 +157,7 @@ public abstract class AudioBroadcaster implements Listener /** * Stream name for the broadcast configuration for this broadcaster + * * @return stream name or null */ public String getStreamName() @@ -246,7 +244,7 @@ public abstract class AudioBroadcaster implements Listener { if(mBroadcastState != state) { - mLog.info("[" + getStreamName() + "] status: " + state); + mLog.info("[" + getStreamName() + "] status: " + state); mBroadcastState = state; broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE)); @@ -386,7 +384,7 @@ public abstract class AudioBroadcaster implements Listener { mStreamedAudioCount++; broadcast(new BroadcastEvent(AudioBroadcaster.this, - BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE)); + BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE)); metadataUpdateRequired = true; } @@ -431,7 +429,7 @@ public abstract class AudioBroadcaster implements Listener if(connected()) { - broadcastMetadata(nextRecording.getAudioMetadata()); + broadcastMetadata(nextRecording.getMetadata()); } metadataUpdateRequired = false; diff --git a/src/audio/broadcast/AudioRecording.java b/src/audio/broadcast/AudioRecording.java index 1d4c3f16..f80a0c66 100644 --- a/src/audio/broadcast/AudioRecording.java +++ b/src/audio/broadcast/AudioRecording.java @@ -1,6 +1,6 @@ /******************************************************************************* * sdrtrunk - * Copyright (C) 2014-2016 Dennis Sheirer + * Copyright (C) 2014-2017 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 @@ -18,7 +18,7 @@ ******************************************************************************/ package audio.broadcast; -import audio.metadata.AudioMetadata; +import channel.metadata.Metadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,22 +30,23 @@ public class AudioRecording implements Comparable private final static Logger mLog = LoggerFactory.getLogger(StreamManager.class); private Path mPath; - private AudioMetadata mAudioMetadata; private long mStartTime; private long mRecordingLength; private AtomicInteger mPendingReplayCount = new AtomicInteger(); + private Metadata mMetadata; /** * Audio recording that is ready to be streamed + * * @param path to the audio recording file - * @param audioMetadata associated with the recording + * @param metadata associated with the recording * @param start time of recording in milliseconds since epoch * @param recordingLength in milliseconds */ - public AudioRecording(Path path, AudioMetadata audioMetadata, long start, long recordingLength) + public AudioRecording(Path path, Metadata metadata, long start, long recordingLength) { mPath = path; - mAudioMetadata = audioMetadata; + mMetadata = metadata; mStartTime = start; mRecordingLength = recordingLength; } @@ -61,9 +62,9 @@ public class AudioRecording implements Comparable /** * Optional audio metadata for the recording. */ - public AudioMetadata getAudioMetadata() + public Metadata getMetadata() { - return mAudioMetadata; + return mMetadata; } /** diff --git a/src/audio/broadcast/BroadcastModel.java b/src/audio/broadcast/BroadcastModel.java index 911eaf39..f6e9b587 100644 --- a/src/audio/broadcast/BroadcastModel.java +++ b/src/audio/broadcast/BroadcastModel.java @@ -20,7 +20,7 @@ package audio.broadcast; import alias.id.broadcast.BroadcastChannel; import audio.AudioPacket; -import audio.metadata.AudioMetadata; +import channel.metadata.Metadata; import controller.ThreadPoolManager; import icon.IconManager; import org.slf4j.Logger; @@ -48,7 +48,7 @@ import java.util.regex.Pattern; public class BroadcastModel extends AbstractTableModel implements Listener { - private final static Logger mLog = LoggerFactory.getLogger( BroadcastModel.class ); + private final static Logger mLog = LoggerFactory.getLogger(BroadcastModel.class); public static final String TEMPORARY_STREAM_DIRECTORY = "streaming"; public static final String TEMPORARY_STREAM_FILE_SUFFIX = "temporary_streaming_file_"; @@ -84,7 +84,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener