Compare commits

..

12 Commits

Author SHA1 Message Date
Denny Sheirer
88580af436 Merge pull request #284 from DSheirer/283-broadcastify-text-html-response
283 Broadcastify mount point in use error
2017-11-04 13:31:40 +00:00
Denny
64688a3c20 Updates CHANGELOG 2017-11-04 09:07:46 -04:00
Denny
8be3399a6b Issue #283
Updates Icecast TCP broadcaster to correctly handle content type:text/html
message that normally follows mount point in use error received from
broadcastify.

Changes mount point in use broadcast state to be a temporary error so that
the stream will attempt to reconnect after receiving this error.

Updates logging to include memory allocation/usage metrics with each
logging entry in order to support debugging issues related to high
memory usage situations.  Updates initial application logging to better
identify the host system operating environment.
2017-11-04 09:02:03 -04:00
Denny Sheirer
28340809ca Merge pull request #279 from DSheirer/276-traffic-channel-manager-deadlocked-threads
276 TrafficController Infinite Loop
2017-10-21 09:43:42 -04:00
Denny
f3d3a8187a Resolves #276 Traffic Channel threads locking in overflow state. Issue
occured when the ChannelProcessionManager was allocating a new channel
causing the tuner to invoke the updateLOFrequency() method that was
trying to find a center tune frequency that would accommodate all of the
channels, specifically in the instance where the distance between the
highest channel and the middle channel was exactly one-half of the
tuner's bandwidth.  This situation would cause the calculated adjustment
to be zero and would cause an infinite loop, locking all channels from
accessing the any channel startup/teardown events and causing running
channels to block with buffer overflows. Added 1 to the adjustment to
ensure that the initial adjustment placed the center overlapping channel
fully to the left of the center DC spike so that on the next iteration
that channel would no longer be overlapping.
2017-10-21 09:31:34 -04:00
Denny Sheirer
8484e837b8 Merge pull request #277 from DSheirer/273-gui-title-version
273 Application Title Version String
2017-10-16 19:38:15 -04:00
Denny
01b188ea25 Resolves #273 issue with dangling dash in application title version string. 2017-10-16 19:37:11 -04:00
Denny Sheirer
108691dc3c Merge pull request #274 from DSheirer/272-java-9-compatibility
272 java 9 compatibility
2017-10-09 12:40:04 -04:00
Denny
455aa97286 Updates build number to 'final' 2017-10-09 12:35:28 -04:00
Denny
d608834214 Resolves #272. Updates the windows launch script to perform conditional
launching based on the JRE version 8 or 9.
2017-10-09 16:33:58 +00:00
Denny
41660e1881 Updates the build number to 0.3.1 for the next patch release
Updates the build script for conditional compilation using either Java
8 or Java 9 options.  Java 9 deprecated the java.xml.bind package and
removed it as a default Java SE package, so we have to reference it via
the --add-modules argument.

Updates the linux/mac launch script to check the java runtime version
and use either a Java 8 or Java 9 launch command.
2017-10-08 09:00:10 -04:00
Denny Sheirer
110ccb158c Update README.md
Removed Version 0.2.0 user manual link
2017-09-22 06:16:34 -04:00
11 changed files with 572 additions and 470 deletions

View File

@@ -1,5 +1,13 @@
0.3.2 20171105
- Resolves issue with tuner frequency adjustments that was causing channel processing
to lockup and show persistent 'overflow' state in each channel status
- Resolves issue with broadcastify 'mountpoint-in-use' error handling and updates
memory usage logging
0.3.0-beta1 20170129
0.3.1 20171009
- Resolves Java 9 incompatibility issues
0.3.0 20170922
- REQUIRES: Java 8 (or newer) for compatibility and eventual changeover to JavaFX
- FEATURE: audio streaming - adds capability to stream to multiple remote broadcast
servers (Broadcastify, Icecast v2.3 and v2.4+, and Shoutcast V1 and V2). Each

View File

@@ -2,10 +2,9 @@
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)
* [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)
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/images/ApplicationOverview_V0.3.0.png)
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot

View File

