Compare commits

...

13 Commits

Author SHA1 Message Date
Denny
980ad72d4d Incrementing build to 0.3.0 beta7 2017-03-03 06:01:09 -05:00
Denny Sheirer
9b746cc032 Merge pull request #215 from DSheirer/198-audio-recorder-handles-left-open
Resolves issue audio recorders left open
2017-03-03 10:58:08 +00:00
Denny
f51ae0b5da Resolves issue with periodic instances of audio recorders not being shutdown correctly. Adds recorder monitor as a fail-safe to automatically shutdown recorders that don't receive an end-call indication. 2017-03-03 05:56:32 -05:00
Denny Sheirer
561078e9bf Merge pull request #214 from DSheirer/210-playlist-saving-all-times
Resolves issue with the playlist saving all of the time
2017-03-03 00:34:25 +00:00
Denny
e052b01f9b Resolves issue with the playlist saving all of the time due to audio streaming broadcast events. 2017-03-02 19:33:10 -05:00
Denny
09cf4fcfd7 Resolves issue with empty string list models in channel editor 2017-02-26 10:29:14 -05:00
Denny
5e731d0e9f Resolves issue with alias list and group combo box value editing. 2017-02-26 06:51:58 -05:00
Denny Sheirer
670a02b0ba Merge pull request #209 from DSheirer/153-cursor-frequency-artifacts
Resolves issue with spectral display rendering artifacts
2017-02-26 10:48:59 +00:00
Denny
9ef156b061 Resolves issue with spectral display rendering artifacts from cursor frequency readout when accessing menu items directly above the spectral display. 2017-02-26 05:47:26 -05:00
Denny
d9cf1b19f3 Removes assert() usage throughout project and replaces with Validate from apache commons. 2017-02-25 16:03:35 -05:00
Denny
a4a339b505 Resolves issue with FCD2 tuner frequency. 2017-02-25 14:58:51 -05:00
Denny
119370c46e Resolves issue with FCD2 tuner frequency. 2017-02-25 14:57:25 -05:00
Denny
4eb66e6045 Updated readme file with screen shot and link to new Version 0.3.0 user's manual. 2017-02-25 06:18:59 -05:00
31 changed files with 1325 additions and 1299 deletions

View File

