diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java index d3a1690d..0f9f6b65 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java @@ -24,6 +24,7 @@ import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.dsp.symbol.ISyncDetectListener; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.SyncLossMessage; +import io.github.dsheirer.module.decode.dmr.audio.DMRCallSequenceRecorder; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.DMRBurst; import io.github.dsheirer.module.decode.dmr.message.DMRMessage; @@ -32,8 +33,10 @@ import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket; import io.github.dsheirer.module.decode.ip.lrrp.token.Identity; import io.github.dsheirer.module.decode.ip.lrrp.token.Token; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.record.binary.BinaryReader; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.ReusableByteBuffer; import org.slf4j.Logger; @@ -266,11 +269,11 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene if(message instanceof DMRBurst && ((DMRBurst)message).hasCACH()) { CACH cach = ((DMRBurst)message).getCACH(); -// mLog.info(cach.toString() + " TS:" + message.getTimeslot() + " " + message.toString()); + mLog.info(cach.toString() + " TS:" + message.getTimeslot() + " " + message.toString()); } else { -// mLog.info(" TS:" + message.getTimeslot() + " " + message.toString()); + mLog.info(" TS:" + message.getTimeslot() + " " + message.toString()); } if(message instanceof DMRPacketMessage dpm) @@ -388,7 +391,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene // String file = path + "20200514_063507_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 2579 // String file = path + "20200514_064224_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 5056035 // String file = path + "20200514_131623_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Grant: 5074193 -// String file = path + "20200514_133947_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; //<<<<<<<------ Basic Encryption + String file = path + "20200514_133947_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; //<<<<<<<------ Basic Encryption // String file = path + "20200514_142249_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; // String file = path + "20200514_144534_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Con+ Control w/GPS Window Announce @@ -426,15 +429,24 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene // String file = path + "20200730_090634_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits"; //Cap+ BP Scrambling - String file = path + "20200829_065610_9600BPS_DMR_Albany_Medical_Center_Albany_LCN_3.bits"; +// String file = path + "20200829_065610_9600BPS_DMR_Albany_Medical_Center_Albany_LCN_3.bits"; // String file = "/home/denny/SDRTrunk/recordings/20200927_054837_9600BPS_DMR_Blair_Communications_(Capacity_Plus)_Dallas_Control_9.bits"; - MessageListener listener = new MessageListener(); DecodeConfigDMR config = new DecodeConfigDMR(); - LrrpProcessor lrrpProcessor = new LrrpProcessor(); - boolean multi = true; + Broadcaster messageBroadcaster = new Broadcaster<>(); + MessageListener messageListener = new MessageListener(); + messageBroadcaster.addListener(messageListener); + + LrrpProcessor lrrpProcessor = new LrrpProcessor(); + messageBroadcaster.addListener(lrrpProcessor); + + DMRCallSequenceRecorder ambeRecorder = new DMRCallSequenceRecorder(new UserPreferences(), 123456789l, + "Denny System", "Denny Site"); + messageBroadcaster.addListener(ambeRecorder); + + boolean multi = false; if(multi) { @@ -451,9 +463,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene DMRMessageFramer messageFramer = new DMRMessageFramer(null); DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config); messageFramer.setListener(messageProcessor); -// messageProcessor.setMessageListener(listener); - messageProcessor.setMessageListener(lrrpProcessor); - + messageProcessor.setMessageListener(messageBroadcaster); try(BinaryReader reader = new BinaryReader(path, 200)) { @@ -468,7 +478,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene ioe.printStackTrace(); } - if(!listener.hasData()) + if(!messageListener.hasData()) { // mLog.info("Has Data: " + listener.hasData() + " File:" + path.toString()); // try @@ -481,11 +491,11 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene // } } - listener.reset(); + messageListener.reset(); - System.out.println("TS0 VOICE:" + listener.mTS0Count + " TS1 VOICE:" + listener.mTS1Count); - listener.mTS0Count = 0; - listener.mTS1Count = 0; + System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count); + messageListener.mTS0Count = 0; + messageListener.mTS1Count = 0; } }); } @@ -494,7 +504,6 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene ioe.printStackTrace(); } - System.out.println("Logging LRRP Packets ..."); lrrpProcessor.log(); } else @@ -502,7 +511,7 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene DMRMessageFramer messageFramer = new DMRMessageFramer(null); DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config); messageFramer.setListener(messageProcessor); - messageProcessor.setMessageListener(listener); + messageProcessor.setMessageListener(messageBroadcaster); try(BinaryReader reader = new BinaryReader(Path.of(file), 200)) { @@ -517,9 +526,9 @@ public class DMRMessageFramer implements Listener, IDMRBurstDetectListene ioe.printStackTrace(); } - System.out.println("TS0 VOICE:" + listener.mTS0Count + " TS1 VOICE:" + listener.mTS1Count); - listener.mTS0Count = 0; - listener.mTS1Count = 0; + System.out.println("TS0 VOICE:" + messageListener.mTS0Count + " TS1 VOICE:" + messageListener.mTS1Count); + messageListener.mTS0Count = 0; + messageListener.mTS1Count = 0; } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java index 8b81955d..676001d2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java @@ -23,9 +23,10 @@ package io.github.dsheirer.module.decode.dmr.audio; import io.github.dsheirer.audio.codec.mbe.MBECallSequence; import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.dmr.message.DMRMessage; -import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser; @@ -36,6 +37,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.Capaci import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,10 +50,9 @@ import java.util.List; public class DMRCallSequenceRecorder extends MBECallSequenceRecorder { private final static Logger mLog = LoggerFactory.getLogger(DMRCallSequenceRecorder.class); - private static final String PROTOCOL = "DMR"; - - private MBECallSequence mCallSequence; + private TimeslotProcessor mTimeslotProcessor1 = new TimeslotProcessor(); + private TimeslotProcessor mTimeslotProcessor2 = new TimeslotProcessor(); /** * Constructs a DMR MBE call sequence recorder. @@ -72,7 +73,8 @@ public class DMRCallSequenceRecorder extends MBECallSequenceRecorder @Override public void stop() { - flush(); + mTimeslotProcessor1.flush(); + mTimeslotProcessor2.flush(); } /** @@ -85,129 +87,238 @@ public class DMRCallSequenceRecorder extends MBECallSequenceRecorder { if(dmr.isValid()) { - process(dmr); + switch(dmr.getTimeslot()) + { + case 1: + mTimeslotProcessor1.receive(dmr); + break; + case 2: + mTimeslotProcessor2.receive(dmr); + break; + } } } } - /** - * Flushes any partial call sequence + * Processes DMR messages for a specific timeslot */ - public void flush() + private class TimeslotProcessor implements Listener { - if(mCallSequence != null) - { - writeCallSequence(mCallSequence); - mCallSequence = null; - } - } + private MBECallSequence mCallSequence; + private Identifier mFromIdentifier; + private Identifier mToIdentifier; + private String mCallType; + private boolean mEncrypted = false; - /** - * Processes any DMR audio and terminator messages - */ - public void process(DMRMessage message) - { - if(message instanceof VoiceMessage voiceMessage) + /** + * Flushes any partial call sequence + */ + public void flush() { - process(voiceMessage); - } - else if(message instanceof FullLCMessage fullLCMessage) - { - process(fullLCMessage); - } - else if(message instanceof Terminator) - { - flush(); - } - } - - /** - * Processes Voice messages - */ - private void process(VoiceMessage voiceMessage) - { - if(mCallSequence == null) - { - mCallSequence = new MBECallSequence(PROTOCOL); - } - - List voiceFrames = voiceMessage.getAMBEFrames(); - - long baseTimestamp = voiceMessage.getTimestamp(); - - for(byte[] frame : voiceFrames) - { - BinaryMessage frameBits = BinaryMessage.from(frame); - mCallSequence.addVoiceFrame(baseTimestamp, frameBits.toHexString()); - - //Voice frames are 20 milliseconds each, so we increment the timestamp by 20 for each one - baseTimestamp += 20; - } - } - - /** - * Process full link control messages to extract call details - */ - private void process(FullLCMessage message) - { - if(message.isValid() && mCallSequence != null) - { - switch(message.getOpcode()) + if(mCallSequence != null) { - case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER: - if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu) - { - mCallSequence.setFromIdentifier(cpvcu.getRadio()); - mCallSequence.setToIdentifier(cpvcu.getTalkgroup()); - mCallSequence.setEncrypted(cpvcu.getServiceOptions().isEncrypted()); - mCallSequence.setCallType(CALL_TYPE_GROUP); - } - break; - case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER: - if(message instanceof CapacityPlusWideAreaVoiceChannelUser cpwavcu) - { - mCallSequence.setToIdentifier(cpwavcu.getTalkgroup()); - mCallSequence.setEncrypted(cpwavcu.getServiceOptions().isEncrypted()); - mCallSequence.setCallType(CALL_TYPE_GROUP); - } - break; - case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER: - if(message instanceof HyteraGroupVoiceChannelUser hgvcu) - { - mCallSequence.setToIdentifier(hgvcu.getTalkgroup()); - mCallSequence.setFromIdentifier(hgvcu.getSourceRadio()); - mCallSequence.setCallType(CALL_TYPE_GROUP); - mCallSequence.setEncrypted(hgvcu.isEncrypted()); - } - break; - case FULL_HYTERA_UNIT_TO_UNIT_VOICE_CHANNEL_USER: - if(message instanceof HyteraUnitToUnitVoiceChannelUser huuvcu) - { - mCallSequence.setToIdentifier(huuvcu.getTargetRadio()); - mCallSequence.setFromIdentifier(huuvcu.getSourceRadio()); - mCallSequence.setCallType(CALL_TYPE_INDIVIDUAL); - mCallSequence.setEncrypted(huuvcu.isEncrypted()); - } - break; - case FULL_STANDARD_GROUP_VOICE_CHANNEL_USER: - if(message instanceof GroupVoiceChannelUser gvcu) - { - mCallSequence.setFromIdentifier(gvcu.getRadio()); - mCallSequence.setToIdentifier(gvcu.getTalkgroup()); - mCallSequence.setCallType(CALL_TYPE_GROUP); - mCallSequence.setEncrypted(gvcu.getServiceOptions().isEncrypted()); - } - break; - case FULL_STANDARD_UNIT_TO_UNIT_VOICE_CHANNEL_USER: - if(message instanceof UnitToUnitVoiceChannelUser uuvcu) - { - mCallSequence.setFromIdentifier(uuvcu.getSourceRadio()); - mCallSequence.setToIdentifier(uuvcu.getTargetRadio()); - mCallSequence.setCallType(CALL_TYPE_INDIVIDUAL); - mCallSequence.setEncrypted(uuvcu.getServiceOptions().isEncrypted()); - } - break; + writeCallSequence(mCallSequence); + } + + mCallSequence = null; + mToIdentifier = null; + mFromIdentifier = null; + mCallType = null; + mEncrypted = false; + } + + /** + * Processes any DMR audio and terminator messages + */ + @Override + public void receive(DMRMessage message) + { + if(message instanceof VoiceMessage voiceMessage) + { + process(voiceMessage); + } + else if(message instanceof VoiceHeader voiceHeader) + { + process(voiceHeader); + } + else if(message instanceof FullLCMessage fullLCMessage) + { + process(fullLCMessage); + } + else if(message instanceof Terminator terminator) + { + //One last attempt to get the to/from identifiers from the terminator's link control message + if(mCallSequence != null && terminator.getLCMessage() instanceof FullLCMessage flc) + { + process(flc); + } + + flush(); + } + } + + /** + * Process voice header message + */ + private void process(VoiceHeader voiceHeader) + { + if(voiceHeader.getLCMessage() instanceof FullLCMessage flc) + { + process(flc); + } + } + + /** + * Processes Voice messages + */ + private void process(VoiceMessage voiceMessage) + { + if(mCallSequence == null) + { + mCallSequence = new MBECallSequence(PROTOCOL); + mCallSequence.setFromIdentifier(mFromIdentifier); + mCallSequence.setToIdentifier(mToIdentifier); + mCallSequence.setCallType(mCallType); + mCallSequence.setEncrypted(mEncrypted); + } + + List voiceFrames = voiceMessage.getAMBEFrames(); + + long baseTimestamp = voiceMessage.getTimestamp(); + + for(byte[] frame : voiceFrames) + { + BinaryMessage frameBits = BinaryMessage.from(frame); + mCallSequence.addVoiceFrame(baseTimestamp, frameBits.toHexString()); + + //Voice frames are 20 milliseconds each, so we increment the timestamp by 20 for each one + baseTimestamp += 20; + } + } + + /** + * Process full link control messages to extract call details + */ + private void process(FullLCMessage message) + { + if(message.isValid()) + { + switch(message.getOpcode()) + { + case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER: + if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu) + { + if(mCallSequence == null) + { + mFromIdentifier = cpvcu.getRadio(); + mToIdentifier = cpvcu.getTalkgroup(); + mEncrypted = cpvcu.getServiceOptions().isEncrypted(); + mCallType = CALL_TYPE_GROUP; + } + else + { + mCallSequence.setFromIdentifier(cpvcu.getRadio()); + mCallSequence.setToIdentifier(cpvcu.getTalkgroup()); + mCallSequence.setEncrypted(cpvcu.getServiceOptions().isEncrypted()); + mCallSequence.setCallType(CALL_TYPE_GROUP); + } + } + break; + case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER: + if(message instanceof CapacityPlusWideAreaVoiceChannelUser cpwavcu) + { + if(mCallSequence == null) + { + mToIdentifier = cpwavcu.getTalkgroup(); + mEncrypted = cpwavcu.getServiceOptions().isEncrypted(); + mCallType = CALL_TYPE_GROUP; + } + else + { + mCallSequence.setToIdentifier(cpwavcu.getTalkgroup()); + mCallSequence.setEncrypted(cpwavcu.getServiceOptions().isEncrypted()); + mCallSequence.setCallType(CALL_TYPE_GROUP); + } + } + break; + case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER: + if(message instanceof HyteraGroupVoiceChannelUser hgvcu) + { + if(mCallSequence == null) + { + mToIdentifier = hgvcu.getTalkgroup(); + mFromIdentifier = hgvcu.getSourceRadio(); + mCallType = CALL_TYPE_GROUP; + mEncrypted = hgvcu.isEncrypted(); + } + else + { + mCallSequence.setToIdentifier(hgvcu.getTalkgroup()); + mCallSequence.setFromIdentifier(hgvcu.getSourceRadio()); + mCallSequence.setCallType(CALL_TYPE_GROUP); + mCallSequence.setEncrypted(hgvcu.isEncrypted()); + } + } + break; + case FULL_HYTERA_UNIT_TO_UNIT_VOICE_CHANNEL_USER: + if(message instanceof HyteraUnitToUnitVoiceChannelUser huuvcu) + { + if(mCallSequence == null) + { + mToIdentifier = huuvcu.getTargetRadio(); + mFromIdentifier = huuvcu.getSourceRadio(); + mCallType = CALL_TYPE_INDIVIDUAL; + mEncrypted = huuvcu.isEncrypted(); + } + else + { + mCallSequence.setToIdentifier(huuvcu.getTargetRadio()); + mCallSequence.setFromIdentifier(huuvcu.getSourceRadio()); + mCallSequence.setCallType(CALL_TYPE_INDIVIDUAL); + mCallSequence.setEncrypted(huuvcu.isEncrypted()); + } + } + break; + case FULL_STANDARD_GROUP_VOICE_CHANNEL_USER: + if(message instanceof GroupVoiceChannelUser gvcu) + { + if(mCallSequence == null) + { + mFromIdentifier = gvcu.getRadio(); + mToIdentifier = gvcu.getTalkgroup(); + mCallType = CALL_TYPE_GROUP; + mEncrypted = gvcu.getServiceOptions().isEncrypted(); + } + else + { + mCallSequence.setFromIdentifier(gvcu.getRadio()); + mCallSequence.setToIdentifier(gvcu.getTalkgroup()); + mCallSequence.setCallType(CALL_TYPE_GROUP); + mCallSequence.setEncrypted(gvcu.getServiceOptions().isEncrypted()); + } + } + break; + case FULL_STANDARD_UNIT_TO_UNIT_VOICE_CHANNEL_USER: + if(message instanceof UnitToUnitVoiceChannelUser uuvcu) + { + if(mCallSequence == null) + { + mFromIdentifier = uuvcu.getSourceRadio(); + mToIdentifier = uuvcu.getTargetRadio(); + mCallType = CALL_TYPE_INDIVIDUAL; + mEncrypted = uuvcu.getServiceOptions().isEncrypted(); + } + else + { + mCallSequence.setFromIdentifier(uuvcu.getSourceRadio()); + mCallSequence.setToIdentifier(uuvcu.getTargetRadio()); + mCallSequence.setCallType(CALL_TYPE_INDIVIDUAL); + mCallSequence.setEncrypted(uuvcu.getServiceOptions().isEncrypted()); + } + } + break; + } } } }