#1110 Updates DMR MBE frame recorder to correctly handle calls on separate timeslots and to fully capture identifiers associated with calls. (#1120)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2021-11-28 07:12:40 -05:00
committed by GitHub
parent f0f0329f9b
commit 5d9d232585
2 changed files with 258 additions and 138 deletions

View File

@@ -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<Dibit>, 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<Dibit>, 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<Dibit>, 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<IMessage> 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<Dibit>, 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<Dibit>, 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<Dibit>, 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<Dibit>, IDMRBurstDetectListene
ioe.printStackTrace();
}
System.out.println("Logging LRRP Packets ...");
lrrpProcessor.log();
}
else
@@ -502,7 +511,7 @@ public class DMRMessageFramer implements Listener<Dibit>, 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<Dibit>, 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;
}
}
}

View File

@@ -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<DMRMessage>
{
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<byte[]> 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<byte[]> 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;
}
}
}
}