@@ -2,18 +2,10 @@
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR). A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted) * [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual) * [User's Manual Version 0.2.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0)
* [Download](https://github.com/DSheirer/sdrtrunk/releases) * [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk) * [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
![](https://github.com/DSheirer/sdrtrunk/wiki/images/SDRTrunk_application.png) ![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/images/ApplicationOverview_V0.3.0.png)
<script> **Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-67489234-1', 'auto');
ga('send', 'pageview');
</script>

View File

@@ -7,7 +7,7 @@
<property name="major" value="0"/> <property name="major" value="0"/>
<property name="minor" value="3"/> <property name="minor" value="3"/>
<property name="patch" value="0"/> <property name="patch" value="0"/>
<property name="release" value="-beta6"/> <property name="release" value="-beta7"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/> <property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties --> <!-- Build properties -->

View File

@@ -40,10 +40,11 @@ public class AliasNameEditor extends Editor<Alias>
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>(); private static ComboBoxModel<String> EMPTY_LIST_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_GROUP_MODEL = new DefaultComboBoxModel<>();
private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_MODEL); private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_LIST_MODEL);
private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_MODEL); private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_GROUP_MODEL);
private JComboBox<Icon> mIconCombo; private JComboBox<Icon> mIconCombo;
private JTextField mName; private JTextField mName;
private JButton mButtonColor; private JButton mButtonColor;
@@ -110,7 +111,7 @@ public class AliasNameEditor extends Editor<Alias>
if(groups.isEmpty()) if(groups.isEmpty())
{ {
mGroupCombo.setModel(EMPTY_MODEL); mGroupCombo.setModel(EMPTY_GROUP_MODEL);
} }
else else
{ {
@@ -238,7 +239,7 @@ public class AliasNameEditor extends Editor<Alias>
if(listNames.isEmpty()) if(listNames.isEmpty())
{ {
mListCombo.setModel(EMPTY_MODEL); mListCombo.setModel(EMPTY_LIST_MODEL);
} }
else else
{ {
@@ -253,7 +254,7 @@ public class AliasNameEditor extends Editor<Alias>
if(groupNames.isEmpty()) if(groupNames.isEmpty())
{ {
mGroupCombo.setModel(EMPTY_MODEL); mGroupCombo.setModel(EMPTY_GROUP_MODEL);
} }
else else
{ {
@@ -285,8 +286,8 @@ public class AliasNameEditor extends Editor<Alias>
} }
else else
{ {
mListCombo.setModel(EMPTY_MODEL); mListCombo.setModel(EMPTY_LIST_MODEL);
mGroupCombo.setModel(EMPTY_MODEL); mGroupCombo.setModel(EMPTY_GROUP_MODEL);
mName.setText(null); mName.setText(null);
mButtonColor.setBackground(getBackground()); mButtonColor.setBackground(getBackground());

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package alias.id; package alias.id;
import org.apache.commons.lang3.Validate;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class WildcardID implements Comparable<WildcardID> public class WildcardID implements Comparable<WildcardID>
@@ -45,7 +47,7 @@ public class WildcardID implements Comparable<WildcardID>
*/ */
public WildcardID(String value) public WildcardID(String value)
{ {
assert(value != null && value.contains(WILDCARD) && value.length() < 10); Validate.isTrue(value != null && value.contains(WILDCARD) && value.length() < 10);
mValue = value; mValue = value;

View File

@@ -21,6 +21,7 @@ package audio.broadcast.shoutcast.v2;
import audio.broadcast.BroadcastConfiguration; import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat; import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType; import audio.broadcast.BroadcastServerType;
import org.apache.commons.lang3.Validate;
import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlAttribute;
@@ -90,7 +91,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
*/ */
public void setStreamID(int streamID) public void setStreamID(int streamID)
{ {
assert(1 <= streamID && streamID <= Integer.MAX_VALUE); Validate.isTrue(1 <= streamID && streamID <= Integer.MAX_VALUE);
mStreamID = streamID; mStreamID = streamID;
} }

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package audio.broadcast.shoutcast.v2.ultravox; package audio.broadcast.shoutcast.v2.ultravox;
import org.apache.commons.lang3.Validate;
public class NegotiateMaxPayloadSize extends UltravoxMessage public class NegotiateMaxPayloadSize extends UltravoxMessage
{ {
/** /**
@@ -47,8 +49,8 @@ public class NegotiateMaxPayloadSize extends UltravoxMessage
*/ */
public void setMaximumPayloadSize(int desiredMaximum, int minimum) public void setMaximumPayloadSize(int desiredMaximum, int minimum)
{ {
assert(desiredMaximum <= 16377); Validate.isTrue(desiredMaximum <= 16377);
assert(minimum <= 16377); Validate.isTrue(minimum <= 16377);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(desiredMaximum).append(":").append(minimum); sb.append(desiredMaximum).append(":").append(minimum);

View File

@@ -19,6 +19,7 @@
package audio.broadcast.shoutcast.v2.ultravox; package audio.broadcast.shoutcast.v2.ultravox;
import bits.BinaryMessage; import bits.BinaryMessage;
import org.apache.commons.lang3.Validate;
import java.util.Arrays; import java.util.Arrays;
@@ -154,7 +155,7 @@ public abstract class UltravoxMessage
*/ */
public void setSendQueuePriority(int priority) public void setSendQueuePriority(int priority)
{ {
assert(0 <= priority && priority <= 7); Validate.isTrue(0 <= priority && priority <= 7);
mMessage.setInt(priority, SEND_QUEUE_PRIORITY); mMessage.setInt(priority, SEND_QUEUE_PRIORITY);
} }

View File

@@ -22,6 +22,7 @@ import java.util.BitSet;
import java.util.logging.LogManager; import java.util.logging.LogManager;
import edac.CRC; import edac.CRC;
import org.apache.commons.lang3.Validate;
public class BinaryMessage extends BitSet public class BinaryMessage extends BitSet
{ {
@@ -456,7 +457,7 @@ public class BinaryMessage extends BitSet
*/ */
public byte getByte(int index) public byte getByte(int index)
{ {
assert((index + 7) <= size()); Validate.isTrue((index + 7) <= size());
int value = 0; int value = 0;
@@ -481,7 +482,7 @@ public class BinaryMessage extends BitSet
*/ */
public void setByte(int index, byte value) public void setByte(int index, byte value)
{ {
assert((index + 8) <= size()); Validate.isTrue((index + 8) <= size());
int mask = 0x80; int mask = 0x80;

View File

@@ -17,6 +17,8 @@
******************************************************************************/ ******************************************************************************/
package bits; package bits;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -39,7 +41,7 @@ public class MultiSyncPatternMatcher
public MultiSyncPatternMatcher( int syncSize ) public MultiSyncPatternMatcher( int syncSize )
{ {
assert( syncSize < 64 ); Validate.isTrue(syncSize < 64);
//Setup a bit mask of all ones the length of the sync pattern //Setup a bit mask of all ones the length of the sync pattern
mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 ); mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 );

View File

@@ -26,6 +26,7 @@ import controller.channel.ChannelUtils;
import icon.IconManager; import icon.IconManager;
import module.ProcessingChain; import module.ProcessingChain;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import properties.SystemProperties; import properties.SystemProperties;
@@ -140,7 +141,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
public AliasedValueCellRenderer(Attribute attribute) public AliasedValueCellRenderer(Attribute attribute)
{ {
assert(attribute != null); Validate.isTrue(attribute != null);
mAttribute = attribute; mAttribute = attribute;
setHorizontalAlignment(JLabel.CENTER); setHorizontalAlignment(JLabel.CENTER);
} }

View File

@@ -1,269 +1,267 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer * Copyright (C) 2014-2016 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/ ******************************************************************************/
package controller.channel; package controller.channel;
import alias.AliasModel;
import gui.editor.Editor; import gui.editor.Editor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
import alias.AliasModel;
public class NameConfigurationEditor extends Editor<Channel> implements DocumentListener public class NameConfigurationEditor extends Editor<Channel> implements DocumentListener
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( NameConfigurationEditor.class ); private final static Logger mLog = LoggerFactory.getLogger(NameConfigurationEditor.class);
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>(); private static ComboBoxModel<String> EMPTY_SYSTEM_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_SITE_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_ALIAS_LIST_MODEL = new DefaultComboBoxModel<>();
private AliasModel mAliasModel; private AliasModel mAliasModel;
private ChannelModel mChannelModel; private ChannelModel mChannelModel;
private JTextField mChannelName;
private JComboBox<String> mSystemNameCombo;
private JComboBox<String> mSiteNameCombo;
private JComboBox<String> mAliasListCombo;
public NameConfigurationEditor( AliasModel aliasModel, ChannelModel model )
{
mAliasModel = aliasModel;
mChannelModel = model;
init(); private JTextField mChannelName;
} private JComboBox<String> mSystemNameCombo;
private JComboBox<String> mSiteNameCombo;
private void init() private JComboBox<String> mAliasListCombo;
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]" ) );
mChannelName = new JTextField( "" ); public NameConfigurationEditor(AliasModel aliasModel, ChannelModel model)
mChannelName.getDocument().addDocumentListener( this ); {
mChannelName.setEnabled( false ); mAliasModel = aliasModel;
mChannelModel = model;
add( new JLabel( "Name:" ) ); init();
add( mChannelName ); }
mSystemNameCombo = new JComboBox<>( EMPTY_MODEL ); private void init()
mSystemNameCombo.setEnabled( false ); {
mSystemNameCombo.setEditable( true ); setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]"));
mSystemNameCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
Object selected = mSystemNameCombo.getSelectedItem();
if( selected != null )
{
String system = (String)selected;
List<String> sites = mChannelModel.getSites( system );
if( !sites.contains( getItem().getSite() ) ) mChannelName = new JTextField("");
{ mChannelName.getDocument().addDocumentListener(this);
sites.add( getItem().getSite() ); mChannelName.setEnabled(false);
}
if( sites.isEmpty() )
{
mSiteNameCombo.setModel( EMPTY_MODEL );
}
else
{
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>(
sites.toArray( new String[ sites.size() ] ) ) );;
}
mSiteNameCombo.setSelectedItem( getItem().getSite() );
}
setModified( true );
}
} );
add( new JLabel( "System:" ) );
add( mSystemNameCombo );
mAliasListCombo = new JComboBox<String>(); add(new JLabel("Name:"));
mAliasListCombo.setEnabled( false ); add(mChannelName);
mAliasListCombo.setEditable( true );
mAliasListCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( new JLabel( "Alias List:" ) );
add( mAliasListCombo );
mSiteNameCombo = new JComboBox<>( EMPTY_MODEL );
mSiteNameCombo.setEnabled( false );
mSiteNameCombo.setEditable( true );
mSiteNameCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( new JLabel( "Site:" ) );
add( mSiteNameCombo );
}
@Override
public void save()
{
if( hasItem() && isModified() )
{
getItem().setName( mChannelName.getText() );
Object system = mSystemNameCombo.getSelectedItem();
getItem().setSystem( ( system == null ? null : (String)system ) );
Object site = mSiteNameCombo.getSelectedItem(); mSystemNameCombo = new JComboBox<>(EMPTY_SYSTEM_MODEL);
getItem().setSite( ( site == null ? null : (String)site ) ); mSystemNameCombo.setEnabled(false);
mSystemNameCombo.setEditable(true);
mSystemNameCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Object selected = mSystemNameCombo.getSelectedItem();
Object aliasList = mAliasListCombo.getSelectedItem(); if(selected != null)
getItem().setAliasListName( aliasList == null ? null : (String)aliasList ); {
} String system = (String)selected;
setModified( false );
}
private void setControlsEnabled( boolean enabled ) List<String> sites = mChannelModel.getSites(system);
{
if( mChannelName.isEnabled() != enabled )
{
mChannelName.setEnabled( enabled );
}
if( mAliasListCombo.isEnabled() != enabled ) if(!sites.contains(getItem().getSite()))
{ {
mAliasListCombo.setEnabled( enabled ); sites.add(getItem().getSite());
} }
if( mSystemNameCombo.isEnabled() != enabled ) if(sites.isEmpty())
{ {
mSystemNameCombo.setEnabled( enabled ); mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
} }
else
if( mSiteNameCombo.isEnabled() != enabled ) {
{ mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
mSiteNameCombo.setEnabled( enabled ); sites.toArray(new String[sites.size()])));
} ;
} }
@Override mSiteNameCombo.setSelectedItem(getItem().getSite());
public void setItem( Channel channel ) }
{
super.setItem( channel );
if( hasItem() )
{
setControlsEnabled( true );
mChannelName.setText( channel.getName() );
List<String> systems = mChannelModel.getSystems(); setModified(true);
}
if( systems.isEmpty() ) });
{
mSystemNameCombo.setModel( EMPTY_MODEL );
}
else
{
mSystemNameCombo.setModel( new DefaultComboBoxModel<String>(
systems.toArray( new String[ systems.size() ] ) ) );;
}
mSystemNameCombo.setSelectedItem( channel.getSystem() );
List<String> sites = mChannelModel.getSites( channel.getSystem() ); add(new JLabel("System:"));
add(mSystemNameCombo);
if( sites.isEmpty() ) mAliasListCombo = new JComboBox<String>(EMPTY_ALIAS_LIST_MODEL);
{ mAliasListCombo.setEnabled(false);
mSiteNameCombo.setModel( EMPTY_MODEL ); mAliasListCombo.setEditable(true);
} mAliasListCombo.addActionListener(new ActionListener()
else {
{ @Override
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>( public void actionPerformed(ActionEvent e)
sites.toArray( new String[ sites.size() ] ) ) );; {
} setModified(true);
}
});
if( channel.getSite() != null ) add(new JLabel("Alias List:"));
{ add(mAliasListCombo);
mSiteNameCombo.setSelectedItem( channel.getSite() );
}
List<String> lists = mAliasModel.getListNames();
Collections.sort( lists );
mAliasListCombo.setModel( new DefaultComboBoxModel<String>(
lists.toArray( new String[ lists.size() ] ) ) );
if( channel.getAliasListName() != null )
{
mAliasListCombo.setSelectedItem( channel.getAliasListName() );
}
}
else
{
setControlsEnabled( false );
mChannelName.setText( null );
}
setModified( false );
}
@Override mSiteNameCombo = new JComboBox<>(EMPTY_SITE_MODEL);
public void insertUpdate( DocumentEvent e ) mSiteNameCombo.setEnabled(false);
{ mSiteNameCombo.setEditable(true);
setModified( true ); mSiteNameCombo.addActionListener(new ActionListener()
} {
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
@Override add(new JLabel("Site:"));
public void changedUpdate( DocumentEvent e ) add(mSiteNameCombo);
{ }
setModified( true );
} @Override
public void save()
@Override {
public void removeUpdate( DocumentEvent e ) if(hasItem() && isModified())
{ {
setModified( true ); getItem().setName(mChannelName.getText());
}
Object system = mSystemNameCombo.getSelectedItem();
getItem().setSystem((system == null ? null : (String)system));
Object site = mSiteNameCombo.getSelectedItem();
getItem().setSite((site == null ? null : (String)site));
Object aliasList = mAliasListCombo.getSelectedItem();
getItem().setAliasListName(aliasList == null ? null : (String)aliasList);
}
setModified(false);
}
private void setControlsEnabled(boolean enabled)
{
if(mChannelName.isEnabled() != enabled)
{
mChannelName.setEnabled(enabled);
}
if(mAliasListCombo.isEnabled() != enabled)
{
mAliasListCombo.setEnabled(enabled);
}
if(mSystemNameCombo.isEnabled() != enabled)
{
mSystemNameCombo.setEnabled(enabled);
}
if(mSiteNameCombo.isEnabled() != enabled)
{
mSiteNameCombo.setEnabled(enabled);
}
}
@Override
public void setItem(Channel channel)
{
super.setItem(channel);
if(hasItem())
{
setControlsEnabled(true);
mChannelName.setText(channel.getName());
List<String> systems = mChannelModel.getSystems();
if(systems.isEmpty())
{
mSystemNameCombo.setModel(EMPTY_SYSTEM_MODEL);
}
else
{
mSystemNameCombo.setModel(new DefaultComboBoxModel<String>(
systems.toArray(new String[systems.size()])));
;
}
mSystemNameCombo.setSelectedItem(channel.getSystem());
List<String> sites = mChannelModel.getSites(channel.getSystem());
if(sites.isEmpty())
{
mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
}
else
{
mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
sites.toArray(new String[sites.size()])));
;
}
if(channel.getSite() != null)
{
mSiteNameCombo.setSelectedItem(channel.getSite());
}
List<String> lists = mAliasModel.getListNames();
Collections.sort(lists);
mAliasListCombo.setModel(new DefaultComboBoxModel<String>(
lists.toArray(new String[lists.size()])));
if(channel.getAliasListName() != null)
{
mAliasListCombo.setSelectedItem(channel.getAliasListName());
}
}
else
{
setControlsEnabled(false);
mChannelName.setText(null);
}
setModified(false);
}
@Override
public void insertUpdate(DocumentEvent e)
{
setModified(true);
}
@Override
public void changedUpdate(DocumentEvent e)
{
setModified(true);
}
@Override
public void removeUpdate(DocumentEvent e)
{
setModified(true);
}
} }

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
public class FloatFIRFilter implements RealSampleListener public class FloatFIRFilter implements RealSampleListener
@@ -139,7 +140,7 @@ public class FloatFIRFilter implements RealSampleListener
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
mIndexMap = new int[ size ][ size ]; mIndexMap = new int[ size ][ size ];

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
public class FloatHalfBandFilter implements RealSampleListener public class FloatHalfBandFilter implements RealSampleListener
@@ -149,7 +150,7 @@ public class FloatHalfBandFilter implements RealSampleListener
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1; int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener; import sample.real.RealSampleListener;
public class FloatHalfBandNoDecimateFilter implements RealSampleListener public class FloatHalfBandNoDecimateFilter implements RealSampleListener
@@ -139,7 +140,7 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1; int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -20,6 +20,7 @@ package dsp.filter.cic;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.Validate;
import sample.Listener; import sample.Listener;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListener; import sample.complex.ComplexSampleListener;
@@ -67,7 +68,7 @@ public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
public ComplexPrimeCICDecimate( int decimation, int order, public ComplexPrimeCICDecimate( int decimation, int order,
int passFrequency, int attenuation, WindowType windowType ) int passFrequency, int attenuation, WindowType windowType )
{ {
assert( decimation <= 700 ); Validate.isTrue(decimation <= 700);
List<Integer> stageSizes = getPrimeFactors( decimation ); List<Integer> stageSizes = getPrimeFactors( decimation );

View File

@@ -20,6 +20,7 @@ package dsp.filter.cic;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -81,7 +82,7 @@ public class RealPrimeCICDecimate
public RealPrimeCICDecimate( int decimation, int order, int passFrequency, public RealPrimeCICDecimate( int decimation, int order, int passFrequency,
int attenuation, WindowType windowType, int outputBufferSize ) int attenuation, WindowType windowType, int outputBufferSize )
{ {
assert( decimation <= 700 ); Validate.isTrue(decimation <= 700);
mOutputBufferSize = outputBufferSize; mOutputBufferSize = outputBufferSize;

View File

@@ -17,6 +17,8 @@
******************************************************************************/ ******************************************************************************/
package dsp.filter.halfband.real; package dsp.filter.halfband.real;
import org.apache.commons.lang3.Validate;
public class HalfBandFilter public class HalfBandFilter
{ {
private float[] mBuffer; private float[] mBuffer;
@@ -122,7 +124,7 @@ public class HalfBandFilter
private void generateIndexMap( int size ) private void generateIndexMap( int size )
{ {
//Ensure we have an odd size //Ensure we have an odd size
assert( size % 2 == 1 ); Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1; int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -1,5 +1,6 @@
package dsp.filter.interpolator; package dsp.filter.interpolator;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -31,7 +32,7 @@ public class RealInterpolator extends Interpolator
public float filter( float[] samples, int offset, float mu ) public float filter( float[] samples, int offset, float mu )
{ {
/* Ensure we have enough samples in the array */ /* Ensure we have enough samples in the array */
assert( samples.length >= offset + 7 ); Validate.isTrue(samples.length >= offset + 7);
/* Identify the filter bank that corresponds to mu */ /* Identify the filter bank that corresponds to mu */
int index = (int)( NSTEPS * mu ); int index = (int)( NSTEPS * mu );

View File

@@ -18,6 +18,7 @@
******************************************************************************/ ******************************************************************************/
package dsp.filter.polyphase; package dsp.filter.polyphase;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -53,7 +54,7 @@ public class PolyphaseFIRInterpolatingFilter
public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation ) public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation )
{ {
/* Ensure coefficients are an integral multiple of the interpolation factor */ /* Ensure coefficients are an integral multiple of the interpolation factor */
assert( coefficients.length % interpolation == 0 ); Validate.isTrue(coefficients.length % interpolation == 0);
mTaps = coefficients; mTaps = coefficients;

View File

@@ -17,6 +17,7 @@
******************************************************************************/ ******************************************************************************/
package dsp.gain; package dsp.gain;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -49,7 +50,7 @@ public class DirectGainControl implements GainController, RealSampleListener
*/ */
public DirectGainControl( float gain, float minimum, float maximum, float increment ) public DirectGainControl( float gain, float minimum, float maximum, float increment )
{ {
assert( minimum > 0 ); Validate.isTrue(minimum > 0);
mDefaultGain = gain; mDefaultGain = gain;
mGain = gain; mGain = gain;

View File

@@ -89,6 +89,7 @@ public class PlaylistManager implements ChannelEventListener
@Override @Override
public void receive(AliasEvent t) public void receive(AliasEvent t)
{ {
//Save the playlist for all alias events
schedulePlaylistSave(); schedulePlaylistSave();
} }
}); });
@@ -98,6 +99,7 @@ public class PlaylistManager implements ChannelEventListener
@Override @Override
public void receive(ChannelMapEvent t) public void receive(ChannelMapEvent t)
{ {
//Save the playlist for all channel map events
schedulePlaylistSave(); schedulePlaylistSave();
} }
}); });
@@ -107,7 +109,23 @@ public class PlaylistManager implements ChannelEventListener
@Override @Override
public void receive(BroadcastEvent broadcastEvent) public void receive(BroadcastEvent broadcastEvent)
{ {
schedulePlaylistSave(); switch(broadcastEvent.getEvent())
{
case CONFIGURATION_ADD:
case CONFIGURATION_CHANGE:
case CONFIGURATION_DELETE:
schedulePlaylistSave();
break;
case BROADCASTER_ADD:
case BROADCASTER_QUEUE_CHANGE:
case BROADCASTER_STATE_CHANGE:
case BROADCASTER_STREAMED_COUNT_CHANGE:
case BROADCASTER_AGED_OFF_COUNT_CHANGE:
case BROADCASTER_DELETE:
default:
//Do nothing
break;
}
} }
}); });
} }

View File

@@ -29,79 +29,102 @@ import util.ThreadPool;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class RecorderManager implements Listener<AudioPacket> public class RecorderManager implements Listener<AudioPacket>
{ {
private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class); private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class);
public static final int AUDIO_SAMPLE_RATE = 8000; public static final int AUDIO_SAMPLE_RATE = 8000;
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>(); private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();
private ScheduledFuture<?> mRecorderMonitorFuture;
private boolean mCanStartNewRecorders = true; private boolean mCanStartNewRecorders = true;
/** /**
* Manages all audio recording for all processing channels. Reconstructs audio streams and distributes channel * Audio recording manager. Monitors stream of audio packets produced by decoding channels and automatically starts
* audio to each of the recorders, starting and stopping the recorders as needed. * audio recorders when the channel's metadata designates a call as recordable. Routes call audio to each recorder
* based on audio packet metadata. Recorders are shutdown when the channel sends an end-call audio packet
* indicating that the call is complete. A separate recording monitor periodically checks for idled recorders to
* be stopped for cases when the channel fails to send an end-call audio packet.
*/ */
public RecorderManager() public RecorderManager()
{ {
mRecorderMonitorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecorderMonitor(), 2,
2, TimeUnit.SECONDS);
} }
/**
* Prepares this class for shutdown
*/
public void dispose() public void dispose()
{ {
if(mRecorderMonitorFuture != null)
{
mRecorderMonitorFuture.cancel(true);
}
} }
/**
* Primary ingest point for audio packets from all decoding channels
* @param audioPacket to process
*/
@Override @Override
public void receive(AudioPacket audioPacket) public void receive(AudioPacket audioPacket)
{ {
if(audioPacket.hasMetadata() && if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable())
audioPacket.getMetadata().isRecordable())
{ {
String identifier = audioPacket.getMetadata().getUniqueIdentifier(); String identifier = audioPacket.getMetadata().getUniqueIdentifier();
if(mRecorders.containsKey(identifier)) synchronized(mRecorders)
{ {
RealBufferWaveRecorder recorder = mRecorders.get(identifier); if(mRecorders.containsKey(identifier))
if(audioPacket.getType() == AudioPacket.Type.AUDIO)
{ {
recorder.receive(audioPacket.getAudioBuffer()); RealBufferWaveRecorder recorder = mRecorders.get(identifier);
}
else if(audioPacket.getType() == AudioPacket.Type.END)
{
RealBufferWaveRecorder finished = mRecorders.remove(identifier);
finished.stop();
}
}
else if(audioPacket.getType() == AudioPacket.Type.AUDIO)
{
if(mCanStartNewRecorders)
{
String filePrefix = getFilePrefix(audioPacket);
RealBufferWaveRecorder recorder = null; if(audioPacket.getType() == AudioPacket.Type.AUDIO)
try
{ {
recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix);
recorder.start(ThreadPool.SCHEDULED);
recorder.receive(audioPacket.getAudioBuffer()); recorder.receive(audioPacket.getAudioBuffer());
mRecorders.put(identifier, recorder);
} }
catch(Exception ioe) else if(audioPacket.getType() == AudioPacket.Type.END)
{ {
mCanStartNewRecorders = false; RealBufferWaveRecorder finished = mRecorders.remove(identifier);
finished.stop();
}
}
else if(audioPacket.getType() == AudioPacket.Type.AUDIO)
{
if(mCanStartNewRecorders)
{
String filePrefix = getFilePrefix(audioPacket);
mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " + RealBufferWaveRecorder recorder = null;
"is disabled", ioe);
if(recorder != null) try
{ {
recorder.stop(); recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix);
recorder.start(ThreadPool.SCHEDULED);
recorder.receive(audioPacket.getAudioBuffer());
mRecorders.put(identifier, recorder);
}
catch(Exception ioe)
{
mCanStartNewRecorders = false;
mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " +
"is disabled", ioe);
if(recorder != null)
{
recorder.stop();
}
} }
} }
} }
@@ -150,4 +173,31 @@ public class RecorderManager implements Listener<AudioPacket>
return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString()); return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString());
} }
/**
* Monitors currently running recorders and removes/closes any recorders that are idle more than 6 seconds
*/
public class RecorderMonitor implements Runnable
{
@Override
public void run()
{
synchronized(mRecorders)
{
Iterator<Map.Entry<String,RealBufferWaveRecorder>> it = mRecorders.entrySet().iterator();
while(it.hasNext())
{
Map.Entry<String,RealBufferWaveRecorder> entry = it.next();
if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis())
{
mLog.info("Removing idle recorder [" + entry.getKey() + "]");
it.remove();
entry.getValue().stop();
}
}
}
}
}
} }

