#2482 JMBE Library version 2.0 (#2483)

Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
Denny Sheirer
2026-10-09 10:51:29 -04:00
committed by GitHub
parent a9120ed65e
commit c05328cd1e
8 changed files with 230 additions and 56 deletions

View File

@@ -52,11 +52,6 @@ plugins {
id 'org.beryx.runtime' version '2.0.1'
}
repositories {
mavenCentral()
maven { url = "https://jitpack.io" }
}
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
version = project.findProperty('projectVersion')
@@ -83,11 +78,11 @@ dependencies {
//Jitpack imports
implementation 'com.github.dnault:libresample4j:master'
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
implementation 'com.github.DSheirer.jmbe:api:v2.0.0'
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
//mavenCentral/jcenter imports
implementation 'ch.qos.logback:logback-classic:1.5.38'
implementation 'ch.qos.logback:logback-classic:1.6.3'
implementation 'ch.qos.logback:logback-core:1.5.38'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.22.1'
implementation 'com.fazecast:jSerialComm:2.11.4'

View File

@@ -2,3 +2,11 @@ plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
rootProject.name = 'sdr-trunk'
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

View File

@@ -35,9 +35,11 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jmbe.iface.IAudioCodec;
import jmbe.iface.IAudioCodecLibrary;
import jmbe.iface.IAudioCodecV2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,7 +49,7 @@ import org.slf4j.LoggerFactory;
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
ISquelchStateListener
{
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
private static final Logger LOG = LoggerFactory.getLogger(JmbeAudioModule.class);
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
private static final List<String> mLibraryLoadStatusLogged = new ArrayList<>();
private IAudioCodec mAudioCodec;
@@ -62,6 +64,37 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
loadConverter();
}
/**
* List of features supported by the JMBE audio library. Note: features are only supported in Version 2.0+
* @return list of features or an empty list if the current library version doesn't support features.
*/
public List<String> getFeatures()
{
if(mAudioCodec instanceof IAudioCodecV2 v2)
{
return v2.getFeatures();
}
return Collections.emptyList();
}
/**
* Sets the JMBE feature
* @param name of the feature
* @param value for the feature
*/
public void setFeature(String name, Object value)
{
if(mAudioCodec instanceof IAudioCodecV2 v2)
{
v2.setFeature(name, value);
}
else
{
LOG.info("Feature [" + name + "] is not supported by the current JMBE audio codec library version");
}
}
@Override
protected void closeAudioSegment()
{
@@ -135,7 +168,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.info("Loading JMBE library from [" + path + "]");
LOG.info("Loading JMBE library from [" + path + "]");
}
try
@@ -149,7 +182,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
{
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
LOG.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
}
}
@@ -157,7 +190,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
LOG.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -165,7 +198,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - class not found");
LOG.error("Couldn't load JMBE audio conversion library - class not found");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -187,7 +220,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.info("JMBE audio conversion library loaded: " + library.getVersion());
LOG.info("JMBE audio conversion library loaded: " + library.getVersion());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -195,7 +228,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.warn("JMBE library version 1.0.0 or higher is required - found: " + library.getVersion());
LOG.warn("JMBE library version 1.0.0 or higher is required - found: " + library.getVersion());
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -204,7 +237,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.info("JMBE audio conversion library NOT FOUND");
LOG.info("JMBE audio conversion library NOT FOUND");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -213,7 +246,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - invocation target exception", ite);
LOG.error("Couldn't load JMBE audio conversion library - invocation target exception", ite);
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -221,7 +254,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - instantiation exception", e1);
LOG.error("Couldn't load JMBE audio conversion library - instantiation exception", e1);
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -229,7 +262,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - security restrictions");
LOG.error("Couldn't load JMBE audio conversion library - security restrictions");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -237,7 +270,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
LOG.error("Couldn't load JMBE audio conversion library - no such method exception");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}
@@ -246,7 +279,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
{
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
{
mLog.warn("JMBE audio library path is NOT SET in your User Preferences.");
LOG.warn("JMBE audio library path is NOT SET in your User Preferences.");
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
}
}

View File

