Compare commits

...

10 Commits

Author SHA1 Message Date
Denny
64ccacf04a Releasing Version 0.3.0 (final). 2017-09-22 05:56:27 -04:00
Denny Sheirer
f00b9555fe Merge pull request #271 from DSheirer/269-viterbi-3-4-rate
269 Viterbi decoder for DMR 3/4-rate packet messages
2017-09-15 11:50:19 -04:00
Denny
85442a9648 Resolves #269 - creates a generic Viterbi decoder and a Digital Mobile Radio (DMR) 3/4 Rate Viterbi decoder implementation for decoding Trellis Coded Modulation (TCM) encoded DMR Data packets. 2017-09-15 11:40:23 -04:00
Denny Sheirer
c67a0b329d Merge pull request #267 from DSheirer/266-licence-directory
266 Update build script to use gitlab license file
2017-09-10 04:55:57 -04:00
Denny
8035d64fcd Resolves #266 - updates build script to use new gitlab license file and
removes previous license file.
2017-09-10 04:54:26 -04:00
Denny Sheirer
df2a061a08 Create LICENSE 2017-08-28 19:51:13 -04:00
Denny
e430319937 Updates build to 0.3.0 Beta 14 2017-08-19 07:15:57 -04:00
Denny Sheirer
724f769771 Merge pull request #262 from DSheirer/260-icecast-stream-doesn't-reconnect
260 - Icecast and Shoutcast streaming doesn't auto-reconnect
2017-08-19 07:14:55 -04:00
Denny
c196cfebed Resolves issue #260 - applies changes to Shoutcast V1 and v2 that were
applied to the Icecast streamer for issue releated to reconnecting.
2017-08-19 07:13:08 -04:00
Denny
e80b5866d4 Resolves issue #260 where audio streaming was not re-connecting after a
disconnect.
2017-08-18 20:07:53 -04:00
14 changed files with 829 additions and 92 deletions

View File