@@ -1,120 +1,142 @@
<project name="SDRTrunk" default="SDRTrunk" basedir=".">
<description>
Ant build script for SDRTrunk Decoder Application
</description>
<description>
Ant build script for SDRTrunk Decoder Application
</description>
<!-- version properties -->
<property name="major" value="0"/>
<property name="minor" value="3"/>
<property name="patch" value="0"/>
<property name="release" value=""/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties -->
<property name="repo" location=".."/>
<property name="source.code" location="${repo}/src"/>
<property name="source.configs" location="${repo}/config"/>
<property name="source.images" location="${repo}/images"/>
<property name="source.libs" location="${repo}/imports"/>
<property name="source.scripts" location="${repo}/scripts"/>
<!-- version properties -->
<property name="major" value="0"/>
<property name="minor" value="3"/>
<property name="patch" value="1"/>
<property name="release" value=""/> <!-- Use dash (-) prefix (e.g. -beta1) -->
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<property name="output" location="${repo}/product"/>
<property name="output.classes" location="${repo}/classes"/>
<property name="output.folder" location="${output}/sdrtrunk"/>
<property name="output.config" location="${output.folder}/config"/>
<property name="output.images" location="${output.folder}/images"/>
<property name="output.libs" location="${output.folder}/libs"/>
<!-- Build properties -->
<property name="repo" location=".."/>
<property name="source.code" location="${repo}/src"/>
<property name="source.configs" location="${repo}/config"/>
<property name="source.images" location="${repo}/images"/>
<property name="source.libs" location="${repo}/imports"/>
<property name="source.scripts" location="${repo}/scripts"/>
<path id="classpath">
<fileset dir="${source.libs}" includes="*.jar" />
</path>
<target name="init">
<!-- Create the classes folder to contain the compiled java classes -->
<mkdir dir="${output.classes}"/>
<!-- Create version file -->
<echo message="${major}.${minor}.${patch}-${release}" file="${output.classes}/sdrtrunk-version"/>
</target>
<property name="output" location="${repo}/product"/>
<property name="output.classes" location="${repo}/classes"/>
<property name="output.folder" location="${output}/sdrtrunk"/>
<property name="output.config" location="${output.folder}/config"/>
<property name="output.images" location="${output.folder}/images"/>
<property name="output.libs" location="${output.folder}/libs"/>
<target name="compile" depends="clean,init" description="Compile java classes" >
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false" />
</target>
<path id="classpath">
<fileset dir="${source.libs}" includes="*.jar"/>
</path>
<target name="SDRTrunk" depends="compile" description="Create SDRTrunk application" >
<!-- Create the distribution directory -->
<mkdir dir="${output.folder}"/>
<target name="init">
<!-- Create the classes folder to contain the compiled java classes -->
<mkdir dir="${output.classes}"/>
<!-- Create jar file -->
<jar jarfile="${output.folder}/sdrtrunk.jar" basedir="${output.classes}">
<manifest>
<attribute name="Title" value="sdrtrunk"/>
<attribute name="Main-Class" value="gui.SDRTrunk"/>
<attribute name="Version" value="${major}.${minor}.${patch}-${release}"/>
</manifest>
<fileset dir="${source.configs}">
<exclude name="*.rules"/>
</fileset>
<fileset dir="${repo}">
<include name="LICENSE"/>
</fileset>
</jar>
<!-- Copy third-party libraries -->
<mkdir dir="${output.libs}"/>
<copy todir="${output.libs}">
<fileset dir="${source.libs}" includes="*.jar" />
</copy>
<!-- Copy config files -->
<mkdir dir="${output.config}"/>
<!-- Create version file -->
<echo message="${major}.${minor}.${patch}${release}" file="${output.classes}/sdrtrunk-version"/>
</target>
<copy todir="${output.config}">
<fileset dir="${source.configs}">
<include name="*.rules"/>
</fileset>
</copy>
<!-- Copy image files -->
<mkdir dir="${output.images}"/>
<copy todir="${output.images}">
<fileset dir="${source.images}" />
</copy>
<!-- Copy license file -->
<copy todir="${output.folder}">
<fileset dir="${repo}">
<include name="LICENSE"/>
</fileset>
</copy>
<!-- Copy program scripts -->
<copy todir="${output.folder}">
<fileset dir="${source.scripts}" />
</copy>
<target name="compile" depends="clean,init,compile-java-8,compile-java-9"
description="Compile java classes" />
<target name="compile-java-8" depends="check-java-version" if="java.8" description="Compiles code with Java 8 arguments">
<echo message="Compiling with Java 8 Options"/>
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false"/>
</target>
<target name="compile-java-9" depends="check-java-version" if="java.9" description="Compiles code with Java 9 arguments">
<echo message="Compiling with Java 9 Options"/>
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false">
<compilerarg value="--add-modules"/>
<compilerarg value="java.xml.bind"/>
</javac>
</target>
<target name="check-java-version" description="Determines if the compiler is Java 8 or 9">
<condition property="java.8" value="true">
<contains string="${java.version}" substring="1.8"/>
</condition>
<condition property="java.9" value="true">
<equals arg1="${java.version}" arg2="9"/>
</condition>
<echo message="Java Version: ${java.version}"/>
</target>
<target name="SDRTrunk" depends="compile" description="Create SDRTrunk application">
<!-- Create the distribution directory -->
<mkdir dir="${output.folder}"/>
<!-- Create jar file -->
<jar jarfile="${output.folder}/sdrtrunk.jar" basedir="${output.classes}">
<manifest>
<attribute name="Title" value="sdrtrunk"/>
<attribute name="Main-Class" value="gui.SDRTrunk"/>
<attribute name="Version" value="${major}.${minor}.${patch}-${release}"/>
</manifest>
<fileset dir="${source.configs}">
<exclude name="*.rules"/>
</fileset>
<fileset dir="${repo}">
<include name="LICENSE"/>
</fileset>
</jar>
<!-- Copy third-party libraries -->
<mkdir dir="${output.libs}"/>
<copy todir="${output.libs}">
<fileset dir="${source.libs}" includes="*.jar"/>
</copy>
<!-- Copy config files -->
<mkdir dir="${output.config}"/>
<copy todir="${output.config}">
<fileset dir="${source.configs}">
<include name="*.rules"/>
</fileset>
</copy>
<!-- Copy image files -->
<mkdir dir="${output.images}"/>
<copy todir="${output.images}">
<fileset dir="${source.images}"/>
</copy>
<!-- Copy license file -->
<copy todir="${output.folder}">
<fileset dir="${repo}">
<include name="LICENSE"/>
</fileset>
</copy>
<!-- Copy program scripts -->
<copy todir="${output.folder}">
<fileset dir="${source.scripts}"/>
</copy>
<!-- Bundle the files into .zip -->
<zip destfile="${output}/${filename}.zip" basedir="${output}" />
<!-- Bundle the files into .zip -->
<zip destfile="${output}/${filename}.zip" basedir="${output}"/>
<!-- Remove linux start script before tar task -->
<delete file="${output.folder}/run_sdrtrunk_linux.sh"/>
<!-- Remove linux start script before tar task -->
<delete file="${output.folder}/run_sdrtrunk_linux.sh"/>
<!-- Bundle files into .tar.gz and copy/set permissions on linux start script -->
<tar destfile="${output}/${filename}.tar.gz" compression="gzip">
<tarfileset dir="${output.folder}" prefix="sdrtrunk" />
<tarfileset dir="${source.scripts}" prefix="sdrtrunk" filemode="777">
<include name="run_sdrtrunk_linux.sh"/>
</tarfileset>
</tar>
<delete dir="${output.folder}"/>
</target>
<!-- Bundle files into .tar.gz and copy/set permissions on linux start script -->
<tar destfile="${output}/${filename}.tar.gz" compression="gzip">
<tarfileset dir="${output.folder}" prefix="sdrtrunk"/>
<tarfileset dir="${source.scripts}" prefix="sdrtrunk" filemode="777">
<include name="run_sdrtrunk_linux.sh"/>
</tarfileset>
</tar>
<target name="clean" description="Clean previous build artifacts" >
<delete dir="${output.classes}"/>
<delete dir="${output}"/>
</target>
<delete dir="${output.folder}"/>
</target>
<target name="clean" description="Clean previous build artifacts">
<delete dir="${output.classes}"/>
<delete dir="${output}"/>
</target>
</project>