View File

@@ -18,6 +18,7 @@
******************************************************************************/ ******************************************************************************/
package record.wave; package record.wave;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sample.ConversionUtils; import sample.ConversionUtils;
@@ -58,7 +59,7 @@ public class MonoWaveReader implements AutoCloseable
*/ */
public MonoWaveReader(Path path, boolean realTime) throws IOException public MonoWaveReader(Path path, boolean realTime) throws IOException
{ {
assert( path != null ); Validate.isTrue(path != null);
mPath = path; mPath = path;
mRealTime = realTime; mRealTime = realTime;

View File

@@ -1,22 +1,33 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014,2015 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package record.wave; package record.wave;
import module.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.ConversionUtils;
import sample.Listener;
import sample.real.IFilteredRealBufferListener;
import sample.real.RealBuffer;
import util.TimeStamp;
import javax.sound.sampled.AudioFormat;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -26,206 +37,175 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.AudioFormat;
import module.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.ConversionUtils;
import sample.Listener;
import sample.real.IFilteredRealBufferListener;
import sample.real.RealBuffer;
import util.TimeStamp;
/** /**
* WAVE audio recorder module for recording real sample buffers to a wave file * WAVE audio recorder module for recording real sample buffers to a wave file
*/ */
public class RealBufferWaveRecorder extends Module public class RealBufferWaveRecorder extends Module
implements IFilteredRealBufferListener, Listener<RealBuffer> implements IFilteredRealBufferListener, Listener<RealBuffer>
{ {
private final static Logger mLog = private final static Logger mLog = LoggerFactory.getLogger(RealBufferWaveRecorder.class);
LoggerFactory.getLogger( RealBufferWaveRecorder.class );
private WaveWriter mWriter; private WaveWriter mWriter;
private String mFilePrefix; private String mFilePrefix;
private Path mFile; private Path mFile;
private AudioFormat mAudioFormat; private AudioFormat mAudioFormat;
private BufferProcessor mBufferProcessor;
private ScheduledFuture<?> mProcessorHandle;
private LinkedBlockingQueue<RealBuffer> mBuffers =
new LinkedBlockingQueue<>( 500 );
private long mLastBufferReceived;
private AtomicBoolean mRunning = new AtomicBoolean();
public RealBufferWaveRecorder( int sampleRate, String filePrefix )
{
mAudioFormat = new AudioFormat( sampleRate, //SampleRate
16, //Sample Size
1, //Channels
true, //Signed
false ); //Little Endian
mFilePrefix = filePrefix; private BufferProcessor mBufferProcessor;
} private ScheduledFuture<?> mProcessorHandle;
private LinkedBlockingQueue<RealBuffer> mBuffers = new LinkedBlockingQueue<>(500);
private long mLastBufferReceived;
/** private AtomicBoolean mRunning = new AtomicBoolean();
* Timestamp of when the latest buffer was received by this recorder
*/
public long getLastBufferReceived()
{
return mLastBufferReceived;
}
public Path getFile() public RealBufferWaveRecorder(int sampleRate, String filePrefix)
{ {
return mFile; mAudioFormat = new AudioFormat(sampleRate, //SampleRate
} 16, //Sample Size
1, //Channels
public void start( ScheduledExecutorService executor ) true, //Signed
{ false); //Little Endian
if( mRunning.compareAndSet( false, true ) )
{
if( mBufferProcessor == null )
{
mBufferProcessor = new BufferProcessor();
}
try mFilePrefix = filePrefix;
{ }
StringBuilder sb = new StringBuilder();
sb.append( mFilePrefix );
sb.append( "_" );
sb.append( TimeStamp.getLongTimeStamp( "_" ) );
sb.append( ".wav" );
mFile = Paths.get( sb.toString() );
mWriter = new WaveWriter( mAudioFormat, mFile ); /**
* Indicates if the recorder is currently running.
*/
public boolean isRunning()
{
return mRunning.get();
}
/**
* Timestamp of when the latest buffer was received by this recorder
*/
public long getLastBufferReceived()
{
return mLastBufferReceived;
}
public Path getFile()
{
return mFile;
}
public void start(ScheduledExecutorService executor)
{
if(mRunning.compareAndSet(false, true))
{
if(mBufferProcessor == null)
{
mBufferProcessor = new BufferProcessor();
}
try
{
StringBuilder sb = new StringBuilder();
sb.append(mFilePrefix);
sb.append("_");
sb.append(TimeStamp.getLongTimeStamp("_"));
sb.append(".wav");
mFile = Paths.get(sb.toString());
mWriter = new WaveWriter(mAudioFormat, mFile);
/* Schedule the processor to run every 500 milliseconds */ /* Schedule the processor to run every 500 milliseconds */
mProcessorHandle = executor.scheduleAtFixedRate( mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS);
mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS );
}
catch( IOException io )
{
mLog.error( "Error starting real buffer recorder", io );
}
}
}
public void stop()
{
/* Signal the buffer processor to end with a poison pill */
receive( new PoisonPill() );
mRunning.set( false );
}
@Override
public void receive( RealBuffer buffer )
{
if( mRunning.get() )
{
boolean success = mBuffers.offer( buffer );
if( !success )
{
mLog.error( "recorder buffer overflow - purging [" +
mFile.toFile().getAbsolutePath() + "]" );
mBuffers.clear();
}
mLastBufferReceived = System.currentTimeMillis();
}
}
@Override
public Listener<RealBuffer> getFilteredRealBufferListener()
{
return this;
}
@Override
public void dispose()
{
stop();
}
@Override
public void reset()
{
}
public class BufferProcessor implements Runnable
{
public void run()
{
try
{
RealBuffer buffer = mBuffers.poll();
while( buffer != null )
{
if( buffer instanceof PoisonPill )
{
buffer = null;
mBuffers.clear();
if( mWriter != null )
{
try
{
mWriter.close();
}
catch( IOException io )
{
mLog.error( "Error stopping complex wave recorder [" +
getFile() + "]", io );
}
}
if( mProcessorHandle != null )
{
mProcessorHandle.cancel( true );
}
mProcessorHandle = null;
mWriter = null;
mFile = null;
}
else
{
mWriter.write( ConversionUtils.convertToSigned16BitSamples( buffer ) );
buffer = mBuffers.poll();
}
}
} }
catch ( IOException ioe ) catch(IOException io)
{ {
/* Stop this module if/when we get an IO exception */ mLog.error("Error starting real buffer recorder", io);
mBuffers.clear(); }
stop(); }
}
mLog.error( "IOException while trying to write to the wave "
+ "writer", ioe ); public void stop()
} {
} if(mRunning.compareAndSet(true, false))
{
try
{
write();
if(mWriter != null)
{
mWriter.close();
mWriter = null;
}
}
catch(IOException ioe)
{
mLog.error("Error writing final audio buffers to recording during shutdown", ioe);
}
}
}
@Override
public void receive(RealBuffer buffer)
{
if(mRunning.get())
{
boolean success = mBuffers.offer(buffer);
if(!success)
{
mLog.error("recorder buffer overflow - purging [" + mFile.toFile().getAbsolutePath() + "]");
mBuffers.clear();
}
mLastBufferReceived = System.currentTimeMillis();
}
}
@Override
public Listener<RealBuffer> getFilteredRealBufferListener()
{
return this;
}
@Override
public void dispose()
{
stop();
}
@Override
public void reset()
{
}
/**
* Writes all audio currently in the queue to the file
* @throws IOException if there are any errors writing the audio
*/
private void write() throws IOException
{
RealBuffer buffer = mBuffers.poll();
while(buffer != null)
{
mWriter.write(ConversionUtils.convertToSigned16BitSamples(buffer));
buffer = mBuffers.poll();
}
}
public class BufferProcessor implements Runnable
{
public void run()
{
try
{
write();
}
catch(IOException ioe)
{
/* Stop this module if/when we get an IO exception */
mBuffers.clear();
stop();
mLog.error("IO Exception while trying to write to the wave writer", ioe);
}
}
} }
/**
* This is used as a sentinel value to signal the buffer processor to end
*/
public class PoisonPill extends RealBuffer
{
public PoisonPill()
{
super( new float[ 1 ] );
}
}
} }

View File

@@ -1,22 +1,25 @@
/******************************************************************************* /*******************************************************************************
* SDR Trunk * sdrtrunk
* Copyright (C) 2014 Dennis Sheirer * Copyright (C) 2014-2017 Dennis Sheirer
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/ ******************************************************************************/
package record.wave; package record.wave;
import org.apache.commons.lang3.Validate;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@@ -32,10 +35,7 @@ import javax.sound.sampled.AudioFormat;
public class WaveWriter implements AutoCloseable public class WaveWriter implements AutoCloseable
{ {
// private final static Logger mLog = LoggerFactory.getLogger( WaveWriter.class ); private static final Pattern FILENAME_PATTERN = Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
private static final Pattern FILENAME_PATTERN =
Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE; public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE;
private AudioFormat mAudioFormat; private AudioFormat mAudioFormat;
@@ -56,11 +56,10 @@ public class WaveWriter implements AutoCloseable
* @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes ) * @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes )
* @throws IOException - if there are any IO issues * @throws IOException - if there are any IO issues
*/ */
public WaveWriter( AudioFormat format, Path file, long maxSize ) public WaveWriter( AudioFormat format, Path file, long maxSize ) throws IOException
throws IOException
{ {
assert( format != null ); Validate.isTrue(format != null);
assert( file != null ); Validate.isTrue( file != null );
mAudioFormat = format; mAudioFormat = format;
mFile = file; mFile = file;
@@ -100,15 +99,11 @@ public class WaveWriter implements AutoCloseable
while( Files.exists( mFile ) ) while( Files.exists( mFile ) )
{ {
mFile = Paths.get( mFile.toFile().getAbsolutePath() mFile = Paths.get( mFile.toFile().getAbsolutePath().replace( ".wav", "_" + version + ".wav" ) );
.replace( ".wav", "_" + version + ".wav" ) );
version++; version++;
} }
Files.createFile( mFile ); mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW ) );
mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE ) );
ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat ); ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat );
@@ -127,9 +122,18 @@ public class WaveWriter implements AutoCloseable
{ {
mFileChannel.force( true ); mFileChannel.force( true );
mFileChannel.close(); mFileChannel.close();
mFileChannel = null;
} }
/** @Override
protected void finalize() throws IOException
{
mFileChannel.force( true );
mFileChannel.close();
mFileChannel = null;
}
/**
* Writes the buffer contents to the file. Assumes that the buffer is full * Writes the buffer contents to the file. Assumes that the buffer is full
* and the first byte of data is at position 0. * and the first byte of data is at position 0.
*/ */