@@ -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 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. 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.> {one line to give the program's name and a brief idea of what it does.}
Copyright (C) <year> <name of author> Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -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 If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: 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 program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.

View File

@@ -7,7 +7,7 @@
<property name="major" value="0"/> <property name="major" value="0"/>
<property name="minor" value="3"/> <property name="minor" value="3"/>
<property name="patch" value="0"/> <property name="patch" value="0"/>
<property name="release" value="-beta13"/> <property name="release" value=""/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/> <property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties --> <!-- Build properties -->
@@ -16,7 +16,6 @@
<property name="source.configs" location="${repo}/config"/> <property name="source.configs" location="${repo}/config"/>
<property name="source.images" location="${repo}/images"/> <property name="source.images" location="${repo}/images"/>
<property name="source.libs" location="${repo}/imports"/> <property name="source.libs" location="${repo}/imports"/>
<property name="source.license" location="${repo}/license"/>
<property name="source.scripts" location="${repo}/scripts"/> <property name="source.scripts" location="${repo}/scripts"/>
<property name="output" location="${repo}/product"/> <property name="output" location="${repo}/product"/>
@@ -57,8 +56,8 @@
<exclude name="*.rules"/> <exclude name="*.rules"/>
</fileset> </fileset>
<fileset dir="${source.license}"> <fileset dir="${repo}">
<include name="license.txt"/> <include name="LICENSE"/>
</fileset> </fileset>
</jar> </jar>
@@ -85,8 +84,8 @@
<!-- Copy license file --> <!-- Copy license file -->
<copy todir="${output.folder}"> <copy todir="${output.folder}">
<fileset dir="${source.license}"> <fileset dir="${repo}">
<include name="license.txt"/> <include name="LICENSE"/>
</fileset> </fileset>
</copy> </copy>

View File

@@ -36,6 +36,7 @@ import util.ThreadPool;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -44,7 +45,8 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class); private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
private final static String TERMINATOR = "\r\n"; private final static String TERMINATOR = "\r\n";
private final static String SEPARATOR = ":"; private final static String SEPARATOR = ":";
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
private static final long CONNECTION_ATTEMPT_TIMEOUT = 5000; //5 seconds
private NioSocketConnector mSocketConnector; private NioSocketConnector mSocketConnector;
private IoSession mStreamingSession = null; private IoSession mStreamingSession = null;
@@ -99,10 +101,11 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
if(mSocketConnector == null) if(mSocketConnector == null)
{ {
mSocketConnector = new NioSocketConnector(); mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutCheckInterval(10000); mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT);
// mSocketConnector.getFilterChain().addLast("logger", // LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
// new LoggingFilter(IcecastTCPAudioBroadcaster.class)); // loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
mSocketConnector.getFilterChain().addLast("codec", mSocketConnector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new IcecastCodecFactory())); new ProtocolCodecFilter(new IcecastCodecFactory()));
@@ -123,9 +126,20 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
ConnectFuture future = mSocketConnector ConnectFuture future = mSocketConnector
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(), .connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
getBroadcastConfiguration().getPort())); getBroadcastConfiguration().getPort()));
future.awaitUninterruptibly();
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT, TimeUnit.MILLISECONDS);
mStreamingSession = future.getSession(); mStreamingSession = future.getSession();
} }
catch(UnresolvedAddressException uae)
{
//What do you do with this? Nothing.
}
catch(InterruptedException ie)
{
//We couldn't connect within the timeout period -- disconnect so that we can try again later
disconnect();
}
catch(RuntimeIoException rie) catch(RuntimeIoException rie)
{ {
Throwable throwableCause = rie.getCause(); Throwable throwableCause = rie.getCause();
@@ -137,16 +151,20 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
else if(throwableCause != null) else if(throwableCause != null)
{ {
setBroadcastState(BroadcastState.ERROR); setBroadcastState(BroadcastState.ERROR);
mLog.debug("Failed to connect", rie); mLog.debug("[" + getStreamName() + "] failed to connect", rie);
} }
else else
{ {
setBroadcastState(BroadcastState.ERROR); setBroadcastState(BroadcastState.ERROR);
mLog.debug("Failed to connect - no exception is available"); mLog.debug("[" + getStreamName() + "] failed to connect - no exception is available");
} }
disconnect(); disconnect();
} }
catch(Throwable t)
{
disconnect();
}
mConnecting.set(false); mConnecting.set(false);
} }
@@ -164,12 +182,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
*/ */
public void disconnect() public void disconnect()
{ {
if(connected()) if(connected() && mStreamingSession != null)
{ {
if(mStreamingSession != null) mStreamingSession.closeNow();
{ }
mStreamingSession.closeNow(); else
} {
mLastConnectionAttempt = System.currentTimeMillis();
} }
} }
@@ -226,17 +245,18 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
@Override @Override
public void sessionClosed(IoSession session) throws Exception public void sessionClosed(IoSession session) throws Exception
{ {
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected //If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState()) if(!getBroadcastState().isErrorState())
{ {
setBroadcastState(BroadcastState.DISCONNECTED); setBroadcastState(BroadcastState.DISCONNECTED);
} }
mSocketConnector.dispose();
mStreamingSession = null; mStreamingSession = null;
mSocketConnector = null;
mConnecting.set(false); mConnecting.set(false);
super.sessionClosed(session); super.sessionClosed(session);
} }
@@ -253,45 +273,36 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
if(connected()) if(connected())
{ {
mLog.info("Streaming connection error detected - resetting connection - " + reason);
disconnect(); disconnect();
connect();
} }
else if(reason.startsWith("Connection reset")) else if(reason.startsWith("Connection reset"))
{ {
mLog.info("Streaming connection reset by remote server - reestablishing connection");
disconnect(); disconnect();
connect();
} }
else if(reason.startsWith("Operation timed out")) else if(reason.startsWith("Operation timed out"))
{ {
mLog.info("Streaming connection timed out - resetting connection");
disconnect(); disconnect();
connect();
} }
else else
{ {
setBroadcastState(BroadcastState.ERROR); setBroadcastState(BroadcastState.ERROR);
disconnect(); disconnect();
mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.", cause);
} }
} }
else if(ioe instanceof WriteTimeoutException) else if(ioe instanceof WriteTimeoutException)
{ {
mLog.info("Network write timeout error - resetting connection");
disconnect(); disconnect();
connect();
} }
else else
{ {
setBroadcastState(BroadcastState.ERROR); setBroadcastState(BroadcastState.ERROR);
disconnect(); disconnect();
mLog.error("Unspecified IO error - streaming halted.", cause); mLog.error("[" + getStreamName() + "] Unspecified IO error - streaming halted.", cause);
} }
} }
else else
{ {
mLog.error("Broadcast error", cause); mLog.error("[" + getStreamName() + "] Broadcast error", cause);
setBroadcastState(BroadcastState.ERROR); setBroadcastState(BroadcastState.ERROR);
disconnect(); disconnect();
} }
@@ -319,6 +330,10 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{ {
setBroadcastState(BroadcastState.INVALID_CREDENTIALS); setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
} }
else if(message.contains("HTTP/1.1 501"))
{
disconnect(); //So that we can reconnect later
}
else else
{ {
mLog.error("Unrecognized server response:" + message); mLog.error("Unrecognized server response:" + message);
@@ -328,7 +343,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
} }
else else
{ {
mLog.error("Icecast TCP broadcaster - unrecognized message [ " + object.getClass() + mLog.error("[" + getStreamName() + "]Icecast TCP broadcaster - unrecognized message [ " + object.getClass() +
"] received:" + object.toString()); "] received:" + object.toString());
} }
} }