View File

@@ -1,4 +1,6 @@
<configuration>
<conversionRule conversionWord="memory_usage" converterClass="util.MemoryUsageLogger" />
<timestamp key="appstart" datePattern="HHmmss"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
@@ -9,7 +11,7 @@
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-25(%d{yyyyMMdd HHmmss.SSS} [%thread]) %-5level %logger{30} - %msg%n</pattern>
<pattern>%-25(%d{yyyyMMdd HHmmss.SSS} [%thread]) %-5level %logger{30} - %msg %memory_usage%n</pattern>
</encoder>
</appender>
@@ -19,7 +21,7 @@
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-12(%d{HH:mm:ss.SSS}) %-5level %logger{25} - %msg%n</pattern>
<pattern>%-12(%d{HH:mm:ss.SSS}) %-5level %logger{25} - %msg %memory_usage%n</pattern>
</encoder>
</appender>

View File

@@ -1,2 +1,16 @@
#!/bin/bash
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk
java -version
# Test for java version 9
JAVA_9=$(java -version 2>&1 | grep -i version | sed 's/.* "\([9]\).*/\1/')
if [ -z ${JAVA_9+x} ]; then
echo Using Java 9 program options
# Java 9 uses the G1 garbage collector by default and requires that we specify the
# java.xml.bind package since it's been removed from the java se core library
java -cp "*:libs/*" --add-modules java.xml.bind gui.SDRTrunk;
else
echo Using Java 8 program options
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk;
fi

