#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.
This commit is contained in:
Denny
2017-01-17 19:24:54 -05:00
parent 16b69d4aef
commit bc8873de01
67 changed files with 6207 additions and 10127 deletions

View File

@@ -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<AudioMetadata>
public class AudioMetadataProcessor implements Listener<Metadata>
{
@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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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<RealBuffer>
public class AudioModule extends Module implements IAudioPacketProvider, IFilteredRealBufferListener,
ISquelchStateListener, Listener<RealBuffer>
{
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<AudioPacket> 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<RealBuffer>()
@@ -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<Metadata> getMetadataListener()
{
return mAudioMetadata;
}
@Override
public void setAudioPacketListener(Listener<AudioPacket> listener)
{
@@ -179,41 +163,12 @@ public class AudioModule extends Module implements IAudioPacketProvider,
mAudioPacketListener = null;
}
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mChannelEventListener;
}
@Override
public Listener<SquelchState> 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<ChannelEvent>
{
@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;

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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 <http://www.gnu.org/licenses/>
******************************************************************************/
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;
}
}

View File

@@ -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<AudioRecording>
{
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<AudioRecording>
/**
* 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<AudioRecording>
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<AudioRecording>
/**
* 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<AudioRecording>
{
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<AudioRecording>
{
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<AudioRecording>
if(connected())
{
broadcastMetadata(nextRecording.getAudioMetadata());
broadcastMetadata(nextRecording.getMetadata());
}
metadataUpdateRequired = false;

View File

@@ -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<AudioRecording>
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<AudioRecording>
/**
* Optional audio metadata for the recording.
*/
public AudioMetadata getAudioMetadata()
public Metadata getMetadata()
{
return mAudioMetadata;
return mMetadata;
}
/**

View File

@@ -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<AudioPacket>
{
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<Audio
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_RECORDING,
new RecordingDeletionMonitor(), 15l, TimeUnit.SECONDS );
new RecordingDeletionMonitor(), 15l, TimeUnit.SECONDS);
removeOrphanedTemporaryRecordings();
}
@@ -96,7 +96,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
{
List<String> names = new ArrayList<>();
for(BroadcastConfiguration configuration: mBroadcastConfigurations)
for(BroadcastConfiguration configuration : mBroadcastConfigurations)
{
names.add(configuration.getName());
}
@@ -117,7 +117,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
*/
public void addBroadcastConfigurations(List<BroadcastConfiguration> configurations)
{
for(BroadcastConfiguration configuration: configurations)
for(BroadcastConfiguration configuration : configurations)
{
addBroadcastConfiguration(configuration);
}
@@ -138,11 +138,11 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
int index = mBroadcastConfigurations.size() - 1;
fireTableRowsInserted( index, index );
fireTableRowsInserted(index, index);
mBroadcastConfigurationMap.put(configuration.getName(), configuration);
process( new BroadcastEvent( configuration, BroadcastEvent.Event.CONFIGURATION_ADD) );
process(new BroadcastEvent(configuration, BroadcastEvent.Event.CONFIGURATION_ADD));
}
}
}
@@ -164,6 +164,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
/**
* Updates the configuration's name so that it is unique among all other broadcast configurations
*
* @param configuration
*/
private void ensureUniqueName(BroadcastConfiguration configuration)
@@ -235,11 +236,11 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
return false;
}
for(BroadcastConfiguration configurationToCompare: mBroadcastConfigurations)
for(BroadcastConfiguration configurationToCompare : mBroadcastConfigurations)
{
if(configurationToCompare != configuration &&
configurationToCompare.getName() != null &&
configurationToCompare.getName().equals(name))
configurationToCompare.getName() != null &&
configurationToCompare.getName().equals(name))
{
return false;
}
@@ -258,9 +259,9 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
mBroadcastConfigurationMap.remove(broadcastConfiguration.getName());
process(new BroadcastEvent( broadcastConfiguration, BroadcastEvent.Event.CONFIGURATION_DELETE));
process(new BroadcastEvent(broadcastConfiguration, BroadcastEvent.Event.CONFIGURATION_DELETE));
fireTableRowsDeleted( index, index );
fireTableRowsDeleted(index, index);
}
}
@@ -275,8 +276,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
@Override
public void receive(AudioPacket audioPacket)
{
if (audioPacket.hasAudioMetadata() &&
audioPacket.getAudioMetadata().isStreamable())
if(audioPacket.hasMetadata() &&
audioPacket.getMetadata().isStreamable())
{
mStreamManager.receive(audioPacket);
}
@@ -287,7 +288,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
*/
private void createBroadcaster(BroadcastConfiguration broadcastConfiguration)
{
if (broadcastConfiguration != null &&
if(broadcastConfiguration != null &&
broadcastConfiguration.isEnabled() &&
broadcastConfiguration.isValid() &&
!mBroadcasterMap.containsKey(broadcastConfiguration.getName()))
@@ -295,7 +296,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
AudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(mThreadPoolManager,
broadcastConfiguration);
if (audioBroadcaster != null)
if(audioBroadcaster != null)
{
audioBroadcaster.setListener(new Listener<BroadcastEvent>()
{
@@ -377,7 +378,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
*/
private void broadcast(BroadcastEvent event)
{
mBroadcastEventBroadcaster.broadcast( event );
mBroadcastEventBroadcaster.broadcast(event);
}
/**
@@ -395,7 +396,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
case CONFIGURATION_CHANGE:
BroadcastConfiguration broadcastConfiguration = broadcastEvent.getBroadcastConfiguration();
int index = mBroadcastConfigurations.indexOf(broadcastConfiguration);
fireTableRowsUpdated( index, index );
fireTableRowsUpdated(index, index);
//Delete and recreate the broadcaster for any broadcast configuration changes
String previousChannelName = cleanupMapAssociations(broadcastConfiguration);
@@ -452,7 +453,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
{
String oldName = null;
for(Map.Entry<String,BroadcastConfiguration> entry: mBroadcastConfigurationMap.entrySet())
for(Map.Entry<String,BroadcastConfiguration> entry : mBroadcastConfigurationMap.entrySet())
{
if(entry.getValue() == broadcastConfiguration)
{
@@ -488,7 +489,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
{
try
{
if( rowIndex <= mBroadcastConfigurations.size())
if(rowIndex <= mBroadcastConfigurations.size())
{
BroadcastConfiguration configuration = mBroadcastConfigurations.get(rowIndex);
@@ -686,19 +687,19 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
@Override
public void receive(AudioRecording audioRecording)
{
AudioMetadata metadata = audioRecording.getAudioMetadata();
Metadata metadata = audioRecording.getMetadata();
if(metadata != null && metadata.isStreamable())
{
for (BroadcastChannel broadcastChannel : metadata.getBroadcastChannels())
for(BroadcastChannel broadcastChannel : metadata.getBroadcastChannels())
{
String channelName = broadcastChannel.getChannelName();
if (channelName != null)
if(channelName != null)
{
AudioBroadcaster audioBroadcaster = getBroadcaster(channelName);
if (audioBroadcaster != null)
if(audioBroadcaster != null)
{
audioRecording.addPendingReplay();
audioBroadcaster.receive(audioRecording);
@@ -739,7 +740,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
if(!recordingsToDelete.isEmpty())
{
for(AudioRecording recordingToDelete: recordingsToDelete)
for(AudioRecording recordingToDelete : recordingsToDelete)
{
mRecordingQueue.remove(recordingToDelete);
removeRecording(recordingToDelete);

View File

@@ -18,7 +18,7 @@
******************************************************************************/
package audio.broadcast;
import audio.metadata.AudioMetadata;
import channel.metadata.Metadata;
public interface IBroadcastMetadataUpdater
{
@@ -27,5 +27,5 @@ public interface IBroadcastMetadataUpdater
*
* @param metadata containing metadata attributes
*/
void update(AudioMetadata metadata);
void update(Metadata metadata);
}

View File

@@ -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
@@ -48,7 +48,7 @@ public class StreamManager implements Listener<AudioPacket>
private Listener<AudioRecording> mAudioRecordingListener;
private BroadcastFormat mBroadcastFormat;
private Path mTempDirectory;
private Map<Integer, AudioRecorder> mStreamRecorders = new HashMap<>();
private Map<Integer,AudioRecorder> mStreamRecorders = new HashMap<>();
private Runnable mRecorderMonitor;
private ScheduledFuture<?> mRecorderMonitorFuture;
private AtomicBoolean mRunning = new AtomicBoolean();
@@ -68,8 +68,8 @@ public class StreamManager implements Listener<AudioPacket>
public StreamManager(ThreadPoolManager threadPoolManager, Listener<AudioRecording> listener,
BroadcastFormat broadcastFormat, Path tempDirectory)
{
assert(tempDirectory != null && Files.isDirectory(tempDirectory));
assert(mThreadPoolManager != null);
assert (tempDirectory != null && Files.isDirectory(tempDirectory));
assert (mThreadPoolManager != null);
mThreadPoolManager = threadPoolManager;
mAudioRecordingListener = listener;
@@ -91,7 +91,7 @@ public class StreamManager implements Listener<AudioPacket>
}
mRecorderMonitorFuture = mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_PROCESSING,
mRecorderMonitor, 2, TimeUnit.SECONDS);
mRecorderMonitor, 2, TimeUnit.SECONDS);
}
}
@@ -107,11 +107,11 @@ public class StreamManager implements Listener<AudioPacket>
mRecorderMonitorFuture.cancel(true);
}
synchronized (mStreamRecorders)
synchronized(mStreamRecorders)
{
List<Integer> streamKeys = new ArrayList<>(mStreamRecorders.keySet());
for(Integer streamKey: streamKeys)
for(Integer streamKey : streamKeys)
{
removeRecorder(streamKey);
}
@@ -122,19 +122,19 @@ public class StreamManager implements Listener<AudioPacket>
@Override
public void receive(AudioPacket audioPacket)
{
if(mRunning.get() && audioPacket.hasAudioMetadata())
if(mRunning.get() && audioPacket.hasMetadata())
{
synchronized (mStreamRecorders)
synchronized(mStreamRecorders)
{
int sourceChannelID = audioPacket.getAudioMetadata().getSource();
int channelMetadataID = audioPacket.getMetadata().getMetadataID();
AudioPacket.Type type = audioPacket.getType();
if(type == AudioPacket.Type.AUDIO)
{
if(mStreamRecorders.containsKey(sourceChannelID))
if(mStreamRecorders.containsKey(channelMetadataID))
{
AudioRecorder recorder = mStreamRecorders.get(sourceChannelID);
AudioRecorder recorder = mStreamRecorders.get(channelMetadataID);
if(recorder != null)
{
@@ -144,15 +144,15 @@ public class StreamManager implements Listener<AudioPacket>
else
{
AudioRecorder recorder = BroadcastFactory.getAudioRecorder(getTemporaryRecordingPath(),
mBroadcastFormat);
mBroadcastFormat);
recorder.start(mThreadPoolManager.getScheduledExecutorService());
recorder.receive(audioPacket);
mStreamRecorders.put(sourceChannelID, recorder);
mStreamRecorders.put(channelMetadataID, recorder);
}
}
else if(type == AudioPacket.Type.END)
{
removeRecorder(sourceChannelID);
removeRecorder(channelMetadataID);
}
else
{
@@ -203,8 +203,8 @@ public class StreamManager implements Listener<AudioPacket>
StringBuilder sb = new StringBuilder();
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
sb.append(sNextRecordingNumber.incrementAndGet()).append("_");
sb.append( TimeStamp.getLongTimeStamp( "_" ) );
sb.append( mBroadcastFormat.getFileExtension() );
sb.append(TimeStamp.getLongTimeStamp("_"));
sb.append(mBroadcastFormat.getFileExtension());
Path temporaryRecordingPath = mTempDirectory.resolve(sb.toString());
@@ -219,19 +219,19 @@ public class StreamManager implements Listener<AudioPacket>
@Override
public void run()
{
synchronized (mStreamRecorders)
synchronized(mStreamRecorders)
{
long now = System.currentTimeMillis();
mStreamRecorders.entrySet().stream()
.filter(entry -> entry.getValue().getTimeRecordingStart() + MAXIMUM_RECORDER_LIFESPAN_MILLIS < now)
.forEach(entry ->
{
mLog.debug("Maximum recording time limit reached - removing recorder: " +
entry.getValue().getPath().toString());
{
mLog.debug("Maximum recording time limit reached - removing recorder: " +
entry.getValue().getPath().toString());
removeRecorder(entry.getKey());
});
removeRecorder(entry.getKey());
});
}
}
}

View File

@@ -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
@@ -19,9 +19,7 @@
package audio.broadcast.icecast;
import audio.broadcast.IBroadcastMetadataUpdater;
import audio.metadata.AudioMetadata;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
import channel.metadata.Metadata;
import controller.ThreadPoolManager;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
@@ -58,7 +56,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
private NioSocketConnector mSocketConnector;
private AtomicBoolean mUpdating = new AtomicBoolean();
private Queue<String> mMetadataQueue = new LinkedTransferQueue<>();
private Map<String, String> mHTTPHeaders;
private Map<String,String> mHTTPHeaders;
private boolean mStackTraceLoggingSuppressed;
/**
@@ -78,7 +76,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
*/
private NioSocketConnector getSocketConnector()
{
if (mSocketConnector == null)
if(mSocketConnector == null)
{
mSocketConnector = new NioSocketConnector();
// mSocketConnector.getFilterChain().addLast("logger", new LoggingFilter(IcecastBroadcastMetadataUpdater.class));
@@ -116,19 +114,19 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
* and will remain in the update queue until the next metadata update is requested. However, this is a design
* trade-off to avoid having a scheduled runnable repeatedly processing the update queue.
*/
public void update(AudioMetadata audioMetadata)
public void update(Metadata metadata)
{
mMetadataQueue.offer(getSong(audioMetadata));
mMetadataQueue.offer(getSong(metadata));
if (mUpdating.compareAndSet(false, true))
if(mUpdating.compareAndSet(false, true))
{
String song = mMetadataQueue.poll();
while (song != null)
while(song != null)
{
HttpRequest updateRequest = createUpdateRequest(song);
if (updateRequest != null)
if(updateRequest != null)
{
mThreadPoolManager.scheduleOnce(new Runnable()
{
@@ -143,22 +141,22 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
connectFuture.awaitUninterruptibly();
IoSession session = connectFuture.getSession();
if (session != null)
if(session != null)
{
session.write(updateRequest);
}
}
catch (Exception e)
catch(Exception e)
{
Throwable throwableCause = e.getCause();
if (throwableCause instanceof ConnectException)
if(throwableCause instanceof ConnectException)
{
//Do nothing, the server is unavailable
}
else
{
if (!mStackTraceLoggingSuppressed)
if(!mStackTraceLoggingSuppressed)
{
mLog.error("Error sending metadata update. Future errors will " +
"be suppressed", e);
@@ -182,52 +180,45 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
/**
* Creates the song information for a metadata update
*/
private static String getSong(AudioMetadata audioMetadata)
private static String getSong(Metadata metadata)
{
StringBuilder sb = new StringBuilder();
if(audioMetadata != null)
if(metadata != null)
{
Metadata to = audioMetadata.getMetadata(MetadataType.TO);
String to = metadata.getPrimaryAddressTo().getIdentifier();
sb.append("TO:");
if(to != null)
if(metadata.getPrimaryAddressTo().hasAlias())
{
if(to.hasAlias())
{
sb.append(to.getAlias().getName());
}
else
{
sb.append(to.getValue());
}
sb.append(metadata.getPrimaryAddressTo().getAlias().getName());
}
else if(metadata.getPrimaryAddressTo().hasIdentifier())
{
sb.append(metadata.getPrimaryAddressTo().getIdentifier());
}
else
{
sb.append("UNKNOWN");
}
Metadata from = audioMetadata.getMetadata(MetadataType.FROM);
sb.append(" FROM:");
if(from != null)
if(metadata.getPrimaryAddressFrom().hasAlias())
{
if(from.hasAlias())
{
sb.append(from.getAlias().getName());
}
else
{
sb.append(from.getValue());
}
sb.append(metadata.getPrimaryAddressFrom().getAlias().getName());
}
else if(metadata.getPrimaryAddressFrom().hasIdentifier())
{
sb.append(metadata.getPrimaryAddressFrom().getIdentifier());
}
else
{
sb.append("UNKNOWN");
}
}
else
{
@@ -255,7 +246,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
return request;
}
catch (UnsupportedEncodingException e)
catch(UnsupportedEncodingException e)
{
//This should never happen
mLog.error("UTF-8 encoding is not supported - can't update song metadata");

View File

@@ -19,9 +19,7 @@
package audio.broadcast.shoutcast.v1;
import audio.broadcast.IBroadcastMetadataUpdater;
import audio.metadata.AudioMetadata;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
import channel.metadata.Metadata;
import controller.ThreadPoolManager;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
@@ -57,7 +55,6 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
private NioSocketConnector mSocketConnector;
private AtomicBoolean mUpdating = new AtomicBoolean();
private Queue<String> mMetadataQueue = new LinkedTransferQueue<>();
private Map<String, String> mHTTPHeaders;
private boolean mStackTraceLoggingSuppressed;
/**
@@ -77,7 +74,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
*/
private NioSocketConnector getSocketConnector()
{
if (mSocketConnector == null)
if(mSocketConnector == null)
{
mSocketConnector = new NioSocketConnector();
@@ -116,19 +113,19 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
* and will remain in the update queue until the next metadata update is requested. However, this is a design
* trade-off to avoid having a scheduled runnable repeatedly processing the update queue.
*/
public void update(AudioMetadata audioMetadata)
public void update(Metadata metadata)
{
mMetadataQueue.offer(getSong(audioMetadata));
mMetadataQueue.offer(getSong(metadata));
if (mUpdating.compareAndSet(false, true))
if(mUpdating.compareAndSet(false, true))
{
String song = mMetadataQueue.poll();
while (song != null)
while(song != null)
{
HttpRequest updateRequest = createUpdateRequest(song);
if (updateRequest != null)
if(updateRequest != null)
{
mThreadPoolManager.scheduleOnce(new Runnable()
{
@@ -143,22 +140,22 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
connectFuture.awaitUninterruptibly();
IoSession session = connectFuture.getSession();
if (session != null)
if(session != null)
{
session.write(updateRequest);
}
}
catch (Exception e)
catch(Exception e)
{
Throwable throwableCause = e.getCause();
if (throwableCause instanceof ConnectException)
if(throwableCause instanceof ConnectException)
{
//Do nothing, the server is unavailable
}
else
{
if (!mStackTraceLoggingSuppressed)
if(!mStackTraceLoggingSuppressed)
{
mLog.error("Error sending metadata update. Future errors will " +
"be suppressed", e);
@@ -182,52 +179,45 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
/**
* Creates the song information for a metadata update
*/
private static String getSong(AudioMetadata audioMetadata)
private static String getSong(Metadata metadata)
{
StringBuilder sb = new StringBuilder();
if(audioMetadata != null)
if(metadata != null)
{
Metadata to = audioMetadata.getMetadata(MetadataType.TO);
String to = metadata.getPrimaryAddressTo().getIdentifier();
sb.append("TO:");
if(to != null)
if(metadata.getPrimaryAddressTo().hasAlias())
{
if(to.hasAlias())
{
sb.append(to.getAlias().getName());
}
else
{
sb.append(to.getValue());
}
sb.append(metadata.getPrimaryAddressTo().getAlias().getName());
}
else if(metadata.getPrimaryAddressTo().hasIdentifier())
{
sb.append(metadata.getPrimaryAddressTo().getIdentifier());
}
else
{
sb.append("UNKNOWN");
}
Metadata from = audioMetadata.getMetadata(MetadataType.FROM);
sb.append(" FROM:");
if(from != null)
if(metadata.getPrimaryAddressFrom().hasAlias())
{
if(from.hasAlias())
{
sb.append(from.getAlias().getName());
}
else
{
sb.append(from.getValue());
}
sb.append(metadata.getPrimaryAddressFrom().getAlias().getName());
}
else if(metadata.getPrimaryAddressFrom().hasIdentifier())
{
sb.append(metadata.getPrimaryAddressFrom().getIdentifier());
}
else
{
sb.append("UNKNOWN");
}
}
else
{
@@ -256,7 +246,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
return request;
}
catch (UnsupportedEncodingException e)
catch(UnsupportedEncodingException e)
{
//This should never happen
mLog.error("UTF-8 encoding is not supported - can't update song metadata");

View File

@@ -37,9 +37,7 @@ import audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageFactory;
import audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageType;
import audio.broadcast.shoutcast.v2.ultravox.UltravoxMetadata;
import audio.broadcast.shoutcast.v2.ultravox.UltravoxProtocolFactory;
import audio.metadata.AudioMetadata;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
import channel.metadata.Metadata;
import controller.ThreadPoolManager;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
@@ -62,7 +60,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBroadcastMetadataUpdater
{
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2AudioBroadcaster.class );
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV2AudioBroadcaster.class);
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
private int mMaxPayloadSize = 16377;
@@ -93,7 +91,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
public ShoutcastV2Configuration getConfiguration()
{
return (ShoutcastV2Configuration)getBroadcastConfiguration();
return (ShoutcastV2Configuration) getBroadcastConfiguration();
}
/**
@@ -122,7 +120,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
}
@Override
public void update(AudioMetadata metadata)
public void update(Metadata metadata)
{
List<UltravoxMessage> metadataMessages = getMetadataMessages(metadata);
@@ -149,7 +147,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
private boolean connect()
{
if(!connected() && canConnect() &&
(mLastConnectionAttempt + RECONNECT_INTERVAL_MILLISECONDS < System.currentTimeMillis()) &&
(mLastConnectionAttempt + RECONNECT_INTERVAL_MILLISECONDS < System.currentTimeMillis()) &&
mConnecting.compareAndSet(false, true))
{
mLastConnectionAttempt = System.currentTimeMillis();
@@ -242,7 +240,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
* @param metadata containing audio metadata tags and attributes
* @return a sequence of CacheableXMLMetadata messages sufficient to carry the complete set of metadata
*/
private List<UltravoxMessage> getMetadataMessages(AudioMetadata metadata)
private List<UltravoxMessage> getMetadataMessages(Metadata metadata)
{
StringBuilder sb = new StringBuilder();
@@ -264,30 +262,22 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
if(metadata != null)
{
Metadata to = metadata.getMetadata(MetadataType.TO);
if(to != null)
if(metadata.getPrimaryAddressTo().hasAlias())
{
if(to.hasAlias())
{
sb.append(UltravoxMetadata.TITLE_2.asXML(to.getAlias().toString()));
}
else
{
sb.append(UltravoxMetadata.TITLE_2.asXML(to.getValue()));
}
sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getAlias().getName()));
}
else if(metadata.getPrimaryAddressTo().hasIdentifier())
{
sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getIdentifier()));
}
Metadata from = metadata.getMetadata(MetadataType.FROM);
if(from != null)
if(metadata.getPrimaryAddressFrom().hasAlias())
{
if(from.hasAlias())
{
sb.append(UltravoxMetadata.TITLE_3.asXML("From: " + from.getAlias().toString()));
}
else
{
sb.append(UltravoxMetadata.TITLE_3.asXML("From: " + from.getValue()));
}
sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getAlias().getName()));
}
else if(metadata.getPrimaryAddressFrom().hasIdentifier())
{
sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getIdentifier()));
}
}
else
@@ -301,7 +291,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
int pointer = 0;
int messageCounter = 1;
int messageCount = (int)Math.ceil((double)xml.length / (double)(mMaxPayloadSize - 6));
int messageCount = (int) Math.ceil((double) xml.length / (double) (mMaxPayloadSize - 6));
if(messageCount > 32)
{
@@ -317,9 +307,9 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
byte[] payload = new byte[payloadSize + 6];
payload[1] = (byte)(0x01);
payload[3] = (byte)(messageCount & 0xFF);
payload[5] = (byte)(messageCounter & 0xFF);
payload[1] = (byte) (0x01);
payload[3] = (byte) (messageCount & 0xFF);
payload[5] = (byte) (messageCounter & 0xFF);
System.arraycopy(xml, pointer, payload, 6, payloadSize);
@@ -368,9 +358,9 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
{
if(cause instanceof IOException && ((IOException)cause).getMessage().startsWith("Connection reset"))
if(cause instanceof IOException && ((IOException) cause).getMessage().startsWith("Connection reset"))
{
IOException ioe = (IOException)cause;
IOException ioe = (IOException) cause;
if(ioe.getMessage() != null && ioe.getMessage().startsWith("Connection reset"))
{
@@ -398,12 +388,12 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
{
if(object instanceof UltravoxMessage)
{
UltravoxMessage ultravoxMessage = (UltravoxMessage)object;
UltravoxMessage ultravoxMessage = (UltravoxMessage) object;
switch (ultravoxMessage.getMessageType())
switch(ultravoxMessage.getMessageType())
{
case REQUEST_CIPHER:
String cipherKey = ((RequestCipher)ultravoxMessage).getCipher();
String cipherKey = ((RequestCipher) ultravoxMessage).getCipher();
AuthenticateBroadcast authenticateBroadcast = new AuthenticateBroadcast();
@@ -476,7 +466,7 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
}
else
{
mMaxPayloadSize = ((NegotiateMaxPayloadSize)ultravoxMessage).getMaximumPayloadSize();
mMaxPayloadSize = ((NegotiateMaxPayloadSize) ultravoxMessage).getMaximumPayloadSize();
ConfigureIcyPublic configureIcyPublic = new ConfigureIcyPublic();
configureIcyPublic.setPublic(getConfiguration().isPublic());

View File

@@ -1,357 +0,0 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
package audio.metadata;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import alias.id.broadcast.BroadcastChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import alias.Alias;
import alias.id.priority.Priority;
public class AudioMetadata implements Listener<Metadata>
{
protected static final Logger mLog = LoggerFactory.getLogger(AudioMetadata.class);
private int mSource;
private Map<MetadataType, Metadata> mMetadata = new HashMap<>();
private boolean mUpdated = false;
/* Channel Selected == highest priority */
private boolean mSelected = false;
/* Call Priority */
private int mPriority = Priority.DEFAULT_PRIORITY;
/* Indicates if the overall source is recordable */
private boolean mSourceRecordable = false;
/* Indicates the current recordable state */
private boolean mRecordable = false;
/* Indicates the current streamable state */
private boolean mStreamable = false;
/* A unique identifier for this metadata */
private String mIdentifier;
public AudioMetadata(int source, boolean recordable)
{
mSource = source;
setIdentifier(mSource);
mSourceRecordable = recordable;
setRecordable(mSourceRecordable);
}
/**
* Returns a unique identifier for this audio metadata that contains the
* source id and optionally the TO metadata string.
*/
public String getIdentifier()
{
return mIdentifier;
}
public void setIdentifier(int source)
{
setIdentifier(source, null);
}
public void setIdentifier(int source, String id)
{
mIdentifier = "SRC:" + source + " ID:" + (id == null ? "UNKNOWN" : id);
mUpdated = true;
}
/**
* Returns a copy of the current audio metadata.
*/
public AudioMetadata copyOf()
{
AudioMetadata copy = new AudioMetadata(mSource, mSourceRecordable);
copy.mPriority = mPriority;
copy.mSelected = mSelected;
copy.mRecordable = mRecordable;
copy.mStreamable = mStreamable;
copy.mMetadata.putAll(mMetadata);
copy.mUpdated = mUpdated;
copy.mIdentifier = new String(mIdentifier);
mUpdated = false;
return copy;
}
/**
* Removes any accumulated temporal metadata and returns to a default set.
*/
public void reset()
{
//Reset recordable state, identifier and audio call priority levels and reprocess the metadata
mRecordable = mSourceRecordable;
mStreamable = false;
setIdentifier(mSource);
mPriority = Priority.DEFAULT_PRIORITY;
List<MetadataType> toRemove = new ArrayList<>();
for (MetadataType type : mMetadata.keySet())
{
Metadata metadata = mMetadata.get(type);
if (metadata != null)
{
if (metadata.isTemporal())
{
toRemove.add(type);
}
else
{
processMetadata(metadata);
}
}
}
for (MetadataType type : toRemove)
{
mMetadata.remove(type);
}
mUpdated = true;
}
private void processMetadata(Metadata metadata)
{
if (metadata.hasAlias())
{
Alias alias = metadata.getAlias();
if (metadata.getMetadataType() == MetadataType.FROM || metadata.getMetadataType() == MetadataType.TO)
{
if (!alias.isRecordable())
{
mRecordable = false;
}
if (alias.isStreamable())
{
mStreamable = true;
}
}
if (metadata.getMetadataType() == MetadataType.TO)
{
setIdentifier(mSource, metadata.getValue());
}
if (alias != null && alias.getCallPriority() < mPriority)
{
mPriority = alias.getCallPriority();
}
}
}
/**
* Indicates if this audio metadata contains updated information. The flag
* will only be set when new data is added and will be reset by a copyOf()
* method invocation.
*/
public boolean isUpdated()
{
return mUpdated;
}
public void setUpdated(boolean updated)
{
mUpdated = updated;
}
/**
* Adds the metadata. Any temporal metadata will be removed when the
* dispose() method is invoked.
*/
@Override
public void receive(Metadata metadata)
{
if (metadata.isReset())
{
reset();
}
else
{
Metadata existing = mMetadata.get(metadata.getMetadataType());
if (existing == null || !existing.equals(metadata))
{
mMetadata.put(metadata.getMetadataType(), metadata);
processMetadata(metadata);
setUpdated(true);
}
}
mUpdated = true;
}
/**
* Returns the complete set of metadata
*/
public Map<MetadataType, Metadata> getMetadata()
{
return mMetadata;
}
/**
* Returns the metadata object matching the type or null
*/
public Metadata getMetadata(MetadataType type)
{
return mMetadata.get(type);
}
/**
* Source channel ID
*/
public void setSource(int source)
{
mSource = source;
mUpdated = true;
}
public int getSource()
{
return mSource;
}
/**
* Sets the priority of this audio packet within the defined min/max priority range
*/
public void setPriority(int priority)
{
mPriority = priority;
mUpdated = true;
}
/**
* Returns the priority of this audio metadata
*
* @return - priority of audio packet where lower numbers are higher priority
*/
public int getPriority()
{
return mPriority;
}
public boolean isDoNotMonitor()
{
return mPriority == Priority.DO_NOT_MONITOR;
}
/**
* Selected audio channel. A selected channel will override the priority
* level so that the audio can be listened to immediately.
*/
public void setSelected(boolean selected)
{
mSelected = selected;
mUpdated = true;
}
public boolean isSelected()
{
return mSelected;
}
/**
* Indicates if audio associated with this metadata is recordable.
*/
public boolean isRecordable()
{
return mRecordable;
}
public void setRecordable(boolean recordable)
{
mRecordable = recordable;
mUpdated = true;
}
/**
* Indicates if the audio associated with this metadata is streamable
*/
public boolean isStreamable()
{
return mStreamable;
}
public Collection<BroadcastChannel> getBroadcastChannels()
{
Set<BroadcastChannel> broadcastChannels = new TreeSet<>();
for(Metadata metadata: mMetadata.values())
{
if(metadata.hasAlias() && metadata.getAlias().isStreamable())
{
Alias alias = metadata.getAlias();
broadcastChannels.addAll(alias.getBroadcastChannels());
}
}
return broadcastChannels;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Audio Metadata - source:");
sb.append(getSource());
sb.append(" priority:");
sb.append(getPriority());
sb.append(" updated:" + isUpdated());
sb.append(" ");
for (Entry<MetadataType, Metadata> entry : getMetadata().entrySet())
{
sb.append(entry.getValue().getKey());
sb.append(":");
sb.append(entry.getValue().getValue());
sb.append(" ");
}
return sb.toString();
}
}

View File

@@ -1,8 +0,0 @@
package audio.metadata;
import sample.Listener;
public interface IMetadataListener
{
public Listener<Metadata> getMetadataListener();
}

View File

@@ -1,9 +0,0 @@
package audio.metadata;
import sample.Listener;
public interface IMetadataProvider
{
public void setMetadataListener( Listener<Metadata> listener );
public void removeMetadataListener();
}

View File

@@ -1,162 +0,0 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
package audio.metadata;
import alias.Alias;
public class Metadata
{
private MetadataType mMetadataType;
private String mValue;
private Alias mValueAlias;
private boolean mTemporal;
/**
* Constucts a metadata object with the temporal settings. Temporal
* indicates that this metadata will be removed at the end of a reset
* event, or call end event.
*/
public Metadata( MetadataType metadataType, String value, Alias alias, boolean temporal )
{
mMetadataType = metadataType;
mValue = value;
mValueAlias = alias;
mTemporal = temporal;
}
/**
* Constucts a metadata object with the temporal settings. Temporal
* indicates that this metadata will be removed at the end of a reset
* event, or call end event.
*/
public Metadata( MetadataType metadataType, String value, boolean temporal )
{
this( metadataType, value, null, temporal );
}
/**
* Constucts a non-temporal metadata object
*/
public Metadata( MetadataType metadataType, String value, Alias alias )
{
this( metadataType, value, alias, false );
}
/**
* Constucts a non-temporal metadata object
*/
public Metadata( MetadataType metadataType, String value )
{
this( metadataType, value, null, false );
}
public MetadataType getMetadataType()
{
return mMetadataType;
}
public String getKey()
{
return mMetadataType.getLabel();
}
public String getValue()
{
return mValue;
}
public Alias getAlias()
{
return mValueAlias;
}
public boolean hasAlias()
{
return mValueAlias != null;
}
/**
* Indicates that this piece of metadata is only valid for the duration of
* a call event and should be removed during a reset
*/
public boolean isTemporal()
{
return mTemporal;
}
public boolean isReset()
{
return false;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( mMetadataType.getLabel() );
sb.append( ":" );
sb.append( mValue );
sb.append( " Alias:" );
sb.append( mValueAlias == null ? "null" : getAlias().getName() );
sb.append( " [" );
sb.append( mMetadataType.name() );
sb.append( "] temporal:" );
sb.append( mTemporal );
return sb.toString();
}
public boolean equals( Metadata other )
{
if( mMetadataType != other.getMetadataType() )
{
return false;
}
if( mTemporal != other.isTemporal() )
{
return false;
}
if( mValue != null && other.getValue() != null &&
!mValue.contentEquals( other.getValue() ))
{
return false;
}
if( ( mValue == null && other.getValue() != null ) ||
( mValue != null && other.getValue() == null ) )
{
return false;
}
if( mValueAlias != null && other.getAlias() != null &&
!mValueAlias.getName().contentEquals( other.getAlias().getName() ))
{
return false;
}
if( ( mValueAlias == null && other.getAlias() != null ) ||
( mValueAlias != null && other.getAlias() == null ) )
{
return false;
}
return true;
}
}

View File

@@ -1,14 +0,0 @@
package audio.metadata;
public class MetadataReset extends Metadata
{
public MetadataReset()
{
super( MetadataType.TEMPORAL_RESET, "reset", true );
}
public boolean isReset()
{
return true;
}
}

View File

@@ -1,40 +0,0 @@
package audio.metadata;
public enum MetadataType
{
CHANNEL_NUMBER( "Channel Number" ),
CHANNEL_NAME( "Channel Name" ),
DETAILS( "Details" ),
ESN( "ESN" ),
FLEETSYNC( "Fleetsync" ),
FREQUENCY( "Frequency" ),
FROM( "From" ),
MDC1200( "MDC-1200" ),
MOBILE_ID( "Mobile ID" ),
MPT1327( "MPT-1327" ),
PROTOCOL( "Protocol" ),
SITE_ID( "Site ID" ),
STATUS( "Status" ),
SYSTEM( "System" ),
TO( "To" ),
UNIQUE_ID( "Unique ID" ),
TEMPORAL_RESET( "Reset" );
private String mLabel;
private MetadataType( String label )
{
mLabel = label;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return mLabel;
}
}

View File

@@ -1,29 +1,34 @@
/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>
* 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 audio.output;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import audio.AudioEvent;
import audio.AudioPacket;
import audio.AudioPacket.Type;
import channel.metadata.Metadata;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import source.mixer.MixerChannel;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.BooleanControl;
@@ -35,418 +40,412 @@ import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Broadcaster;
import sample.Listener;
import source.mixer.MixerChannel;
import audio.AudioEvent;
import audio.AudioPacket;
import audio.AudioPacket.Type;
import audio.metadata.AudioMetadata;
import controller.ThreadPoolManager;
import controller.ThreadPoolManager.ThreadType;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AudioOutput implements Listener<AudioPacket>, LineListener
{
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
private LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
private int mBufferStartThreshold;
private int mBufferStopThreshold;
private final static Logger mLog = LoggerFactory.getLogger(AudioOutput.class);
private Listener<AudioMetadata> mAudioMetadataListener;
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
private LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
private int mBufferStartThreshold;
private int mBufferStopThreshold;
private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture<?> mProcessorTask;
private Listener<Metadata> mMetadataListener;
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
private SourceDataLine mOutput;
private Mixer mMixer;
private MixerChannel mMixerChannel;
private FloatControl mGainControl;
private BooleanControl mMuteControl;
private ThreadPoolManager mThreadPoolManager;
private ScheduledFuture<?> mProcessorTask;
private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent;
private SourceDataLine mOutput;
private Mixer mMixer;
private MixerChannel mMixerChannel;
private FloatControl mGainControl;
private BooleanControl mMuteControl;
private boolean mCanProcessAudio = false;
private long mLastActivity = System.currentTimeMillis();
private AudioEvent mAudioStartEvent;
private AudioEvent mAudioStopEvent;
/**
* Single audio channel playback with automatic starting and stopping of the
* underlying sourcedataline specified by the mixer and mixer channel
* arguments.
*
* Maintains an internal non-blocking audio packet queue and processes this
* queue 25 times a second (every 40 ms).
*
* @param threadPoolManager for assigning buffer processing schedule task
* @param mixer to obtain source data line
* @param mixerChannel either mono or left/right stereo
* @param audioFormat to use during playback
* @param lineInfo to use when obtaining the source data line
* @param requestedBufferSize of approximately 1 second of audio
*/
public AudioOutput( ThreadPoolManager threadPoolManager,
Mixer mixer,
MixerChannel mixerChannel,
AudioFormat audioFormat,
Line.Info lineInfo,
int requestedBufferSize )
{
mThreadPoolManager = threadPoolManager;
mMixer = mixer;
mMixerChannel = mixerChannel;
try
{
mOutput = (SourceDataLine)mMixer.getLine( lineInfo );
if( mOutput != null )
{
mOutput.open( audioFormat, requestedBufferSize );
//Start threshold: buffer is full with 10% or less of capacity remaining
mBufferStartThreshold = (int)( mOutput.getBufferSize() * 0.10 );
private boolean mCanProcessAudio = false;
private long mLastActivity = System.currentTimeMillis();
//Stop threshold: buffer is empty with 90% or more capacity available
mBufferStopThreshold = (int)( mOutput.getBufferSize() * 0.90 );
/**
* Single audio channel playback with automatic starting and stopping of the
* underlying sourcedataline specified by the mixer and mixer channel
* arguments.
*
* Maintains an internal non-blocking audio packet queue and processes this
* queue 25 times a second (every 40 ms).
*
* @param threadPoolManager for assigning buffer processing schedule task
* @param mixer to obtain source data line
* @param mixerChannel either mono or left/right stereo
* @param audioFormat to use during playback
* @param lineInfo to use when obtaining the source data line
* @param requestedBufferSize of approximately 1 second of audio
*/
public AudioOutput(ThreadPoolManager threadPoolManager,
Mixer mixer,
MixerChannel mixerChannel,
AudioFormat audioFormat,
Line.Info lineInfo,
int requestedBufferSize)
{
mThreadPoolManager = threadPoolManager;
mMixer = mixer;
mMixerChannel = mixerChannel;
try
{
mOutput = (SourceDataLine) mMixer.getLine(lineInfo);
if(mOutput != null)
{
mOutput.open(audioFormat, requestedBufferSize);
//Start threshold: buffer is full with 10% or less of capacity remaining
mBufferStartThreshold = (int) (mOutput.getBufferSize() * 0.10);
//Stop threshold: buffer is empty with 90% or more capacity available
mBufferStopThreshold = (int) (mOutput.getBufferSize() * 0.90);
mOutput.addLineListener(this);
if(mOutput != null)
{
try
{
Control gain = mOutput.getControl(FloatControl.Type.MASTER_GAIN);
mGainControl = (FloatControl) gain;
}
catch(IllegalArgumentException iae)
{
mLog.warn("Couldn't obtain MASTER GAIN control for stereo line [" +
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
}
try
{
Control mute = mOutput.getControl(BooleanControl.Type.MUTE);
mMuteControl = (BooleanControl) mute;
}
catch(IllegalArgumentException iae)
{
mLog.warn("Couldn't obtain MUTE control for stereo line [" +
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
}
mOutput.addLineListener( this );
if( mOutput != null )
{
try
{
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
mGainControl = (FloatControl)gain;
}
catch( IllegalArgumentException iae )
{
mLog.warn( "Couldn't obtain MASTER GAIN control for stereo line [" +
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
}
try
{
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
mMuteControl = (BooleanControl)mute;
}
catch( IllegalArgumentException iae )
{
mLog.warn( "Couldn't obtain MUTE control for stereo line [" +
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
}
/* Run the queue processor task every 40 milliseconds or 25 times a second */
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
ThreadType.AUDIO_PROCESSING, new BufferProcessor(),
40, TimeUnit.MILLISECONDS );
}
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
getChannelName() );
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
getChannelName() );
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
ThreadType.AUDIO_PROCESSING, new BufferProcessor(),
40, TimeUnit.MILLISECONDS);
}
mCanProcessAudio = true;
}
}
catch ( LineUnavailableException e )
{
mLog.error( "Couldn't obtain audio source data line for "
+ "audio output - mixer [" + mMixer.getMixerInfo().getName() + "]" );
}
}
public void reset()
{
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, getChannelName() ) );
}
public void dispose()
{
mCanProcessAudio = false;
mAudioStartEvent = new AudioEvent(AudioEvent.Type.AUDIO_STARTED,
getChannelName());
mAudioStopEvent = new AudioEvent(AudioEvent.Type.AUDIO_STOPPED,
getChannelName());
if( mProcessorTask != null && mThreadPoolManager != null )
{
mThreadPoolManager.cancel( mProcessorTask );
}
mThreadPoolManager = null;
mProcessorTask = null;
mCanProcessAudio = true;
}
}
catch(LineUnavailableException e)
{
mLog.error("Couldn't obtain audio source data line for "
+ "audio output - mixer [" + mMixer.getMixerInfo().getName() + "]");
}
}
mBuffer.clear();
public void reset()
{
broadcast(new AudioEvent(AudioEvent.Type.AUDIO_STOPPED, getChannelName()));
}
mAudioEventBroadcaster.dispose();
mAudioEventBroadcaster = null;
mAudioMetadataListener = null;
if( mOutput != null )
{
mOutput.close();
}
mOutput = null;
mGainControl = null;
mMuteControl = null;
}
public void dispose()
{
mCanProcessAudio = false;
/**
* Converts the audio packet data into a byte buffer format appropriate for
* the underlying source data line.
*/
protected abstract ByteBuffer convert( AudioPacket packet );
if(mProcessorTask != null && mThreadPoolManager != null)
{
mThreadPoolManager.cancel(mProcessorTask);
}
/**
* Audio output channel name
*/
public String getChannelName()
{
return mMixerChannel.getLabel();
}
/**
* Mixer Channel for this audio output
*/
protected MixerChannel getMixerChannel()
{
return mMixerChannel;
}
mThreadPoolManager = null;
mProcessorTask = null;
/**
* Registers a single listener to receive audio start and audio stop events
*/
public void addAudioEventListener( Listener<AudioEvent> listener )
{
mAudioEventBroadcaster.addListener( listener );
}
public void removeAudioEventListener( Listener<AudioEvent> listener )
{
mAudioEventBroadcaster.removeListener( listener );
}
/**
* Broadcasts an audio event to the registered listener
*/
private void broadcast( AudioEvent audioEvent )
{
mAudioEventBroadcaster.broadcast( audioEvent );
}
/**
* Registers a single listener to receive the audio metadata from each
* audio packet
*/
public void setAudioMetadataListener( Listener<AudioMetadata> listener )
{
mAudioMetadataListener = listener;
}
public void removeAudioMetadataListener()
{
mAudioMetadataListener = null;
}
mBuffer.clear();
/**
* Broadcasts audio metadata to the registered listener
*/
private void broadcast( AudioMetadata metadata )
{
if( mAudioMetadataListener != null )
{
mAudioMetadataListener.receive( metadata );
}
}
mAudioEventBroadcaster.dispose();
mAudioEventBroadcaster = null;
mMetadataListener = null;
/**
* Timestamp of either last buffer received or last buffer processed
*/
public long getLastActivityTimestamp()
{
return mLastActivity;
}
if(mOutput != null)
{
mOutput.close();
}
/**
* Updates the last activity timestamp to current system time
*/
public void updateTimestamp()
{
mLastActivity = System.currentTimeMillis();
}
@Override
public void receive( AudioPacket packet )
{
if( mCanProcessAudio )
{
//Update the activity timestamp so that this audio output doesn't
//get disconnected before it starts processing the audio stream
updateTimestamp();
mBuffer.add( packet );
}
}
public class BufferProcessor implements Runnable
{
private AtomicBoolean mProcessing = new AtomicBoolean();
public BufferProcessor()
{
}
mOutput = null;
mGainControl = null;
mMuteControl = null;
}
@Override
public void run()
{
try
{
/**
* Converts the audio packet data into a byte buffer format appropriate for
* the underlying source data line.
*/
protected abstract ByteBuffer convert(AudioPacket packet);
/**
* Audio output channel name
*/
public String getChannelName()
{
return mMixerChannel.getLabel();
}
/**
* Mixer Channel for this audio output
*/
protected MixerChannel getMixerChannel()
{
return mMixerChannel;
}
/**
* Registers a single listener to receive audio start and audio stop events
*/
public void addAudioEventListener(Listener<AudioEvent> listener)
{
mAudioEventBroadcaster.addListener(listener);
}
public void removeAudioEventListener(Listener<AudioEvent> listener)
{
mAudioEventBroadcaster.removeListener(listener);
}
/**
* Broadcasts an audio event to the registered listener
*/
private void broadcast(AudioEvent audioEvent)
{
mAudioEventBroadcaster.broadcast(audioEvent);
}
/**
* Registers a single listener to receive the audio metadata from each
* audio packet
*/
public void setMetadataListener(Listener<Metadata> listener)
{
mMetadataListener = listener;
}
public void removeAudioMetadataListener()
{
mMetadataListener = null;
}
/**
* Broadcasts audio metadata to the registered listener
*/
private void broadcast(Metadata metadata)
{
if(mMetadataListener != null)
{
mMetadataListener.receive(metadata);
}
}
/**
* Timestamp of either last buffer received or last buffer processed
*/
public long getLastActivityTimestamp()
{
return mLastActivity;
}
/**
* Updates the last activity timestamp to current system time
*/
public void updateTimestamp()
{
mLastActivity = System.currentTimeMillis();
}
@Override
public void receive(AudioPacket packet)
{
if(mCanProcessAudio)
{
//Update the activity timestamp so that this audio output doesn't
//get disconnected before it starts processing the audio stream
updateTimestamp();
mBuffer.add(packet);
}
}
public class BufferProcessor implements Runnable
{
private AtomicBoolean mProcessing = new AtomicBoolean();
public BufferProcessor()
{
}
@Override
public void run()
{
try
{
/* The processing flag ensures that only one instance of the
* processor can run at any given time */
if( mProcessing.compareAndSet( false, true ) )
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
mBuffer.drainTo( packets );
for( AudioPacket packet: packets )
{
if( packet.getType() == Type.AUDIO )
{
broadcast( packet.getAudioMetadata() );
if(mProcessing.compareAndSet(false, true))
{
List<AudioPacket> packets = new ArrayList<AudioPacket>();
ByteBuffer buffer = convert( packet );
int wrote = 0;
if( !mOutput.isRunning() )
{
int toWrite = mOutput.available();
if( toWrite > buffer.array().length )
{
toWrite = buffer.array().length;
}
//Top off the buffer and check if we can start it
wrote += mOutput.write( buffer.array(), 0, toWrite );
checkStart();
}
mBuffer.drainTo(packets);
if( mOutput.isRunning() && wrote < buffer.array().length )
{
//Blocking write
wrote += mOutput.write( buffer.array(), wrote,
buffer.array().length - wrote );
}
updateTimestamp();
}
}
for(AudioPacket packet : packets)
{
if(packet.getType() == Type.AUDIO)
{
broadcast(packet.getMetadata());
checkStop();
mProcessing.set( false );
}
}
catch( Exception e )
{
mLog.error( "Error while processing audio buffers", e );
}
}
/**
* Starts audio playback once audio buffer is almost full and remaining
* capacity falls below the start threshold.
*/
private void checkStart()
{
if( mCanProcessAudio &&
!mOutput.isRunning() &&
mOutput.available() <= mBufferStartThreshold )
{
mOutput.start();
}
}
ByteBuffer buffer = convert(packet);
/**
* Stops audio playback and drains the audio buffer to empty when the
* audio buffer is mostly empty and the available buffer capacity
* exceeds the stop threshold
*/
private void checkStop()
{
if( mCanProcessAudio &&
mOutput.isRunning() &&
mOutput.available() >= mBufferStopThreshold )
{
mOutput.drain();
mOutput.stop();
}
}
}
/**
* Sets the mute state for this audio output channel
*/
public void setMuted( boolean muted )
{
if( mMuteControl != null )
{
mMuteControl.setValue( muted );
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
}
}
int wrote = 0;
/**
* Current mute state for this audio output channel
*/
public boolean isMuted()
{
if( mMuteControl != null )
{
return mMuteControl.getValue();
}
return false;
}
if(!mOutput.isRunning())
{
int toWrite = mOutput.available();
/**
* Gain/volume control for this audio output channel, if one is available.
*/
public FloatControl getGainControl()
{
return mGainControl;
}
if(toWrite > buffer.array().length)
{
toWrite = buffer.array().length;
}
public boolean hasGainControl()
{
return mGainControl != null;
}
/**
* Monitors the source data line playback state and broadcasts audio events
* to the registered listener as the state changes
*/
@Override
public void update( LineEvent event )
{
LineEvent.Type type = event.getType();
//Top off the buffer and check if we can start it
wrote += mOutput.write(buffer.array(), 0, toWrite);
if( type == LineEvent.Type.START )
{
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
}
else if( type == LineEvent.Type.STOP )
{
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
}
}
checkStart();
}
if(mOutput.isRunning() && wrote < buffer.array().length)
{
//Blocking write
wrote += mOutput.write(buffer.array(), wrote,
buffer.array().length - wrote);
}
updateTimestamp();
}
}
checkStop();
mProcessing.set(false);
}
}
catch(Exception e)
{
mLog.error("Error while processing audio buffers", e);
}
}
/**
* Starts audio playback once audio buffer is almost full and remaining
* capacity falls below the start threshold.
*/
private void checkStart()
{
if(mCanProcessAudio &&
!mOutput.isRunning() &&
mOutput.available() <= mBufferStartThreshold)
{
mOutput.start();
}
}
/**
* Stops audio playback and drains the audio buffer to empty when the
* audio buffer is mostly empty and the available buffer capacity
* exceeds the stop threshold
*/
private void checkStop()
{
if(mCanProcessAudio &&
mOutput.isRunning() &&
mOutput.available() >= mBufferStopThreshold)
{
mOutput.drain();
mOutput.stop();
}
}
}
/**
* Sets the mute state for this audio output channel
*/
public void setMuted(boolean muted)
{
if(mMuteControl != null)
{
mMuteControl.setValue(muted);
broadcast(new AudioEvent(muted ? AudioEvent.Type.AUDIO_MUTED :
AudioEvent.Type.AUDIO_UNMUTED, getChannelName()));
}
}
/**
* Current mute state for this audio output channel
*/
public boolean isMuted()
{
if(mMuteControl != null)
{
return mMuteControl.getValue();
}
return false;
}
/**
* Gain/volume control for this audio output channel, if one is available.
*/
public FloatControl getGainControl()
{
return mGainControl;
}
public boolean hasGainControl()
{
return mGainControl != null;
}
/**
* Monitors the source data line playback state and broadcasts audio events
* to the registered listener as the state changes
*/
@Override
public void update(LineEvent event)
{
LineEvent.Type type = event.getType();
if(type == LineEvent.Type.START)
{
mAudioEventBroadcaster.broadcast(mAudioStartEvent);
}
else if(type == LineEvent.Type.STOP)
{
mAudioEventBroadcaster.broadcast(mAudioStopEvent);
}
}
}