/******************************************************************************* * 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 <http://www.gnu.org/licenses/> * ******************************************************************************/ package controller.channel; import alias.AliasModel; import audio.AudioPacket; import channel.metadata.Attribute; import channel.metadata.AttributeChangeRequest; import channel.metadata.ChannelMetadataModel; import controller.channel.Channel.ChannelType; import controller.channel.ChannelEvent.Event; import controller.channel.map.ChannelMapModel; import filter.FilterSet; import message.Message; import module.Module; import module.ProcessingChain; import module.decode.DecoderFactory; import module.decode.DecoderType; import module.decode.event.MessageActivityModel; import module.log.EventLogManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import record.RecorderManager; import record.RecorderType; import sample.Listener; import source.Source; import source.SourceException; import source.SourceManager; import source.SourceType; import source.config.SourceConfigTuner; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; public class ChannelProcessingManager implements ChannelEventListener { private final static Logger mLog = LoggerFactory.getLogger(ChannelProcessingManager.class); private Map<Integer,ProcessingChain> mProcessingChains = new HashMap<>(); private List<Listener<AudioPacket>> mAudioPacketListeners = new CopyOnWriteArrayList<>(); private List<Listener<Message>> mMessageListeners = new CopyOnWriteArrayList<>(); private ChannelModel mChannelModel; private ChannelMapModel mChannelMapModel; private ChannelMetadataModel mChannelMetadataModel = new ChannelMetadataModel(); private AliasModel mAliasModel; private EventLogManager mEventLogManager; private RecorderManager mRecorderManager; private SourceManager mSourceManager; public ChannelProcessingManager(ChannelModel channelModel, ChannelMapModel channelMapModel, AliasModel aliasModel, EventLogManager eventLogManager, RecorderManager recorderManager, SourceManager sourceManager) { mChannelModel = channelModel; mChannelMapModel = channelMapModel; mAliasModel = aliasModel; mEventLogManager = eventLogManager; mRecorderManager = recorderManager; mSourceManager = sourceManager; } /** * Channel metadata model containing metadata for each channel or channel time-slice that is currently processing. */ public ChannelMetadataModel getChannelMetadataModel() { return mChannelMetadataModel; } /** * Channel model */ public ChannelModel getChannelModel() { return mChannelModel; } /** * Indicates if a processing chain is constructed for the channel and that * the processing chain is currently processing. */ private boolean isProcessing(Channel channel) { return mProcessingChains.containsKey(channel.getChannelID()) && mProcessingChains.get(channel.getChannelID()).isProcessing(); } /** * Returns the current processing chain associated with the channel, or * null if a processing chain is not currently setup for the channel */ public ProcessingChain getProcessingChain(Channel channel) { return mProcessingChains.get(channel.getChannelID()); } /** * Returns the channel associated with the processing chain * @param processingChain * @return channel associated with the processing chain or null */ public Channel getChannel(ProcessingChain processingChain) { return mChannelModel.getChannelFromChannelID(getChannelID(processingChain)); } /** * Returns the channel ID associated with the processing chain */ private Integer getChannelID(ProcessingChain processingChain) { if(processingChain != null) { for(Map.Entry<Integer,ProcessingChain> entry: mProcessingChains.entrySet()) { if(entry.getValue() == processingChain) { return entry.getKey(); } } } return null; } @Override public synchronized void channelChanged(ChannelEvent event) { Channel channel = event.getChannel(); switch(event.getEvent()) { case REQUEST_ENABLE: if(!mProcessingChains.containsKey(channel.getChannelID()) || !mProcessingChains.get(channel.getChannelID()).isProcessing()) { startProcessing(event); } break; case REQUEST_DISABLE: if(channel.getEnabled()) { switch(channel.getChannelType()) { case STANDARD: stopProcessing(channel, true); break; case TRAFFIC: //Don't remove traffic channel processing chains //until explicitly deleted, so that we can reuse them stopProcessing(channel, false); break; default: break; } } case NOTIFICATION_DELETE: if(channel.getEnabled()) { stopProcessing(channel, true); } break; case NOTIFICATION_CONFIGURATION_CHANGE: if(isProcessing(channel)) { stopProcessing(channel, true); startProcessing(event); } break; default: break; } } private void startProcessing(ChannelEvent event) { Channel channel = event.getChannel(); ProcessingChain processingChain = mProcessingChains.get(channel.getChannelID()); //If we're already processing, ignore the request if(processingChain != null && processingChain.isProcessing()) { return; } //Ensure that we can get a source before we construct a new processing chain Source source = null; try { source = mSourceManager.getSource(channel.getSourceConfiguration(), channel.getDecodeConfiguration().getDecoderType().getChannelBandwidth()); } catch(SourceException se) { mLog.debug("Error obtaining source for channel [" + channel.getName() + "]", se); } if(source == null) { channel.setEnabled(false); mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ENABLE_REJECTED)); return; } if(processingChain == null) { processingChain = new ProcessingChain(channel.getChannelType()); /* Register global listeners */ for(Listener<AudioPacket> listener : mAudioPacketListeners) { processingChain.addAudioPacketListener(listener); } for(Listener<Message> listener : mMessageListeners) { processingChain.addMessageListener(listener); } //Register channel to receive frequency correction events to show in the spectral display (hack!) processingChain.addFrequencyChangeListener(channel); /* Processing Modules */ List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this, mAliasModel, channel, processingChain.getChannelState().getMutableMetadata()); processingChain.addModules(modules); /* Setup message activity model with filtering */ FilterSet<Message> messageFilter = DecoderFactory.getMessageFilters(modules); MessageActivityModel messageModel = new MessageActivityModel(messageFilter); processingChain.setMessageActivityModel(messageModel); } //Set the recordable flag to true if the user has requested recording. The metadata class can still //override recordability if any of the aliased values has 'Do Not Record' alias identifier. boolean recordable = channel.getRecordConfiguration() != null && channel.getRecordConfiguration().getRecorders().contains(RecorderType.AUDIO); processingChain.getChannelState().getMutableMetadata().setRecordable(recordable); //Inject channel metadata that will be inserted into audio packets for recorder manager and streaming processingChain.getChannelState().getMutableMetadata().receive( new AttributeChangeRequest<String>(Attribute.CHANNEL_CONFIGURATION_SYSTEM, channel.getSystem())); processingChain.getChannelState().getMutableMetadata().receive( new AttributeChangeRequest<String>(Attribute.CHANNEL_CONFIGURATION_SITE, channel.getSite())); processingChain.getChannelState().getMutableMetadata().receive( new AttributeChangeRequest<String>(Attribute.CHANNEL_CONFIGURATION_NAME, channel.getName())); if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER) { long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency(); processingChain.getChannelState().getMutableMetadata().receive( new AttributeChangeRequest<Long>(Attribute.CHANNEL_FREQUENCY, frequency)); } processingChain.getChannelState().getMutableMetadata().receive( new AttributeChangeRequest<DecoderType>(Attribute.PRIMARY_DECODER_TYPE, channel.getDecodeConfiguration().getDecoderType())); /* Setup event logging */ List<Module> loggers = mEventLogManager.getLoggers(channel.getEventLogConfiguration(), channel.getName()); if(!loggers.isEmpty()) { processingChain.addModules(loggers); } /* Setup recorders */ List<RecorderType> recorders = channel.getRecordConfiguration().getRecorders(); if(!recorders.isEmpty()) { /* Add baseband recorder */ if((recorders.contains(RecorderType.BASEBAND) && channel.getChannelType() == ChannelType.STANDARD)) { processingChain.addModule(mRecorderManager.getBasebandRecorder(channel.toString())); } /* Add traffic channel baseband recorder */ if(recorders.contains(RecorderType.TRAFFIC_BASEBAND) && channel.getChannelType() == ChannelType.TRAFFIC) { processingChain.addModule(mRecorderManager.getBasebandRecorder(channel.toString())); } } processingChain.setSource(source); if(event instanceof TrafficChannelEvent) { TrafficChannelEvent trafficChannelEvent = (TrafficChannelEvent) event; processingChain.getChannelState().configureAsTrafficChannel( trafficChannelEvent.getTrafficChannelManager(), trafficChannelEvent.getCallEvent()); } processingChain.start(); getChannelMetadataModel().add(processingChain.getChannelState().getMutableMetadata(), channel); channel.setEnabled(true); mProcessingChains.put(channel.getChannelID(), processingChain); mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_START)); } private void stopProcessing(Channel channel, boolean remove) { channel.setEnabled(false); if(mProcessingChains.containsKey(channel.getChannelID())) { ProcessingChain processingChain = mProcessingChains.get(channel.getChannelID()); getChannelMetadataModel().remove(processingChain.getChannelState().getMutableMetadata()); processingChain.stop(); processingChain.removeEventLoggingModules(); processingChain.removeRecordingModules(); //Deregister channel from receive frequency correction events to show in the spectral display (hack!) processingChain.removeFrequencyChangeListener(channel); channel.resetFrequencyCorrection(); mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP)); if(remove) { mProcessingChains.remove(channel.getChannelID()); processingChain.dispose(); } } } /** * Adds a message listener that will be added to all channels to receive * any messages. */ public void addAudioPacketListener(Listener<AudioPacket> listener) { mAudioPacketListeners.add(listener); } /** * Removes a message listener. */ public void removeAudioPacketListener(Listener<AudioPacket> listener) { mAudioPacketListeners.remove(listener); } /** * Adds a message listener that will be added to all channels to receive * any messages. */ public void addMessageListener(Listener<Message> listener) { mMessageListeners.add(listener); } /** * Removes a message listener. */ public void removeMessageListener(Listener<Message> listener) { mMessageListeners.remove(listener); } }