@@ -22,6 +22,7 @@ import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.alias.action.AliasActionManager;
import io.github.dsheirer.audio.*;
import io.github.dsheirer.audio.codec.mbe.JmbeAudioModule;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.channel.state.State;
import io.github.dsheirer.controller.channel.Channel;
@@ -40,6 +41,7 @@ import io.github.dsheirer.module.decode.am.AMDecoder;
import io.github.dsheirer.module.decode.am.AMDecoderState;
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfigJMBE;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.nbfm.DeemphasisMode;
import io.github.dsheirer.module.decode.squelch.dcs.DCSDecoder;
@@ -116,6 +118,7 @@ import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.tuner.channel.rotation.ChannelRotationMonitor;
import java.util.ArrayList;
import java.util.List;
import jmbe.iface.IAudioCodecV2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -256,8 +259,16 @@ public class DecoderFactory
decoderState2.setCurrentChannel(channelDescriptor);
modules.add(decoderState1);
modules.add(decoderState2);
modules.add(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_1, aliasList));
modules.add(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_2, aliasList));
if(channel.getDecodeConfiguration() instanceof DecodeConfigP25Phase2 configP25P2)
{
P25P2AudioModule audioModule1 = new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_1, aliasList);
P25P2AudioModule audioModule2 = new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_2, aliasList);
configureJMBEAudioModule(audioModule1, configP25P2);
configureJMBEAudioModule(audioModule2, configP25P2);
modules.add(audioModule1);
modules.add(audioModule2);
}
//Add a channel rotation monitor when we have multiple control channel frequencies specified
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&
@@ -280,9 +291,9 @@ public class DecoderFactory
AliasList aliasList, TrafficChannelManager trafficChannelManager,
IChannelDescriptor channelDescriptor)
{
if(channel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 p1)
if(channel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 configP25P1)
{
switch(p1.getModulation())
switch(configP25P1.getModulation())
{
case C4FM:
modules.add(new P25P1DecoderC4FM());
@@ -291,36 +302,38 @@ public class DecoderFactory
modules.add(new P25P1DecoderLSM());
break;
default:
throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" + p1.getModulation() + "]");
throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" + configP25P1.getModulation() + "]");
}
}
if(channel.getChannelType() == ChannelType.STANDARD)
{
P25TrafficChannelManager primaryTCM = new P25TrafficChannelManager(channel);
modules.add(primaryTCM);
modules.add(new P25P1DecoderState(channel, primaryTCM));
}
else if(trafficChannelManager instanceof P25TrafficChannelManager parentTCM)
{
P25P1DecoderState decoderState = new P25P1DecoderState(channel, parentTCM);
decoderState.setCurrentChannel(channelDescriptor);
modules.add(decoderState);
}
else
{
mLog.warn("Expected non-null traffic channel manager for channel " + channel.getName());
}
if(channel.getChannelType() == ChannelType.STANDARD)
{
P25TrafficChannelManager primaryTCM = new P25TrafficChannelManager(channel);
modules.add(primaryTCM);
modules.add(new P25P1DecoderState(channel, primaryTCM));
}
else if(trafficChannelManager instanceof P25TrafficChannelManager parentTCM)
{
P25P1DecoderState decoderState = new P25P1DecoderState(channel, parentTCM);
decoderState.setCurrentChannel(channelDescriptor);
modules.add(decoderState);
}
else
{
mLog.warn("Expected non-null traffic channel manager for channel " + channel.getName());
}
modules.add(new P25P1AudioModule(userPreferences, aliasList));
P25P1AudioModule audioModule = new P25P1AudioModule(userPreferences, aliasList);
configureJMBEAudioModule(audioModule, configP25P1);
modules.add(audioModule);
//Add a channel rotation monitor when we have multiple control channel frequencies specified
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&
sctmf.hasMultipleFrequencies())
{
List<State> activeStates = new ArrayList<>();
activeStates.add(State.CONTROL);
modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences));
//Add a channel rotation monitor when we have multiple control channel frequencies specified
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&
sctmf.hasMultipleFrequencies())
{
List<State> activeStates = new ArrayList<>();
activeStates.add(State.CONTROL);
modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences));
}
}
}
@@ -520,7 +533,10 @@ public class DecoderFactory
}
modules.add(new NXDNDecoder(configNXDN));
modules.add(new NXDNAudioModule(userPreferences, aliasList));
NXDNAudioModule audioModule = new NXDNAudioModule(userPreferences, aliasList);
configureJMBEAudioModule(audioModule, configNXDN);
modules.add(audioModule);
if(channel.getChannelType() == ChannelType.STANDARD)
{
@@ -546,6 +562,31 @@ public class DecoderFactory
}
}
/**
* Configures the JMBE audio module with library version 2.0+ features.
* @param audioModule with optional supported features
* @param config with JMBE feature values
*/
private static void configureJMBEAudioModule(JmbeAudioModule audioModule, DecodeConfigJMBE config)
{
List<String> features = audioModule.getFeatures();
if(features.contains(IAudioCodecV2.FEATURE_AUTOMATIC_GAIN_CONTROL))
{
audioModule.setFeature(IAudioCodecV2.FEATURE_AUTOMATIC_GAIN_CONTROL, config.isAGC());
}
if(features.contains(IAudioCodecV2.FEATURE_NOISE_GENERATOR_GAIN))
{
audioModule.setFeature(IAudioCodecV2.FEATURE_NOISE_GENERATOR_GAIN, config.getNoiseGain());
}
if(features.contains(IAudioCodecV2.FEATURE_TONE_GENERATOR_GAIN))
{
audioModule.setFeature(IAudioCodecV2.FEATURE_TONE_GENERATOR_GAIN, config.getToneGain());
}
}
/**
* Creates modules for DMR decoder setup.
*
@@ -638,8 +679,13 @@ public class DecoderFactory
modules.add(state1);
modules.add(state2);
modules.add(new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_1));
modules.add(new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_2));
DMRAudioModule audioModule1 = new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_1);
DMRAudioModule audioModule2 = new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_2);
configureJMBEAudioModule(audioModule1, decodeConfig);
configureJMBEAudioModule(audioModule2, decodeConfig);
modules.add(audioModule1);
modules.add(audioModule2);
//Add a channel rotation monitor when we have multiple control channel frequencies specified
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&

View File

@@ -0,0 +1,89 @@
package io.github.dsheirer.module.decode.config;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
public abstract class DecodeConfigJMBE extends DecodeConfiguration
{
private boolean mAGC;
private float mToneGain = 1.0f;
private float mNoiseGain = 1.0f;
/**
* Automatic Gain Control (AGC) for the JMBE audio codec
* @return is AGC enabled.
*/
@JacksonXmlProperty(isAttribute = true, localName = "jmbeAGC", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public boolean isAGC()
{
return mAGC;
}
/**
* Sets the AGC enabled state for the JMBE audio codec
* @param enabled true to enable AGC
*/
public void setAGC(boolean enabled)
{
mAGC = enabled;
}
/**
* Tone generation gain (1.0 default) for the JMBE audio codec
* @return gain in range 0.0 (no tones) to 1.0 (default gain).
*/
@JacksonXmlProperty(isAttribute = true, localName = "jmbeToneGain", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public float getToneGain()
{
return mToneGain;
}
/**
* Sets the tone generation gain for the JMBE audio codec
* @param gain in range 0.0 (no tones) to 1.0 (default gain)
*/
public void setToneGain(float gain)
{
if(gain > 1.0f)
{
mToneGain = 1.0f;
}
else if(gain < 0)
{
mToneGain = 0;
}
else
{
mToneGain = gain;
}
}
/**
* Sets the comfort noise insertion gain for the JMBE audio codec
* @return noise gain in range 0.0 (no noise) to 1.0 (default noise)
*/
@JacksonXmlProperty(isAttribute = true, localName = "jmbeNoiseGain", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public float getNoiseGain()
{
return mNoiseGain;
}
/**
* Sets the comfort noise insertion gain for the JMBE audio codec
* @param gain noise gain in range 0.0 (no noise) to 1.0 (default noise)
*/
public void setNoiseGain(float gain)
{
if(gain > 1.0f)
{
mNoiseGain = 1.0f;
}
else if(gain < 0)
{
mNoiseGain = 0;
}
else
{
mNoiseGain = gain;
}
}
}

View File

@@ -21,6 +21,7 @@ package io.github.dsheirer.module.decode.dmr;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfigJMBE;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
@@ -32,7 +33,7 @@ import java.util.List;
/**
* DMR Decoder Configuration
*/
public class DecodeConfigDMR extends DecodeConfiguration
public class DecodeConfigDMR extends DecodeConfigJMBE
{
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 200;
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;

View File

@@ -22,6 +22,7 @@ package io.github.dsheirer.module.decode.nxdn;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfigJMBE;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.nxdn.channel.ChannelFrequency;
import io.github.dsheirer.module.decode.nxdn.layer3.proprietary.Encoding;
@@ -33,7 +34,7 @@ import java.util.List;
/**
* NXDN decoder configuration
*/
public class DecodeConfigNXDN extends DecodeConfiguration
public class DecodeConfigNXDN extends DecodeConfigJMBE
{
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 200;
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;

View File

@@ -22,6 +22,7 @@ package io.github.dsheirer.module.decode.p25.phase1;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.config.DecodeConfigJMBE;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
@@ -30,7 +31,7 @@ import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
@JsonSubTypes.Type(value = DecodeConfigP25Phase1.class, name = "decodeConfigP25Phase1"),
@JsonSubTypes.Type(value = DecodeConfigP25Phase2.class, name = "decodeConfigP25Phase2"),
})
public abstract class DecodeConfigP25 extends DecodeConfiguration
public abstract class DecodeConfigP25 extends DecodeConfigJMBE
{
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private boolean mIgnoreDataCalls = false;