mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#1014 Adds AMBE voice frame recording support.
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package 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
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package 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;
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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<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())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user