View File

@@ -1 +1,25 @@
java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
@echo off
java -version
SETLOCAL ENABLEDELAYEDEXPANSION
for /f "usebackq tokens=3" %%a in (`java -version 2^>^&1 ^| find "version"`) do (
rem set b to the unquoted version string parsed from the java version command
set b=%%~a
rem set c to the first character of the unquoted version string
set c=!b:~0,1!
if !c! equ 9 (
echo Using Java 9 options
java -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" --add-modules java.xml.bind gui.SDRTrunk
) else (
echo Using Java 8 options
java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
)
)
ENDLOCAL
@echo on

View File

@@ -68,7 +68,7 @@ public enum BroadcastState
/**
* Specified mount point is already in use
*/
MOUNT_POINT_IN_USE("Mount Point In Use", true),
MOUNT_POINT_IN_USE("Mount Point In Use", false),
/**
* Server is not known or reachable

View File

@@ -325,6 +325,10 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
}
else if(message.contains("Content-Type: text/html"))
{
//Disregard ... this normally follows the mount point in use error
}
else if(message.contains("Invalid Password") ||
message.contains("Authentication Required"))
{

View File

@@ -33,6 +33,7 @@ import icon.IconManager;
import map.MapService;
import module.log.EventLogManager;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import playlist.PlaylistManager;
@@ -80,16 +81,16 @@ public class SDRTrunk implements Listener<TunerEvent>
public SDRTrunk()
{
mLog.info("");
mLog.info("*******************************************************************");
mLog.info("**** sdrtrunk: a trunked radio and digital decoding application ***");
mLog.info("**** website: https://github.com/dsheirer/sdrtrunk ***");
mLog.info("*******************************************************************");
mLog.info("");
mLog.info("Host CPU Cores: " + Runtime.getRuntime().availableProcessors());
mLog.info("Host Memory Total: " + Runtime.getRuntime().totalMemory());
mLog.info("Host Memory Max: " + Runtime.getRuntime().maxMemory());
mLog.info("Host Memory Free: " + Runtime.getRuntime().freeMemory());
mLog.info("Memory Logging Format: [Used/Allocated PercentUsed%]");
mLog.info("Host CPU Cores: " + Runtime.getRuntime().availableProcessors());
mLog.info("Host OS Name: " + System.getProperty("os.name"));
mLog.info("Host OS Arch: " + System.getProperty("os.arch"));
mLog.info("Host OS Version: " + System.getProperty("os.version"));
mLog.info("Host Max Java Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
//Setup the application home directory
Path home = getHomePath();

View File

@@ -1,29 +1,24 @@
/*******************************************************************************
* SDR Trunk
* 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
* 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 source.tuner;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.RejectedExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import source.SourceException;
import source.tuner.configuration.TunerConfiguration;
import source.tuner.frequency.FrequencyChangeEvent;
@@ -32,365 +27,344 @@ import source.tuner.frequency.FrequencyController;
import source.tuner.frequency.FrequencyController.Tunable;
import source.tuner.frequency.IFrequencyChangeProcessor;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.RejectedExecutionException;
public abstract class TunerController implements Tunable, IFrequencyChangeProcessor
{
private final static Logger mLog =
LoggerFactory.getLogger( TunerController.class );
private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
/* List of currently tuned channels being served to demod channels */
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
protected FrequencyController mFrequencyController;
private int mMiddleUnusable;
private double mUsableBandwidthPercentage;
/**
* Abstract tuner controller class. The tuner controller manages frequency
* bandwidth and currently tuned channels that are being fed samples from
* the tuner.
*
* @param minimumFrequency minimum uncorrected tunable frequency
* @param maximumFrequency maximum uncorrected tunable frequency
* @param middleUnusable is the +/- value center DC spike to avoid for channels
* @param usableBandwidthPercentage is the unusable space at the extreme ends of the spectrum
*
* @throws SourceException - for any issues related to constructing the
* class, tuning a frequency, or setting the bandwidth
*/
public TunerController( long minimumFrequency,
long maximumFrequency,
int middleUnusable,
double usableBandwidthPercentage )
{
mFrequencyController = new FrequencyController( this,
minimumFrequency,
maximumFrequency,
0.0d );
mMiddleUnusable = middleUnusable;
mUsableBandwidthPercentage = usableBandwidthPercentage;
}
/**
* Applies the settings in the tuner configuration
*/
public abstract void apply( TunerConfiguration config )
throws SourceException;
/* List of currently tuned channels being served to demod channels */
private SortedSet<TunerChannel> mTunedChannels = new ConcurrentSkipListSet<>();
protected FrequencyController mFrequencyController;
private int mMiddleUnusable;
private double mUsableBandwidthPercentage;
/**
* Responds to requests to set the frequency
*/
@Override
public void frequencyChanged( FrequencyChangeEvent event ) throws SourceException
{
if( event.getEvent() == Event.REQUEST_FREQUENCY_CHANGE )
{
setFrequency( event.getValue().longValue() );
}
}
public int getBandwidth()
/**
* Abstract tuner controller class. The tuner controller manages frequency bandwidth and currently tuned channels
* that are being fed samples from the tuner.
*
* @param minimumFrequency minimum uncorrected tunable frequency
* @param maximumFrequency maximum uncorrected tunable frequency
* @param middleUnusable is the +/- value center DC spike to avoid for channels
* @param usableBandwidth percentage of usable bandwidth relative to space at the extreme ends of the spectrum
* @throws SourceException - for any issues related to constructing the
* class, tuning a frequency, or setting the bandwidth
*/
public TunerController(long minimumFrequency, long maximumFrequency, int middleUnusable, double usableBandwidth)
{
return mFrequencyController.getBandwidth();
mFrequencyController = new FrequencyController(this, minimumFrequency, maximumFrequency, 0.0d);
mMiddleUnusable = middleUnusable;
mUsableBandwidthPercentage = usableBandwidth;
}
/**
* Applies the settings in the tuner configuration
*/
public abstract void apply(TunerConfiguration config) throws SourceException;
/**
* Responds to requests to set the frequency
*/
@Override
public void frequencyChanged(FrequencyChangeEvent event) throws SourceException
{
if(event.getEvent() == Event.REQUEST_FREQUENCY_CHANGE)
{
setFrequency(event.getValue().longValue());
}
}
public int getBandwidth()
{
return mFrequencyController.getBandwidth();
}
/**
* Sets the center frequency of the local oscillator.
*
*
* @param frequency in hertz
* @throws SourceException - if the tuner has any issues
*/
public void setFrequency( long frequency ) throws SourceException
{
mFrequencyController.setFrequency( frequency );
}
/**
* Gets the center frequency of the local oscillator
*
* @return frequency in hertz
*/
public long getFrequency()
{
return mFrequencyController.getFrequency();
}
public int getSampleRate()
{
return mFrequencyController.getBandwidth();
}
public double getFrequencyCorrection()
{
return mFrequencyController.getFrequencyCorrection();
}
public void setFrequencyCorrection( double correction ) throws SourceException
{
mFrequencyController.setFrequencyCorrection( correction );
}
public long getMinFrequency()
{
return mFrequencyController.getMinimumFrequency();
}
public long getMaxFrequency()
{
return mFrequencyController.getMaximumFrequency();
}
private long getMinTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() - ( getUsableBandwidth() / 2 );
}
private long getMaxTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() + ( getUsableBandwidth() / 2 );
}
/**
* Indicates if channel along with all of the other currently sourced
* channels can fit within the tunable bandwidth.
*/
private boolean canTune( TunerChannel channel )
{
//Make sure we're within the tunable frequency range of this tuner
if( getMinFrequency() < channel.getMinFrequency() &&
getMaxFrequency() > channel.getMaxFrequency() )
{
//If this is the first lock, then we're good
if( mTunedChannels.isEmpty() )
{
return true;
}
else
{
int usableBandwidth = getUsableBandwidth();
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Requested channel is within current locked channel frequency range
if( minLockedFrequency <= channel.getMinFrequency() &&
channel.getMaxFrequency() <= maxLockedFrequency )
{
return true;
}
//Requested channel is higher than min locked frequency
if( channel.getMaxFrequency() > minLockedFrequency &&
channel.getMaxFrequency() - minLockedFrequency <= usableBandwidth )
{
return true;
}
//Requested channel is lower than the max locked frequency
if( channel.getMinFrequency() <= maxLockedFrequency &&
maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth )
{
return true;
}
}
}
return false;
}
/**
* Constructs a digital drop channel (DDC) as a tuner channel from from
* the tuner, or returns null if the channel cannot be sourced from the
* tuner.
*
* This controller can't provide a channel if the channel, along with the
* current set of sourced channels, can't be accomodated within the current
* tuner bandwidth, or if a center frequency cannot be calculated that will
* ensure all of the channels can be accomodated and any defined central DC
* spike avoided.
*
* @param tuner to source the channel from
* @param channel with defined center frequency and bandwidth
*
* @return fully constructed tuner channel or null
*
* @throws RejectedExecutionException if the decimation processor has an error
*/
public TunerChannelSource getChannel( Tuner tuner, TunerChannel channel )
throws RejectedExecutionException
{
TunerChannelSource source = null;
if( canTune( channel ) )
{
try
{
mTunedChannels.add( channel );
if( requiresLOUpdate( channel ) )
{
updateLOFrequency();
}
source = new TunerChannelSource( tuner, channel );
}
catch( SourceException se )
{
mTunedChannels.remove( channel );
source = null;
}
}
return source;
}
public int getChannelCount()
{
return mTunedChannels.size();
}
/**
* Indicates if the tuner's LO frequency must be updated in order to
* accomodate the tuner channel
*/
private boolean requiresLOUpdate( TunerChannel channel ) throws SourceException
{
return !( getMinTunedFrequency() <= channel.getMinFrequency() &&
channel.getMaxFrequency() <= getMaxTunedFrequency() &&
( ( mMiddleUnusable == 0 ) ||
( !channel.overlaps( getTunedFrequency() - mMiddleUnusable,
getTunedFrequency() + mMiddleUnusable ) ) ) );
}
/**
* Releases the currently sourced tuner channel from this tuner and shuts
* down the tuner if no other sources exist.
*/
public void releaseChannel( TunerChannelSource tunerChannelSource )
{
if( tunerChannelSource != null )
{
mTunedChannels.remove( tunerChannelSource.getTunerChannel() );
}
}
/**
* Sets the Local Oscillator frequency accomodate the current set of tuned
* channels.
*
* If there is only a single tuned channel, it is placed immediately to the
* right of the usable bandwidth right of the central DC spike.
*
* Otherwise, it places the highest channel frequency at the upper end of
* the tuner bandwidth, and then iteratively moves the center frequency
* higher until all channels fit within the bandwidth and none of the
* channels overlap any defined central DC spike unusable region. If a
* center tune frequency cannot be calculated, throw an exception so that
* the most recently added channel can be removed.
*
* Note: the tuned frequency is not changed until a legitimate new frequency
* can be calculated. If an exception is thrown, the current frequency is
* retained, so that the recently added channel can be removed and all other
* channels can continue as previously arranged.
*
* @throws SourceException if the set of tuner channels, including a recently
* added channel, cannot be tuned within the current tuner bandwidth and any
* central DC spike unusable region.
*/
private void updateLOFrequency() throws SourceException
{
long frequency = getFrequency();
boolean frequencyValid = true;
//If there is only 1 channel, position it to the right of center
if( mTunedChannels.size() == 1 )
{
frequency = mTunedChannels.first().getMinFrequency() - mMiddleUnusable;
}
else
{
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Start by placing the highest frequency channel at the high end of
//the spectrum
frequency = maxLockedFrequency - ( getUsableBandwidth() / 2 );
//Iterate the channels and make sure that none of them overlap the
//center DC spike buffer, if one exists
if( mMiddleUnusable > 0 )
{
boolean processingRequired = true;
while( frequencyValid && processingRequired )
{
processingRequired = false;
long minAvoid = frequency - mMiddleUnusable;
long maxAvoid = frequency + mMiddleUnusable;
for( TunerChannel channel: mTunedChannels )
{
if( channel.overlaps( minAvoid, maxAvoid ) )
{
//Can we move the frequency lower so that the
//channel sits to the right of center?
long adjustment = channel.getMaxFrequency() - minAvoid;
if( frequency + adjustment -
( getUsableBandwidth() / 2 ) <= minLockedFrequency )
{
frequency += adjustment;
processingRequired = true;
}
else
{
frequencyValid = false;
}
//break out of the for/each loop, so that we can
//start over again with all of the channels
break;
}
}
}
}
}
if( frequencyValid )
{
mFrequencyController.setFrequency( frequency );
}
else
{
throw new SourceException( "Couldn't calculate viable center "
+ "frequency from set of tuner channels" );
}
}
/**
* Usable bandwidth - total bandwidth minus the unusable space at either end
* of the spectrum.
*/
private int getUsableBandwidth()
{
return (int)( getBandwidth() * mUsableBandwidthPercentage );
}
/**
* Sets the listener to be notified any time that the tuner changes frequency
* or bandwidth/sample rate.
*
* Note: this is normally used by the Tuner. Any additional listeners can
* be registered on the tuner.
*/
public void addListener( IFrequencyChangeProcessor processor )
public void setFrequency(long frequency) throws SourceException
{
mFrequencyController.addListener( processor );
mFrequencyController.setFrequency(frequency);
}
/**
* Gets the center frequency of the local oscillator
*
* @return frequency in hertz
*/
public long getFrequency()
{
return mFrequencyController.getFrequency();
}
public int getSampleRate()
{
return mFrequencyController.getBandwidth();
}
public double getFrequencyCorrection()
{
return mFrequencyController.getFrequencyCorrection();
}
public void setFrequencyCorrection(double correction) throws SourceException
{
mFrequencyController.setFrequencyCorrection(correction);
}
public long getMinFrequency()
{
return mFrequencyController.getMinimumFrequency();
}
public long getMaxFrequency()
{
return mFrequencyController.getMaximumFrequency();
}
private long getMinTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() - (getUsableBandwidth() / 2);
}
private long getMaxTunedFrequency() throws SourceException
{
return mFrequencyController.getFrequency() + (getUsableBandwidth() / 2);
}
/**
* Indicates if channel along with all of the other currently sourced
* channels can fit within the tunable bandwidth.
*/
private boolean canTune(TunerChannel channel)
{
//Make sure we're within the tunable frequency range of this tuner
if(getMinFrequency() < channel.getMinFrequency() && getMaxFrequency() > channel.getMaxFrequency())
{
//If this is the first lock, then we're good
if(mTunedChannels.isEmpty())
{
return true;
}
else
{
int usableBandwidth = getUsableBandwidth();
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Requested channel is within current locked channel frequency range
if(minLockedFrequency <= channel.getMinFrequency() && channel.getMaxFrequency() <= maxLockedFrequency)
{
return true;
}
//Requested channel is higher than min locked frequency
if(channel.getMaxFrequency() > minLockedFrequency &&
channel.getMaxFrequency() - minLockedFrequency <= usableBandwidth)
{
return true;
}
//Requested channel is lower than the max locked frequency
if(channel.getMinFrequency() <= maxLockedFrequency &&
maxLockedFrequency - channel.getMinFrequency() <= usableBandwidth)
{
return true;
}
}
}
return false;
}
/**
* Constructs a digital drop channel (DDC) as a tuner channel from from the tuner, or returns null if the channel
* cannot be sourced from the tuner.
*
* This controller can't provide a channel if the channel, along with the current set of sourced channels, can't be
* accomodated within the current tuner bandwidth, or if a center frequency cannot be calculated that will ensure
* all of the channels can be accomodated and any defined central DC spike avoided.
*
* @param tuner to source the channel from
* @param channel with defined center frequency and bandwidth
* @return fully constructed tuner channel or null
* @throws RejectedExecutionException if the decimation processor has an error
*/
public TunerChannelSource getChannel(Tuner tuner, TunerChannel channel) throws RejectedExecutionException
{
TunerChannelSource source = null;
if(canTune(channel))
{
try
{
mTunedChannels.add(channel);
if(requiresLOUpdate(channel))
{
updateLOFrequency();
}
source = new TunerChannelSource(tuner, channel);
}
catch(SourceException se)
{
mTunedChannels.remove(channel);
source = null;
}
}
return source;
}
public int getChannelCount()
{
return mTunedChannels.size();
}
/**
* Indicates if the tuner's LO frequency must be updated in order to accommodate the tuner channel
*/
private boolean requiresLOUpdate(TunerChannel channel) throws SourceException
{
boolean isBelow = channel.getMinFrequency() < getMinTunedFrequency();
boolean isAbove = channel.getMaxFrequency() > getMaxTunedFrequency();
boolean overlapsCenter = mMiddleUnusable > 0 &&
channel.overlaps(getFrequency() - mMiddleUnusable, getFrequency() + mMiddleUnusable);
return isAbove || isBelow || overlapsCenter;
}
/**
* Releases the currently sourced tuner channel from this tuner and shuts down the tuner if no other sources exist.
*/
public void releaseChannel(TunerChannelSource tunerChannelSource)
{
if(tunerChannelSource != null)
{
mTunedChannels.remove(tunerChannelSource.getTunerChannel());
}
}
/**
* Sets the Local Oscillator frequency to accomodate the current set of tuned channels.
*
* If there is only a single tuned channel, it is placed immediately to the right of the usable bandwidth right
* of the central DC spike.
*
* Otherwise, it places the highest channel frequency at the upper end of the tuner bandwidth, and then iteratively
* moves the center frequency higher until all channels fit within the bandwidth and none of the channels overlap
* any defined central DC spike unusable region. If a center tune frequency cannot be calculated, throw an
* exception so that the most recently added channel can be removed.
*
* Note: the tuned frequency is not changed until a legitimate new frequency can be calculated. If an exception
* is thrown, the current frequency is retained, so that the recently added channel can be removed and all other
* channels can continue as previously arranged.
*
* @throws SourceException if the set of tuner channels, including a recently added channel, cannot be tuned
* within the current tuner bandwidth and any central DC spike unusable region.
*/
private void updateLOFrequency() throws SourceException
{
long candidateFrequency;
boolean isValidCandidateFrequency = true;
//If there is only 1 channel set the center frequency so that the channel is positioned to the right of center
if(mTunedChannels.size() == 1)
{
candidateFrequency = mTunedChannels.first().getMinFrequency() - mMiddleUnusable + 1;
}
else
{
long minLockedFrequency = mTunedChannels.first().getMinFrequency();
long maxLockedFrequency = mTunedChannels.last().getMaxFrequency();
//Start by placing the highest frequency channel at the high end of the spectrum
candidateFrequency = maxLockedFrequency - (getUsableBandwidth() / 2);
//Iterate the channels and make sure that none of them overlap the center DC spike buffer, if one exists
if(mMiddleUnusable > 0)
{
boolean processingRequired = true;
while(isValidCandidateFrequency && processingRequired)
{
processingRequired = false;
long minAvoid = candidateFrequency - mMiddleUnusable;
long maxAvoid = candidateFrequency + mMiddleUnusable;
//If any of the center channel(s) overlap the central DC spike avoid area, we'll iteratively
//increase the tuned frequency causing the set of channels to move left in the tuned bandwidth until
//we either find a good center tune frequency, or we walk the lowest frequency channel out of the
//minimum tuned range, in which case we'll throw an exception indicating we don't have a solution.
for(TunerChannel channel : mTunedChannels)
{
if(channel.overlaps(minAvoid, maxAvoid))
{
//Calculate a tuned frequency adjustment that places this overlapping channel just to the
//left of the central DC spike avoid zone
long adjustment = channel.getMaxFrequency() - minAvoid + 1;
//If the candidate frequency doesn't push the lowest channel out of bounds, make adjustment
if(candidateFrequency + adjustment - (getUsableBandwidth() / 2) <= minLockedFrequency)
{
candidateFrequency += adjustment;
processingRequired = true;
}
//Otherwise, punt and indicate that we can't find a center frequency or add the channel
else
{
isValidCandidateFrequency = false;
}
//break out of the for/each loop, so that we can start over again with all of the channels
break;
}
}
}
}
}
if(isValidCandidateFrequency)
{
mFrequencyController.setFrequency(candidateFrequency);
}
else
{
throw new SourceException("Couldn't calculate viable center frequency from set of tuner channels");
}
}
/**
* Usable bandwidth - total bandwidth minus the unusable space at either end of the spectrum.
*/
private int getUsableBandwidth()
{
return (int)(getBandwidth() * mUsableBandwidthPercentage);
}
/**
* Sets the listener to be notified any time that the tuner changes frequency or bandwidth/sample rate.
*
* Note: this is normally used by the Tuner. Any additional listeners can be registered on the tuner.
*/
public void addListener(IFrequencyChangeProcessor processor)
{
mFrequencyController.addListener(processor);
}
/**
* Removes the frequency change listener
*/
public void removeListener( IFrequencyChangeProcessor processor )
public void removeListener(IFrequencyChangeProcessor processor)
{
mFrequencyController.removeFrequencyChangeProcessor( processor );
mFrequencyController.removeFrequencyChangeProcessor(processor);
}
}

View File

@@ -0,0 +1,54 @@
/*******************************************************************************
* 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 util;
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.apache.commons.io.FileUtils;
/**
* Custom logging plugin for logging memory usage with logback
*
* Add the following to the logback.xml configuration file to use this custom converter:
*
* <conversionRule conversionWord="memory_usage" converterClass="util.MemoryUsageLogger" />
*
* Update the encoder pattern to include %memory_usage within the pattern
*/
public class MemoryUsageLogger extends ClassicConverter
{
@Override
public String convert(ILoggingEvent iLoggingEvent)
{
//Method argument is ignored - we simply return memory usage statistics
long allocated = Runtime.getRuntime().totalMemory();
long free = Runtime.getRuntime().freeMemory();
long used = allocated - free;
int usedPercentage = (int)((double)(used) / (double)allocated * 100.0);
StringBuilder sb = new StringBuilder();
sb.append("[").append(FileUtils.byteCountToDisplaySize(used).replace(" ", ""));
sb.append("/").append(FileUtils.byteCountToDisplaySize(allocated).replace(" ", ""));
sb.append(" ").append(usedPercentage).append("%]");
return sb.toString();
}
}