diff --git a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequence.java b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequence.java index 15776dd5..05b71c6a 100644 --- a/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequence.java +++ b/src/main/java/io/github/dsheirer/audio/codec/mbe/MBECallSequence.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2021 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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 io.github.dsheirer.audio.codec.mbe; @@ -27,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonRootName; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.encryption.EncryptionKey; import io.github.dsheirer.module.decode.p25.audio.VoiceFrame; @@ -132,6 +130,18 @@ public class MBECallSequence } } + /** + * Sets the from radio identifier + * @param identifier for from entity + */ + public void setFromIdentifier(Identifier identifier) + { + if(identifier != null) + { + setFromIdentifier(identifier.toString()); + } + } + /** * Radio identifier that originated the call * @@ -156,6 +166,18 @@ public class MBECallSequence } } + /** + * Sets the TO identifier + * @param identifier for the TO entity + */ + public void setToIdentifier(Identifier identifier) + { + if(identifier != null) + { + setToIdentifier(identifier.toString()); + } + } + /** * Radio identifier that received the call * diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java index aca18a9c..770090f3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2021 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 @@ -43,7 +43,7 @@ import io.github.dsheirer.module.decode.dmr.DMRDecoder; import io.github.dsheirer.module.decode.dmr.DMRDecoderState; import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager; import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR; -import io.github.dsheirer.module.decode.dmr.message.voice.DMRAudioModule; +import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule; import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder; import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState; import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/DMRAudioModule.java b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java similarity index 91% rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/DMRAudioModule.java rename to src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java index dddebb0a..1007554d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/DMRAudioModule.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRAudioModule.java @@ -1,25 +1,22 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2021 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 Dennis Sheirer, Zhenyu Mao - * * - * * 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 - * * ***************************************************************************** + * 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 io.github.dsheirer.module.decode.dmr.message.voice; +package io.github.dsheirer.module.decode.dmr.audio; import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.audio.codec.mbe.AmbeAudioModule; @@ -40,6 +37,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceCh import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions; +import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage; +import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; import jmbe.iface.IAudioWithMetadata; 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 new file mode 100644 index 00000000..8b81955d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java @@ -0,0 +1,214 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2021 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 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.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.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; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio + * frames, optional encryption and call identifiers. + */ +public class DMRCallSequenceRecorder extends MBECallSequenceRecorder +{ + private final static Logger mLog = LoggerFactory.getLogger(DMRCallSequenceRecorder.class); + + private static final String PROTOCOL = "DMR"; + + private MBECallSequence mCallSequence; + + /** + * Constructs a DMR MBE call sequence recorder. + * + * @param userPreferences to obtain the recording directory + * @param channelFrequency for the channel to record + * @param system defined by the user + * @param site defined by the user + */ + public DMRCallSequenceRecorder(UserPreferences userPreferences, long channelFrequency, String system, String site) + { + super(userPreferences, channelFrequency, system, site); + } + + /** + * Stops and flushes any partial frame sequence from the processors + */ + @Override + public void stop() + { + flush(); + } + + /** + * Primary message interface for receiving frames and metadata messages to record + */ + @Override + public void receive(IMessage message) + { + if(message instanceof DMRMessage dmr) + { + if(dmr.isValid()) + { + process(dmr); + } + } + } + + + /** + * Flushes any partial call sequence + */ + public void flush() + { + if(mCallSequence != null) + { + writeCallSequence(mCallSequence); + mCallSequence = null; + } + } + + /** + * Processes any DMR audio and terminator messages + */ + public void process(DMRMessage message) + { + if(message instanceof VoiceMessage voiceMessage) + { + 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()) + { + 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; + } + } + } +}