#1358 Resolves issue where decoded AMBE (DMR & P25-2) tone identifiers are not sent to the alias action manager. (#1359)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-12-17 07:35:16 -05:00
committed by GitHub
parent bc462b9652
commit 24f4c22cdd
4 changed files with 185 additions and 38 deletions

View File

@@ -22,14 +22,16 @@ import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.audio.codec.mbe.AmbeAudioModule;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.tone.AmbeTone;
import io.github.dsheirer.identifier.tone.Tone;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifierMessage;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageProvider;
import io.github.dsheirer.module.decode.dmr.identifier.DMRToneIdentifier;
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
@@ -40,19 +42,19 @@ 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.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import jmbe.iface.IAudioWithMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* DMR Audio Module for converting transmitted AMBE audio frames to 8 kHz PCM audio
*/
public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateProvider
public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateProvider, IMessageProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DMRAudioModule.class);
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
@@ -61,6 +63,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
private List<byte[]> mQueuedAmbeFrames = new ArrayList<>();
private boolean mEncryptedCallStateEstablished = false;
private boolean mEncryptedCall = false;
private Listener<IMessage> mMessageListener;
/**
* Constructs an instance
@@ -111,7 +114,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
List<byte[]> frames = vm.getAMBEFrames();
for(byte[] frame: frames)
{
processAudio(frame);
processAudio(frame, message.getTimestamp());
}
}
//Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames
@@ -127,7 +130,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
List<byte[]> frames = voiceMessage.getAMBEFrames();
for(byte[] frame: frames)
{
processAudio(frame);
processAudio(frame, message.getTimestamp());
}
}
else if(message instanceof VoiceMessage voiceMessage)
@@ -135,7 +138,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
List<byte[]> frames = voiceMessage.getAMBEFrames();
for(byte[] frame: frames)
{
processAudio(frame);
processAudio(frame, message.getTimestamp());
}
}
else if(!mEncryptedCallStateEstablished && message instanceof VoiceHeader voiceHeader)
@@ -171,7 +174,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
* Processes the audio frame. Queues the frame until encryption state is determined. Once determined, the audio
* frames are dequeued and audio is generated.
*/
private void processAudio(byte[] frame)
private void processAudio(byte[] frame, long timestamp)
{
if(mEncryptedCallStateEstablished)
{
@@ -182,7 +185,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
{
for(byte[] queuedFrame: mQueuedAmbeFrames)
{
produceAudio(queuedFrame);
produceAudio(queuedFrame, timestamp);
}
mQueuedAmbeFrames.clear();
@@ -191,7 +194,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
mQueuedAmbeFrames.clear();
}
produceAudio(frame);
produceAudio(frame, timestamp);
}
else
{
@@ -199,13 +202,13 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
}
}
private void produceAudio(byte[] frame)
private void produceAudio(byte[] frame, long timestamp)
{
try
{
IAudioWithMetadata audioWithMetadata = getAudioCodec().getAudioWithMetadata(frame);
addAudio(audioWithMetadata.getAudio());
processMetadata(audioWithMetadata);
processMetadata(audioWithMetadata, timestamp);
}
catch(Exception e)
{
@@ -217,7 +220,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
* Processes optional metadata that can be included with decoded audio (ie dtmf, tones, knox, etc.) so that the
* tone metadata can be converted into a FROM identifier and included with any call segment.
*/
private void processMetadata(IAudioWithMetadata audioWithMetadata)
private void processMetadata(IAudioWithMetadata audioWithMetadata, long timestamp)
{
if(audioWithMetadata.hasMetadata())
{
@@ -225,11 +228,11 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
for(Map.Entry<String,String> entry: audioWithMetadata.getMetadata().entrySet())
{
//Each metadata map entry contains a tone-type (key) and tone (value)
Identifier metadataIdentifier = mToneMetadataProcessor.process(entry.getKey(), entry.getValue());
ToneIdentifier metadataIdentifier = mToneMetadataProcessor.process(entry.getKey(), entry.getValue());
if(metadataIdentifier != null)
{
broadcast(metadataIdentifier);
broadcast(metadataIdentifier, timestamp);
}
}
}
@@ -242,13 +245,25 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
/**
* Broadcasts the identifier to a registered listener
*/
private void broadcast(Identifier identifier)
private void broadcast(ToneIdentifier identifier, long timestamp)
{
if(mIdentifierUpdateNotificationListener != null)
{
mIdentifierUpdateNotificationListener.receive(new IdentifierUpdateNotification(identifier,
IdentifierUpdateNotification.Operation.ADD, getTimeslot()));
}
if(mMessageListener != null)
{
StringBuilder sb = new StringBuilder();
sb.append("DMR Timeslot ");
sb.append(getTimeslot());
sb.append("Audio Tone Sequence Decoded: ");
sb.append(identifier.toString());
mMessageListener.receive(new ToneIdentifierMessage(Protocol.DMR, getTimeslot(), timestamp,
identifier, sb.toString()));
}
}
/**
@@ -269,6 +284,25 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
mIdentifierUpdateNotificationListener = null;
}
/**
* Registers the listener to receive tone identifier messages from this module.
* @param listener to receive messages
*/
@Override
public void setMessageListener(Listener<IMessage> listener)
{
mMessageListener = listener;
}
/**
* Unregisters the message listener
*/
@Override
public void removeMessageListener()
{
mMessageListener = null;
}
/**
* Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
* flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
@@ -310,7 +344,7 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
* @param value of tone
* @return an identifier with the accumulated tone metadata set
*/
public Identifier process(String type, String value)
public ToneIdentifier process(String type, String value)
{
if(type == null || value == null)
{

View File

@@ -24,31 +24,33 @@ import io.github.dsheirer.audio.codec.mbe.AmbeAudioModule;
import io.github.dsheirer.audio.squelch.SquelchState;
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.tone.AmbeTone;
import io.github.dsheirer.identifier.tone.P25ToneIdentifier;
import io.github.dsheirer.identifier.tone.Tone;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifierMessage;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageProvider;
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk;
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
import jmbe.iface.IAudioWithMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import jmbe.iface.IAudioWithMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdateProvider
public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdateProvider, IMessageProvider
{
private final static Logger mLog = LoggerFactory.getLogger(P25P2AudioModule.class);
@@ -58,6 +60,7 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
private Queue<AbstractVoiceTimeslot> mQueuedAudioTimeslots = new ArrayDeque<>();
private boolean mEncryptedCallStateEstablished = false;
private boolean mEncryptedCall = false;
private Listener<IMessage> mMessageListener;
public P25P2AudioModule(UserPreferences userPreferences, int timeslot, AliasList aliasList)
{
@@ -116,7 +119,7 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
{
if(!mEncryptedCall)
{
processAudio(abstractVoiceTimeslot.getVoiceFrames());
processAudio(abstractVoiceTimeslot.getVoiceFrames(), message.getTimestamp());
}
}
else
@@ -156,7 +159,12 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
}
}
private void processAudio(List<BinaryMessage> voiceFrames)
/**
* Process the audio voice frames
* @param voiceFrames to process
* @param timestamp of the carrier message
*/
private void processAudio(List<BinaryMessage> voiceFrames, long timestamp)
{
if(hasAudioCodec())
{
@@ -168,7 +176,7 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
{
IAudioWithMetadata audioWithMetadata = getAudioCodec().getAudioWithMetadata(voiceFrameBytes);
addAudio(audioWithMetadata.getAudio());
processMetadata(audioWithMetadata);
processMetadata(audioWithMetadata, timestamp);
}
catch(Exception e)
{
@@ -182,7 +190,7 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
* Processes optional metadata that can be included with decoded audio (ie dtmf, tones, knox, etc.) so that the
* tone metadata can be converted into a FROM identifier and included with any call segment.
*/
private void processMetadata(IAudioWithMetadata audioWithMetadata)
private void processMetadata(IAudioWithMetadata audioWithMetadata, long timestamp)
{
if(audioWithMetadata.hasMetadata())
{
@@ -190,11 +198,11 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
for(Map.Entry<String,String> entry: audioWithMetadata.getMetadata().entrySet())
{
//Each metadata map entry contains a tone-type (key) and tone (value)
Identifier metadataIdentifier = mToneMetadataProcessor.process(entry.getKey(), entry.getValue());
ToneIdentifier toneIdentifier = mToneMetadataProcessor.process(entry.getKey(), entry.getValue());
if(metadataIdentifier != null)
if(toneIdentifier != null)
{
broadcast(metadataIdentifier);
broadcast(toneIdentifier, timestamp);
}
}
}
@@ -205,15 +213,28 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
}
/**
* Broadcasts the identifier to a registered listener
* Broadcasts the identifier to a registered listener and creates a new AMBE tone identifier message when tones are
* present to send to the alias action manager
*/
private void broadcast(Identifier identifier)
private void broadcast(ToneIdentifier identifier, long timestamp)
{
if(mIdentifierUpdateNotificationListener != null)
{
mIdentifierUpdateNotificationListener.receive(new IdentifierUpdateNotification(identifier,
IdentifierUpdateNotification.Operation.ADD, getTimeslot()));
}
if(mMessageListener != null)
{
StringBuilder sb = new StringBuilder();
sb.append("P25.2 Timeslot ");
sb.append(getTimeslot());
sb.append("Audio Tone Sequence Decoded: ");
sb.append(identifier.toString());
mMessageListener.receive(new ToneIdentifierMessage(Protocol.APCO25_PHASE2, getTimeslot(), timestamp,
identifier, sb.toString()));
}
}
/**
@@ -234,6 +255,25 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
mIdentifierUpdateNotificationListener = null;
}
/**
* Registers a message listener to receive AMBE tone identifier messages.
* @param listener to register
*/
@Override
public void setMessageListener(Listener<IMessage> listener)
{
mMessageListener = listener;
}
/**
* Removes the message listener
*/
@Override
public void removeMessageListener()
{
mMessageListener = null;
}
/**
* Process AMBE audio frame tone metadata. Tracks the count of sequential frames containing tone metadata to
* provide a list of each unique tone and a time duration (milliseconds) for the tone. Tones are concatenated into
@@ -258,7 +298,7 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
* @param value of tone
* @return an identifier with the accumulated tone metadata set
*/
public Identifier process(String type, String value)
public ToneIdentifier process(String type, String value)
{
if(type == null || value == null)
{

View File

@@ -52,9 +52,6 @@ import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.record.AudioRecordingManager;
import io.github.dsheirer.record.binary.BinaryReader;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
@@ -63,6 +60,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* P25 Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors