Skip to content

Commit

Permalink
#1014 Adds AMBE voice frame recording support.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis Sheirer committed Nov 21, 2021
1 parent 6a54fe1 commit 31b9ee5
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -27,6 +24,7 @@
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;

Expand Down Expand Up @@ -132,6 +130,18 @@ public void setFromIdentifier(String from)
}
}

/**
* 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
*
Expand All @@ -156,6 +166,18 @@ public void setToIdentifier(String to)
}
}

/**
* 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
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -43,7 +43,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -40,6 +37,8 @@
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}

0 comments on commit 31b9ee5

Please sign in to comment.