From 1a432197ac62b33d073de2653451b47340a8c133 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 20 May 2017 06:51:53 -0400 Subject: [PATCH 01/20] Resolves #245 issue with NPE for frequency change event listener when using the sound card/mixer as a source. --- src/module/ProcessingChain.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java index a018e360..7c0bf5cd 100644 --- a/src/module/ProcessingChain.java +++ b/src/module/ProcessingChain.java @@ -320,7 +320,13 @@ public class ProcessingChain implements IChannelEventListener if(module instanceof IFrequencyChangeListener) { - mFrequencyChangeEventBroadcaster.addListener(((IFrequencyChangeListener) module).getFrequencyChangeListener()); + Listener frequencyChangeEventListener = + ((IFrequencyChangeListener)module).getFrequencyChangeListener(); + + if(frequencyChangeEventListener != null) + { + mFrequencyChangeEventBroadcaster.addListener(frequencyChangeEventListener); + } } if(module instanceof IMessageListener) From a872e0d6643fefd434455362853ce3a0a6c09c67 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 20 May 2017 07:24:27 -0400 Subject: [PATCH 02/20] Updates version to Beta 0.3.0 Beta 11 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 86210e2b..5a6be3f0 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From e4323df3477a554a7218c98d1b0ea15c9d3986bf Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 28 May 2017 07:17:09 -0400 Subject: [PATCH 03/20] Resolves issue #244 - updates the P25 IMBE audio module to correctly detect encrypted audio state so that encrypted audio is not (incorrectly) decoded and played back to the user. Processes HDU and LDU2 messages to identify encrypted call state. Once established, all subsequent LDU1/2 messages are decoded only if the call is unencrypted. If an LDU1 message is received out-of-sequence, meaning that the initial HDU message was missed, then the LDU1 is cached until we receive an LDU2 message and correctly determine the call encryption state. --- src/module/decode/p25/P25DecoderState.java | 14 +- .../decode/p25/audio/P25AudioModule.java | 129 ++++-- .../decode/p25/message/hdu/HDUMessage.java | 2 +- .../decode/p25/message/ldu/LDU1Message.java | 406 +++++++++--------- .../decode/p25/message/ldu/LDU2Message.java | 8 +- .../decode/p25/message/ldu/LDUMessage.java | 2 - 6 files changed, 308 insertions(+), 253 deletions(-) diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index 828be84c..8dc31d68 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -421,7 +421,7 @@ public class P25DecoderState extends DecoderState mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL) .aliasList(getAliasList()) .channel(mCurrentChannel) - .details((hdu.isEncrypted() ? "ENCRYPTED " : "")) + .details((hdu.isEncryptedAudio() ? "ENCRYPTED " : "")) .frequency(mCurrentChannelFrequency) .to(to) .build(); @@ -1107,7 +1107,7 @@ public class P25DecoderState extends DecoderState broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) .aliasList(getAliasList()) .channel(gvcu.getChannelA()) - .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) + .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : "")) .frequency(gvcu.getDownlinkFrequencyA()) .to(userA) .build()); @@ -1122,7 +1122,7 @@ public class P25DecoderState extends DecoderState broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) .aliasList(getAliasList()) .channel(gvcu.getChannelB()) - .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) + .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : "")) .frequency(gvcu.getDownlinkFrequencyB()) .to(userB) .build()); @@ -1151,7 +1151,7 @@ public class P25DecoderState extends DecoderState broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) .aliasList(getAliasList()) .channel(gvcue.getTransmitChannel()) - .details((gvcue.isEncrypted() ? "ENCRYPTED" : "")) + .details((gvcue.isEncryptedLinkControlWord() ? "ENCRYPTED" : "")) .frequency(gvcue.getDownlinkFrequency()) .to(group) .build()); @@ -1187,7 +1187,7 @@ public class P25DecoderState extends DecoderState if(mCurrentCallEvent.getDetails() == null) { mCurrentCallEvent.setDetails( - (gvcuser.isEncrypted() ? "ENCRYPTED " : "") + + (gvcuser.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") + (gvcuser.isEmergency() ? "EMERGENCY " : "")); broadcast(mCurrentCallEvent); @@ -1399,7 +1399,7 @@ public class P25DecoderState extends DecoderState if(mCurrentCallEvent.getDetails() == null) { mCurrentCallEvent.setDetails( - (tivcu.isEncrypted() ? "ENCRYPTED " : "") + + (tivcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") + (tivcu.isEmergency() ? "EMERGENCY " : "")); broadcast(mCurrentCallEvent); @@ -1482,7 +1482,7 @@ public class P25DecoderState extends DecoderState if(mCurrentCallEvent.getDetails() == null) { mCurrentCallEvent.setDetails( - (uuvcu.isEncrypted() ? "ENCRYPTED " : "") + + (uuvcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") + (uuvcu.isEmergency() ? "EMERGENCY " : "")); broadcast(mCurrentCallEvent); diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index 14279427..a8f7383f 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -12,6 +12,9 @@ import jmbe.iface.AudioConverter; import message.IMessageListener; import message.Message; import module.Module; +import module.decode.p25.message.hdu.HDUMessage; +import module.decode.p25.message.ldu.LDU1Message; +import module.decode.p25.message.ldu.LDU2Message; import module.decode.p25.message.ldu.LDUMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,14 +28,18 @@ public class P25AudioModule extends Module implements Listener, IAudioP private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class); private static final String IMBE_CODEC = "IMBE"; - private static boolean mLibraryLoadStatusLogged = false; + private boolean mCanConvertAudio = false; + private boolean mEncryptedCall = false; + private boolean mEncryptedCallStateEstablished = false; + private AudioConverter mAudioConverter; private Listener mAudioPacketListener; private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f); private Metadata mMetadata; + private LDU1Message mCachedLDU1Message = null; public P25AudioModule(Metadata metadata) { @@ -76,32 +83,73 @@ public class P25AudioModule extends Module implements Listener, IAudioP } /** - * Primary processing method for p25 imbe audio frame messages. Each LDU - * audio message contains 9 imbe voice frames. Each frame is converted to - * audio when the JMBE library is loaded, processed by auto-gain, and - * broadcast to the audio packet listener. + * Processes call header (HDU) and voice frame (LDU1/LDU2) messages to decode audio and to determine the + * encrypted audio status of a call event. Only the HDU and LDU2 messages convey encrypted call status. If an + * LDU1 message is received without a preceding HDU message, then the LDU1 message is cached until the first + * LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are + * then processed for audio if the call is unencrypted. */ public void receive(Message message) { - if (mCanConvertAudio && mAudioPacketListener != null) + if(mCanConvertAudio && mAudioPacketListener != null) { - if (message instanceof LDUMessage) + if(mEncryptedCallStateEstablished) { - LDUMessage ldu = (LDUMessage) message; - - /* Only process unencrypted audio frames */ - if (!(ldu.isValid() && ldu.isEncrypted())) + if(message instanceof LDUMessage) { - for (byte[] frame : ldu.getIMBEFrames()) - { - float[] audio = mAudioConverter.decode(frame); - - audio = mGain.apply(audio); - - mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf())); - } + processAudio((LDUMessage)message); } } + else + { + if(message instanceof HDUMessage) + { + mEncryptedCallStateEstablished = true; + mEncryptedCall = ((HDUMessage)message).isEncryptedAudio(); + } + else if(message instanceof LDU1Message) + { + //When we receive an LDU1 message with first receiving the HDU message, cache the LDU1 Message + //until we can determine the encrypted call state from the next LDU2 message + mCachedLDU1Message = (LDU1Message)message; + } + else if(message instanceof LDU2Message) + { + mEncryptedCallStateEstablished = true; + LDU2Message ldu2 = (LDU2Message)message; + mEncryptedCall = ldu2.isEncryptedAudio(); + + if(mCachedLDU1Message != null) + { + processAudio(mCachedLDU1Message); + mCachedLDU1Message = null; + } + + processAudio(ldu2); + } + } + } + } + + /** + * Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets. + */ + private void processAudio(LDUMessage ldu) + { + if(!mEncryptedCall) + { + for(byte[] frame : ldu.getIMBEFrames()) + { + float[] audio = mAudioConverter.decode(frame); + + audio = mGain.apply(audio); + + mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf())); + } + } + else + { + //Encrypted audio processing not implemented } } @@ -119,18 +167,18 @@ public class P25AudioModule extends Module implements Listener, IAudioP @SuppressWarnings("rawtypes") Class temp = Class.forName("jmbe.JMBEAudioLibrary"); - library = (AudioConversionLibrary) temp.newInstance(); + library = (AudioConversionLibrary)temp.newInstance(); - if ((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 && - library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1) + if((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 && + library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1) { mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO); - if (mAudioConverter != null) + if(mAudioConverter != null) { mCanConvertAudio = true; - if (!mLibraryLoadStatusLogged) + if(!mLibraryLoadStatusLogged) { StringBuilder sb = new StringBuilder(); sb.append("JMBE audio conversion library ["); @@ -144,7 +192,7 @@ public class P25AudioModule extends Module implements Listener, IAudioP } else { - if (!mLibraryLoadStatusLogged) + if(!mLibraryLoadStatusLogged) { mLog.info("JMBE audio conversion library NOT FOUND"); mLibraryLoadStatusLogged = true; @@ -156,28 +204,28 @@ public class P25AudioModule extends Module implements Listener, IAudioP mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion()); } } - catch (ClassNotFoundException e1) + catch(ClassNotFoundException e1) { - if (!mLibraryLoadStatusLogged) + if(!mLibraryLoadStatusLogged) { mLog.error("Couldn't find/load JMBE audio conversion library"); mLibraryLoadStatusLogged = true; } } - catch (InstantiationException e1) + catch(InstantiationException e1) { - if (!mLibraryLoadStatusLogged) + if(!mLibraryLoadStatusLogged) { mLog.error("Couldn't instantiate JMBE audio conversion library class"); mLibraryLoadStatusLogged = true; } } - catch (IllegalAccessException e1) + catch(IllegalAccessException e1) { - if (!mLibraryLoadStatusLogged) + if(!mLibraryLoadStatusLogged) { mLog.error("Couldn't load JMBE audio conversion library due to " - + "security restrictions"); + + "security restrictions"); mLibraryLoadStatusLogged = true; } } @@ -196,19 +244,24 @@ public class P25AudioModule extends Module implements Listener, IAudioP } /** - * Wrapper for squelch state listener. Internally, the P25 audio module - * doesn't have a squelch state. If there are IMBE audio frames, we have - * audio. We use this listener to signal to the recorder manager that the - * channel state is resetting and it should end the recording. + * Wrapper for squelch state to process end of call actions. At call end the encrypted call state established + * flag is reset so that the encrypted audio state for the next call can be properly detected and we send an + * END audio packet so that downstream processors like the audio recorder can properly close out a call sequence. */ public class SquelchStateListener implements Listener { @Override public void receive(SquelchState state) { - if (state == SquelchState.SQUELCH && mAudioPacketListener != null) + if(state == SquelchState.SQUELCH) { - mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf())); + if(mAudioPacketListener != null) + { + mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf())); + } + + mEncryptedCallStateEstablished = false; + mEncryptedCall = false; } } } diff --git a/src/module/decode/p25/message/hdu/HDUMessage.java b/src/module/decode/p25/message/hdu/HDUMessage.java index 2e7d5da3..eb3ab336 100644 --- a/src/module/decode/p25/message/hdu/HDUMessage.java +++ b/src/module/decode/p25/message/hdu/HDUMessage.java @@ -245,7 +245,7 @@ public class HDUMessage extends P25Message return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); } - public boolean isEncrypted() + public boolean isEncryptedAudio() { return getEncryption() != Encryption.UNENCRYPTED; } diff --git a/src/module/decode/p25/message/ldu/LDU1Message.java b/src/module/decode/p25/message/ldu/LDU1Message.java index 929bac1e..03f53741 100644 --- a/src/module/decode/p25/message/ldu/LDU1Message.java +++ b/src/module/decode/p25/message/ldu/LDU1Message.java @@ -1,244 +1,246 @@ package module.decode.p25.message.ldu; -import module.decode.p25.reference.DataUnitID; -import module.decode.p25.reference.LinkControlOpcode; -import module.decode.p25.reference.Vendor; -import module.decode.p25.reference.VendorLinkControlOpcode; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import alias.AliasList; import bits.BinaryMessage; import edac.CRC; import edac.Hamming10; import edac.ReedSolomon_63_47_17; +import module.decode.p25.reference.DataUnitID; +import module.decode.p25.reference.LinkControlOpcode; +import module.decode.p25.reference.Vendor; +import module.decode.p25.reference.VendorLinkControlOpcode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LDU1Message extends LDUMessage { - private final static Logger mLog = LoggerFactory.getLogger( LDU1Message.class ); - - public static final int ENCRYPTION_FLAG = 352; - public static final int IMPLICIT_VENDOR_FLAG = 353; - public static final int[] OPCODE = { 354,355,356,357,362,363 }; - public static final int[] VENDOR = { 364,365,366,367,372,373,374,375 }; - - public static final int[] GOLAY_WORD_STARTS = { - 352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088, - 1098,1108,1118,1272,1282,1292,1302 }; + private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class); - public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 }; - public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 }; - public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 }; - public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 }; - public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 }; - public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 }; - public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 }; - public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 }; - public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 }; - public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 }; - public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 }; - public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 }; - public static final int[] RS_HEX_0 = { 904,905,906,907,908,909 }; - public static final int[] RS_HEX_1 = { 914,915,916,917,918,919 }; - public static final int[] RS_HEX_2 = { 924,925,926,927,928,929 }; - public static final int[] RS_HEX_3 = { 934,935,936,937,938,939 }; - public static final int[] RS_HEX_4 = { 1088,1089,1090,1091,1092,1093 }; - public static final int[] RS_HEX_5 = { 1098,1099,1100,1101,1102,1103 }; - public static final int[] RS_HEX_6 = { 1108,1109,1110,1111,1112,1113 }; - public static final int[] RS_HEX_7 = { 1118,1119,1120,1121,1122,1123 }; - public static final int[] RS_HEX_8 = { 1272,1273,1274,1275,1276,1277 }; - public static final int[] RS_HEX_9 = { 1282,1283,1284,1285,1286,1287 }; - public static final int[] RS_HEX_10 = { 1292,1293,1294,1295,1296,1297 }; - public static final int[] RS_HEX_11 = { 1302,1303,1304,1305,1306,1307 }; + public static final int ENCRYPTED_LINK_CONTROL_WORD_FLAG = 352; + public static final int IMPLICIT_VENDOR_FLAG = 353; + public static final int[] OPCODE = {354, 355, 356, 357, 362, 363}; + public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375}; - /* Reed-Solomon(24,12,13) code protects the link control word. Maximum - * correctable errors are: Hamming Distance(13) / 2 = 6 */ - public static final ReedSolomon_63_47_17 mReedSolomonDecoder = - new ReedSolomon_63_47_17( 6 ); + public static final int[] GOLAY_WORD_STARTS = { + 352, 362, 372, 382, 536, 546, 556, 566, 720, 730, 740, 750, 904, 914, 924, 934, 1088, + 1098, 1108, 1118, 1272, 1282, 1292, 1302}; - public LDU1Message( BinaryMessage message, DataUnitID duid, - AliasList aliasList ) + public static final int[] CW_HEX_0 = {352, 353, 354, 355, 356, 357}; + public static final int[] CW_HEX_1 = {362, 363, 364, 365, 366, 367}; + public static final int[] CW_HEX_2 = {372, 373, 374, 375, 376, 377}; + public static final int[] CW_HEX_3 = {382, 383, 384, 385, 386, 387}; + public static final int[] CW_HEX_4 = {536, 537, 538, 539, 540, 541}; + public static final int[] CW_HEX_5 = {546, 547, 548, 549, 550, 551}; + public static final int[] CW_HEX_6 = {556, 557, 558, 559, 560, 561}; + public static final int[] CW_HEX_7 = {566, 567, 568, 569, 570, 571}; + public static final int[] CW_HEX_8 = {720, 721, 722, 723, 724, 725}; + public static final int[] CW_HEX_9 = {730, 731, 732, 733, 734, 735}; + public static final int[] CW_HEX_10 = {740, 741, 742, 743, 744, 745}; + public static final int[] CW_HEX_11 = {750, 751, 752, 753, 754, 755}; + public static final int[] RS_HEX_0 = {904, 905, 906, 907, 908, 909}; + public static final int[] RS_HEX_1 = {914, 915, 916, 917, 918, 919}; + public static final int[] RS_HEX_2 = {924, 925, 926, 927, 928, 929}; + public static final int[] RS_HEX_3 = {934, 935, 936, 937, 938, 939}; + public static final int[] RS_HEX_4 = {1088, 1089, 1090, 1091, 1092, 1093}; + public static final int[] RS_HEX_5 = {1098, 1099, 1100, 1101, 1102, 1103}; + public static final int[] RS_HEX_6 = {1108, 1109, 1110, 1111, 1112, 1113}; + public static final int[] RS_HEX_7 = {1118, 1119, 1120, 1121, 1122, 1123}; + public static final int[] RS_HEX_8 = {1272, 1273, 1274, 1275, 1276, 1277}; + public static final int[] RS_HEX_9 = {1282, 1283, 1284, 1285, 1286, 1287}; + public static final int[] RS_HEX_10 = {1292, 1293, 1294, 1295, 1296, 1297}; + public static final int[] RS_HEX_11 = {1302, 1303, 1304, 1305, 1306, 1307}; + + /* Reed-Solomon(24,12,13) code protects the link control word. Maximum + * correctable errors are: Hamming Distance(13) / 2 = 6 */ + public static final ReedSolomon_63_47_17 mReedSolomonDecoder = + new ReedSolomon_63_47_17(6); + + public LDU1Message(BinaryMessage message, DataUnitID duid, + AliasList aliasList) { - super( message, duid, aliasList ); + super(message, duid, aliasList); /* NID CRC is checked in the message framer, thus a constructed message - * means it passed the CRC */ - mCRC = new CRC[ 3 ]; - mCRC[ 0 ] = CRC.PASSED; - - checkCRC(); - } - - /** - * Subclass constructor from an existing LDU1Message - */ - protected LDU1Message( LDU1Message message ) - { - super( message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList() ); - - mCRC = message.mCRC; - } + * means it passed the CRC */ + mCRC = new CRC[3]; + mCRC[0] = CRC.PASSED; - @Override - public boolean isEncrypted() - { - return mMessage.get( ENCRYPTION_FLAG ); - } - - private void checkCRC() - { - mCRC[ 1 ] = CRC.PASSED; + checkCRC(); + } + + /** + * Subclass constructor from an existing LDU1Message + */ + protected LDU1Message(LDU1Message message) + { + super(message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList()); + + mCRC = message.mCRC; + } + + /** + * Indicates if the contents of the Link Control Word are encrypted. + * + * @return true if the contents of the LCW are encrypted. + */ + public boolean isEncryptedLinkControlWord() + { + return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_FLAG); + } + + private void checkCRC() + { + mCRC[1] = CRC.PASSED; /* Hamming( 10,6,3 ) error detection and correction */ - for( int index: GOLAY_WORD_STARTS ) - { - int errors = Hamming10.checkAndCorrect( mMessage, index ); - - if( errors > 1 ) - { - mCRC[ 1 ] = CRC.FAILED_CRC; - } - } + for(int index : GOLAY_WORD_STARTS) + { + int errors = Hamming10.checkAndCorrect(mMessage, index); + + if(errors > 1) + { + mCRC[1] = CRC.FAILED_CRC; + } + } /* Reed-Solomon( 24,16,9 ) error detection and correction * Check the Reed-Solomon parity bits. The RS decoder expects the code * words and reed solomon parity hex codewords in reverse order. * * Since this is a truncated RS(63) codes, we pad the code with zeros */ - int[] input = new int [63]; - int[] output = new int [63]; - - input[ 0 ] = mMessage.getInt( RS_HEX_11 ); - input[ 1 ] = mMessage.getInt( RS_HEX_10 ); - input[ 2 ] = mMessage.getInt( RS_HEX_9 ); - input[ 3 ] = mMessage.getInt( RS_HEX_8 ); - input[ 4 ] = mMessage.getInt( RS_HEX_7 ); - input[ 5 ] = mMessage.getInt( RS_HEX_6 ); - input[ 6 ] = mMessage.getInt( RS_HEX_5 ); - input[ 7 ] = mMessage.getInt( RS_HEX_4 ); - input[ 8 ] = mMessage.getInt( RS_HEX_3 ); - input[ 9 ] = mMessage.getInt( RS_HEX_2 ); - input[ 10 ] = mMessage.getInt( RS_HEX_1 ); - input[ 11 ] = mMessage.getInt( RS_HEX_0 ); - - input[ 12 ] = mMessage.getInt( CW_HEX_11 ); - input[ 13 ] = mMessage.getInt( CW_HEX_10 ); - input[ 14 ] = mMessage.getInt( CW_HEX_9 ); - input[ 15 ] = mMessage.getInt( CW_HEX_8 ); - input[ 16 ] = mMessage.getInt( CW_HEX_7 ); - input[ 17 ] = mMessage.getInt( CW_HEX_6 ); - input[ 18 ] = mMessage.getInt( CW_HEX_5 ); - input[ 19 ] = mMessage.getInt( CW_HEX_4 ); - input[ 20 ] = mMessage.getInt( CW_HEX_3 ); - input[ 21 ] = mMessage.getInt( CW_HEX_2 ); - input[ 22 ] = mMessage.getInt( CW_HEX_1 ); - input[ 23 ] = mMessage.getInt( CW_HEX_0 ); + int[] input = new int[63]; + int[] output = new int[63]; + + input[0] = mMessage.getInt(RS_HEX_11); + input[1] = mMessage.getInt(RS_HEX_10); + input[2] = mMessage.getInt(RS_HEX_9); + input[3] = mMessage.getInt(RS_HEX_8); + input[4] = mMessage.getInt(RS_HEX_7); + input[5] = mMessage.getInt(RS_HEX_6); + input[6] = mMessage.getInt(RS_HEX_5); + input[7] = mMessage.getInt(RS_HEX_4); + input[8] = mMessage.getInt(RS_HEX_3); + input[9] = mMessage.getInt(RS_HEX_2); + input[10] = mMessage.getInt(RS_HEX_1); + input[11] = mMessage.getInt(RS_HEX_0); + + input[12] = mMessage.getInt(CW_HEX_11); + input[13] = mMessage.getInt(CW_HEX_10); + input[14] = mMessage.getInt(CW_HEX_9); + input[15] = mMessage.getInt(CW_HEX_8); + input[16] = mMessage.getInt(CW_HEX_7); + input[17] = mMessage.getInt(CW_HEX_6); + input[18] = mMessage.getInt(CW_HEX_5); + input[19] = mMessage.getInt(CW_HEX_4); + input[20] = mMessage.getInt(CW_HEX_3); + input[21] = mMessage.getInt(CW_HEX_2); + input[22] = mMessage.getInt(CW_HEX_1); + input[23] = mMessage.getInt(CW_HEX_0); /* indexes 24 - 62 are defaulted to zero */ - boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output ); + boolean irrecoverableErrors = mReedSolomonDecoder.decode(input, output); - if( irrecoverableErrors ) + if(irrecoverableErrors) { - mCRC[ 2 ] = CRC.FAILED_CRC; + mCRC[2] = CRC.FAILED_CRC; } else { /* Since we've detected that we can correct the RS words, mark the * hamming crc as corrected as well, if it is currently failed */ - if( mCRC[ 1 ] == CRC.FAILED_CRC ) - { - mCRC[ 1 ] = CRC.CORRECTED; - } - - mCRC[ 2 ] = CRC.PASSED; + if(mCRC[1] == CRC.FAILED_CRC) + { + mCRC[1] = CRC.CORRECTED; + } + + mCRC[2] = CRC.PASSED; /* Only fix the codeword data hex codewords */ - repairHexCodeword( input, output, 12, CW_HEX_11 ); - repairHexCodeword( input, output, 13, CW_HEX_10 ); - repairHexCodeword( input, output, 14, CW_HEX_9 ); - repairHexCodeword( input, output, 15, CW_HEX_8 ); - repairHexCodeword( input, output, 16, CW_HEX_7 ); - repairHexCodeword( input, output, 17, CW_HEX_6 ); - repairHexCodeword( input, output, 18, CW_HEX_5 ); - repairHexCodeword( input, output, 19, CW_HEX_4 ); - repairHexCodeword( input, output, 20, CW_HEX_3 ); - repairHexCodeword( input, output, 21, CW_HEX_2 ); - repairHexCodeword( input, output, 22, CW_HEX_1 ); - repairHexCodeword( input, output, 23, CW_HEX_0 ); + repairHexCodeword(input, output, 12, CW_HEX_11); + repairHexCodeword(input, output, 13, CW_HEX_10); + repairHexCodeword(input, output, 14, CW_HEX_9); + repairHexCodeword(input, output, 15, CW_HEX_8); + repairHexCodeword(input, output, 16, CW_HEX_7); + repairHexCodeword(input, output, 17, CW_HEX_6); + repairHexCodeword(input, output, 18, CW_HEX_5); + repairHexCodeword(input, output, 19, CW_HEX_4); + repairHexCodeword(input, output, 20, CW_HEX_3); + repairHexCodeword(input, output, 21, CW_HEX_2); + repairHexCodeword(input, output, 22, CW_HEX_1); + repairHexCodeword(input, output, 23, CW_HEX_0); } - } - - private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet ) - { - if( input[ index ] != output[ index ] ) - { - mMessage.load( indexSet[ 0 ], 6, output[ index ] ); - mCRC[ 2 ] = CRC.CORRECTED; - } - } - - @Override - public String getMessage() - { - StringBuilder sb = new StringBuilder(); - - sb.append( getMessageStub() ); - sb.append( " " ); - sb.append( mMessage.toString() ); - - return sb.toString(); - } - - public String getMessageStub() - { - StringBuilder sb = new StringBuilder(); - - sb.append( super.getMessageStub() ); - - if( isEncrypted() ) - { - sb.append( "ENCRYPTED" ); - } - else - { - if( isImplicitFormat() || getVendor() == Vendor.STANDARD ) - { - sb.append( getOpcode().getLabel() ); - } - else - { - sb.append( "VENDOR:" + getVendor().getLabel() + " " ); - sb.append( getVendorOpcode().getLabel() ); - } - } - - return sb.toString(); } - public boolean isImplicitFormat() - { - return mMessage.get( IMPLICIT_VENDOR_FLAG ); - } - - public LinkControlOpcode getOpcode() - { - return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } + private void repairHexCodeword(int[] input, int[] output, int index, int[] indexSet) + { + if(input[index] != output[index]) + { + mMessage.load(indexSet[0], 6, output[index]); + mCRC[2] = CRC.CORRECTED; + } + } - public VendorLinkControlOpcode getVendorOpcode() - { - return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); - } - - public Vendor getVendor() - { - if( isImplicitFormat() ) - { - return Vendor.STANDARD; - } - - return Vendor.fromValue( mMessage.getInt( VENDOR ) ); - } + @Override + public String getMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getMessageStub()); + sb.append(" "); + sb.append(mMessage.toString()); + + return sb.toString(); + } + + public String getMessageStub() + { + StringBuilder sb = new StringBuilder(); + + sb.append(super.getMessageStub()); + + if(isEncryptedLinkControlWord()) + { + sb.append("ENCRYPTED"); + } + else + { + if(isImplicitFormat() || getVendor() == Vendor.STANDARD) + { + sb.append(getOpcode().getLabel()); + } + else + { + sb.append("VENDOR:" + getVendor().getLabel() + " "); + sb.append(getVendorOpcode().getLabel()); + } + } + + return sb.toString(); + } + + public boolean isImplicitFormat() + { + return mMessage.get(IMPLICIT_VENDOR_FLAG); + } + + public LinkControlOpcode getOpcode() + { + return LinkControlOpcode.fromValue(mMessage.getInt(OPCODE)); + } + + public VendorLinkControlOpcode getVendorOpcode() + { + return VendorLinkControlOpcode.fromValue(mMessage.getInt(OPCODE)); + } + + public Vendor getVendor() + { + if(isImplicitFormat()) + { + return Vendor.STANDARD; + } + + return Vendor.fromValue(mMessage.getInt(VENDOR)); + } } diff --git a/src/module/decode/p25/message/ldu/LDU2Message.java b/src/module/decode/p25/message/ldu/LDU2Message.java index 8a4f196a..5fffa1dd 100644 --- a/src/module/decode/p25/message/ldu/LDU2Message.java +++ b/src/module/decode/p25/message/ldu/LDU2Message.java @@ -72,9 +72,11 @@ public class LDU2Message extends LDUMessage checkCRC(); } - - @Override - public boolean isEncrypted() + + /** + * Indicates if the audio stream is encrypted + */ + public boolean isEncryptedAudio() { Encryption encryption = getEncryption(); diff --git a/src/module/decode/p25/message/ldu/LDUMessage.java b/src/module/decode/p25/message/ldu/LDUMessage.java index 2d67e4f8..6433afd0 100644 --- a/src/module/decode/p25/message/ldu/LDUMessage.java +++ b/src/module/decode/p25/message/ldu/LDUMessage.java @@ -35,8 +35,6 @@ public abstract class LDUMessage extends P25Message super( message, duid, aliasList ); } - public abstract boolean isEncrypted(); - @Override public String getMessage() { From 1dc386348978c0e40cd04f38367d914d45dc9f80 Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 28 May 2017 07:22:43 -0400 Subject: [PATCH 04/20] Updates build to 0.3.0 Beta 12 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 5a6be3f0..b75db977 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From 5efd89632effb134fcae843baf6a1a6b32c1d864 Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 28 May 2017 07:30:05 -0400 Subject: [PATCH 05/20] Ensures that any leftover cached LDU1 message is reset when the call ends. --- src/module/decode/p25/audio/P25AudioModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index a8f7383f..d6aa4c9f 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -262,6 +262,7 @@ public class P25AudioModule extends Module implements Listener, IAudioP mEncryptedCallStateEstablished = false; mEncryptedCall = false; + mCachedLDU1Message = null; } } } From ab4e31ae0001d090bbab89392a4df9bad2967eb7 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 1 Jul 2017 07:36:55 -0400 Subject: [PATCH 06/20] Ensures that any leftover cached LDU1 message is reset when the call ends. --- src/channel/state/ChannelState.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java index d9407c0f..1f116dd4 100644 --- a/src/channel/state/ChannelState.java +++ b/src/channel/state/ChannelState.java @@ -661,9 +661,9 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder } } } - catch(Exception e) + catch(Throwable e) { - mLog.error("Exception thrown while state monitor is running " + + mLog.error("An error occurred while state monitor was running " + "- state [" + getState() + "] current [" + System.currentTimeMillis() + "] mResetTimeout [" + mEndTimeout + From d5ad3f807d40256eaef88ceb3305e6fb41b7a4f5 Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 9 Jul 2017 08:21:07 -0400 Subject: [PATCH 07/20] Resolves issue where the tuner's buffer processor is logging that an overflow state has been cleared, caused by a clearing of the buffer when the tuner is being shutdown because there are no more channel consumers registered on the tuner. Tweaked buffer size limits and buffer handling to attempt to avoid overflow situations in the first place. Resolved an issue where the tuner processor was not deregistering correctly from the LIBUSB tuner processor during tuner shutdown. --- src/sample/OverflowableTransferQueue.java | 4 --- .../tuner/usb/USBTransferProcessor.java | 32 +++++++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sample/OverflowableTransferQueue.java b/src/sample/OverflowableTransferQueue.java index f9c9c190..2deeca07 100644 --- a/src/sample/OverflowableTransferQueue.java +++ b/src/sample/OverflowableTransferQueue.java @@ -124,10 +124,6 @@ public class OverflowableTransferQueue mQueue.clear(); mCounter.set(0); mOverflow.set(false); - if(mOverflowListener != null) - { - mOverflowListener.sourceOverflow(false); - } } } } diff --git a/src/source/tuner/usb/USBTransferProcessor.java b/src/source/tuner/usb/USBTransferProcessor.java index aeb56bb9..7052db0c 100644 --- a/src/source/tuner/usb/USBTransferProcessor.java +++ b/src/source/tuner/usb/USBTransferProcessor.java @@ -54,7 +54,7 @@ public class USBTransferProcessor implements TransferCallback private static final int TRANSFER_BUFFER_POOL_SIZE = 16; //Maximum number of filled buffers for the blocking queue - private static final int FILLED_BUFFER_MAX_CAPACITY = 300; + private static final int FILLED_BUFFER_MAX_CAPACITY = 500; //Threshold for resetting buffer overflow condition private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 100; @@ -200,7 +200,7 @@ public class USBTransferProcessor implements TransferCallback //Unregister from LibUSB processor so that it auto-stops LibUSB processing - TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this); + TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this); //Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed. int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, @@ -415,23 +415,27 @@ public class USBTransferProcessor implements TransferCallback @Override public void run() { - try - { - mFilledBuffers.drainTo(mBuffersToDispatch, 15); + mFilledBuffers.drainTo(mBuffersToDispatch, 50); - for(byte[] buffer : mBuffersToDispatch) + while(!mBuffersToDispatch.isEmpty()) + { + try { - float[] complexSamples = mSampleAdapter.convert(buffer); + for(byte[] buffer : mBuffersToDispatch) + { + float[] complexSamples = mSampleAdapter.convert(buffer); - mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples)); + mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples)); + } + } + catch(Exception e) + { + mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e); } - } - catch(Exception e) - { - mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e); - } - mBuffersToDispatch.clear(); + mBuffersToDispatch.clear(); + mFilledBuffers.drainTo(mBuffersToDispatch, 50); + } } } } From be62ae0459a4dfcbc459e509cc6f1f5eb13f950a Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 22 Jul 2017 05:42:13 -0400 Subject: [PATCH 08/20] Resolves #249 - removes the state monitor runnable from the channel state and replaces it with a processing chain heartbeat that is generated by the sample source. Previously, the state monitor was running on a separate thread and this was causing multi-threading issues. With this update, all channel state operations (and all module operations) occur on the source's buffer processing thread. --- src/channel/heartbeat/Heartbeat.java | 31 ++ src/channel/heartbeat/IHeartbeatListener.java | 26 + src/channel/heartbeat/IHeartbeatProvider.java | 28 ++ src/channel/state/ChannelState.java | 222 ++++----- src/module/ProcessingChain.java | 28 +- src/module/decode/p25/P25DecoderState.java | 58 ++- src/source/Source.java | 4 +- src/source/mixer/RealMixerSource.java | 443 +++++++++--------- src/source/tuner/TunerChannelSource.java | 30 ++ src/source/wave/ComplexWaveSource.java | 351 +++++++------- src/source/wave/RealWaveSource.java | 403 ++++++++-------- 11 files changed, 892 insertions(+), 732 deletions(-) create mode 100644 src/channel/heartbeat/Heartbeat.java create mode 100644 src/channel/heartbeat/IHeartbeatListener.java create mode 100644 src/channel/heartbeat/IHeartbeatProvider.java diff --git a/src/channel/heartbeat/Heartbeat.java b/src/channel/heartbeat/Heartbeat.java new file mode 100644 index 00000000..4799754e --- /dev/null +++ b/src/channel/heartbeat/Heartbeat.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package channel.heartbeat; + +public class Heartbeat +{ + /** + * Periodic pulse that is broadcast to subscribing modules within the demodulating chain so that + * monitoring of state and other attributes can occur on the primary demodulation and decoding + * thread. + */ + public Heartbeat() + { + } +} diff --git a/src/channel/heartbeat/IHeartbeatListener.java b/src/channel/heartbeat/IHeartbeatListener.java new file mode 100644 index 00000000..904d0501 --- /dev/null +++ b/src/channel/heartbeat/IHeartbeatListener.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package channel.heartbeat; + +import sample.Listener; + +public interface IHeartbeatListener +{ + public Listener getHeartbeatListener(); +} diff --git a/src/channel/heartbeat/IHeartbeatProvider.java b/src/channel/heartbeat/IHeartbeatProvider.java new file mode 100644 index 00000000..3583cd42 --- /dev/null +++ b/src/channel/heartbeat/IHeartbeatProvider.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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 + * + ******************************************************************************/ +package channel.heartbeat; + +import sample.Listener; + +public interface IHeartbeatProvider +{ + public void setHeartbeatListener(Listener listener); + + public void removeHeartbeatListener(); +} diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java index 1f116dd4..30d7d796 100644 --- a/src/channel/state/ChannelState.java +++ b/src/channel/state/ChannelState.java @@ -20,6 +20,8 @@ package channel.state; import audio.squelch.ISquelchStateProvider; import audio.squelch.SquelchState; +import channel.heartbeat.Heartbeat; +import channel.heartbeat.IHeartbeatListener; import channel.metadata.Attribute; import channel.metadata.AttributeChangeRequest; import channel.metadata.IAttributeChangeRequestListener; @@ -39,16 +41,13 @@ import sample.real.IOverflowListener; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.IFrequencyChangeListener; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE; public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener, - IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener, - IOverflowListener + IDecoderStateEventProvider, IFrequencyChangeListener, IHeartbeatListener, ISquelchStateProvider, + IAttributeChangeRequestListener, IOverflowListener { private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class); @@ -61,8 +60,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder private Listener mDecoderStateListener; private Listener mSquelchStateListener; private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver(); - private StateMonitor mStateMonitor = new StateMonitor(); - private ScheduledFuture mStateMonitorFuture; + private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver(); private ChannelType mChannelType; private TrafficChannelManager mTrafficChannelEndListener; private CallEvent mTrafficChannelCallEvent; @@ -79,30 +77,21 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder /** - * Channel state tracks the overall state of all processing modules and decoders - * configured for the channel and provides squelch control and decoder state - * reset events. + * Channel state tracks the overall state of all processing modules and decoders configured for the channel and + * provides squelch control and decoder state reset events. * - * Uses a state enumeration that defines allowable channel state transitions in - * order to track a call or data decode event from start to finish. Uses a - * timer to monitor for inactivity and to provide a FADE period that indicates - * to the user that the activity has stopped while continuing to provide details - * about the call, before the state is reset to IDLE. - * - * Since this class is multi-threaded between decoder events and the internal - * timer that monitors for inactivity, this class uses the mState object for - * thread synchronization. + * Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode + * event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates + * to the user that the activity has stopped while continuing to provide details about the call, before the state is + * reset to IDLE. * * State Descriptions: - * IDLE Normal state. No voice or data call activity - * CALL/DATA/ENCRYPTED/CONTROL - * Decoding states. - * FADE The phase after a voice or data call when either an explicit - * call end has been received, or when no new signalling updates - * have been received, and the fade timer has expired. This phase - * allows for gui updates to signal to the user that the call is - * ended, while continuing to display the call details for the user - * TEARDOWN Indicates a traffic channel that will be torn down for reuse. + * IDLE: Normal state. No voice or data call activity + * CALL/DATA/ENCRYPTED/CONTROL: Decoding states. + * FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new + * signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to + * signal to the user that the call is ended, while continuing to display the call details for the user + * TEARDOWN: Indicates a traffic channel that will be torn down for reuse. */ public ChannelState(ChannelType channelType) { @@ -137,23 +126,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder @Override public void start(ScheduledExecutorService executor) { - if(mStateMonitorFuture == null && mStateMonitor != null) + mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); + + if(mTrafficChannelEndListener != null) { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState)); - - if(mTrafficChannelEndListener != null) - { - setState(State.CALL); - } - - try - { - mStateMonitorFuture = executor.scheduleAtFixedRate(mStateMonitor, 0, 20, TimeUnit.MILLISECONDS); - } - catch(RejectedExecutionException ree) - { - mLog.error("state monitor scheduled task rejected", ree); - } + setState(State.CALL); } } @@ -162,16 +139,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder { mMutableMetadata.resetAllAttributes(); - if(mStateMonitorFuture != null) - { - boolean success = mStateMonitorFuture.cancel(true); - - if(!success) - { - mLog.error("Couldn't stop monitoring scheduled future"); - } - } - mTrafficChannelEndListener = null; if(mTrafficChannelCallEvent != null) @@ -182,8 +149,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder mTrafficChannelCallEvent = null; - mStateMonitorFuture = null; - mSquelchLocked = false; } @@ -192,7 +157,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder mCallEventListener = null; mDecoderStateListener = null; mSquelchStateListener = null; - mStateMonitor = null; } @Override @@ -312,70 +276,74 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder } /** - * Sets the channel state to the specified state, or updates the timeout - * values so that the state monitor will not change state. Broadcasts a - * squelch event when the state changes and the audio squelch state should - * change. Also broadcasts changed attribute and decoder state events so - * that external processes can maintain sync with this channel state. + * Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not + * change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also + * broadcasts changed attribute and decoder state events so that external processes can maintain sync with this + * channel state. */ protected void setState(State state) { - synchronized(mState) + if(state == mState) { - if(state == mState) + if(State.CALL_STATES.contains(state)) { - if(State.CALL_STATES.contains(state)) - { - updateFadeTimeout(); - } + updateFadeTimeout(); } - else if(state != mState && mState.canChangeTo(state)) + } + else if(mState.canChangeTo(state)) + { + switch(state) { - switch(state) - { - case CONTROL: - //Don't allow traffic channels to be control channels, - //otherwise they can't transition to call tear down - if(isStandardChannel()) - { - broadcast(SquelchState.SQUELCH); - updateFadeTimeout(); - mState = state; - } - break; - case DATA: - case ENCRYPTED: + case CONTROL: + //Don't allow traffic channels to be control channels, otherwise they can't transition to teardown + if(isStandardChannel()) + { broadcast(SquelchState.SQUELCH); updateFadeTimeout(); mState = state; - break; - case CALL: - broadcast(SquelchState.UNSQUELCH); - updateFadeTimeout(); - mState = state; - break; - case FADE: - processFadeState(); - break; - case IDLE: - processIdleState(); - break; - case TEARDOWN: - processTeardownState(); - break; - case RESET: - mState = State.IDLE; - break; - default: - break; - } - - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, - mSourceOverflow ? State.OVERFLOW : mState)); + } + break; + case DATA: + case ENCRYPTED: + broadcast(SquelchState.SQUELCH); + updateFadeTimeout(); + mState = state; + break; + case CALL: + broadcast(SquelchState.UNSQUELCH); + updateFadeTimeout(); + mState = state; + break; + case FADE: + processFadeState(); + break; + case IDLE: + processIdleState(); + break; + case TEARDOWN: + processTeardownState(); + break; + case RESET: + mState = State.IDLE; + break; + default: + break; } + + mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, + mSourceOverflow ? State.OVERFLOW : mState)); } } + /** + * Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages. + */ + @Override + public Listener getHeartbeatListener() + { + return mHeartbeatReceiver; + } + /** * This method is invoked if the source buffer provider goes into overflow state. Since this is an external state, * we use the mSourceOverflow variable to override the internal state reported to external listeners. @@ -625,39 +593,35 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder } /** - * Monitors decoder state events to automatically transition the channel - * state to IDLE (standard channel) or to TEARDOWN (traffic channel) when - * decoding stops or the monitored channel returns to a no signal state. + * Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup + * functions. * - * Provides a FADE transition state to allow for momentary decoding dropouts - * and to allow the user access to call details for a fade period upon - * call end. + * Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to + * TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state. + * + * Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call + * details for a fade period upon call end. */ - public class StateMonitor implements Runnable + public class HeartbeatReceiver implements Listener { @Override - public void run() + public void receive(Heartbeat heartbeat) { try { - synchronized(mState) + if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis()) { - if(State.CALL_STATES.contains(mState) && - mFadeTimeout <= System.currentTimeMillis()) + processFadeState(); + } + else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis()) + { + if(isTrafficChannel()) { - processFadeState(); + processTeardownState(); } - else if(mState == State.FADE && - mEndTimeout <= System.currentTimeMillis()) + else { - if(isTrafficChannel()) - { - processTeardownState(); - } - else - { - processIdleState(); - } + processIdleState(); } } } diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java index 7c0bf5cd..5eaa4cbb 100644 --- a/src/module/ProcessingChain.java +++ b/src/module/ProcessingChain.java @@ -24,16 +24,17 @@ import audio.IAudioPacketProvider; import audio.squelch.ISquelchStateListener; import audio.squelch.ISquelchStateProvider; import audio.squelch.SquelchState; +import channel.heartbeat.Heartbeat; +import channel.heartbeat.IHeartbeatListener; +import channel.heartbeat.IHeartbeatProvider; import channel.metadata.AttributeChangeRequest; import channel.metadata.IAttributeChangeRequestListener; import channel.metadata.IAttributeChangeRequestProvider; import channel.state.ChannelState; import channel.state.DecoderState; import channel.state.DecoderStateEvent; -import channel.state.DecoderStateEvent.Event; import channel.state.IDecoderStateEventListener; import channel.state.IDecoderStateEventProvider; -import channel.state.State; import controller.channel.Channel.ChannelType; import controller.channel.ChannelEvent; import controller.channel.IChannelEventListener; @@ -63,8 +64,6 @@ import sample.real.RealBuffer; import source.ComplexSource; import source.RealSource; import source.Source; -import source.SourceException; -import source.tuner.TunerChannelSource; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.IFrequencyChangeListener; import source.tuner.frequency.IFrequencyChangeProvider; @@ -103,6 +102,7 @@ public class ProcessingChain implements IChannelEventListener private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>(); private Broadcaster mDecoderStateEventBroadcaster = new Broadcaster<>(); private Broadcaster mFrequencyChangeEventBroadcaster = new Broadcaster<>(); + private Broadcaster mHeartbeatBroadcaster = new Broadcaster<>(); private Broadcaster mMessageBroadcaster = new Broadcaster<>(); private Broadcaster mFilteredRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster mUnFilteredRealBufferBroadcaster = new Broadcaster<>(); @@ -329,6 +329,11 @@ public class ProcessingChain implements IChannelEventListener } } + if(module instanceof IHeartbeatListener) + { + mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener()); + } + if(module instanceof IMessageListener) { mMessageBroadcaster.addListener(((IMessageListener) module).getMessageListener()); @@ -391,6 +396,11 @@ public class ProcessingChain implements IChannelEventListener mFrequencyChangeEventBroadcaster.removeListener(((IFrequencyChangeListener) module).getFrequencyChangeListener()); } + if(module instanceof IHeartbeatListener) + { + mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener()); + } + if(module instanceof IMessageListener) { mMessageBroadcaster.removeListener(((IMessageListener) module).getMessageListener()); @@ -448,6 +458,11 @@ public class ProcessingChain implements IChannelEventListener ((IFrequencyChangeProvider) module).setFrequencyChangeListener(mFrequencyChangeEventBroadcaster); } + if(module instanceof IHeartbeatProvider) + { + ((IHeartbeatProvider)module).setHeartbeatListener(mHeartbeatBroadcaster); + } + if(module instanceof IMessageProvider) { ((IMessageProvider) module).setMessageListener(mMessageBroadcaster); @@ -505,6 +520,11 @@ public class ProcessingChain implements IChannelEventListener ((IFrequencyChangeProvider) module).setFrequencyChangeListener(null); } + if(module instanceof IHeartbeatProvider) + { + ((IHeartbeatProvider)module).removeHeartbeatListener(); + } + if(module instanceof IMessageProvider) { ((IMessageProvider) module).setMessageListener(null); diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index 8dc31d68..5072bc31 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -1713,33 +1713,41 @@ public class P25DecoderState extends DecoderState .build()); break; case SNDCP_RF_CONFIRMED_DATA: - SNDCPUserData sud = (SNDCPUserData)pduc; - - StringBuilder sbFrom = new StringBuilder(); - StringBuilder sbTo = new StringBuilder(); - - sbFrom.append(sud.getSourceIPAddress()); - sbTo.append(sud.getDestinationIPAddress()); - - if(sud.getIPProtocol() == IPProtocol.UDP) + if(pduc instanceof SNDCPUserData) { - sbFrom.append(":"); - sbFrom.append(sud.getUDPSourcePort()); - sbTo.append(":"); - sbTo.append(sud.getUDPDestinationPort()); + SNDCPUserData sud = (SNDCPUserData)pduc; + + StringBuilder sbFrom = new StringBuilder(); + StringBuilder sbTo = new StringBuilder(); + + sbFrom.append(sud.getSourceIPAddress()); + sbTo.append(sud.getDestinationIPAddress()); + + if(sud.getIPProtocol() == IPProtocol.UDP) + { + sbFrom.append(":"); + sbFrom.append(sud.getUDPSourcePort()); + sbTo.append(":"); + sbTo.append(sud.getUDPDestinationPort()); + } + + broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); + + broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("DATA: " + sud.getPayload() + + " RADIO IP:" + sbTo.toString()) + .frequency(mCurrentChannelFrequency) + .from(sbFrom.toString()) + .to(pduc.getLogicalLinkID()) + .build()); + } + else + { + mLog.error("Error - expected SNDCPUserData instance but class was: " + pduc.getClass() + + " and PDU Type:" + pduc.getPDUType().name()); } - - broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); - - broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL) - .aliasList(getAliasList()) - .channel(mCurrentChannel) - .details("DATA: " + sud.getPayload() + - " RADIO IP:" + sbTo.toString()) - .frequency(mCurrentChannelFrequency) - .from(sbFrom.toString()) - .to(pduc.getLogicalLinkID()) - .build()); break; case SNDCP_RF_UNCONFIRMED_DATA: break; diff --git a/src/source/Source.java b/src/source/Source.java index 932c2749..9b253fa5 100644 --- a/src/source/Source.java +++ b/src/source/Source.java @@ -17,6 +17,7 @@ ******************************************************************************/ package source; +import channel.heartbeat.IHeartbeatProvider; import module.Module; import sample.SampleType; import sample.real.IOverflowListener; @@ -26,7 +27,8 @@ import source.tuner.frequency.IFrequencyChangeProvider; /** * Abstract class to define the minimum functionality of a sample data provider. */ -public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider +public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider, + IHeartbeatProvider { protected SampleType mSampleType; protected IOverflowListener mOverflowListener; diff --git a/src/source/mixer/RealMixerSource.java b/src/source/mixer/RealMixerSource.java index cfc96997..7f0dfba2 100644 --- a/src/source/mixer/RealMixerSource.java +++ b/src/source/mixer/RealMixerSource.java @@ -1,174 +1,191 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014 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 ******************************************************************************/ package source.mixer; -import java.util.Iterator; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.LineUnavailableException; -import javax.sound.sampled.TargetDataLine; - +import channel.heartbeat.Heartbeat; +import dsp.gain.AutomaticGainControl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sample.Listener; import sample.adapter.ISampleAdapter; import sample.real.RealBuffer; import source.RealSource; import source.SourceException; -import dsp.gain.AutomaticGainControl; import source.tuner.frequency.FrequencyChangeEvent; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.TargetDataLine; +import java.util.Iterator; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + public class RealMixerSource extends RealSource { - private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class ); + private final static Logger mLog = LoggerFactory.getLogger(RealMixerSource.class); - private long mFrequency = 0; - private int mBufferSize = 16384; - - private BufferReader mBufferReader = new BufferReader(); - private TargetDataLine mTargetDataLine; - private AudioFormat mAudioFormat; - private int mBytesPerFrame = 0; - private ISampleAdapter mSampleAdapter; - private AutomaticGainControl mAGC = new AutomaticGainControl(); - - CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>(); - - /** - * Real Mixer Source - constructs a reader on the mixer/sound card target - * data line using the specified audio format (sample size, sample rate ) - * and broadcasts real buffers (float arrays) to all registered listeners. - * Reads buffers sized to 10% of the sample rate specified in audio format. - * - * @param targetDataLine - mixer or sound card to be used - * - * @param format - audio format - * - * @param name - token name to use for this source - * - * @param sampleAdapter - adapter to convert byte array data read from the - * mixer into float array data. Can optionally invert the channel data if - * the left/right stereo channels are inverted. - */ - public RealMixerSource( TargetDataLine targetDataLine, - AudioFormat format, - ISampleAdapter sampleAdapter ) + private long mFrequency = 0; + private int mBufferSize = 16384; + + private BufferReader mBufferReader = new BufferReader(); + private TargetDataLine mTargetDataLine; + private AudioFormat mAudioFormat; + private int mBytesPerFrame = 0; + private ISampleAdapter mSampleAdapter; + private AutomaticGainControl mAGC = new AutomaticGainControl(); + private Listener mHeartbeatListener; + private static final Heartbeat HEARTBEAT = new Heartbeat(); + + private CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>(); + + /** + * Real Mixer Source - constructs a reader on the mixer/sound card target + * data line using the specified audio format (sample size, sample rate ) + * and broadcasts real buffers (float arrays) to all registered listeners. + * Reads buffers sized to 10% of the sample rate specified in audio format. + * + * @param targetDataLine - mixer or sound card to be used + * @param format - audio format + * @param sampleAdapter - adapter to convert byte array data read from the + * mixer into float array data. Can optionally invert the channel data if + * the left/right stereo channels are inverted. + */ + public RealMixerSource(TargetDataLine targetDataLine, + AudioFormat format, + ISampleAdapter sampleAdapter) { - mTargetDataLine = targetDataLine; + mTargetDataLine = targetDataLine; mAudioFormat = format; mSampleAdapter = sampleAdapter; } - @Override - public void setFrequencyChangeListener(Listener listener) - { - //Not implemented - } - - @Override - public void removeFrequencyChangeListener() - { - //Not implemented - } - - @Override - public Listener getFrequencyChangeListener() - { - //Not implemented - return null; - } - - @Override - public void reset() - { - } - - @Override - public void start( ScheduledExecutorService executor ) - { - } - - @Override - public void stop() - { - } - - public void setListener( Listener listener ) + @Override + public void setFrequencyChangeListener(Listener listener) { - mSampleListeners.add( listener ); - - /* If this is the first listener, start the reader thread */ - if( !mBufferReader.isRunning() ) - { - Thread thread = new Thread( mBufferReader ); - thread.setDaemon( true ); - thread.setName( "Sample Reader" ); - thread.start(); - } + //Not implemented } - public void removeListener( Listener listener ) + @Override + public void removeFrequencyChangeListener() { - mSampleListeners.remove( listener ); + //Not implemented + } + + @Override + public Listener getFrequencyChangeListener() + { + //Not implemented + return null; + } + + /** + * Registers the listener to receive heartbeats from this source. + * + * @param listener to receive heartbeats + */ + @Override + public void setHeartbeatListener(Listener listener) + { + mHeartbeatListener = listener; + } + + /** + * Removes the currently registered heartbeat listener + */ + @Override + public void removeHeartbeatListener() + { + mHeartbeatListener = null; + } + + @Override + public void reset() + { + } + + @Override + public void start(ScheduledExecutorService executor) + { + } + + @Override + public void stop() + { + } + + public void setListener(Listener listener) + { + mSampleListeners.add(listener); + + /* If this is the first listener, start the reader thread */ + if(!mBufferReader.isRunning()) + { + Thread thread = new Thread(mBufferReader); + thread.setDaemon(true); + thread.setName("Sample Reader"); + thread.start(); + } + } + + public void removeListener(Listener listener) + { + mSampleListeners.remove(listener); /* If this is the laster listener, stop the reader thread */ - if( mSampleListeners.isEmpty() ) - { - mBufferReader.stop(); - } + if(mSampleListeners.isEmpty()) + { + mBufferReader.stop(); + } } - public void broadcast( RealBuffer samples ) + public void broadcast(RealBuffer samples) { - Iterator> it = mSampleListeners.iterator(); - - while( it.hasNext() ) - { - Listener next = it.next(); + Iterator> it = mSampleListeners.iterator(); + + while(it.hasNext()) + { + Listener next = it.next(); /* if this is the last (or only) listener, send him the original * buffer, otherwise send him a copy of the buffer */ - if( it.hasNext() ) - { - next.receive( samples.copyOf() ); - } - else - { - next.receive( samples ); - } - } + if(it.hasNext()) + { + next.receive(samples.copyOf()); + } + else + { + next.receive(samples); + } + } } - + public int getSampleRate() { - if( mTargetDataLine != null ) - { - return (int)mTargetDataLine.getFormat().getSampleRate(); - } - else - { - return 0; - } + if(mTargetDataLine != null) + { + return (int)mTargetDataLine.getFormat().getSampleRate(); + } + else + { + return 0; + } } /** @@ -176,7 +193,7 @@ public class RealMixerSource extends RealSource */ public long getFrequency() throws SourceException { - return mFrequency; + return mFrequency; } /** @@ -184,122 +201,128 @@ public class RealMixerSource extends RealSource * be useful if you are streaming an external audio source in through the * sound card and you want to specify a frequency for that source */ - public void setFrequency( long frequency ) + public void setFrequency(long frequency) { - mFrequency = frequency; + mFrequency = frequency; } - /** - * Reader thread. Performs blocking read against the mixer target data - * line, converts the samples to an array of floats using the specified - * adapter. Dispatches float arrays to all registered listeners. - */ - public class BufferReader implements Runnable - { - private AtomicBoolean mRunning = new AtomicBoolean(); - - @Override + /** + * Reader thread. Performs blocking read against the mixer target data + * line, converts the samples to an array of floats using the specified + * adapter. Dispatches float arrays to all registered listeners. + */ + public class BufferReader implements Runnable + { + private AtomicBoolean mRunning = new AtomicBoolean(); + + @Override public void run() { - if( mRunning.compareAndSet( false, true ) ) - { - if( mTargetDataLine == null ) - { - mRunning.set( false ); + if(mRunning.compareAndSet(false, true)) + { + //Send a heartbeat every time we run + if(mHeartbeatListener != null) + { + mHeartbeatListener.receive(HEARTBEAT); + } - mLog.error( "ComplexMixerSource - mixer target data line" - + " is null" ); - } - else - { - mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8; + if(mTargetDataLine == null) + { + mRunning.set(false); - if( mBytesPerFrame == AudioSystem.NOT_SPECIFIED ) - { - mBytesPerFrame = 2; - } + mLog.error("ComplexMixerSource - mixer target data line" + + " is null"); + } + else + { + mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8; + + if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED) + { + mBytesPerFrame = 2; + } /* Set buffer size to 1/10 second of samples */ - mBufferSize = (int)( mAudioFormat.getSampleRate() - * 0.05 ) * mBytesPerFrame; + mBufferSize = (int)(mAudioFormat.getSampleRate() + * 0.05) * mBytesPerFrame; /* We'll reuse the same buffer for each read */ - byte[] buffer = new byte[ mBufferSize ]; - - try - { - mTargetDataLine.open( mAudioFormat ); + byte[] buffer = new byte[mBufferSize]; - mTargetDataLine.start(); - } - catch ( LineUnavailableException e ) + try { - mLog.error( "ComplexMixerSource - mixer target data line" - + "not available to read data from", e ); - - mRunning.set( false ); + mTargetDataLine.open(mAudioFormat); + + mTargetDataLine.start(); } - - while( mRunning.get() ) - { - try + catch(LineUnavailableException e) + { + mLog.error("ComplexMixerSource - mixer target data line" + + "not available to read data from", e); + + mRunning.set(false); + } + + while(mRunning.get()) + { + try { /* Blocking read - waits until the buffer fills */ - mTargetDataLine.read( buffer, 0, buffer.length ); + mTargetDataLine.read(buffer, 0, buffer.length); /* Convert samples to float array */ - float[] samples = - mSampleAdapter.convert( buffer ); + float[] samples = + mSampleAdapter.convert(buffer); /* Dispatch samples to registered listeners */ - broadcast( new RealBuffer( samples ) ); + broadcast(new RealBuffer(samples)); } - catch ( Exception e ) + catch(Exception e) { - mLog.error( "ComplexMixerSource - error while reading" - + "from the mixer target data line", e ); - - mRunning.set( false ); + mLog.error("ComplexMixerSource - error while reading" + + "from the mixer target data line", e); + + mRunning.set(false); } - } - } + } + } /* Close the data line if it is still open */ - if( mTargetDataLine != null && mTargetDataLine.isOpen() ) - { - mTargetDataLine.close(); - } - } + if(mTargetDataLine != null && mTargetDataLine.isOpen()) + { + mTargetDataLine.close(); + } + } } - /** - * Stops the reader thread - */ - public void stop() - { - if( mRunning.compareAndSet( true, false ) ) - { - mTargetDataLine.stop(); - mTargetDataLine.close(); - } - } + /** + * Stops the reader thread + */ + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + mTargetDataLine.stop(); + mTargetDataLine.close(); + } + } - /** - * Indicates if the reader thread is running - */ - public boolean isRunning() - { - return mRunning.get(); - } - } + /** + * Indicates if the reader thread is running + */ + public boolean isRunning() + { + return mRunning.get(); + } + } - @Override + @Override public void dispose() { - if( mBufferReader != null ) - { - mBufferReader.stop(); - mSampleListeners.clear(); - } + if(mBufferReader != null) + { + mBufferReader.stop(); + mSampleListeners.clear(); + } } } diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index 7556e7b5..ba7900b8 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -18,6 +18,8 @@ ******************************************************************************/ package source.tuner; +import channel.heartbeat.Heartbeat; +import channel.heartbeat.IHeartbeatProvider; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; import dsp.filter.cic.ComplexPrimeCICDecimate; @@ -58,6 +60,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang private static int CHANNEL_RATE = 48000; private static int CHANNEL_PASS_FREQUENCY = 12000; + private static final Heartbeat HEARTBEAT = new Heartbeat(); private OverflowableTransferQueue mBuffer; @@ -66,6 +69,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang private Oscillator mMixer; private ComplexPrimeCICDecimate mDecimationFilter; private Listener mListener; + private Listener mHeartbeatListener; private IFrequencyChangeProcessor mFrequencyChangeProcessor; private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor(); private ScheduledFuture mTaskHandle; @@ -352,6 +356,25 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener(); } + /** + * Registers the listener to receive heartbeats from this source. + * @param listener to receive heartbeats + */ + @Override + public void setHeartbeatListener(Listener listener) + { + mHeartbeatListener = listener; + } + + /** + * Removes the currently registered heartbeat listener + */ + @Override + public void removeHeartbeatListener() + { + mHeartbeatListener = null; + } + /** * Managers frequency change requests and notifications from/to any downstream component. Downstream * components are those that receive samples from this tuner channel source. These downstream components will be @@ -477,6 +500,13 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang { if(mProcessing) { + //Send a heartbeat every time this runs to allow downstream components to perform periodic + //state monitoring functions on this thread + if(mHeartbeatListener != null) + { + mHeartbeatListener.receive(HEARTBEAT); + } + mBuffer.drainTo(mSampleBuffers, 20); for(Buffer buffer : mSampleBuffers) diff --git a/src/source/wave/ComplexWaveSource.java b/src/source/wave/ComplexWaveSource.java index 8d80ce1d..39e6ef26 100644 --- a/src/source/wave/ComplexWaveSource.java +++ b/src/source/wave/ComplexWaveSource.java @@ -18,19 +18,9 @@ ******************************************************************************/ package source.wave; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.concurrent.ScheduledExecutorService; - -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; - +import channel.heartbeat.Heartbeat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sample.ConversionUtils; import sample.Listener; import sample.complex.ComplexBuffer; @@ -39,22 +29,31 @@ import source.IControllableFileSource; import source.IFrameLocationListener; import source.tuner.frequency.FrequencyChangeEvent; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.ScheduledExecutorService; + public class ComplexWaveSource extends ComplexSource implements IControllableFileSource { - private final static Logger mLog = - LoggerFactory.getLogger( ComplexWaveSource.class ); + private final static Logger mLog = + LoggerFactory.getLogger(ComplexWaveSource.class); - private IFrameLocationListener mFrameLocationListener; - private int mBytesPerFrame; - private int mFrameCounter = 0; + private IFrameLocationListener mFrameLocationListener; + private int mBytesPerFrame; + private int mFrameCounter = 0; private long mFrequency = 0; private Listener mListener; private AudioInputStream mInputStream; private File mFile; - - public ComplexWaveSource( File file ) throws IOException + + public ComplexWaveSource(File file) throws IOException { - mFile = file; + mFile = file; } @Override @@ -76,89 +75,105 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil return null; } + /** + * Not implemented + */ @Override - public void reset() - { - stop(); - start( null ); - } - - @Override - public void start( ScheduledExecutorService executor ) - { - try - { - open(); - } - catch ( IOException | UnsupportedAudioFileException e ) - { - mLog.error( "Error starting complex wave source" ); - } - } - - @Override - public void stop() - { - try - { - close(); - } - catch ( IOException e ) - { - mLog.error( "Error stopping complex wave source" ); - } - } - - @Override - public long getFrameCount() throws IOException - { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int getSampleRate() + public void setHeartbeatListener(Listener listener) { - if( mInputStream != null ) - { - return (int)mInputStream.getFormat().getSampleRate(); - } - - return 0; } - /** - * Returns the frequency set for this file. Normally returns zero, but - * the value can be set with setFrequency() method. - */ + /** + * Not implemented + */ + @Override + public void removeHeartbeatListener() + { + } + + @Override + public void reset() + { + stop(); + start(null); + } + + @Override + public void start(ScheduledExecutorService executor) + { + try + { + open(); + } + catch(IOException | UnsupportedAudioFileException e) + { + mLog.error("Error starting complex wave source"); + } + } + + @Override + public void stop() + { + try + { + close(); + } + catch(IOException e) + { + mLog.error("Error stopping complex wave source"); + } + } + + @Override + public long getFrameCount() throws IOException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getSampleRate() + { + if(mInputStream != null) + { + return (int)mInputStream.getFormat().getSampleRate(); + } + + return 0; + } + + /** + * Returns the frequency set for this file. Normally returns zero, but + * the value can be set with setFrequency() method. + */ public long getFrequency() { - return mFrequency; + return mFrequency; } /** * Changes the value returned from getFrequency() for this source. */ - public void setFrequency( long frequency ) + public void setFrequency(long frequency) { - mFrequency = frequency; + mFrequency = frequency; } - + /** * Closes the source file */ public void close() throws IOException { - if( mInputStream != null ) - { - mInputStream.close(); - } - else - { - throw new IOException( "Can't close wave source - was not opened" ); - } - - mInputStream = null; + if(mInputStream != null) + { + mInputStream.close(); + } + else + { + throw new IOException("Can't close wave source - was not opened"); + } + + mInputStream = null; } /** @@ -166,121 +181,121 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil */ public void open() throws IOException, UnsupportedAudioFileException { - if( mInputStream == null ) - { - mInputStream = AudioSystem.getAudioInputStream( mFile ); - } - else - { - throw new IOException( "Can't open wave source - is already opened" ); - } + if(mInputStream == null) + { + mInputStream = AudioSystem.getAudioInputStream(mFile); + } + else + { + throw new IOException("Can't open wave source - is already opened"); + } AudioFormat format = mInputStream.getFormat(); - + mBytesPerFrame = format.getFrameSize(); - if( format.getChannels() != 2 || format.getSampleSizeInBits() != 16 ) + if(format.getChannels() != 2 || format.getSampleSizeInBits() != 16) { - throw new IOException( "Unsupported Wave Format - EXPECTED: 2 " + - "channels 16-bit samples FOUND: " + - mInputStream.getFormat().getChannels() + " channels " + - mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" ); + throw new IOException("Unsupported Wave Format - EXPECTED: 2 " + + "channels 16-bit samples FOUND: " + + mInputStream.getFormat().getChannels() + " channels " + + mInputStream.getFormat().getSampleSizeInBits() + "-bit samples"); } /* Broadcast that we're at frame location 0 */ - broadcast( 0 ); + broadcast(0); } /** * Reads the number of frames and sends a buffer to the listener */ - @Override - public void next( int frames ) throws IOException - { - next( frames, true ); - } - - /** - * Reads the number of frames and optionally sends the buffer to the listener - */ - public void next( int frames, boolean broadcast ) throws IOException + @Override + public void next(int frames) throws IOException { - if( mInputStream != null ) + next(frames, true); + } + + /** + * Reads the number of frames and optionally sends the buffer to the listener + */ + public void next(int frames, boolean broadcast) throws IOException + { + if(mInputStream != null) { - byte[] buffer = new byte[ mBytesPerFrame * frames ]; - + byte[] buffer = new byte[mBytesPerFrame * frames]; + /* Fill the buffer with samples from the file */ - int samplesRead = mInputStream.read( buffer ); + int samplesRead = mInputStream.read(buffer); - mFrameCounter += samplesRead; - - broadcast( mFrameCounter ); - - if( broadcast && mListener != null ) - { - if( samplesRead < buffer.length ) - { - buffer = Arrays.copyOf( buffer, samplesRead ); - } + mFrameCounter += samplesRead; - float[] samples = ConversionUtils - .convertFromSigned16BitSamples( buffer ); - - mListener.receive( new ComplexBuffer( samples ) ); - } + broadcast(mFrameCounter); + + if(broadcast && mListener != null) + { + if(samplesRead < buffer.length) + { + buffer = Arrays.copyOf(buffer, samplesRead); + } + + float[] samples = ConversionUtils + .convertFromSigned16BitSamples(buffer); + + mListener.receive(new ComplexBuffer(samples)); + } } } - + /** - * Registers the listener to receive sample buffers as they are read from + * Registers the listener to receive sample buffers as they are read from * the wave file */ - @Override - public void setListener( Listener listener ) - { - mListener = listener; - } - - /** - * Unregisters the listener from receiving sample buffers - */ - public void removeListener( Listener listener ) + @Override + public void setListener(Listener listener) { - mListener = null; + mListener = listener; } - @Override + /** + * Unregisters the listener from receiving sample buffers + */ + public void removeListener(Listener listener) + { + mListener = null; + } + + @Override public void dispose() { - mListener = null; + mListener = null; } - @Override - public File getFile() - { - return mFile; - } + @Override + public File getFile() + { + return mFile; + } - private void broadcast( int byteLocation ) - { - int frameLocation = (int)( byteLocation / mBytesPerFrame ); - - if( mFrameLocationListener != null ) - { - mFrameLocationListener.frameLocationUpdated( frameLocation ); - } - } - - @Override - public void setListener( IFrameLocationListener listener ) - { - mFrameLocationListener = listener; - } + private void broadcast(int byteLocation) + { + int frameLocation = (int)(byteLocation / mBytesPerFrame); - @Override - public void removeListener( IFrameLocationListener listener ) - { - mFrameLocationListener = null; - } + if(mFrameLocationListener != null) + { + mFrameLocationListener.frameLocationUpdated(frameLocation); + } + } + + @Override + public void setListener(IFrameLocationListener listener) + { + mFrameLocationListener = listener; + } + + @Override + public void removeListener(IFrameLocationListener listener) + { + mFrameLocationListener = null; + } } diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java index 4b9b120b..a17efb49 100644 --- a/src/source/wave/RealWaveSource.java +++ b/src/source/wave/RealWaveSource.java @@ -1,35 +1,25 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014,2015 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 ******************************************************************************/ package source.wave; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.concurrent.ScheduledExecutorService; - -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; - +import channel.heartbeat.Heartbeat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sample.ConversionUtils; import sample.Listener; import sample.real.RealBuffer; @@ -38,131 +28,154 @@ import source.IFrameLocationListener; import source.RealSource; import source.tuner.frequency.FrequencyChangeEvent; -public class RealWaveSource extends RealSource - implements IControllableFileSource, AutoCloseable -{ - private final static Logger mLog = - LoggerFactory.getLogger( RealWaveSource.class ); +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.ScheduledExecutorService; - private IFrameLocationListener mFrameLocationListener; - private int mBytesPerFrame; - private int mFrameCounter = 0; +public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable +{ + private final static Logger mLog = LoggerFactory.getLogger(RealWaveSource.class); + + private IFrameLocationListener mFrameLocationListener; + private int mBytesPerFrame; + private int mFrameCounter = 0; private long mFrequency = 0; private Listener mListener; private AudioInputStream mInputStream; private File mFile; - - public RealWaveSource( File file ) throws IOException + + public RealWaveSource(File file) throws IOException { - mFile = file; + mFile = file; } - @Override - public void setFrequencyChangeListener(Listener listener) - { - //Not implemented - } + @Override + public void setFrequencyChangeListener(Listener listener) + { + //Not implemented + } - @Override - public void removeFrequencyChangeListener() - { - //Not implemented - } + @Override + public void removeFrequencyChangeListener() + { + //Not implemented + } - @Override - public Listener getFrequencyChangeListener() - { - //Not implemented - return null; - } + @Override + public Listener getFrequencyChangeListener() + { + //Not implemented + return null; + } + + /** + * Not implemented + */ + @Override + public void setHeartbeatListener(Listener listener) + { + } + + /** + * Not implemented + */ + @Override + public void removeHeartbeatListener() + { + } - @Override - public void reset() - { - stop(); - start( null ); - } + @Override + public void reset() + { + stop(); + start(null); + } - @Override - public void start( ScheduledExecutorService executor ) - { - try - { - open(); - } - catch ( IOException | UnsupportedAudioFileException e ) - { - mLog.error( "Error starting real wave source", e ); - } - } + @Override + public void start(ScheduledExecutorService executor) + { + try + { + open(); + } + catch(IOException | UnsupportedAudioFileException e) + { + mLog.error("Error starting real wave source", e); + } + } - @Override - public void stop() - { - try - { - close(); - } - catch ( IOException e ) - { - mLog.error( "Error stopping real wave source", e ); - } - } + @Override + public void stop() + { + try + { + close(); + } + catch(IOException e) + { + mLog.error("Error stopping real wave source", e); + } + } - @Override - public long getFrameCount() throws IOException - { - // TODO Auto-generated method stub - return 0; - } + @Override + public long getFrameCount() throws IOException + { + // TODO Auto-generated method stub + return 0; + } - @Override + @Override public int getSampleRate() { - if( mInputStream != null ) - { - return (int)mInputStream.getFormat().getSampleRate(); - } - - return 0; + if(mInputStream != null) + { + return (int)mInputStream.getFormat().getSampleRate(); + } + + return 0; } - /** - * Returns the frequency set for this file. Normally returns zero, but - * the value can be set with setFrequency() method. - */ + /** + * Returns the frequency set for this file. Normally returns zero, but + * the value can be set with setFrequency() method. + */ public long getFrequency() { - return mFrequency; + return mFrequency; } /** * Changes the value returned from getFrequency() for this source. */ - public void setFrequency( long frequency ) + public void setFrequency(long frequency) { - mFrequency = frequency; + mFrequency = frequency; } - + /** * Closes the source file */ public void close() throws IOException { - if( mInputStream != null ) - { - mInputStream.close(); - } - else - { - throw new IOException( "Can't close wave source - was not opened" ); - } - - mInputStream = null; + if(mInputStream != null) + { + mInputStream.close(); + } + else + { + throw new IOException("Can't close wave source - was not opened"); + } + + mInputStream = null; } /** @@ -170,126 +183,126 @@ public class RealWaveSource extends RealSource */ public void open() throws IOException, UnsupportedAudioFileException { - if( mInputStream == null ) - { - mInputStream = AudioSystem.getAudioInputStream( mFile ); - } - else - { - throw new IOException( "Can't open wave source - is already opened" ); - } + if(mInputStream == null) + { + mInputStream = AudioSystem.getAudioInputStream(mFile); + } + else + { + throw new IOException("Can't open wave source - is already opened"); + } AudioFormat format = mInputStream.getFormat(); - + mBytesPerFrame = format.getFrameSize(); - if( format.getChannels() != 1 || format.getSampleSizeInBits() != 16 ) + if(format.getChannels() != 1 || format.getSampleSizeInBits() != 16) { - throw new IOException( "Unsupported Wave Format - EXPECTED: 1 " + - "channel 16-bit samples FOUND: " + - mInputStream.getFormat().getChannels() + " channels " + - mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" ); + throw new IOException("Unsupported Wave Format - EXPECTED: 1 " + + "channel 16-bit samples FOUND: " + + mInputStream.getFormat().getChannels() + " channels " + + mInputStream.getFormat().getSampleSizeInBits() + "-bit samples"); } /* Broadcast that we're at frame location 0 */ - broadcast( 0 ); + broadcast(0); } /** * Reads the number of frames and sends a buffer to the listener */ - @Override - public void next( int frames ) throws IOException - { - next( frames, true ); - } - - /** - * Reads the number of frames and optionally sends the buffer to the listener - */ - public void next( int frames, boolean broadcast ) throws IOException + @Override + public void next(int frames) throws IOException { - if( mInputStream != null ) - { - byte[] buffer = new byte[ mBytesPerFrame * frames ]; - - /* Fill the buffer with samples from the file */ - int samplesRead = mInputStream.read( buffer ); - - mFrameCounter += samplesRead; - - broadcast( mFrameCounter ); - - if( broadcast && mListener != null ) - { - if( samplesRead < buffer.length ) - { - if( samplesRead == -1 ) - { - throw new IOException( "End of recording" ); - } - - buffer = Arrays.copyOf( buffer, samplesRead ); - } + next(frames, true); + } - float[] samples = ConversionUtils - .convertFromSigned16BitSamples( buffer ); - - mListener.receive( new RealBuffer( samples ) ); - } + /** + * Reads the number of frames and optionally sends the buffer to the listener + */ + public void next(int frames, boolean broadcast) throws IOException + { + if(mInputStream != null) + { + byte[] buffer = new byte[mBytesPerFrame * frames]; + + /* Fill the buffer with samples from the file */ + int samplesRead = mInputStream.read(buffer); + + mFrameCounter += samplesRead; + + broadcast(mFrameCounter); + + if(broadcast && mListener != null) + { + if(samplesRead < buffer.length) + { + if(samplesRead == -1) + { + throw new IOException("End of recording"); + } + + buffer = Arrays.copyOf(buffer, samplesRead); + } + + float[] samples = ConversionUtils + .convertFromSigned16BitSamples(buffer); + + mListener.receive(new RealBuffer(samples)); + } } } - + /** - * Registers the listener to receive sample buffers as they are read from + * Registers the listener to receive sample buffers as they are read from * the wave file */ - @Override - public void setListener( Listener listener ) - { - mListener = listener; - } - - /** - * Unregisters the listener from receiving sample buffers - */ - public void removeListener( Listener listener ) + @Override + public void setListener(Listener listener) { - mListener = null; + mListener = listener; } - @Override + /** + * Unregisters the listener from receiving sample buffers + */ + public void removeListener(Listener listener) + { + mListener = null; + } + + @Override public void dispose() { - mListener = null; + mListener = null; } - @Override - public File getFile() - { - return mFile; - } - - private void broadcast( int byteLocation ) - { - int frameLocation = (int)( byteLocation / mBytesPerFrame ); - - if( mFrameLocationListener != null ) - { - mFrameLocationListener.frameLocationUpdated( frameLocation ); - } - } - - @Override - public void setListener( IFrameLocationListener listener ) - { - mFrameLocationListener = listener; - } + @Override + public File getFile() + { + return mFile; + } - @Override - public void removeListener( IFrameLocationListener listener ) - { - mFrameLocationListener = null; - } + private void broadcast(int byteLocation) + { + int frameLocation = (int)(byteLocation / mBytesPerFrame); + + if(mFrameLocationListener != null) + { + mFrameLocationListener.frameLocationUpdated(frameLocation); + } + } + + @Override + public void setListener(IFrameLocationListener listener) + { + mFrameLocationListener = listener; + } + + @Override + public void removeListener(IFrameLocationListener listener) + { + mFrameLocationListener = null; + } } From c8994d5593e054946aad4beb2a0d1a28ea473015 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 22 Jul 2017 05:45:36 -0400 Subject: [PATCH 09/20] Updates build to version 0.3.0 Beta 13 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index b75db977..40ef5c49 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From e80b5866d43b8704aff69917c7cc26322c310558 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 18 Aug 2017 20:07:53 -0400 Subject: [PATCH 10/20] Resolves issue #260 where audio streaming was not re-connecting after a disconnect. --- .../icecast/IcecastTCPAudioBroadcaster.java | 67 ++++++++++++------- .../v1/ShoutcastV1AudioBroadcaster.java | 4 ++ .../v2/ShoutcastV2AudioBroadcaster.java | 4 ++ 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java index fa9cd8d5..80c0fb8c 100644 --- a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java +++ b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java @@ -36,6 +36,7 @@ import util.ThreadPool; import java.io.IOException; import java.net.ConnectException; import java.net.InetSocketAddress; +import java.nio.channels.UnresolvedAddressException; import java.util.concurrent.TimeUnit; 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 String TERMINATOR = "\r\n"; 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 IoSession mStreamingSession = null; @@ -99,10 +101,11 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster if(mSocketConnector == null) { mSocketConnector = new NioSocketConnector(); - mSocketConnector.setConnectTimeoutCheckInterval(10000); + mSocketConnector.setConnectTimeoutCheckInterval(1000); -// mSocketConnector.getFilterChain().addLast("logger", -// new LoggingFilter(IcecastTCPAudioBroadcaster.class)); +// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class); +// loggingFilter.setMessageSentLogLevel(LogLevel.NONE); +// mSocketConnector.getFilterChain().addLast("logger", loggingFilter); mSocketConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new IcecastCodecFactory())); @@ -123,9 +126,20 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster ConnectFuture future = mSocketConnector .connect(new InetSocketAddress(getBroadcastConfiguration().getHost(), getBroadcastConfiguration().getPort())); - future.awaitUninterruptibly(); + + boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT, TimeUnit.MILLISECONDS); + 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) { Throwable throwableCause = rie.getCause(); @@ -137,16 +151,20 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster else if(throwableCause != null) { setBroadcastState(BroadcastState.ERROR); - mLog.debug("Failed to connect", rie); + mLog.debug("[" + getStreamName() + "]Failed to connect", rie); } else { setBroadcastState(BroadcastState.ERROR); - mLog.debug("Failed to connect - no exception is available"); + mLog.debug("[" + getStreamName() + "]Failed to connect - no exception is available"); } disconnect(); } + catch(Throwable t) + { + disconnect(); + } mConnecting.set(false); } @@ -164,12 +182,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster */ 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 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(!getBroadcastState().isErrorState()) { setBroadcastState(BroadcastState.DISCONNECTED); } - mSocketConnector.dispose(); mStreamingSession = null; - mSocketConnector = null; mConnecting.set(false); + super.sessionClosed(session); } @@ -253,45 +273,36 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster if(connected()) { - mLog.info("Streaming connection error detected - resetting connection - " + reason); disconnect(); - connect(); } else if(reason.startsWith("Connection reset")) { - mLog.info("Streaming connection reset by remote server - reestablishing connection"); disconnect(); - connect(); } else if(reason.startsWith("Operation timed out")) { - mLog.info("Streaming connection timed out - resetting connection"); disconnect(); - connect(); } else { setBroadcastState(BroadcastState.ERROR); disconnect(); - mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.", cause); } } else if(ioe instanceof WriteTimeoutException) { - mLog.info("Network write timeout error - resetting connection"); disconnect(); - connect(); } else { setBroadcastState(BroadcastState.ERROR); disconnect(); - mLog.error("Unspecified IO error - streaming halted.", cause); + mLog.error("[" + getStreamName() + "] Unspecified IO error - streaming halted.", cause); } } else { - mLog.error("Broadcast error", cause); + mLog.error("[" + getStreamName() + "] Broadcast error", cause); setBroadcastState(BroadcastState.ERROR); disconnect(); } @@ -319,6 +330,10 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster { setBroadcastState(BroadcastState.INVALID_CREDENTIALS); } + else if(message.contains("HTTP/1.1 501")) + { + disconnect(); //So that we can reconnect later + } else { mLog.error("Unrecognized server response:" + message); @@ -328,7 +343,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster } else { - mLog.error("Icecast TCP broadcaster - unrecognized message [ " + object.getClass() + + mLog.error("[" + getStreamName() + "]Icecast TCP broadcaster - unrecognized message [ " + object.getClass() + "] received:" + object.toString()); } } diff --git a/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java b/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java index 5d6d8dab..5032495d 100644 --- a/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java +++ b/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java @@ -168,6 +168,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster disconnect(); } + catch(Throwable t) + { + disconnect(); + } mConnecting.set(false); } diff --git a/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java b/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java index 341b31bf..5c8e3237 100644 --- a/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java +++ b/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java @@ -204,6 +204,10 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr disconnect(); } + catch(Throwable t) + { + disconnect(); + } mConnecting.set(false); } From c196cfebedcff436785efca4e09aeb1bdaf93d4d Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 19 Aug 2017 07:13:08 -0400 Subject: [PATCH 11/20] Resolves issue #260 - applies changes to Shoutcast V1 and v2 that were applied to the Icecast streamer for issue releated to reconnecting. --- .../icecast/IcecastTCPAudioBroadcaster.java | 6 +++--- .../v1/ShoutcastV1AudioBroadcaster.java | 17 ++++++++--------- .../v2/ShoutcastV2AudioBroadcaster.java | 18 ++++++++---------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java index 80c0fb8c..9d37c2e5 100644 --- a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java +++ b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java @@ -101,7 +101,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster if(mSocketConnector == null) { mSocketConnector = new NioSocketConnector(); - mSocketConnector.setConnectTimeoutCheckInterval(1000); + mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT); // LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class); // loggingFilter.setMessageSentLogLevel(LogLevel.NONE); @@ -151,12 +151,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster else if(throwableCause != null) { setBroadcastState(BroadcastState.ERROR); - mLog.debug("[" + getStreamName() + "]Failed to connect", rie); + mLog.debug("[" + getStreamName() + "] failed to connect", rie); } else { setBroadcastState(BroadcastState.ERROR); - mLog.debug("[" + getStreamName() + "]Failed to connect - no exception is available"); + mLog.debug("[" + getStreamName() + "] failed to connect - no exception is available"); } disconnect(); diff --git a/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java b/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java index 5032495d..e1afae65 100644 --- a/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java +++ b/src/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java @@ -189,12 +189,13 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster */ public void disconnect() { - if(connected()) + if(connected() && mStreamingSession != null) { - if(mStreamingSession != null) - { - mStreamingSession.closeNow(); - } + mStreamingSession.closeNow(); + } + else + { + mLastConnectionAttempt = System.currentTimeMillis(); } } @@ -230,15 +231,15 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster @Override 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(!getBroadcastState().isErrorState()) { setBroadcastState(BroadcastState.DISCONNECTED); } - mSocketConnector.dispose(); mStreamingSession = null; - mSocketConnector = null; mConnecting.set(false); super.sessionClosed(session); @@ -259,13 +260,11 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster { mLog.info("Streaming connection reset by remote server - reestablishing connection"); disconnect(); - connect(); } else if(reason.startsWith("Operation timed out")) { mLog.info("Streaming connection timed out - resetting connection"); disconnect(); - connect(); } else { diff --git a/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java b/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java index 5c8e3237..034666e4 100644 --- a/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java +++ b/src/audio/broadcast/shoutcast/v2/ShoutcastV2AudioBroadcaster.java @@ -225,13 +225,13 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr */ public void disconnect() { - if(connected()) + if(connected() && mStreamingSession != null) { - if(mStreamingSession != null) - { - mStreamingSession.write(new TerminateBroadcast()); - mStreamingSession.closeNow(); - } + mStreamingSession.closeNow(); + } + else + { + mLastConnectionAttempt = System.currentTimeMillis(); } } @@ -353,15 +353,15 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr @Override 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(!getBroadcastState().isErrorState()) { setBroadcastState(BroadcastState.DISCONNECTED); } - mSocketConnector.dispose(); mStreamingSession = null; - mSocketConnector = null; mConnecting.set(false); super.sessionClosed(session); @@ -382,13 +382,11 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr { mLog.info("Streaming connection reset by remote server - reestablishing connection"); disconnect(); - connect(); } else if(reason.startsWith("Operation timed out")) { mLog.info("Streaming connection timed out - resetting connection"); disconnect(); - connect(); } else { From e430319937c11e116aac4fff0437b3db397798d9 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 19 Aug 2017 07:15:57 -0400 Subject: [PATCH 12/20] Updates build to 0.3.0 Beta 14 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 40ef5c49..0a50a98f 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From df2a061a08dba42dc64eff7f7d3a516f7140ff32 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Mon, 28 Aug 2017 19:51:13 -0400 Subject: [PATCH 13/20] Create LICENSE --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..9cecc1d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +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} + + 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 . + +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: + + {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. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +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 +. From 8035d64fcdb357f40eef470d875bbed0af22a557 Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 10 Sep 2017 04:54:26 -0400 Subject: [PATCH 14/20] Resolves #266 - updates build script to use new gitlab license file and removes previous license file. --- build/build.xml | 9 +- ...pyright_header.txt => copyright_header.txt | 0 license/license.txt | 674 ------------------ 3 files changed, 4 insertions(+), 679 deletions(-) rename license/copyright_header.txt => copyright_header.txt (100%) delete mode 100644 license/license.txt diff --git a/build/build.xml b/build/build.xml index 0a50a98f..f7d3fd89 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,7 +16,6 @@ - @@ -57,8 +56,8 @@ - - + + @@ -85,8 +84,8 @@ - - + + diff --git a/license/copyright_header.txt b/copyright_header.txt similarity index 100% rename from license/copyright_header.txt rename to copyright_header.txt diff --git a/license/license.txt b/license/license.txt deleted file mode 100644 index 20d40b6b..00000000 --- a/license/license.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -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. - - - Copyright (C) - - 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 . - -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: - - Copyright (C) - 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. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -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 -. \ No newline at end of file From 85442a964839640e7875c8d28bac837f42212914 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 15 Sep 2017 11:40:23 -0400 Subject: [PATCH 15/20] 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. --- src/bits/BinaryMessage.java | 11 + src/bits/CorrectedBinaryMessage.java | 65 +++++ src/edac/trellis/DMRNode.java | 86 +++++++ src/edac/trellis/Node.java | 77 ++++++ src/edac/trellis/Path.java | 131 ++++++++++ src/edac/trellis/ViterbiDecoder.java | 226 ++++++++++++++++++ src/edac/trellis/ViterbiDecoder_3_4_DMR.java | 159 ++++++++++++ .../radioreference/RadioReferenceService.java | 37 --- 8 files changed, 755 insertions(+), 37 deletions(-) create mode 100644 src/bits/CorrectedBinaryMessage.java create mode 100644 src/edac/trellis/DMRNode.java create mode 100644 src/edac/trellis/Node.java create mode 100644 src/edac/trellis/Path.java create mode 100644 src/edac/trellis/ViterbiDecoder.java create mode 100644 src/edac/trellis/ViterbiDecoder_3_4_DMR.java diff --git a/src/bits/BinaryMessage.java b/src/bits/BinaryMessage.java index 31516602..c3caba05 100644 --- a/src/bits/BinaryMessage.java +++ b/src/bits/BinaryMessage.java @@ -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 diff --git a/src/bits/CorrectedBinaryMessage.java b/src/bits/CorrectedBinaryMessage.java new file mode 100644 index 00000000..597830d3 --- /dev/null +++ b/src/bits/CorrectedBinaryMessage.java @@ -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 + * + ******************************************************************************/ +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; + } +} diff --git a/src/edac/trellis/DMRNode.java b/src/edac/trellis/DMRNode.java new file mode 100644 index 00000000..4124bd83 --- /dev/null +++ b/src/edac/trellis/DMRNode.java @@ -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 + * + ******************************************************************************/ +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()]; + } +} diff --git a/src/edac/trellis/Node.java b/src/edac/trellis/Node.java new file mode 100644 index 00000000..3e54ebf0 --- /dev/null +++ b/src/edac/trellis/Node.java @@ -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 + * + ******************************************************************************/ +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); +} diff --git a/src/edac/trellis/Path.java b/src/edac/trellis/Path.java new file mode 100644 index 00000000..667bc7eb --- /dev/null +++ b/src/edac/trellis/Path.java @@ -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 + * + ******************************************************************************/ +package edac.trellis; + +import java.util.ArrayList; +import java.util.List; + +public class Path +{ + private List 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 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); + } +} diff --git a/src/edac/trellis/ViterbiDecoder.java b/src/edac/trellis/ViterbiDecoder.java new file mode 100644 index 00000000..6341f1a6 --- /dev/null +++ b/src/edac/trellis/ViterbiDecoder.java @@ -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 + * + ******************************************************************************/ +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 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 add(Collection survivingPaths, int transmittedOutputValue) + { + Map 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 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; + } +} diff --git a/src/edac/trellis/ViterbiDecoder_3_4_DMR.java b/src/edac/trellis/ViterbiDecoder_3_4_DMR.java new file mode 100644 index 00000000..142c7ada --- /dev/null +++ b/src/edac/trellis/ViterbiDecoder_3_4_DMR.java @@ -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 + * + ******************************************************************************/ +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 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); + } +} diff --git a/src/external/radioreference/RadioReferenceService.java b/src/external/radioreference/RadioReferenceService.java index df46e9f0..12eefcea 100644 --- a/src/external/radioreference/RadioReferenceService.java +++ b/src/external/radioreference/RadioReferenceService.java @@ -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!"); - } } From 64ccacf04ae7a35be266d291844c3f770d1e4859 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 22 Sep 2017 05:56:27 -0400 Subject: [PATCH 16/20] Releasing Version 0.3.0 (final). --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index f7d3fd89..46f46e1b 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From 110ccb158c1e468746757121858e1b347e50e0f2 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Fri, 22 Sep 2017 06:16:34 -0400 Subject: [PATCH 17/20] Update README.md Removed Version 0.2.0 user manual link --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a68ca4b..f8a2fa96 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR). * [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted) -* [User's Manual Version 0.2.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual) * [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0) * [Download](https://github.com/DSheirer/sdrtrunk/releases) * [Support](https://groups.google.com/forum/#!forum/sdrtrunk) ![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/images/ApplicationOverview_V0.3.0.png) -**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot \ No newline at end of file +**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot From 41660e1881d9e2656f783cd717e8af694664753e Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 8 Oct 2017 09:00:10 -0400 Subject: [PATCH 18/20] Updates the build number to 0.3.1 for the next patch release Updates the build script for conditional compilation using either Java 8 or Java 9 options. Java 9 deprecated the java.xml.bind package and removed it as a default Java SE package, so we have to reference it via the --add-modules argument. Updates the linux/mac launch script to check the java runtime version and use either a Java 8 or Java 9 launch command. --- build/build.xml | 232 +++++++++++++++++++--------------- scripts/run_sdrtrunk_linux.sh | 16 ++- 2 files changed, 142 insertions(+), 106 deletions(-) diff --git a/build/build.xml b/build/build.xml index 46f46e1b..6e830507 100644 --- a/build/build.xml +++ b/build/build.xml @@ -1,120 +1,142 @@ - - Ant build script for SDRTrunk Decoder Application - + + Ant build script for SDRTrunk Decoder Application + - - - - - - - - - - - - - - + + + + + + - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - - - - - - - - - + + + + + + + - - - - + + + + + + + \ No newline at end of file diff --git a/scripts/run_sdrtrunk_linux.sh b/scripts/run_sdrtrunk_linux.sh index c95741eb..b0d2ff65 100755 --- a/scripts/run_sdrtrunk_linux.sh +++ b/scripts/run_sdrtrunk_linux.sh @@ -1,2 +1,16 @@ #!/bin/bash -java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk \ No newline at end of file + +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 From d608834214b70af499da2a775d55063a3b12c51f Mon Sep 17 00:00:00 2001 From: Denny Date: Mon, 9 Oct 2017 16:33:58 +0000 Subject: [PATCH 19/20] Resolves #272. Updates the windows launch script to perform conditional launching based on the JRE version 8 or 9. --- scripts/run_sdrtrunk_windows.bat | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/run_sdrtrunk_windows.bat b/scripts/run_sdrtrunk_windows.bat index 1206e039..1c38bd44 100644 --- a/scripts/run_sdrtrunk_windows.bat +++ b/scripts/run_sdrtrunk_windows.bat @@ -1 +1,25 @@ -java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk \ No newline at end of file +@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 From 455aa9728612299ed54b07109faa4f21b86ec9d9 Mon Sep 17 00:00:00 2001 From: Denny Date: Mon, 9 Oct 2017 12:35:28 -0400 Subject: [PATCH 20/20] Updates build number to 'final' --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 6e830507..be632b2b 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - +