mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#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:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user