mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 23:27:24 -05:00
Compare commits
22 Commits
v0.3.0-bet
...
v0.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e321bdaa3 | ||
|
|
f191d28c98 | ||
|
|
afc3d299b6 | ||
|
|
0471d90b54 | ||
|
|
8f68967b6c | ||
|
|
91b950c13d | ||
|
|
581ff9f449 | ||
|
|
67c3cb9639 | ||
|
|
cebd2da89f | ||
|
|
73b6ef164f | ||
|
|
b2c7ac8c66 | ||
|
|
108691dc3c | ||
|
|
455aa97286 | ||
|
|
d608834214 | ||
|
|
41660e1881 | ||
|
|
110ccb158c | ||
|
|
64ccacf04a | ||
|
|
f00b9555fe | ||
|
|
85442a9648 | ||
|
|
c67a0b329d | ||
|
|
8035d64fcd | ||
|
|
df2a061a08 |
10
CHANGELOG
10
CHANGELOG
@@ -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
|
||||
|
||||
@@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
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
|
||||
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
@@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -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)
|
||||
|
||||

|
||||
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
||||
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
||||
|
||||
233
build/build.xml
233
build/build.xml
@@ -1,121 +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="-beta14"/>
|
||||
<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.license" location="${repo}/license"/>
|
||||
<property name="source.scripts" location="${repo}/scripts"/>
|
||||
<!-- version properties -->
|
||||
<property name="major" value="0"/>
|
||||
<property name="minor" value="3"/>
|
||||
<property name="patch" value="2"/>
|
||||
<property name="release" value=""/>
|
||||
<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="${source.license}">
|
||||
<include name="license.txt"/>
|
||||
</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="${source.license}">
|
||||
<include name="license.txt"/>
|
||||
</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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
{
|
||||
|
||||
@@ -71,6 +71,40 @@ public class AuthenticateBroadcast extends UltravoxMessage
|
||||
setPayload(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the value using the XTEA block cipher algorithm. The byte array is sliced into 8-byte chunks and each
|
||||
* chunk is encrypted into a 16 hexadecimal character string. All encrypted chunks are concatenated and returned
|
||||
* as a single string. If the value byte array is not an even multiple of 8 characters, it will be padded with
|
||||
* zero valued bytes to make it an even multiple of eight.
|
||||
*
|
||||
* @param xtea instance of the XTEA algorithm with a loaded encryption key
|
||||
* @param value to encode
|
||||
* @return value encrypted into one or more multiples of 16 hexadecimal characters
|
||||
*/
|
||||
private static String encrypt(XTEA xtea, byte[] value)
|
||||
{
|
||||
if(value.length % 8 != 0 || value.length == 0)
|
||||
{
|
||||
int newLength = ((value.length / 8) + 1) * 8;
|
||||
value = Arrays.copyOf(value, newLength);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(int x = 0; x < value.length; x += 8)
|
||||
{
|
||||
byte[] plaintext = Arrays.copyOfRange(value, x, x + 8);
|
||||
byte[] encrypted = xtea.encrypt(plaintext);
|
||||
|
||||
for(byte b: encrypted)
|
||||
{
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the value using the XTEA block cipher algorithm and returns the encrypted value as a 16 hex value
|
||||
*
|
||||
@@ -80,23 +114,6 @@ public class AuthenticateBroadcast extends UltravoxMessage
|
||||
*/
|
||||
private static String encrypt(XTEA xtea, String value)
|
||||
{
|
||||
byte[] valueBytes = value.getBytes();
|
||||
|
||||
if(valueBytes.length % 8 != 0 || valueBytes.length == 0)
|
||||
{
|
||||
int newLength = ((valueBytes.length / 8) + 1) * 8;
|
||||
valueBytes = Arrays.copyOf(valueBytes, newLength);
|
||||
}
|
||||
|
||||
byte[] encrypted = xtea.encrypt(valueBytes);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(byte b: encrypted)
|
||||
{
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
return encrypt(xtea, value.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,17 @@ public class BinaryMessage extends BitSet
|
||||
{
|
||||
mCRC = crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the number of bit errors that were corrected in this message.
|
||||
*
|
||||
* @see CorrectedBinaryMessage subclass that supports setting this value
|
||||
* @return number of corrected bits in this message
|
||||
*/
|
||||
public int getCorrectedBitCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current pointer index
|
||||
|
||||
65
src/bits/CorrectedBinaryMessage.java
Normal file
65
src/bits/CorrectedBinaryMessage.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*******************************************************************************
|
||||
* 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 bits;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public class CorrectedBinaryMessage extends BinaryMessage
|
||||
{
|
||||
private int mCorrectedBitCount;
|
||||
|
||||
/**
|
||||
* Subclass of binary message class to allow capturing a corrected bits metric.
|
||||
* @param size of message to create.
|
||||
*/
|
||||
public CorrectedBinaryMessage(int size)
|
||||
{
|
||||
super(size);
|
||||
}
|
||||
|
||||
public CorrectedBinaryMessage(int size, boolean[] bitsToPreload)
|
||||
{
|
||||
super(size, bitsToPreload);
|
||||
}
|
||||
|
||||
public CorrectedBinaryMessage(BitSet bitset, int size)
|
||||
{
|
||||
super(bitset, size);
|
||||
}
|
||||
|
||||
public CorrectedBinaryMessage(byte[] data)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorrectedBitCount()
|
||||
{
|
||||
return mCorrectedBitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of bits that were corrected in this message
|
||||
* @param count of corrected bits
|
||||
*/
|
||||
public void setCorrectedBitCount(int count)
|
||||
{
|
||||
mCorrectedBitCount = count;
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class CRCLJ
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies 1 or 2 bit error positions that match the checksum error value.
|
||||
* Identifies 1 bit error positions that match the checksum error value.
|
||||
*/
|
||||
public static int[] findBitErrors( int checksumError )
|
||||
{
|
||||
@@ -180,24 +180,6 @@ public class CRCLJ
|
||||
}
|
||||
}
|
||||
|
||||
/* Two bit errors */
|
||||
for( int x = 0; x < 48; x++ )
|
||||
{
|
||||
for( int y = 0; y < 48; y++ )
|
||||
{
|
||||
if( x != y &&
|
||||
( CHECKSUMS[ x ] ^ CHECKSUMS[ y ] ) == checksumError )
|
||||
{
|
||||
int[] errors = new int[ 2 ];
|
||||
|
||||
errors[ 0 ] = x;
|
||||
errors[ 1 ] = y;
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
86
src/edac/trellis/DMRNode.java
Normal file
86
src/edac/trellis/DMRNode.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
public class DMRNode extends Node
|
||||
{
|
||||
//Hamming error values indicating the number of bits set in each indexed value, 0-15
|
||||
public static final int[] HAMMING_ERROR_COUNT = new int[]{0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
|
||||
|
||||
/**
|
||||
* Transmitted quad-bit value, derived from ETSI TS 102-361-1, Table B.7 Trellis encoder state transition table
|
||||
*
|
||||
* Note: the B.7 table contains constellation point ID numbers that have to be converted into actual bit values
|
||||
* where the bits represent the dibit pair for the constellation point.
|
||||
*/
|
||||
public static final int[][] DMR_TRANSITION_MATRIX = new int[][]
|
||||
{
|
||||
{2,13,14,1,7,8,11,4},
|
||||
{14,1,7,8,11,4,2,13},
|
||||
{10,5,6,9,15,0,3,12},
|
||||
{6,9,15,0,3,12,10,5},
|
||||
{15,0,3,12,10,5,6,9},
|
||||
{3,12,10,5,6,9,15,0},
|
||||
{7,8,11,4,2,13,14,1},
|
||||
{11,4,2,13,14,1,7,8}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) 3/4-rate trellis node for decoding DMR
|
||||
*
|
||||
* @param inputValue for the trellis time instant
|
||||
* @param transmittedOutputValue the actual four-bit output value that was transmitted.
|
||||
*/
|
||||
public DMRNode(int inputValue, int transmittedOutputValue)
|
||||
{
|
||||
super(inputValue, transmittedOutputValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the error between the preceeding node and this node as the Hamming distance between the expected
|
||||
* four-bit output value versus the actual transmitted output value.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return error value for this node relative to the preceeding node argument
|
||||
*/
|
||||
@Override
|
||||
public int getError(Node precedingNode)
|
||||
{
|
||||
if(precedingNode != null)
|
||||
{
|
||||
int expectedOutputValue = getOutputValue(precedingNode);
|
||||
int errorMask = expectedOutputValue ^ getTransmittedOutputValue();
|
||||
return HAMMING_ERROR_COUNT[errorMask];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected output four-bit value when this node is preceeded by the argument node.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return expected output value
|
||||
*/
|
||||
@Override
|
||||
public int getOutputValue(Node precedingNode)
|
||||
{
|
||||
return DMR_TRANSITION_MATRIX[precedingNode.getInputValue()][getInputValue()];
|
||||
}
|
||||
}
|
||||
77
src/edac/trellis/Node.java
Normal file
77
src/edac/trellis/Node.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
public abstract class Node
|
||||
{
|
||||
private int mInputValue;
|
||||
private int mTransmittedOutputValue;
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) node. A node represents a time instant on the trellis and contains an input
|
||||
* value and the corresponding transmitted output value for the time instant. A node does not maintain the
|
||||
* state at the time instant -- the state is determined by the input value of a preceeding node.
|
||||
*
|
||||
* @param inputValue for this node
|
||||
* @param transmittedOutputValue for this node
|
||||
*/
|
||||
public Node(int inputValue, int transmittedOutputValue)
|
||||
{
|
||||
mInputValue = inputValue;
|
||||
mTransmittedOutputValue = transmittedOutputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input value for this node.
|
||||
*
|
||||
* @return input value
|
||||
*/
|
||||
public int getInputValue()
|
||||
{
|
||||
return mInputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmitted output value for this node
|
||||
* @return transmitted output value
|
||||
*/
|
||||
public int getTransmittedOutputValue()
|
||||
{
|
||||
return mTransmittedOutputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the error value for this node (time instant) by determining the expected output value that would have
|
||||
* been transmitted using the state from the preceding node argument and this node's input value and comparing that
|
||||
* expected value to the actual transmitted output value. This error is normally represented as the Hamming
|
||||
* distance between the two values.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return error value for this node relative to the preceeding node
|
||||
*/
|
||||
public abstract int getError(Node precedingNode);
|
||||
|
||||
/**
|
||||
* Expected output value when this node is preceeded by the node specified in the argument.
|
||||
*
|
||||
* @param precedingNode that precedes this node
|
||||
* @return expected output value
|
||||
*/
|
||||
public abstract int getOutputValue(Node precedingNode);
|
||||
}
|
||||
131
src/edac/trellis/Path.java
Normal file
131
src/edac/trellis/Path.java
Normal file
@@ -0,0 +1,131 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Path
|
||||
{
|
||||
private List<Node> mNodes = new ArrayList<>();
|
||||
private int mError = 0;
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) path. Represents a sequence of nodes that traverse various time instants and
|
||||
* states across a trellis.
|
||||
*
|
||||
* Note: each node can be shared across multiple paths. This class does not assume that any nodes are unique.
|
||||
* @param startingNode to start the path. This node normally contains an input value of zero, but is implementation
|
||||
* specific.
|
||||
*/
|
||||
public Path(Node startingNode)
|
||||
{
|
||||
add(startingNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trellis Coded Modulation (TCM) path. Represents a sequence of nodes that traverse various time instants and
|
||||
* states across a trellis.
|
||||
*
|
||||
* Note: each node can be shared across multiple paths. This class does not assume that any nodes are unique.
|
||||
*
|
||||
* @param original path to create a copy from. Each of the nodes in the original will be added to this path and
|
||||
* after construction both this path and the original will contain the exact same set of nodes.
|
||||
*/
|
||||
private Path(Path original)
|
||||
{
|
||||
for(Node node: original.getNodes())
|
||||
{
|
||||
add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cumulative error across all nodes contained within this path. Error value is updated as each new node is added
|
||||
* to this path.
|
||||
*
|
||||
* @return cumulative error value
|
||||
*/
|
||||
public int getError()
|
||||
{
|
||||
return mError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length or number of nodes contained within this path. Note: this value includes the starting node and may
|
||||
* also include a flushing node. The values contained in the starting and flushing nodes are normally not used
|
||||
* when decoding a path.
|
||||
*
|
||||
* @return number of nodes in this path
|
||||
*/
|
||||
public int getLength()
|
||||
{
|
||||
return mNodes.size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of nodes contained in this path.
|
||||
* @return nodes
|
||||
*/
|
||||
public List<Node> getNodes()
|
||||
{
|
||||
return mNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the node to this path and updates the cumulative error value for this path by adding the node's error
|
||||
* value to the running error total.
|
||||
*
|
||||
* @param node to add to this path
|
||||
*/
|
||||
public void add(Node node)
|
||||
{
|
||||
if(!mNodes.isEmpty())
|
||||
{
|
||||
mError += node.getError(getLastNode());
|
||||
}
|
||||
|
||||
mNodes.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Last node in this path.
|
||||
*
|
||||
* @return last node
|
||||
*/
|
||||
public Node getLastNode()
|
||||
{
|
||||
if(!mNodes.isEmpty())
|
||||
{
|
||||
return mNodes.get(mNodes.size() - 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to create an exact copy of this path.
|
||||
*
|
||||
* @return a new copy of this path.
|
||||
*/
|
||||
public Path copyOf()
|
||||
{
|
||||
return new Path(this);
|
||||
}
|
||||
}
|
||||
226
src/edac/trellis/ViterbiDecoder.java
Normal file
226
src/edac/trellis/ViterbiDecoder.java
Normal file
@@ -0,0 +1,226 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class ViterbiDecoder
|
||||
{
|
||||
private int mInputBitLength;
|
||||
private int mInputValueCount;
|
||||
private int mOutputBitLength;
|
||||
private int mOutputValueCount;
|
||||
|
||||
/**
|
||||
* Viterbi decoder for trellis coded modulation (TCM) encoded binary sequences.
|
||||
*
|
||||
* @param inputBitLength for coding (e.g. 3/4 rate input bit length is 3)
|
||||
* @param outputBitLength for coding (e.g. 3/4 rate output bit length is 4)
|
||||
*/
|
||||
public ViterbiDecoder(int inputBitLength, int outputBitLength)
|
||||
{
|
||||
mInputBitLength = inputBitLength;
|
||||
mOutputBitLength = outputBitLength;
|
||||
|
||||
mInputValueCount = (int)Math.pow(2.0, mInputBitLength);
|
||||
mOutputValueCount = (int)Math.pow(2.0, mOutputBitLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a node that represents an input value and the actual/transmitted output value.
|
||||
*
|
||||
* Note: this method must be implemented by a sub-class since the node requires a specific state transition
|
||||
* table/matrix implementation.
|
||||
*
|
||||
* @param inputValue for the specific trellis node
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return node for decoding
|
||||
*/
|
||||
protected abstract Node createNode(int inputValue, int transmittedOutputValue);
|
||||
|
||||
/**
|
||||
* Creates a flushing or final node that represents an actual/transmitted output value and a flushing input value.
|
||||
*
|
||||
* The final flushing input value is implementation-specific, therefore this will be implemented in any subclass
|
||||
* of this decoder. A normal flushing input value is zero.
|
||||
*
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return flushing node
|
||||
*/
|
||||
protected abstract Node createFlushingNode(int transmittedOutputValue);
|
||||
|
||||
/**
|
||||
* Creates a starting node that represents a starting state and notional transmitted output value.
|
||||
*
|
||||
* The starting input value is implementation-specific, therefore this will be implemented in any subclass
|
||||
* of this decoder. A normal starting input value is zero.
|
||||
*
|
||||
* @return starting node
|
||||
*/
|
||||
protected abstract Node createStartingNode();
|
||||
|
||||
/**
|
||||
* Size in bits of the input and state values (e.g. 3/4 rate input size is 3)
|
||||
*
|
||||
* @return input bit size
|
||||
*/
|
||||
public int getInputBitLength()
|
||||
{
|
||||
return mInputBitLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of input or state values where the set of values is in the range of 0 <> count - 1
|
||||
*
|
||||
* @return input or state values count
|
||||
*/
|
||||
public int getInputValueCount()
|
||||
{
|
||||
return mInputValueCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size in bits of the output constellation or transmitted symbol value (e.g. 3/4 rate output size is 4)
|
||||
*
|
||||
* @return output size in bits
|
||||
*/
|
||||
public int getOutputBitLength()
|
||||
{
|
||||
return mOutputBitLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of input or state values where the set of values is in the range of 0 <> count - 1
|
||||
*
|
||||
* @return input or state values count
|
||||
*/
|
||||
public int getOutputValueCount()
|
||||
{
|
||||
return mOutputValueCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the TCM encoded transmitted output values and returns a path the represents the most likely transmitted
|
||||
* sequence of nodes.
|
||||
*
|
||||
* @param transmittedOutputValues from the encoded message
|
||||
* @return most likely path representing the transmitted values
|
||||
*/
|
||||
public Path decode(int[] transmittedOutputValues)
|
||||
{
|
||||
Collection<Path> survivingPaths = new ArrayList<>();
|
||||
|
||||
survivingPaths.add(new Path(createStartingNode()));
|
||||
|
||||
//Add all but the last transmitted value to the starting path, creating a set of survivors at each iteration.
|
||||
for(int x = 0; x < transmittedOutputValues.length - 1; x++)
|
||||
{
|
||||
survivingPaths = add(survivingPaths, transmittedOutputValues[x]);
|
||||
}
|
||||
|
||||
//Flush the survivors with the final transmitted value and return the lone surviving path
|
||||
return flush(survivingPaths, transmittedOutputValues[transmittedOutputValues.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set of new nodes representing all possible input values and the transmitted output value argument
|
||||
* and iteratively adds the set of nodes to a copy of each of the surviving paths. A set of surviving paths is
|
||||
* returned that represents each path that terminates at a distinct input/state value that has the least error
|
||||
* value.
|
||||
*
|
||||
* @param survivingPaths from the previous time instant to evaluate for the next time instant in the trellis
|
||||
* using the transmitted output value.
|
||||
*
|
||||
* @param transmittedOutputValue that will be decoded for this time instant.
|
||||
*
|
||||
* @return paths that have survived (ie have the lowest error value) after adding nodes for the transmitted
|
||||
* output value
|
||||
*
|
||||
*/
|
||||
public Collection<Path> add(Collection<Path> survivingPaths, int transmittedOutputValue)
|
||||
{
|
||||
Map<Integer,Path> survivingPathMap = new HashMap<>();
|
||||
|
||||
for(int x = 0; x < getInputValueCount(); x++)
|
||||
{
|
||||
//Create a single/unique node for each possible input value combined with the transmitted output value
|
||||
Node node = createNode(x, transmittedOutputValue);
|
||||
|
||||
//Evaluate each surviving path by creating a copy and adding the node to it and see if it survives
|
||||
for(Path path: survivingPaths)
|
||||
{
|
||||
Path pathToEvaluate = path.copyOf();
|
||||
|
||||
pathToEvaluate.add(node);
|
||||
|
||||
if(survivingPathMap.containsKey(node.getInputValue()))
|
||||
{
|
||||
Path survivingPath = survivingPathMap.get(node.getInputValue());
|
||||
|
||||
//Replace the current survivor path if this evaluate path has a lower error value
|
||||
if(pathToEvaluate.getError() < survivingPath.getError())
|
||||
{
|
||||
survivingPathMap.put(node.getInputValue(), pathToEvaluate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//No survivor yet -- set this evaluate path as the first survivor
|
||||
survivingPathMap.put(node.getInputValue(), pathToEvaluate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return survivingPathMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes each of the surviving paths with a final/flushing node using the transmitted symbol. A final flushing
|
||||
* input value or state is implementation-specific, but normally uses a value of zero. Since all paths will be
|
||||
* evaluated against each other as terminating at the same state, only the path with the lowest error value will
|
||||
* be returned from this method.
|
||||
*
|
||||
* @param survivingPaths to flush with the final flushing node
|
||||
* @param transmittedOutputValue to use for the final flushing node
|
||||
*
|
||||
* @return the single survivor path that remains after adding the flushing node to each of the candidate argument
|
||||
* survivor paths.
|
||||
*/
|
||||
public Path flush(Collection<Path> survivingPaths, int transmittedOutputValue)
|
||||
{
|
||||
Node flushingNode = createFlushingNode(transmittedOutputValue);
|
||||
|
||||
Path bestPath = null;
|
||||
|
||||
for(Path survivingPath: survivingPaths)
|
||||
{
|
||||
survivingPath.add(flushingNode);
|
||||
|
||||
if(bestPath == null || (survivingPath.getError() < bestPath.getError()))
|
||||
{
|
||||
bestPath = survivingPath;
|
||||
}
|
||||
}
|
||||
|
||||
return bestPath;
|
||||
}
|
||||
}
|
||||
159
src/edac/trellis/ViterbiDecoder_3_4_DMR.java
Normal file
159
src/edac/trellis/ViterbiDecoder_3_4_DMR.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*******************************************************************************
|
||||
* 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 edac.trellis;
|
||||
|
||||
import bits.BinaryMessage;
|
||||
import bits.CorrectedBinaryMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
|
||||
{
|
||||
/**
|
||||
* Viterbi decoder for Digital Mobile Radio (DMR) 3/4 rate Trellis Coded Modulation (TCM) encoded messages.
|
||||
*/
|
||||
public ViterbiDecoder_3_4_DMR()
|
||||
{
|
||||
super(3,4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a 3/4 rate trellis coded modulation (TCM) encoded DMR binary message containing 196 bits that have
|
||||
* already been deinterleaved.
|
||||
*
|
||||
* @param encodedMessage to decode that has already been deinterleaved.
|
||||
* @return decoded message
|
||||
*/
|
||||
public BinaryMessage decode(BinaryMessage encodedMessage)
|
||||
{
|
||||
int[] symbols = getSymbols(encodedMessage);
|
||||
|
||||
Path mostLikelyPath = decode(symbols);
|
||||
|
||||
return getMessage(mostLikelyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the decoded/corrected message from the surviving path argument.
|
||||
*
|
||||
* @param path to extract a message from
|
||||
* @return corrected binary message.
|
||||
*/
|
||||
private static CorrectedBinaryMessage getMessage(Path path)
|
||||
{
|
||||
List<Node> mNodes = path.getNodes();
|
||||
|
||||
//Each node contains an input value excluding the starting(0) and final flushing(0) nodes.
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage((mNodes.size() - 2) * 3);
|
||||
|
||||
if(mNodes.size() > 2)
|
||||
{
|
||||
for(int x = 1; x < mNodes.size() - 1; x++)
|
||||
{
|
||||
int inputValue = mNodes.get(x).getInputValue();
|
||||
|
||||
int messageOffset = 3 * (x - 1);
|
||||
|
||||
if((inputValue & 4) == 4)
|
||||
{
|
||||
message.set(messageOffset);
|
||||
}
|
||||
|
||||
if((inputValue & 2) == 2)
|
||||
{
|
||||
message.set(messageOffset + 1);
|
||||
}
|
||||
|
||||
if((inputValue & 1) == 1)
|
||||
{
|
||||
message.set(messageOffset + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Transfer the corrected error count to the message
|
||||
message.setCorrectedBitCount(path.getError());
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts (49) four-bit symbols (196 bits / 4) from the deinterleaved DMR binary message
|
||||
*
|
||||
* @param encodedMessage that deinterleaved and contains 196-bits of 3/4 TCM encoding
|
||||
* @return symbols
|
||||
*/
|
||||
public int[] getSymbols(BinaryMessage encodedMessage)
|
||||
{
|
||||
//Ensure we have an integral number of transmitted symbols (nibbles) in the message
|
||||
if(encodedMessage.size() % getOutputBitLength() != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Encoded message must contain an integral number of 4-bit symbols -" +
|
||||
" message size: " + encodedMessage.size());
|
||||
}
|
||||
|
||||
int[] symbols = new int[encodedMessage.size() / getOutputBitLength()];
|
||||
|
||||
int index = 0;
|
||||
int pointer = 0;
|
||||
|
||||
while(index < encodedMessage.size())
|
||||
{
|
||||
int symbol = encodedMessage.getInt(index, index + (getOutputBitLength() - 1));
|
||||
symbols[pointer++] = symbol;
|
||||
index += getOutputBitLength();
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR node
|
||||
* @param inputValue for the specific trellis node
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return created node
|
||||
*/
|
||||
@Override
|
||||
protected Node createNode(int inputValue, int transmittedOutputValue)
|
||||
{
|
||||
return new DMRNode(inputValue, transmittedOutputValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR flushing node. DMR uses an input value of zero to flush the final output value out of the
|
||||
* encoder.
|
||||
*
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected Node createFlushingNode(int transmittedOutputValue)
|
||||
{
|
||||
return new DMRNode(0, transmittedOutputValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR starting node. DMR uses a starting input value of zero for the initial node.
|
||||
*/
|
||||
@Override
|
||||
protected Node createStartingNode()
|
||||
{
|
||||
return new DMRNode(0,0);
|
||||
}
|
||||
}
|
||||
@@ -151,41 +151,4 @@ public class RadioReferenceService
|
||||
|
||||
return mClient.getUserFeedBroadcasts(mUserAuthorization);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
RadioReferenceService service = new RadioReferenceService("dsheirer", "1dodgeram");
|
||||
|
||||
Long expiration = service.getAccountExpirationDate();
|
||||
|
||||
mLog.debug("Expiration Date:" + (expiration != null ? new Date(expiration).toString() : "unknown"));
|
||||
|
||||
mLog.debug("Is Active: " + service.isAccountActive());
|
||||
|
||||
UserFeedBroadcast[] feeds = service.getUserFeedBroadcasts();
|
||||
|
||||
for(UserFeedBroadcast feed: feeds)
|
||||
{
|
||||
mLog.debug("Feed ID: " + feed.getFeedId() + " Desc:" + feed.getDescr());
|
||||
}
|
||||
}
|
||||
catch(AxisFault af)
|
||||
{
|
||||
mLog.debug("Hey, I caught an axis fault!");
|
||||
mLog.debug("Reason:" + af.getFaultReason());
|
||||
}
|
||||
catch(RemoteException re)
|
||||
{
|
||||
re.printStackTrace();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mLog.debug("Finished!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
54
src/util/MemoryUsageLogger.java
Normal file
54
src/util/MemoryUsageLogger.java
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user