View File

@@ -168,6 +168,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
disconnect(); disconnect();
} }
catch(Throwable t)
{
disconnect();
}
mConnecting.set(false); mConnecting.set(false);
} }
@@ -185,12 +189,13 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
*/ */
public void disconnect() public void disconnect()
{ {
if(connected()) if(connected() && mStreamingSession != null)
{ {
if(mStreamingSession != null) mStreamingSession.closeNow();
{ }
mStreamingSession.closeNow(); else
} {
mLastConnectionAttempt = System.currentTimeMillis();
} }
} }
@@ -226,15 +231,15 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
@Override @Override
public void sessionClosed(IoSession session) throws Exception public void sessionClosed(IoSession session) throws Exception
{ {
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected //If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState()) if(!getBroadcastState().isErrorState())
{ {
setBroadcastState(BroadcastState.DISCONNECTED); setBroadcastState(BroadcastState.DISCONNECTED);
} }
mSocketConnector.dispose();
mStreamingSession = null; mStreamingSession = null;
mSocketConnector = null;
mConnecting.set(false); mConnecting.set(false);
super.sessionClosed(session); super.sessionClosed(session);
@@ -255,13 +260,11 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
{ {
mLog.info("Streaming connection reset by remote server - reestablishing connection"); mLog.info("Streaming connection reset by remote server - reestablishing connection");
disconnect(); disconnect();
connect();
} }
else if(reason.startsWith("Operation timed out")) else if(reason.startsWith("Operation timed out"))
{ {
mLog.info("Streaming connection timed out - resetting connection"); mLog.info("Streaming connection timed out - resetting connection");
disconnect(); disconnect();
connect();
} }
else else
{ {

View File

@@ -204,6 +204,10 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
disconnect(); disconnect();
} }
catch(Throwable t)
{
disconnect();
}
mConnecting.set(false); mConnecting.set(false);
} }
@@ -221,13 +225,13 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
*/ */
public void disconnect() public void disconnect()
{ {
if(connected()) if(connected() && mStreamingSession != null)
{ {
if(mStreamingSession != null) mStreamingSession.closeNow();
{ }
mStreamingSession.write(new TerminateBroadcast()); else
mStreamingSession.closeNow(); {
} mLastConnectionAttempt = System.currentTimeMillis();
} }
} }
@@ -349,15 +353,15 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
@Override @Override
public void sessionClosed(IoSession session) throws Exception public void sessionClosed(IoSession session) throws Exception
{ {
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected //If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState()) if(!getBroadcastState().isErrorState())
{ {
setBroadcastState(BroadcastState.DISCONNECTED); setBroadcastState(BroadcastState.DISCONNECTED);
} }
mSocketConnector.dispose();
mStreamingSession = null; mStreamingSession = null;
mSocketConnector = null;
mConnecting.set(false); mConnecting.set(false);
super.sessionClosed(session); super.sessionClosed(session);
@@ -378,13 +382,11 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
{ {
mLog.info("Streaming connection reset by remote server - reestablishing connection"); mLog.info("Streaming connection reset by remote server - reestablishing connection");
disconnect(); disconnect();
connect();
} }
else if(reason.startsWith("Operation timed out")) else if(reason.startsWith("Operation timed out"))
{ {
mLog.info("Streaming connection timed out - resetting connection"); mLog.info("Streaming connection timed out - resetting connection");
disconnect(); disconnect();
connect();
} }
else else
{ {

View File

@@ -137,6 +137,17 @@ public class BinaryMessage extends BitSet
mCRC = crc; 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 * Current pointer index
*/ */

View 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;
}
}

View 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()];
}
}

View 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
View 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);
}
}

View 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;
}
}

View 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);
}
}

View File

@@ -151,41 +151,4 @@ public class RadioReferenceService
return mClient.getUserFeedBroadcasts(mUserAuthorization); 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!");
}
} }