View File

@@ -20,6 +20,7 @@ package sample.adapter;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import org.apache.commons.lang3.Validate;
import source.mixer.MixerChannel; import source.mixer.MixerChannel;
/** /**
@@ -34,7 +35,7 @@ public class ChannelShortAdapter implements ISampleAdapter
public ChannelShortAdapter( MixerChannel channel ) public ChannelShortAdapter( MixerChannel channel )
{ {
/* Only use with LEFT/RIGHT channels */ /* Only use with LEFT/RIGHT channels */
assert( channel != MixerChannel.MONO ); Validate.isTrue(channel != MixerChannel.MONO);
mMixerChannel = channel; mMixerChannel = channel;
} }

View File

@@ -17,6 +17,7 @@
******************************************************************************/ ******************************************************************************/
package sample.complex; package sample.complex;
import org.apache.commons.lang3.Validate;
import sample.Listener; import sample.Listener;
/** /**
@@ -32,7 +33,7 @@ public class ComplexToComplexBufferAssembler implements ComplexSampleListener
public ComplexToComplexBufferAssembler( int bufferSize ) public ComplexToComplexBufferAssembler( int bufferSize )
{ {
assert( bufferSize % 2 == 0 ); Validate.isTrue(bufferSize % 2 == 0);
mBufferSize = bufferSize; mBufferSize = bufferSize;
mBuffer = new float[ mBufferSize ]; mBuffer = new float[ mBufferSize ];

View File

@@ -113,7 +113,16 @@ public class FCD2TunerController extends FCDTunerController
FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config; FCD2TunerConfiguration fcd2 = (FCD2TunerConfiguration)config;
setFrequencyCorrection(fcd2.getFrequencyCorrection()); setFrequencyCorrection(fcd2.getFrequencyCorrection());
setFrequency(fcd2.getFrequency());
long frequency = MINIMUM_TUNABLE_FREQUENCY;
if(fcd2.getFrequency() >= MINIMUM_TUNABLE_FREQUENCY &&
fcd2.getFrequency() <= MAXIMUM_TUNABLE_FREQUENCY)
{
frequency = fcd2.getFrequency();
}
setFrequency(frequency);
setLNAGain(fcd2.getGainLNA()); setLNAGain(fcd2.getGainLNA());
setMixerGain(fcd2.getGainMixer()); setMixerGain(fcd2.getGainMixer());

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,7 @@ import java.util.Arrays;
import javax.swing.JPanel; import javax.swing.JPanel;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -282,7 +283,7 @@ public class SpectrumPanel extends JPanel
*/ */
public void setZoom( int zoom ) public void setZoom( int zoom )
{ {
assert( 0 <= zoom && zoom <= 5 ); Validate.isTrue( 0 <= zoom && zoom <= 5 );
mZoom = zoom; mZoom = zoom;
} }
@@ -305,7 +306,7 @@ public class SpectrumPanel extends JPanel
*/ */
public void setSampleSize( double sampleSize ) public void setSampleSize( double sampleSize )
{ {
assert( 2.0 <= sampleSize && sampleSize <= 32.0 ); Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) ); mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) );
} }

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package util; package util;
import org.apache.commons.lang3.Validate;
import java.util.Arrays; import java.util.Arrays;
public class XTEA public class XTEA
@@ -58,7 +60,7 @@ public class XTEA
*/ */
public void setKey(byte[] key) public void setKey(byte[] key)
{ {
assert(key.length == 16); Validate.isTrue(key.length == 16);
int keyInts[] = new int[8]; int keyInts[] = new int[8];
@@ -94,7 +96,7 @@ public class XTEA
*/ */
public byte[] encrypt(byte[] plainText) public byte[] encrypt(byte[] plainText)
{ {
assert(plainText.length == 8); Validate.isTrue(plainText.length == 8);
//Convert the 8 bytes of plaintext to 2 integers //Convert the 8 bytes of plaintext to 2 integers
int result0 = (plainText[3] << 0) & MASK0 | (plainText[2] << 8) & MASK1 | int result0 = (plainText[3] << 0) & MASK0 | (plainText[2] << 8) & MASK1 |