Compare commits

..

2 Commits

Author SHA1 Message Date
Denny Sheirer
421da1c1ce Cherry Pick: #1405 polyphase channelizer thread leak. 2023-03-05 06:34:29 -05:00
Denny Sheirer
2412833899 Updates release to 0.5.2 2023-03-05 06:32:56 -05:00
2228 changed files with 43627 additions and 218211 deletions

View File

@@ -1,38 +0,0 @@
---
name: Bug Report
about: Create a report to help us improve sdrtrunk
title: ''
labels: bug
assignees: ''
---
**sdrtrunk Version**
Indicate which sdrtrunk version you are using. Use 'master-branch' if you are running the latest code.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Application Log**
If applicable, include either the full application log,or a snippet of the error logging.
**Desktop (optional - complete the following information):**
- OS: [e.g. iOS]
- CPU Cores: [e.g. 4 cores]
- RAM:[e.g. 16GB]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Support and Questions - How do I XXX? Does it support XXX?
url: https://github.com/DSheirer/sdrtrunk/wiki/Support
about: Please ask your user questions here -- DO NOT create an issue if you have a user question please!!

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -21,14 +21,14 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up JDK 27
uses: actions/setup-java@v3
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '27'
java-version: '17'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
with:
arguments: clean build

View File

@@ -1,49 +0,0 @@
# Create nightly release for Linux, Mac and Windows using the Linux runner.
name: Nightly Release
on:
push:
branches: [ master ]
# Don't run nightly build for pull requests - uncomment to test changes to this action within scope of the PR review
# pull_request:
# branches: [ master ]
jobs:
nightly:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 27
uses: actions/setup-java@v3
with:
# Must match the JDK version downloaded in build.gradle (jdk_download_base) - jlink requires an exact version match
java-version: '27.0.0'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Build Linux and Mac releases with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: runtimeZipOthers -PprojectVersion=nightly
- name: Build Windows release with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: runtimeZipWindows -PprojectVersion=nightly
- name: Create Build Info file
run: |
echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" > build/image/build_info.txt
- name: Add build artifacts to nightly release
uses: softprops/action-gh-release@v2
with:
tag_name: nightly
prerelease: true
files: |
build/image/*.zip
build/image/build_info.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,24 +2,18 @@
<dictionary name="denny">
<words>
<w>bahroo</w>
<w>baseband</w>
<w>basebanded</w>
<w>broadcastify</w>
<w>cach</w>
<w>correlator</w>
<w>deinterleave</w>
<w>descrambled</w>
<w>dibit</w>
<w>dinf</w>
<w>dqpsk</w>
<w>esnid</w>
<w>fleetsync</w>
<w>heterdyned</w>
<w>heterodynes</w>
<w>hytera</w>
<w>icecast</w>
<w>ident</w>
<w>lcns</w>
<w>nutall</w>
<w>overflowable</w>
<w>polyphase</w>
@@ -35,7 +29,6 @@
<w>talkgroups</w>
<w>tgid</w>
<w>ultravox</w>
<w>unsynchronized</w>
<w>wacn</w>
<w>xtea</w>
</words>

45
.idea/misc.xml generated
View File

@@ -4,50 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
<option name="myDefaultNotNull" value="org.jetbrains.annotations.NotNull" />
<option name="myOrdered" value="true" />
<option name="myNullables">
<value>
<list size="13">
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="2" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="3" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="4" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="5" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="7" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="8" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="9" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
<item index="10" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
<item index="11" class="java.lang.String" itemvalue="org.springframework.lang.Nullable" />
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="13">
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="2" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="3" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="4" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="5" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
<item index="6" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="7" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="8" class="java.lang.String" itemvalue="lombok.NonNull" />
<item index="9" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
<item index="10" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
<item index="11" class="java.lang.String" itemvalue="org.springframework.lang.NonNull" />
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_27_PREVIEW" project-jdk-name="liberica-27" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -1,28 +1,8 @@
![Gradle Build](https://github.com/dsheirer/sdrtrunk/actions/workflows/gradle.yml/badge.svg)
![Nightly Release](https://github.com/dsheirer/sdrtrunk/actions/workflows/nightly.yml/badge.svg)
# MacOS Tahoe 26.1 Users - Attention:
Changes to USB support in Tahoe version 26.x cause sdrtrunk to fail to launch. Do the following to install the latest libusb and create a symbolic link and then use the nightly build which includes an updated usb4java native library for Tahoe with ARM processor. There may still be issue(s) with MacOS accessing your USB SDR tuners.
```
brew install libusb --HEAD
cd /opt
sudo mkdir local
cd local
sudo mkdir lib
```
Next, find where brew installed the libusb library, for example: ```/opt/homebrew/Cellar/libusb/HEAD-9ceaa52/lib/libusb-1.0.0.dylib``` Note: the folder "HEAD-9ceaa52" is the version stamp for HEAD when you installed from it.
Finally, create a symbolic link from the installed library to the place where usb4java is expecting to find libusb (/opt/local/lib/libusb-1.0.0.dylib)
```
sudo ln -s /opt/homebrew/Cellar/libusb/HEAD-9ceaa52/lib/libusb-1.0.0.dylib /opt/local/lib/libusb-1.0.0.dylib
```
# sdrtrunk
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
* [Help/Wiki Home Page](https://github.com/DSheirer/sdrtrunk/wiki)
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
@@ -38,14 +18,7 @@ All release versions of sdrtrunk are available from the [releases](https://githu
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
* **(final)** These versions have been tested and are the current release version.
## Download Nightly Software Build
The [nightly](https://github.com/DSheirer/sdrtrunk/releases/tag/nightly) release contains current builds of the software
for all supported operating systems. This version of the software may contain bugs and may not run correctly. However,
it let's you preview the most recent changes and fixes before the next software release. **Always backup your
playlist(s) before you use the nightly builds.** Note: the nightly release is updated each time code changes are
committed to the code base, so it's not really 'nightly' as much as it is 'current'.
## Minimum System Requirements
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac (64-bit, 12.x or higher)
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **CPU:** 4-core
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.

View File

@@ -1,229 +0,0 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
//#if defined(_M_X64) || defined(_M_IX86)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.07)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
#define SDRPLAY_MAX_SER_NO_LEN (64)
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
#define SDRPLAY_RSP1_ID (1)
#define SDRPLAY_RSP1A_ID (255)
#define SDRPLAY_RSP2_ID (2)
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
// Enum types
typedef enum
{
sdrplay_api_Success = 0,
sdrplay_api_Fail = 1,
sdrplay_api_InvalidParam = 2,
sdrplay_api_OutOfRange = 3,
sdrplay_api_GainUpdateError = 4,
sdrplay_api_RfUpdateError = 5,
sdrplay_api_FsUpdateError = 6,
sdrplay_api_HwError = 7,
sdrplay_api_AliasingError = 8,
sdrplay_api_AlreadyInitialised = 9,
sdrplay_api_NotInitialised = 10,
sdrplay_api_NotEnabled = 11,
sdrplay_api_HwVerError = 12,
sdrplay_api_OutOfMemError = 13,
sdrplay_api_ServiceNotResponding = 14,
sdrplay_api_StartPending = 15,
sdrplay_api_StopPending = 16,
sdrplay_api_InvalidMode = 17,
sdrplay_api_FailedVerification1 = 18,
sdrplay_api_FailedVerification2 = 19,
sdrplay_api_FailedVerification3 = 20,
sdrplay_api_FailedVerification4 = 21,
sdrplay_api_FailedVerification5 = 22,
sdrplay_api_FailedVerification6 = 23,
sdrplay_api_InvalidServiceVersion = 24
} sdrplay_api_ErrT;
typedef enum
{
sdrplay_api_Update_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_Dev_Fs = 0x00000001,
sdrplay_api_Update_Dev_Ppm = 0x00000002,
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
// Reasons for master and slave mode
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
sdrplay_api_Update_Tuner_Gr = 0x00008000,
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
sdrplay_api_Update_Tuner_Frf = 0x00020000,
sdrplay_api_Update_Tuner_BwType = 0x00040000,
sdrplay_api_Update_Tuner_IfType = 0x00080000,
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
} sdrplay_api_ReasonForUpdateT;
typedef enum
{
sdrplay_api_Update_Ext1_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
// Reasons for master and slave mode
} sdrplay_api_ReasonForUpdateExtension1T;
typedef enum
{
sdrplay_api_DbgLvl_Disable = 0,
sdrplay_api_DbgLvl_Verbose = 1,
sdrplay_api_DbgLvl_Warning = 2,
sdrplay_api_DbgLvl_Error = 3,
sdrplay_api_DbgLvl_Message = 4,
} sdrplay_api_DbgLvl_t;
// Device structure
typedef struct
{
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
unsigned char hwVer;
sdrplay_api_TunerSelectT tuner;
sdrplay_api_RspDuoModeT rspDuoMode;
double rspDuoSampleFreq;
HANDLE dev;
} sdrplay_api_DeviceT;
// Device parameter structure
typedef struct
{
sdrplay_api_DevParamsT *devParams;
sdrplay_api_RxChannelParamsT *rxChannelA;
sdrplay_api_RxChannelParamsT *rxChannelB;
} sdrplay_api_DeviceParamsT;
// Extended error message structure
typedef struct
{
char file[256];
char function[256];
int line;
char message[1024];
} sdrplay_api_ErrorInfoT;
// Comman API function types
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
// Device API function types
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// API function definitions
#ifdef __cplusplus
extern "C"
{
#endif
// Comman API function definitions
sdrplay_api_ErrT sdrplay_api_Open(void);
sdrplay_api_ErrT sdrplay_api_Close(void);
sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
// Device API function definitions
sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

@@ -1,78 +0,0 @@
#ifndef SDRPLAY_API_CALLBACK_H
#define SDRPLAY_API_CALLBACK_H
// Event callback enums
typedef enum
{
sdrplay_api_Overload_Detected = 0,
sdrplay_api_Overload_Corrected = 1,
} sdrplay_api_PowerOverloadCbEventIdT;
typedef enum
{
sdrplay_api_MasterInitialised = 0,
sdrplay_api_SlaveAttached = 1,
sdrplay_api_SlaveDetached = 2,
sdrplay_api_SlaveInitialised = 3,
sdrplay_api_SlaveUninitialised = 4,
sdrplay_api_MasterDllDisappeared = 5,
sdrplay_api_SlaveDllDisappeared = 6,
} sdrplay_api_RspDuoModeCbEventIdT;
typedef enum
{
sdrplay_api_GainChange = 0,
sdrplay_api_PowerOverloadChange = 1,
sdrplay_api_DeviceRemoved = 2,
sdrplay_api_RspDuoModeChange = 3,
} sdrplay_api_EventT;
// Event callback parameter structs
typedef struct
{
unsigned int gRdB;
unsigned int lnaGRdB;
double currGain;
} sdrplay_api_GainCbParamT;
typedef struct
{
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
} sdrplay_api_PowerOverloadCbParamT;
typedef struct
{
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
} sdrplay_api_RspDuoModeCbParamT;
// Event parameters overlay
typedef union
{
sdrplay_api_GainCbParamT gainParams;
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
} sdrplay_api_EventParamsT;
// Stream callback parameter structs
typedef struct
{
unsigned int firstSampleNum;
int grChanged;
int rfChanged;
int fsChanged;
unsigned int numSamples;
} sdrplay_api_StreamCbParamsT;
// Callback function prototypes
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
// Callback function struct
typedef struct
{
sdrplay_api_StreamCallback_t StreamACbFn;
sdrplay_api_StreamCallback_t StreamBCbFn;
sdrplay_api_EventCallback_t EventCbFn;
} sdrplay_api_CallbackFnsT;
#endif //SDRPLAY_API_CALLBACK_H

View File

@@ -1,55 +0,0 @@
#ifndef SDRPLAY_API_CONTROL_H
#define SDRPLAY_API_CONTROL_H
// Control parameter enums
typedef enum
{
sdrplay_api_AGC_DISABLE = 0,
sdrplay_api_AGC_100HZ = 1,
sdrplay_api_AGC_50HZ = 2,
sdrplay_api_AGC_5HZ = 3,
sdrplay_api_AGC_CTRL_EN = 4
} sdrplay_api_AgcControlT;
typedef enum
{
sdrplay_api_ADSB_DECIMATION = 0,
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
} sdrplay_api_AdsbModeT;
// Control parameter structs
typedef struct
{
unsigned char DCenable; // default: 1
unsigned char IQenable; // default: 1
} sdrplay_api_DcOffsetT;
typedef struct
{
unsigned char enable; // default: 0
unsigned char decimationFactor; // default: 1
unsigned char wideBandSignal; // default: 0
} sdrplay_api_DecimationT;
typedef struct
{
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
int setPoint_dBfs; // default: -60
unsigned short attack_ms; // default: 0
unsigned short decay_ms; // default: 0
unsigned short decay_delay_ms; // default: 0
unsigned short decay_threshold_dB; // default: 0
int syncUpdate; // default: 0
} sdrplay_api_AgcT;
typedef struct
{
sdrplay_api_DcOffsetT dcOffset;
sdrplay_api_DecimationT decimation;
sdrplay_api_AgcT agc;
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
} sdrplay_api_ControlParamsT;
#endif //SDRPLAY_API_CONTROL_H

View File

@@ -1,51 +0,0 @@
#ifndef SDRPLAY_API_DEV_H
#define SDRPLAY_API_DEV_H
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
// Dev parameter enums
typedef enum
{
sdrplay_api_ISOCH = 0,
sdrplay_api_BULK = 1
} sdrplay_api_TransferModeT;
// Dev parameter structs
typedef struct
{
double fsHz; // default: 2000000.0
unsigned char syncUpdate; // default: 0
unsigned char reCal; // default: 0
} sdrplay_api_FsFreqT;
typedef struct
{
unsigned int sampleNum; // default: 0
unsigned int period; // default: 0
} sdrplay_api_SyncUpdateT;
typedef struct
{
unsigned char resetGainUpdate; // default: 0
unsigned char resetRfUpdate; // default: 0
unsigned char resetFsUpdate; // default: 0
} sdrplay_api_ResetFlagsT;
typedef struct
{
double ppm; // default: 0.0
sdrplay_api_FsFreqT fsFreq;
sdrplay_api_SyncUpdateT syncUpdate;
sdrplay_api_ResetFlagsT resetFlags;
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
unsigned int samplesPerPkt; // default: 0 (output param)
sdrplay_api_Rsp1aParamsT rsp1aParams;
sdrplay_api_Rsp2ParamsT rsp2Params;
sdrplay_api_RspDuoParamsT rspDuoParams;
sdrplay_api_RspDxParamsT rspDxParams;
} sdrplay_api_DevParamsT;
#endif //SDRPLAY_API_DEV_H

View File

@@ -1,22 +0,0 @@
#ifndef SDRPLAY_API_RSP1A_H
#define SDRPLAY_API_RSP1A_H
#define RSPIA_NUM_LNA_STATES 10
#define RSPIA_NUM_LNA_STATES_AM 7
#define RSPIA_NUM_LNA_STATES_LBAND 9
// RSP1A parameter enums
// RSP1A parameter structs
typedef struct
{
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_Rsp1aParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
} sdrplay_api_Rsp1aTunerParamsT;
#endif //SDRPLAY_API_RSP1A_H

View File

@@ -1,35 +0,0 @@
#ifndef SDRPLAY_API_RSP2_H
#define SDRPLAY_API_RSP2_H
#define RSPII_NUM_LNA_STATES 9
#define RSPII_NUM_LNA_STATES_AMPORT 5
#define RSPII_NUM_LNA_STATES_420MHZ 6
// RSP2 parameter enums
typedef enum
{
sdrplay_api_Rsp2_ANTENNA_A = 5,
sdrplay_api_Rsp2_ANTENNA_B = 6,
} sdrplay_api_Rsp2_AntennaSelectT;
typedef enum
{
sdrplay_api_Rsp2_AMPORT_1 = 1,
sdrplay_api_Rsp2_AMPORT_2 = 0,
} sdrplay_api_Rsp2_AmPortSelectT;
// RSP2 parameter structs
typedef struct
{
unsigned char extRefOutputEn; // default: 0
} sdrplay_api_Rsp2ParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
} sdrplay_api_Rsp2TunerParamsT;
#endif //SDRPLAY_API_RSP2_H

View File

@@ -1,40 +0,0 @@
#ifndef SDRPLAY_API_RSPduo_H
#define SDRPLAY_API_RSPduo_H
#define RSPDUO_NUM_LNA_STATES 10
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
#define RSPDUO_NUM_LNA_STATES_AM 7
#define RSPDUO_NUM_LNA_STATES_LBAND 9
// RSPduo parameter enums
typedef enum
{
sdrplay_api_RspDuoMode_Unknown = 0,
sdrplay_api_RspDuoMode_Single_Tuner = 1,
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
sdrplay_api_RspDuoMode_Master = 4,
sdrplay_api_RspDuoMode_Slave = 8,
} sdrplay_api_RspDuoModeT;
typedef enum
{
sdrplay_api_RspDuo_AMPORT_1 = 1,
sdrplay_api_RspDuo_AMPORT_2 = 0,
} sdrplay_api_RspDuo_AmPortSelectT;
// RSPduo parameter structs
typedef struct
{
int extRefOutputEn; // default: 0
} sdrplay_api_RspDuoParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
unsigned char tuner1AmNotchEnable; // default: 0
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDuoTunerParamsT;
#endif //SDRPLAY_API_RSPduo_H

View File

@@ -1,45 +0,0 @@
#ifndef SDRPLAY_API_RSPDX_H
#define SDRPLAY_API_RSPDX_H
#include "sdrplay_api_tuner.h"
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_60 20 // Number of LNA states when using AM Port 2 between 12 and 60MHz
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
#define RSPDX_NUM_LNA_STATES_DX 26 // Number of LNA states in DX path
// RSPdx parameter enums
typedef enum
{
sdrplay_api_RspDx_ANTENNA_A = 0,
sdrplay_api_RspDx_ANTENNA_B = 1,
sdrplay_api_RspDx_ANTENNA_C = 2,
} sdrplay_api_RspDx_AntennaSelectT;
typedef enum
{
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
} sdrplay_api_RspDx_HdrModeBwT;
// RSPdx parameter structs
typedef struct
{
unsigned char hdrEnable; // default: 0
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDxParamsT;
typedef struct
{
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
} sdrplay_api_RspDxTunerParamsT;
#endif //SDRPLAY_API_RSPDX_H

View File

@@ -1,21 +0,0 @@
#ifndef SDRPLAY_API_RX_CHANNEL_H
#define SDRPLAY_API_RX_CHANNEL_H
#include "sdrplay_api_tuner.h"
#include "sdrplay_api_control.h"
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
typedef struct
{
sdrplay_api_TunerParamsT tunerParams;
sdrplay_api_ControlParamsT ctrlParams;
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
} sdrplay_api_RxChannelParamsT;
#endif //SDRPLAY_API_RX_CHANNEL_H

View File

@@ -1,93 +0,0 @@
#ifndef SDRPLAY_API_TUNER_H
#define SDRPLAY_API_TUNER_H
#define MAX_BB_GR (59)
// Tuner parameter enums
typedef enum
{
sdrplay_api_BW_Undefined = 0,
sdrplay_api_BW_0_200 = 200,
sdrplay_api_BW_0_300 = 300,
sdrplay_api_BW_0_600 = 600,
sdrplay_api_BW_1_536 = 1536,
sdrplay_api_BW_5_000 = 5000,
sdrplay_api_BW_6_000 = 6000,
sdrplay_api_BW_7_000 = 7000,
sdrplay_api_BW_8_000 = 8000
} sdrplay_api_Bw_MHzT;
typedef enum
{
sdrplay_api_IF_Undefined = -1,
sdrplay_api_IF_Zero = 0,
sdrplay_api_IF_0_450 = 450,
sdrplay_api_IF_1_620 = 1620,
sdrplay_api_IF_2_048 = 2048
} sdrplay_api_If_kHzT;
typedef enum
{
sdrplay_api_LO_Undefined = 0,
sdrplay_api_LO_Auto = 1,
sdrplay_api_LO_120MHz = 2,
sdrplay_api_LO_144MHz = 3,
sdrplay_api_LO_168MHz = 4
} sdrplay_api_LoModeT;
typedef enum
{
sdrplay_api_EXTENDED_MIN_GR = 0,
sdrplay_api_NORMAL_MIN_GR = 20
} sdrplay_api_MinGainReductionT;
typedef enum
{
sdrplay_api_Tuner_Neither = 0,
sdrplay_api_Tuner_A = 1,
sdrplay_api_Tuner_B = 2,
sdrplay_api_Tuner_Both = 3,
} sdrplay_api_TunerSelectT;
// Tuner parameter structs
typedef struct
{
float curr;
float max;
float min;
} sdrplay_api_GainValuesT;
typedef struct
{
int gRdB; // default: 50
unsigned char LNAstate; // default: 0
unsigned char syncUpdate; // default: 0
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
sdrplay_api_GainValuesT gainVals; // output parameter
} sdrplay_api_GainT;
typedef struct
{
double rfHz; // default: 200000000.0
unsigned char syncUpdate; // default: 0
} sdrplay_api_RfFreqT;
typedef struct
{
unsigned char dcCal; // default: 3 (Periodic mode)
unsigned char speedUp; // default: 0 (No speedup)
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
} sdrplay_api_DcOffsetTunerT;
typedef struct
{
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
sdrplay_api_GainT gain;
sdrplay_api_RfFreqT rfFreq;
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
} sdrplay_api_TunerParamsT;
#endif //SDRPLAY_API_TUNER_H

View File

@@ -1,230 +0,0 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
//#if defined(_M_X64) || defined(_M_IX86)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.08)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
#define SDRPLAY_MAX_SER_NO_LEN (64)
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
#define SDRPLAY_RSP1_ID (1)
#define SDRPLAY_RSP1A_ID (255)
#define SDRPLAY_RSP2_ID (2)
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
// Enum types
typedef enum
{
sdrplay_api_Success = 0,
sdrplay_api_Fail = 1,
sdrplay_api_InvalidParam = 2,
sdrplay_api_OutOfRange = 3,
sdrplay_api_GainUpdateError = 4,
sdrplay_api_RfUpdateError = 5,
sdrplay_api_FsUpdateError = 6,
sdrplay_api_HwError = 7,
sdrplay_api_AliasingError = 8,
sdrplay_api_AlreadyInitialised = 9,
sdrplay_api_NotInitialised = 10,
sdrplay_api_NotEnabled = 11,
sdrplay_api_HwVerError = 12,
sdrplay_api_OutOfMemError = 13,
sdrplay_api_ServiceNotResponding = 14,
sdrplay_api_StartPending = 15,
sdrplay_api_StopPending = 16,
sdrplay_api_InvalidMode = 17,
sdrplay_api_FailedVerification1 = 18,
sdrplay_api_FailedVerification2 = 19,
sdrplay_api_FailedVerification3 = 20,
sdrplay_api_FailedVerification4 = 21,
sdrplay_api_FailedVerification5 = 22,
sdrplay_api_FailedVerification6 = 23,
sdrplay_api_InvalidServiceVersion = 24
} sdrplay_api_ErrT;
typedef enum
{
sdrplay_api_Update_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_Dev_Fs = 0x00000001,
sdrplay_api_Update_Dev_Ppm = 0x00000002,
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
// Reasons for master and slave mode
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
sdrplay_api_Update_Tuner_Gr = 0x00008000,
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
sdrplay_api_Update_Tuner_Frf = 0x00020000,
sdrplay_api_Update_Tuner_BwType = 0x00040000,
sdrplay_api_Update_Tuner_IfType = 0x00080000,
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
} sdrplay_api_ReasonForUpdateT;
typedef enum
{
sdrplay_api_Update_Ext1_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
// Reasons for master and slave mode
} sdrplay_api_ReasonForUpdateExtension1T;
typedef enum
{
sdrplay_api_DbgLvl_Disable = 0,
sdrplay_api_DbgLvl_Verbose = 1,
sdrplay_api_DbgLvl_Warning = 2,
sdrplay_api_DbgLvl_Error = 3,
sdrplay_api_DbgLvl_Message = 4,
} sdrplay_api_DbgLvl_t;
// Device structure
typedef struct
{
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
unsigned char hwVer;
sdrplay_api_TunerSelectT tuner;
sdrplay_api_RspDuoModeT rspDuoMode;
unsigned char valid;
double rspDuoSampleFreq;
HANDLE dev;
} sdrplay_api_DeviceT;
// Device parameter structure
typedef struct
{
sdrplay_api_DevParamsT *devParams;
sdrplay_api_RxChannelParamsT *rxChannelA;
sdrplay_api_RxChannelParamsT *rxChannelB;
} sdrplay_api_DeviceParamsT;
// Extended error message structure
typedef struct
{
char file[256];
char function[256];
int line;
char message[1024];
} sdrplay_api_ErrorInfoT;
// Comman API function types
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
// Device API function types
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate, double newSampleRate);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// API function definitions
#ifdef __cplusplus
extern "C"
{
#endif
// // Comman API function definitions
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Open(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Close(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
//
// // Device API function definitions
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
// sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
// sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

@@ -1,78 +0,0 @@
#ifndef SDRPLAY_API_CALLBACK_H
#define SDRPLAY_API_CALLBACK_H
// Event callback enums
typedef enum
{
sdrplay_api_Overload_Detected = 0,
sdrplay_api_Overload_Corrected = 1,
} sdrplay_api_PowerOverloadCbEventIdT;
typedef enum
{
sdrplay_api_MasterInitialised = 0,
sdrplay_api_SlaveAttached = 1,
sdrplay_api_SlaveDetached = 2,
sdrplay_api_SlaveInitialised = 3,
sdrplay_api_SlaveUninitialised = 4,
sdrplay_api_MasterDllDisappeared = 5,
sdrplay_api_SlaveDllDisappeared = 6,
} sdrplay_api_RspDuoModeCbEventIdT;
typedef enum
{
sdrplay_api_GainChange = 0,
sdrplay_api_PowerOverloadChange = 1,
sdrplay_api_DeviceRemoved = 2,
sdrplay_api_RspDuoModeChange = 3,
} sdrplay_api_EventT;
// Event callback parameter structs
typedef struct
{
unsigned int gRdB;
unsigned int lnaGRdB;
double currGain;
} sdrplay_api_GainCbParamT;
typedef struct
{
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
} sdrplay_api_PowerOverloadCbParamT;
typedef struct
{
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
} sdrplay_api_RspDuoModeCbParamT;
// Event parameters overlay
typedef union
{
sdrplay_api_GainCbParamT gainParams;
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
} sdrplay_api_EventParamsT;
// Stream callback parameter structs
typedef struct
{
unsigned int firstSampleNum;
int grChanged;
int rfChanged;
int fsChanged;
unsigned int numSamples;
} sdrplay_api_StreamCbParamsT;
// Callback function prototypes
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
// Callback function struct
typedef struct
{
sdrplay_api_StreamCallback_t StreamACbFn;
sdrplay_api_StreamCallback_t StreamBCbFn;
sdrplay_api_EventCallback_t EventCbFn;
} sdrplay_api_CallbackFnsT;
#endif //SDRPLAY_API_CALLBACK_H

View File

@@ -1,55 +0,0 @@
#ifndef SDRPLAY_API_CONTROL_H
#define SDRPLAY_API_CONTROL_H
// Control parameter enums
typedef enum
{
sdrplay_api_AGC_DISABLE = 0,
sdrplay_api_AGC_100HZ = 1,
sdrplay_api_AGC_50HZ = 2,
sdrplay_api_AGC_5HZ = 3,
sdrplay_api_AGC_CTRL_EN = 4
} sdrplay_api_AgcControlT;
typedef enum
{
sdrplay_api_ADSB_DECIMATION = 0,
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
} sdrplay_api_AdsbModeT;
// Control parameter structs
typedef struct
{
unsigned char DCenable; // default: 1
unsigned char IQenable; // default: 1
} sdrplay_api_DcOffsetT;
typedef struct
{
unsigned char enable; // default: 0
unsigned char decimationFactor; // default: 1
unsigned char wideBandSignal; // default: 0
} sdrplay_api_DecimationT;
typedef struct
{
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
int setPoint_dBfs; // default: -60
unsigned short attack_ms; // default: 0
unsigned short decay_ms; // default: 0
unsigned short decay_delay_ms; // default: 0
unsigned short decay_threshold_dB; // default: 0
int syncUpdate; // default: 0
} sdrplay_api_AgcT;
typedef struct
{
sdrplay_api_DcOffsetT dcOffset;
sdrplay_api_DecimationT decimation;
sdrplay_api_AgcT agc;
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
} sdrplay_api_ControlParamsT;
#endif //SDRPLAY_API_CONTROL_H

View File

@@ -1,51 +0,0 @@
#ifndef SDRPLAY_API_DEV_H
#define SDRPLAY_API_DEV_H
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
// Dev parameter enums
typedef enum
{
sdrplay_api_ISOCH = 0,
sdrplay_api_BULK = 1
} sdrplay_api_TransferModeT;
// Dev parameter structs
typedef struct
{
double fsHz; // default: 2000000.0
unsigned char syncUpdate; // default: 0
unsigned char reCal; // default: 0
} sdrplay_api_FsFreqT;
typedef struct
{
unsigned int sampleNum; // default: 0
unsigned int period; // default: 0
} sdrplay_api_SyncUpdateT;
typedef struct
{
unsigned char resetGainUpdate; // default: 0
unsigned char resetRfUpdate; // default: 0
unsigned char resetFsUpdate; // default: 0
} sdrplay_api_ResetFlagsT;
typedef struct
{
double ppm; // default: 0.0
sdrplay_api_FsFreqT fsFreq;
sdrplay_api_SyncUpdateT syncUpdate;
sdrplay_api_ResetFlagsT resetFlags;
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
unsigned int samplesPerPkt; // default: 0 (output param)
sdrplay_api_Rsp1aParamsT rsp1aParams;
sdrplay_api_Rsp2ParamsT rsp2Params;
sdrplay_api_RspDuoParamsT rspDuoParams;
sdrplay_api_RspDxParamsT rspDxParams;
} sdrplay_api_DevParamsT;
#endif //SDRPLAY_API_DEV_H

View File

@@ -1,22 +0,0 @@
#ifndef SDRPLAY_API_RSP1A_H
#define SDRPLAY_API_RSP1A_H
#define RSPIA_NUM_LNA_STATES 10
#define RSPIA_NUM_LNA_STATES_AM 7
#define RSPIA_NUM_LNA_STATES_LBAND 9
// RSP1A parameter enums
// RSP1A parameter structs
typedef struct
{
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_Rsp1aParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
} sdrplay_api_Rsp1aTunerParamsT;
#endif //SDRPLAY_API_RSP1A_H

View File

@@ -1,35 +0,0 @@
#ifndef SDRPLAY_API_RSP2_H
#define SDRPLAY_API_RSP2_H
#define RSPII_NUM_LNA_STATES 9
#define RSPII_NUM_LNA_STATES_AMPORT 5
#define RSPII_NUM_LNA_STATES_420MHZ 6
// RSP2 parameter enums
typedef enum
{
sdrplay_api_Rsp2_ANTENNA_A = 5,
sdrplay_api_Rsp2_ANTENNA_B = 6,
} sdrplay_api_Rsp2_AntennaSelectT;
typedef enum
{
sdrplay_api_Rsp2_AMPORT_1 = 1,
sdrplay_api_Rsp2_AMPORT_2 = 0,
} sdrplay_api_Rsp2_AmPortSelectT;
// RSP2 parameter structs
typedef struct
{
unsigned char extRefOutputEn; // default: 0
} sdrplay_api_Rsp2ParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
} sdrplay_api_Rsp2TunerParamsT;
#endif //SDRPLAY_API_RSP2_H

View File

@@ -1,40 +0,0 @@
#ifndef SDRPLAY_API_RSPduo_H
#define SDRPLAY_API_RSPduo_H
#define RSPDUO_NUM_LNA_STATES 10
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
#define RSPDUO_NUM_LNA_STATES_AM 7
#define RSPDUO_NUM_LNA_STATES_LBAND 9
// RSPduo parameter enums
typedef enum
{
sdrplay_api_RspDuoMode_Unknown = 0,
sdrplay_api_RspDuoMode_Single_Tuner = 1,
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
sdrplay_api_RspDuoMode_Master = 4,
sdrplay_api_RspDuoMode_Slave = 8,
} sdrplay_api_RspDuoModeT;
typedef enum
{
sdrplay_api_RspDuo_AMPORT_1 = 1,
sdrplay_api_RspDuo_AMPORT_2 = 0,
} sdrplay_api_RspDuo_AmPortSelectT;
// RSPduo parameter structs
typedef struct
{
int extRefOutputEn; // default: 0
} sdrplay_api_RspDuoParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
unsigned char tuner1AmNotchEnable; // default: 0
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDuoTunerParamsT;
#endif //SDRPLAY_API_RSPduo_H

View File

@@ -1,45 +0,0 @@
#ifndef SDRPLAY_API_RSPDX_H
#define SDRPLAY_API_RSPDX_H
#include "sdrplay_api_tuner.h"
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_60 20 // Number of LNA states when using AM Port 2 between 12 and 60MHz
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
#define RSPDX_NUM_LNA_STATES_DX 26 // Number of LNA states in DX path
// RSPdx parameter enums
typedef enum
{
sdrplay_api_RspDx_ANTENNA_A = 0,
sdrplay_api_RspDx_ANTENNA_B = 1,
sdrplay_api_RspDx_ANTENNA_C = 2,
} sdrplay_api_RspDx_AntennaSelectT;
typedef enum
{
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
} sdrplay_api_RspDx_HdrModeBwT;
// RSPdx parameter structs
typedef struct
{
unsigned char hdrEnable; // default: 0
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDxParamsT;
typedef struct
{
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
} sdrplay_api_RspDxTunerParamsT;
#endif //SDRPLAY_API_RSPDX_H

View File

@@ -1,21 +0,0 @@
#ifndef SDRPLAY_API_RX_CHANNEL_H
#define SDRPLAY_API_RX_CHANNEL_H
#include "sdrplay_api_tuner.h"
#include "sdrplay_api_control.h"
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
typedef struct
{
sdrplay_api_TunerParamsT tunerParams;
sdrplay_api_ControlParamsT ctrlParams;
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
} sdrplay_api_RxChannelParamsT;
#endif //SDRPLAY_API_RX_CHANNEL_H

View File

@@ -1,93 +0,0 @@
#ifndef SDRPLAY_API_TUNER_H
#define SDRPLAY_API_TUNER_H
#define MAX_BB_GR (59)
// Tuner parameter enums
typedef enum
{
sdrplay_api_BW_Undefined = 0,
sdrplay_api_BW_0_200 = 200,
sdrplay_api_BW_0_300 = 300,
sdrplay_api_BW_0_600 = 600,
sdrplay_api_BW_1_536 = 1536,
sdrplay_api_BW_5_000 = 5000,
sdrplay_api_BW_6_000 = 6000,
sdrplay_api_BW_7_000 = 7000,
sdrplay_api_BW_8_000 = 8000
} sdrplay_api_Bw_MHzT;
typedef enum
{
sdrplay_api_IF_Undefined = -1,
sdrplay_api_IF_Zero = 0,
sdrplay_api_IF_0_450 = 450,
sdrplay_api_IF_1_620 = 1620,
sdrplay_api_IF_2_048 = 2048
} sdrplay_api_If_kHzT;
typedef enum
{
sdrplay_api_LO_Undefined = 0,
sdrplay_api_LO_Auto = 1,
sdrplay_api_LO_120MHz = 2,
sdrplay_api_LO_144MHz = 3,
sdrplay_api_LO_168MHz = 4
} sdrplay_api_LoModeT;
typedef enum
{
sdrplay_api_EXTENDED_MIN_GR = 0,
sdrplay_api_NORMAL_MIN_GR = 20
} sdrplay_api_MinGainReductionT;
typedef enum
{
sdrplay_api_Tuner_Neither = 0,
sdrplay_api_Tuner_A = 1,
sdrplay_api_Tuner_B = 2,
sdrplay_api_Tuner_Both = 3,
} sdrplay_api_TunerSelectT;
// Tuner parameter structs
typedef struct
{
float curr;
float max;
float min;
} sdrplay_api_GainValuesT;
typedef struct
{
int gRdB; // default: 50
unsigned char LNAstate; // default: 0
unsigned char syncUpdate; // default: 0
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
sdrplay_api_GainValuesT gainVals; // output parameter
} sdrplay_api_GainT;
typedef struct
{
double rfHz; // default: 200000000.0
unsigned char syncUpdate; // default: 0
} sdrplay_api_RfFreqT;
typedef struct
{
unsigned char dcCal; // default: 3 (Periodic mode)
unsigned char speedUp; // default: 0 (No speedup)
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
} sdrplay_api_DcOffsetTunerT;
typedef struct
{
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
sdrplay_api_GainT gain;
sdrplay_api_RfFreqT rfFreq;
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
} sdrplay_api_TunerParamsT;
#endif //SDRPLAY_API_TUNER_H

View File

@@ -1,235 +0,0 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
#include "windows.h"
#elif defined (__GNUC__)
typedef void *HANDLE;
#endif
#ifndef _SDRPLAY_DLL_QUALIFIER
#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
#elif defined(STATIC_LIB) || defined(__GNUC__)
#define _SDRPLAY_DLL_QUALIFIER
#endif
#endif // _SDRPLAY_DLL_QUALIFIER
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.15)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
#define SDRPLAY_MAX_SER_NO_LEN (64)
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
#define SDRPLAY_RSP1_ID (1)
#define SDRPLAY_RSP1A_ID (255)
#define SDRPLAY_RSP2_ID (2)
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
#define SDRPLAY_RSP1B_ID (6)
#define SDRPLAY_RSPdxR2_ID (7)
// Enum types
typedef enum
{
sdrplay_api_Success = 0,
sdrplay_api_Fail = 1,
sdrplay_api_InvalidParam = 2,
sdrplay_api_OutOfRange = 3,
sdrplay_api_GainUpdateError = 4,
sdrplay_api_RfUpdateError = 5,
sdrplay_api_FsUpdateError = 6,
sdrplay_api_HwError = 7,
sdrplay_api_AliasingError = 8,
sdrplay_api_AlreadyInitialised = 9,
sdrplay_api_NotInitialised = 10,
sdrplay_api_NotEnabled = 11,
sdrplay_api_HwVerError = 12,
sdrplay_api_OutOfMemError = 13,
sdrplay_api_ServiceNotResponding = 14,
sdrplay_api_StartPending = 15,
sdrplay_api_StopPending = 16,
sdrplay_api_InvalidMode = 17,
sdrplay_api_FailedVerification1 = 18,
sdrplay_api_FailedVerification2 = 19,
sdrplay_api_FailedVerification3 = 20,
sdrplay_api_FailedVerification4 = 21,
sdrplay_api_FailedVerification5 = 22,
sdrplay_api_FailedVerification6 = 23,
sdrplay_api_InvalidServiceVersion = 24
} sdrplay_api_ErrT;
typedef enum
{
sdrplay_api_Update_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_Dev_Fs = 0x00000001,
sdrplay_api_Update_Dev_Ppm = 0x00000002,
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
// Reasons for master and slave mode
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
sdrplay_api_Update_Tuner_Gr = 0x00008000,
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
sdrplay_api_Update_Tuner_Frf = 0x00020000,
sdrplay_api_Update_Tuner_BwType = 0x00040000,
sdrplay_api_Update_Tuner_IfType = 0x00080000,
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
} sdrplay_api_ReasonForUpdateT;
typedef enum
{
sdrplay_api_Update_Ext1_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
sdrplay_api_Update_RspDuo_ResetSlaveFlags = 0x00000040,
// Reasons for master and slave mode
} sdrplay_api_ReasonForUpdateExtension1T;
typedef enum
{
sdrplay_api_DbgLvl_Disable = 0,
sdrplay_api_DbgLvl_Verbose = 1,
sdrplay_api_DbgLvl_Warning = 2,
sdrplay_api_DbgLvl_Error = 3,
sdrplay_api_DbgLvl_Message = 4,
} sdrplay_api_DbgLvl_t;
// Device structure
typedef struct
{
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
unsigned char hwVer;
sdrplay_api_TunerSelectT tuner;
sdrplay_api_RspDuoModeT rspDuoMode;
unsigned char valid;
double rspDuoSampleFreq;
HANDLE dev;
} sdrplay_api_DeviceT;
// Device parameter structure
typedef struct
{
sdrplay_api_DevParamsT *devParams;
sdrplay_api_RxChannelParamsT *rxChannelA;
sdrplay_api_RxChannelParamsT *rxChannelB;
} sdrplay_api_DeviceParamsT;
// Extended error message structure
typedef struct
{
char file[256];
char function[256];
int line;
char message[1024];
} sdrplay_api_ErrorInfoT;
// Comman API function types
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastErrorByType_t)(sdrplay_api_DeviceT *device, int type, unsigned long long *time);
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
// Device API function types
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate, double newSampleRate);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// API function definitions
#ifdef __cplusplus
extern "C"
{
#endif
// Comman API function definitions
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Open(void);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Close(void);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
_SDRPLAY_DLL_QUALIFIER const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastErrorByType(sdrplay_api_DeviceT *device, int type, unsigned long long *time);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
// Device API function definitions
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
_SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

@@ -1,79 +0,0 @@
#ifndef SDRPLAY_API_CALLBACK_H
#define SDRPLAY_API_CALLBACK_H
// Event callback enums
typedef enum
{
sdrplay_api_Overload_Detected = 0,
sdrplay_api_Overload_Corrected = 1,
} sdrplay_api_PowerOverloadCbEventIdT;
typedef enum
{
sdrplay_api_MasterInitialised = 0,
sdrplay_api_SlaveAttached = 1,
sdrplay_api_SlaveDetached = 2,
sdrplay_api_SlaveInitialised = 3,
sdrplay_api_SlaveUninitialised = 4,
sdrplay_api_MasterDllDisappeared = 5,
sdrplay_api_SlaveDllDisappeared = 6,
} sdrplay_api_RspDuoModeCbEventIdT;
typedef enum
{
sdrplay_api_GainChange = 0,
sdrplay_api_PowerOverloadChange = 1,
sdrplay_api_DeviceRemoved = 2,
sdrplay_api_RspDuoModeChange = 3,
sdrplay_api_DeviceFailure = 4,
} sdrplay_api_EventT;
// Event callback parameter structs
typedef struct
{
unsigned int gRdB;
unsigned int lnaGRdB;
double currGain;
} sdrplay_api_GainCbParamT;
typedef struct
{
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
} sdrplay_api_PowerOverloadCbParamT;
typedef struct
{
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
} sdrplay_api_RspDuoModeCbParamT;
// Event parameters overlay
typedef union
{
sdrplay_api_GainCbParamT gainParams;
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
} sdrplay_api_EventParamsT;
// Stream callback parameter structs
typedef struct
{
unsigned int firstSampleNum;
int grChanged;
int rfChanged;
int fsChanged;
unsigned int numSamples;
} sdrplay_api_StreamCbParamsT;
// Callback function prototypes
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
// Callback function struct
typedef struct
{
sdrplay_api_StreamCallback_t StreamACbFn;
sdrplay_api_StreamCallback_t StreamBCbFn;
sdrplay_api_EventCallback_t EventCbFn;
} sdrplay_api_CallbackFnsT;
#endif //SDRPLAY_API_CALLBACK_H

View File

@@ -1,55 +0,0 @@
#ifndef SDRPLAY_API_CONTROL_H
#define SDRPLAY_API_CONTROL_H
// Control parameter enums
typedef enum
{
sdrplay_api_AGC_DISABLE = 0,
sdrplay_api_AGC_100HZ = 1,
sdrplay_api_AGC_50HZ = 2,
sdrplay_api_AGC_5HZ = 3,
sdrplay_api_AGC_CTRL_EN = 4
} sdrplay_api_AgcControlT;
typedef enum
{
sdrplay_api_ADSB_DECIMATION = 0,
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
} sdrplay_api_AdsbModeT;
// Control parameter structs
typedef struct
{
unsigned char DCenable; // default: 1
unsigned char IQenable; // default: 1
} sdrplay_api_DcOffsetT;
typedef struct
{
unsigned char enable; // default: 0
unsigned char decimationFactor; // default: 1
unsigned char wideBandSignal; // default: 0
} sdrplay_api_DecimationT;
typedef struct
{
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
int setPoint_dBfs; // default: -60
unsigned short attack_ms; // default: 0
unsigned short decay_ms; // default: 0
unsigned short decay_delay_ms; // default: 0
unsigned short decay_threshold_dB; // default: 0
int syncUpdate; // default: 0
} sdrplay_api_AgcT;
typedef struct
{
sdrplay_api_DcOffsetT dcOffset;
sdrplay_api_DecimationT decimation;
sdrplay_api_AgcT agc;
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
} sdrplay_api_ControlParamsT;
#endif //SDRPLAY_API_CONTROL_H

View File

@@ -1,51 +0,0 @@
#ifndef SDRPLAY_API_DEV_H
#define SDRPLAY_API_DEV_H
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
// Dev parameter enums
typedef enum
{
sdrplay_api_ISOCH = 0,
sdrplay_api_BULK = 1
} sdrplay_api_TransferModeT;
// Dev parameter structs
typedef struct
{
double fsHz; // default: 2000000.0
unsigned char syncUpdate; // default: 0
unsigned char reCal; // default: 0
} sdrplay_api_FsFreqT;
typedef struct
{
unsigned int sampleNum; // default: 0
unsigned int period; // default: 0
} sdrplay_api_SyncUpdateT;
typedef struct
{
unsigned char resetGainUpdate; // default: 0
unsigned char resetRfUpdate; // default: 0
unsigned char resetFsUpdate; // default: 0
} sdrplay_api_ResetFlagsT;
typedef struct
{
double ppm; // default: 0.0
sdrplay_api_FsFreqT fsFreq;
sdrplay_api_SyncUpdateT syncUpdate;
sdrplay_api_ResetFlagsT resetFlags;
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
unsigned int samplesPerPkt; // default: 0 (output param)
sdrplay_api_Rsp1aParamsT rsp1aParams;
sdrplay_api_Rsp2ParamsT rsp2Params;
sdrplay_api_RspDuoParamsT rspDuoParams;
sdrplay_api_RspDxParamsT rspDxParams;
} sdrplay_api_DevParamsT;
#endif //SDRPLAY_API_DEV_H

View File

@@ -1,22 +0,0 @@
#ifndef SDRPLAY_API_RSP1A_H
#define SDRPLAY_API_RSP1A_H
#define RSPIA_NUM_LNA_STATES 10
#define RSPIA_NUM_LNA_STATES_AM 7
#define RSPIA_NUM_LNA_STATES_LBAND 9
// RSP1A parameter enums
// RSP1A parameter structs
typedef struct
{
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_Rsp1aParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
} sdrplay_api_Rsp1aTunerParamsT;
#endif //SDRPLAY_API_RSP1A_H

View File

@@ -1,35 +0,0 @@
#ifndef SDRPLAY_API_RSP2_H
#define SDRPLAY_API_RSP2_H
#define RSPII_NUM_LNA_STATES 9
#define RSPII_NUM_LNA_STATES_AMPORT 5
#define RSPII_NUM_LNA_STATES_420MHZ 6
// RSP2 parameter enums
typedef enum
{
sdrplay_api_Rsp2_ANTENNA_A = 5,
sdrplay_api_Rsp2_ANTENNA_B = 6,
} sdrplay_api_Rsp2_AntennaSelectT;
typedef enum
{
sdrplay_api_Rsp2_AMPORT_1 = 1,
sdrplay_api_Rsp2_AMPORT_2 = 0,
} sdrplay_api_Rsp2_AmPortSelectT;
// RSP2 parameter structs
typedef struct
{
unsigned char extRefOutputEn; // default: 0
} sdrplay_api_Rsp2ParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
} sdrplay_api_Rsp2TunerParamsT;
#endif //SDRPLAY_API_RSP2_H

View File

@@ -1,47 +0,0 @@
#ifndef SDRPLAY_API_RSPduo_H
#define SDRPLAY_API_RSPduo_H
#define RSPDUO_NUM_LNA_STATES 10
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
#define RSPDUO_NUM_LNA_STATES_AM 7
#define RSPDUO_NUM_LNA_STATES_LBAND 9
// RSPduo parameter enums
typedef enum
{
sdrplay_api_RspDuoMode_Unknown = 0,
sdrplay_api_RspDuoMode_Single_Tuner = 1,
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
sdrplay_api_RspDuoMode_Master = 4,
sdrplay_api_RspDuoMode_Slave = 8,
} sdrplay_api_RspDuoModeT;
typedef enum
{
sdrplay_api_RspDuo_AMPORT_1 = 1,
sdrplay_api_RspDuo_AMPORT_2 = 0,
} sdrplay_api_RspDuo_AmPortSelectT;
// RSPduo parameter structs
typedef struct
{
int extRefOutputEn; // default: 0
} sdrplay_api_RspDuoParamsT;
typedef struct
{
unsigned char resetGainUpdate; // default: 0
unsigned char resetRfUpdate; // default: 0
} sdrplay_api_RspDuo_ResetSlaveFlagsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
unsigned char tuner1AmNotchEnable; // default: 0
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
sdrplay_api_RspDuo_ResetSlaveFlagsT resetSlaveFlags;
} sdrplay_api_RspDuoTunerParamsT;
#endif //SDRPLAY_API_RSPduo_H

View File

@@ -1,46 +0,0 @@
#ifndef SDRPLAY_API_RSPDX_H
#define SDRPLAY_API_RSPDX_H
#include "sdrplay_api_tuner.h"
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_50 20 // Number of LNA states when using AM Port 2 between 12 and 50MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_50_60 25 // Number of LNA states when using AM Port 2 between 50 and 60MHz
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
#define RSPDX_NUM_LNA_STATES_DX 22 // Number of LNA states in DX path
// RSPdx parameter enums
typedef enum
{
sdrplay_api_RspDx_ANTENNA_A = 0,
sdrplay_api_RspDx_ANTENNA_B = 1,
sdrplay_api_RspDx_ANTENNA_C = 2,
} sdrplay_api_RspDx_AntennaSelectT;
typedef enum
{
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
} sdrplay_api_RspDx_HdrModeBwT;
// RSPdx parameter structs
typedef struct
{
unsigned char hdrEnable; // default: 0
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDxParamsT;
typedef struct
{
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
} sdrplay_api_RspDxTunerParamsT;
#endif //SDRPLAY_API_RSPDX_H

View File

@@ -1,21 +0,0 @@
#ifndef SDRPLAY_API_RX_CHANNEL_H
#define SDRPLAY_API_RX_CHANNEL_H
#include "sdrplay_api_tuner.h"
#include "sdrplay_api_control.h"
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
typedef struct
{
sdrplay_api_TunerParamsT tunerParams;
sdrplay_api_ControlParamsT ctrlParams;
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
} sdrplay_api_RxChannelParamsT;
#endif //SDRPLAY_API_RX_CHANNEL_H

View File

@@ -1,93 +0,0 @@
#ifndef SDRPLAY_API_TUNER_H
#define SDRPLAY_API_TUNER_H
#define MAX_BB_GR (59)
// Tuner parameter enums
typedef enum
{
sdrplay_api_BW_Undefined = 0,
sdrplay_api_BW_0_200 = 200,
sdrplay_api_BW_0_300 = 300,
sdrplay_api_BW_0_600 = 600,
sdrplay_api_BW_1_536 = 1536,
sdrplay_api_BW_5_000 = 5000,
sdrplay_api_BW_6_000 = 6000,
sdrplay_api_BW_7_000 = 7000,
sdrplay_api_BW_8_000 = 8000
} sdrplay_api_Bw_MHzT;
typedef enum
{
sdrplay_api_IF_Undefined = -1,
sdrplay_api_IF_Zero = 0,
sdrplay_api_IF_0_450 = 450,
sdrplay_api_IF_1_620 = 1620,
sdrplay_api_IF_2_048 = 2048
} sdrplay_api_If_kHzT;
typedef enum
{
sdrplay_api_LO_Undefined = 0,
sdrplay_api_LO_Auto = 1,
sdrplay_api_LO_120MHz = 2,
sdrplay_api_LO_144MHz = 3,
sdrplay_api_LO_168MHz = 4
} sdrplay_api_LoModeT;
typedef enum
{
sdrplay_api_EXTENDED_MIN_GR = 0,
sdrplay_api_NORMAL_MIN_GR = 20
} sdrplay_api_MinGainReductionT;
typedef enum
{
sdrplay_api_Tuner_Neither = 0,
sdrplay_api_Tuner_A = 1,
sdrplay_api_Tuner_B = 2,
sdrplay_api_Tuner_Both = 3,
} sdrplay_api_TunerSelectT;
// Tuner parameter structs
typedef struct
{
float curr;
float max;
float min;
} sdrplay_api_GainValuesT;
typedef struct
{
int gRdB; // default: 50
unsigned char LNAstate; // default: 0
unsigned char syncUpdate; // default: 0
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
sdrplay_api_GainValuesT gainVals; // output parameter
} sdrplay_api_GainT;
typedef struct
{
double rfHz; // default: 200000000.0
unsigned char syncUpdate; // default: 0
} sdrplay_api_RfFreqT;
typedef struct
{
unsigned char dcCal; // default: 3 (Periodic mode)
unsigned char speedUp; // default: 0 (No speedup)
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
} sdrplay_api_DcOffsetTunerT;
typedef struct
{
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
sdrplay_api_GainT gain;
sdrplay_api_RfFreqT rfFreq;
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
} sdrplay_api_TunerParamsT;
#endif //SDRPLAY_API_TUNER_H

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -16,76 +16,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
import org.beryx.runtime.RuntimeZipTask
import org.gradle.internal.jvm.Jvm
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import java.text.SimpleDateFormat
/**
* Instructions for building/compiling the sdrtrunk application.
*
* Prerequisites:
* Install and configure an OpenJDK version 27+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
* - Optional: install and configure Gradle 9.8+
*
* Scenario 1: run the application via gradle command line from the source code root directory:
* command: ./gradlew run
*
* Scenario 2: create an application release for the current operating system
* command: ./gradlew runtimeZipCurrent
* Note: release image is located in the /build/image/ directory
*
* Scenario 3: create releases for Linux and OSX operating systems using downloaded JDKs
* command: ./gradlew runtimeZipOthers
* Note: release images are located in the /build/image/ directory
*
* Scenario 4: create release for Windows operating system using downloaded JDK
* command: ./gradlew runtimeZipWindows
* Note: release image is located in the /build/image/ directory
*/
plugins {
id 'application'
id 'java'
id 'idea'
id 'org.beryx.runtime' version '2.0.1'
id 'org.beryx.runtime' version '1.12.7'
}
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
version = project.findProperty('projectVersion')
//Java 27 is required for this version of the Project Panama preview/incubator feature
java {
toolchain {
languageVersion = JavaLanguageVersion.of(27)
vendor = JvmVendorSpec.BELLSOFT
}
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
}
/**
* Verifies that the selected toolchain JDK includes the JavaFX modules (e.g. Bellsoft Liberica 'Full JDK').
*
* Gradle's toolchain spec can only select by language version, vendor and implementation, so it can't distinguish
* between the Liberica JDK packages that do and don't bundle JavaFX. This task fails the build with a clear message
* instead of allowing compilation to fail with missing javafx packages.
*/
tasks.register('verifyToolchainJavaFx') {
def jdkHome = javaToolchains.launcherFor(java.toolchain).map { it.metadata.installationPath.asFile }
version = '0.5.2'
sourceCompatibility = '17'
doLast {
def release = new File(jdkHome.get(), 'release')
if(!release.exists() || !release.text.contains('javafx.base')) {
throw new GradleException("The JDK at [${jdkHome.get()}] does not include the JavaFX modules. Install " +
"the Bellsoft Liberica JDK 27 'Full' package (includes LibericaFX), or use " +
"-Porg.gradle.java.installations.paths=<jdk path> to specify the JDK that Gradle should use.")
}
}
}
tasks.named('compileJava') {
dependsOn 'verifyToolchainJavaFx'
sourceSets {
main.java.srcDirs 'src/main'
test.java.srcDirs 'src/test'
}
test {
@@ -95,108 +43,66 @@ test {
dependencies {
// JUnit Tests
testImplementation(platform("org.junit:junit-bom:6.0.1"))
testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.1'
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.0')
testImplementation 'org.mockito:mockito-core:4.8.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
//Jitpack imports
implementation 'com.github.dnault:libresample4j:master'
implementation 'com.github.DSheirer.jmbe:api:v2.1.0'
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
//mavenCentral/jcenter imports
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.4'
implementation 'com.fazecast:jSerialComm:2.11.4'
implementation 'com.formdev:flatlaf:3.7.2'
implementation 'com.formdev:flatlaf-intellij-themes:3.7.2'
implementation 'ch.qos.logback:logback-classic:1.4.5'
implementation 'ch.qos.logback:logback-core:1.4.5'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
implementation 'com.fazecast:jSerialComm:2.9.3'
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
implementation 'com.github.wendykierp:JTransforms:3.2'
implementation 'com.google.code.gson:gson:2.14.0'
implementation 'com.google.guava:guava:33.6.0-jre'
implementation 'com.github.wendykierp:JTransforms:3.1'
implementation 'com.google.code.gson:gson:2.10'
implementation 'com.google.guava:guava:31.1-jre'
implementation 'com.jidesoft:jide-oss:3.6.18'
implementation 'com.miglayout:miglayout-swing:11.4.3'
implementation 'com.miglayout:miglayout-swing:11.0'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'commons-io:commons-io:2.22.0'
implementation 'commons-io:commons-io:2.11.0'
implementation 'eu.hansolo:charts:1.0.5'
implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jhydrasdr:1.1.3' //HydraSDR tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs
implementation 'io.github.dsheirer:usb4java:1.3.5' //Fork with Loader support for OSX Multi-Version
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
implementation 'javax.usb:usb-api:1.0.2'
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
implementation 'org.apache.commons:commons-compress:1.28.0'
implementation 'org.apache.commons:commons-lang3:3.20.0'
implementation 'org.apache.commons:commons-compress:1.21'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-csv:1.14.1'
implementation 'org.apache.mina:mina-core:2.2.9'
implementation 'org.apache.mina:mina-http:2.2.9'
implementation 'org.controlsfx:controlsfx:11.2.3'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'org.apache.mina:mina-core:2.2.1'
implementation 'org.apache.mina:mina-http:2.2.1'
implementation 'org.controlsfx:controlsfx:11.1.2'
implementation 'org.rauschig:jarchivelib:1.2.0'
implementation 'org.slf4j:slf4j-api:2.0.17'
implementation 'org.slf4j:slf4j-api:2.0.5'
implementation 'org.usb4java:libusb4java:1.3.0'
implementation 'org.usb4java:usb4java:1.3.0'
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
implementation 'pl.edu.icm:JLargeArrays:1.6'
}
def os = DefaultNativePlatform.currentOperatingSystem
/**
* This is needed for the JDK23+ vector API ... until it moves out of incubation or preview
* This is needed for the JDK17 vector API ... until it moves out of incubation
*/
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("--enable-preview") //Project Panama Vector API is still in incubator
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Project Panama Vector API is still in incubator
tasks.withType(JavaCompile) {
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
}
/**
* JVM Arguments for running the application on Windows.
*/
def jvmArgsWindows =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', //windows users for JIDE
'--add-modules=jdk.incubator.vector', //Project Panama vector API is still in incubator
'--enable-preview', //Project Panama Vector API is still in incubator
'--enable-native-access=ALL-UNNAMED', //Suppress unsafe warnings for FFM API Foreign Memory calls
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
'--sun-misc-unsafe-memory-access=allow', //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //SDRPlay API library path
/**
* JVM Arguments for running the application on Linux.
*/
def jvmArgsLinux =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Project Panama vector API is still in incubator
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
'--sun-misc-unsafe-memory-access=allow', //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
'--enable-native-access=ALL-UNNAMED'] //SDRPlay API library path
application {
mainClass = "io.github.dsheirer.gui.SDRTrunk"
if(os.isWindows()) {
applicationDefaultJvmArgs = jvmArgsWindows
}
else {
applicationDefaultJvmArgs = jvmArgsLinux
}
}
test {
if(os.isWindows()) {
jvmArgs = jvmArgsWindows
}
else {
jvmArgs = jvmArgsLinux
}
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
applicationDefaultJvmArgs =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
}
jar {
@@ -205,7 +111,7 @@ jar {
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
@@ -219,11 +125,6 @@ idea {
}
}
javadoc {
options.addStringOption('-add-modules', 'jdk.incubator.vector')
options.addStringOption('Xdoclint:none', '-quiet')
}
/**
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
*/
@@ -232,184 +133,96 @@ String targetLinuxX86_64 = 'linux-x86_64-v' + version
String targetOsxAarch64 = 'osx-aarch64-v' + version
String targetOsxX86_64 = 'osx-x86_64-v' + version
String targetWindowsX86_64 = 'windows-x86_64-v' + version
String targetWindowsAarch64 = 'windows-aarch64-v' + version
/**
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
* author's development environment, but can be changed to your environment if you want to use this build pattern.
*/
String jdk_base = '/home/denny/java_jdks/'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-25.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-25.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-25.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-25.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-25.0.1-full'
String jdk_windows_aarch64 = jdk_base + 'windows-arm64/jdk-25.0.1-full'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
/**
* Download URLs to download the JDK as part of the gradle build packaging process
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
* including this option: '-PdownloadJdks=true'
*/
def jdk_download_base = "https://download.bell-sw.com/java/27+36/bellsoft-jdk27+36-"
def jdk_download_suffix = "-full.tar.gz"
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
def jdk_download_osx_x86_64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
def jdk_download_osx_aarch64 = jdk_download_base + "macos-aarch64" + jdk_download_suffix
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
def jdk_download_windows_aarch64 = jdk_download_base + "windows-aarch64-full.zip"
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
/**
* Configures the runtime zip task with additional options/settings.
* @param rt task to configure
* @param jvmArgs to use (windows or linux)
* @return configured task.
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
*
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
*
* >: gradle runtimeZip
*
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
*
* >: gradle runtimeZip -PdownloadJdks=true
*/
def configure(RuntimeZipTask rt, List<String> jvmArgs) {
//Use the correct set of JVM arguments
rt.extension.launcherData.get().jvmArgs = jvmArgs
runtime {
//Build by automatically downloading JDKs for the OS and CPU targets
if(downloadJdks) {
println("Building all runtime images")
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
targetPlatform(targetLinuxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
}
targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
}
targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
}
targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
}
targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
}
}
//Build using pre-downloaded JDKs
else if(hasTargetJdks) {
println("Building all runtime images using pre-downloaded JDKs")
if(file(jdk_linux_aarch64).exists()) {
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
}
if(file(jdk_linux_x86_64).exists()) {
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
}
if(file(jdk_osx_aarch64).exists()) {
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
}
if(file(jdk_osx_x86_64).exists()) {
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
}
if(file(jdk_windows_x86_64).exists()) {
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
}
}
//Build using locally installed JDK only
else {
println("Building a single runtime image using locally installed JDK")
}
//Additional modules to include with auto-detected modules.
//jdk.charsets - NXDN BIG5 encoding support
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
//jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.accessibility is used with assistive technologies like screen readers
//java.management for JVM resource monitoring
rt.extension.addModules('jdk.charsets', 'jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management')
modules = ['jdk.crypto.ec','jdk.accessibility']
//Use auto-detected modules and 'add' any specified modules.
rt.extension.additive.set(true)
additive = true
//Recommended options for reducing/shrinking runtime target image size
rt.extension.addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
// rt.extension.imageDir=layout.buildDirectory.dir('image')
rt.extension.imageZip.set(file(layout.buildDirectory.dir('image').get().file('sdr-trunk.zip')))
}
/**
* Creates a release package for the current operating system.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipCurrent
* Usage: gradlew runtimeZipCurrent (windows - using gradlew)
* Usage: ./gradlew runtimeZipCurrent (linux - using gradlew)
*/
tasks.register('runtimeZipCurrent', RuntimeZipTask) {rt ->
def arch = System.properties.getProperty("os.arch")
def javaHome = Jvm.current().getJavaHome().toString()
if(os.isWindows()) {
if(arch == "amd64") {
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
}
else if(arch == "aarch64") {
rt.extension.targetPlatform(targetWindowsAarch64, javaHome)
}
configure(rt, jvmArgsWindows)
}
else if(os.isLinux())
{
if(arch == "amd64") {
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
}
else if(arch == "aarch64") {
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
}
else if(os.isMacOsX())
{
if(arch == "amd64") {
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
}
else if(arch == "aarch64") {
rt.extension.targetPlatform(targetOsxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
}
}
/**
* Creates a Windows release package.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipWindows
* Usage: gradlew runtimeZipWindows (windows - using gradlew)
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
*/
tasks.register('runtimeZipWindows', RuntimeZipTask) {rt ->
//Windows aarch64
if(file(jdk_windows_aarch64).exists()) {
rt.extension.targetPlatform(targetWindowsAarch64, jdk_windows_aarch64, [])
}
else {
rt.extension.targetPlatform(targetWindowsAarch64) {
jdkHome = jdkDownload(jdk_download_windows_aarch64)
}
}
//Windows x86-64
if(file(jdk_windows_x86_64).exists()) {
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
}
else {
rt.extension.targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload(jdk_download_windows_x86_64)
}
}
configure(rt, jvmArgsWindows)
}
/**
* Creates Linux & OSX release packages.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipOthers
* Usage: gradlew runtimeZipOthers (windows - using gradlew)
* Usage: ./gradlew runtimeZipOthers (linux - using gradlew)
*/
tasks.register('runtimeZipOthers', RuntimeZipTask) {rt ->
//Linux aarch64
if(file(jdk_linux_aarch64).exists()) {
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
}
else {
rt.extension.targetPlatform(targetLinuxAarch64) {
jdkHome = jdkDownload(jdk_download_linux_aarch64)
}
}
//Linux x86-64
if(file(jdk_linux_x86_64).exists()) {
rt.extension.targetPlatform(targetLinuxX86_64, jdk_linux_x86_64, [])
}
else {
rt.extension.targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload(jdk_download_linux_x86_64)
}
}
//OSX aarch64
if(file(jdk_osx_aarch64).exists()) {
rt.extension.targetPlatform(targetOsxAarch64, jdk_osx_aarch64, [])
}
else {
rt.extension.targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload(jdk_download_osx_aarch64)
}
}
//OSX x86-64
if(file(jdk_osx_x86_64).exists()) {
rt.extension.targetPlatform(targetOsxX86_64, jdk_osx_x86_64, [])
}
else {
rt.extension.targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload(jdk_download_osx_x86_64)
}
}
configure(rt, jvmArgsLinux)
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
file("$buildDir/image/sdr-trunk-" + version + ".zip")
}

View File

@@ -1,31 +0,0 @@
#
# ******************************************************************************
# Copyright (C) 2014-2026 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/>
# *****************************************************************************
#
# Version naming conventions:
# Alpha: maj.min.patch-alpha-build
# Beta: maj.min.patch-beta-build
# Release: maj.min.patch
#
# Example: 0.6.0-beta-5
#
# Note: this is so that the file names for the resultant release build products match the GitLab release asset
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
projectVersion=0.6.2-beta-1

Binary file not shown.

View File

@@ -1,9 +1,25 @@
#
# ******************************************************************************
# Copyright (C) 2014-2022 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/>
# *****************************************************************************
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

65
gradlew vendored
View File

@@ -1,26 +1,27 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 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.
#
# https://www.apache.org/licenses/LICENSE-2.0
# 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.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
# *****************************************************************************
#
##############################################################################
#
# gradlew start up script for POSIX generated by Gradle.
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +30,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh gradlew
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +58,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -85,8 +86,10 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,6 +117,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -132,13 +136,10 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -146,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -154,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -171,6 +172,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -198,19 +200,16 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.

58
gradlew.bat vendored
View File

@@ -13,18 +13,16 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem gradlew startup script for Windows
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -45,13 +43,13 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
"%COMSPEC%" /c exit 1
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -59,24 +57,36 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
"%COMSPEC%" /c exit 1
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,28 +1 @@
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' }
//jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jHydraSDR/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jhydrasdr' }
}
//jBladeRF is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jBladeRF/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jbladerf' }
}
}
}
rootProject.name = 'sdr-trunk'

View File

@@ -1,81 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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 com.sun.java.swing.plaf.windows;
import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
/**
* No-op stub for the JDK's Windows look-and-feel.
*
* <p>JIDE 3.6.18's {@code LookAndFeelFactory} contains compile-time
* {@code instanceof com.sun.java.swing.plaf.windows.WindowsLookAndFeel} checks throughout its
* dispatch logic. On Linux and macOS JDK builds that class is genuinely absent from
* {@code java.desktop}, so the JVM throws {@link NoClassDefFoundError} the first time JIDE's
* dispatch code executes the {@code instanceof} bytecode. That happens during
* {@link com.jidesoft.swing.JideSplitPane#updateUI()} on every LAF change.
*
* <p>This stub lives in the classpath so the class reference resolves on non-Windows JDKs.
* On Windows JDK builds the real {@code WindowsLookAndFeel} is loaded from the
* {@code java.desktop} module via the boot class loader and shadows this class.
*
* <p>The stub reports itself as not native and not supported - JIDE never instantiates this
* class, it only uses it for {@code instanceof} comparisons against the active LAF. Since
* sdrtrunk never installs this LAF, the {@code instanceof} check always returns false and
* JIDE proceeds to its next branch.
*/
public class WindowsLookAndFeel extends LookAndFeel
{
@Override
public String getName()
{
return "Windows (stub)";
}
@Override
public String getID()
{
return "WindowsStub";
}
@Override
public String getDescription()
{
return "Compatibility stub for JIDE's instanceof checks on non-Windows JDKs.";
}
@Override
public boolean isNativeLookAndFeel()
{
return false;
}
@Override
public boolean isSupportedLookAndFeel()
{
return false;
}
@Override
public UIDefaults getDefaults()
{
return new UIDefaults();
}
}

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.
* * ******************************************************************************
* * Copyright (C) 2014-2020 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 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.alias;
@@ -27,22 +30,13 @@ import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
@@ -52,6 +46,13 @@ import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* Alias provides an aliasing (e.g. name, color, etc) container that is linked to multiple alias identifiers and
* provides a corresponding set of actions related to the alias.
@@ -73,7 +74,6 @@ public class Alias
private StringProperty mName = new SimpleStringProperty();
private ObservableList<AliasID> mAliasIDs = FXCollections.observableArrayList();
private ObservableList<AliasAction> mAliasActions = FXCollections.observableArrayList();
private ObjectProperty<StreamAsTalkgroup> mStreamTalkgroupAlias = new SimpleObjectProperty<>();
/**
* Constructs an instance and sets the specified name.
@@ -180,12 +180,6 @@ public class Alias
return mColor;
}
@JsonIgnore
public ObjectProperty streamTalkgroupAliasProperty()
{
return mStreamTalkgroupAlias;
}
/**
* Icon name property
*/
@@ -235,24 +229,6 @@ public class Alias
mName.set(name);
}
/**
* Alias name
*/
@JacksonXmlProperty(isAttribute = true, localName = "stream_talkgroup_alias")
public StreamAsTalkgroup getStreamTalkgroupAlias()
{
return mStreamTalkgroupAlias.get();
}
/**
* Sets the stream as talkgroup value that will be used in the TO field for streamed audio calls.
* @param streamTalkgroupAlias with a talkgroup value.
*/
public void setStreamTalkgroupAlias(StreamAsTalkgroup streamTalkgroupAlias)
{
mStreamTalkgroupAlias.set(streamTalkgroupAlias);
}
/**
* Alias list name for the alias list that this alias belongs to
*/
@@ -734,7 +710,6 @@ public class Alias
{
return (Alias a) -> new Observable[] {a.recordableProperty(), a.streamableProperty(), a.colorProperty(),
a.aliasListNameProperty(), a.groupProperty(), a.iconNameProperty(), a.nameProperty(), a.aliasIds(),
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty(), a.priorityProperty(),
a.streamTalkgroupAliasProperty()};
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty()};
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -19,36 +19,38 @@
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadio;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadioRange;
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class AliasFactory
{
private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class);
@@ -62,11 +64,6 @@ public class AliasFactory
BroadcastChannel copyBroadcast = new BroadcastChannel();
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
return copyBroadcast;
case DCS:
Dcs originalDcs = (Dcs)id;
Dcs copyDcs = new Dcs();
copyDcs.setDCSCode(originalDcs.getDCSCode());
return copyDcs;
case ESN:
Esn originalESN = (Esn)id;
Esn copyESN = new Esn();
@@ -83,43 +80,6 @@ public class AliasFactory
Min copyMin = new Min();
copyMin.setMin(originalMin.getMin());
return copyMin;
case NXDN_TYPE_D_RADIO_ID:
NXDNTypeDRadio originalTypeDRadio = (NXDNTypeDRadio)id;
NXDNTypeDRadio copyTypeDRadio = new NXDNTypeDRadio(originalTypeDRadio.getHomeRepeater(),
originalTypeDRadio.getIdentifier());
copyTypeDRadio.setOverlap(originalTypeDRadio.overlapProperty().get());
return copyTypeDRadio;
case NXDN_TYPE_D_RADIO_ID_RANGE:
NXDNTypeDRadioRange originalTypeDRadioRange = (NXDNTypeDRadioRange)id;
NXDNTypeDRadioRange copyTypeDRadioRange = new NXDNTypeDRadioRange(originalTypeDRadioRange.getHomeRepeater(),
originalTypeDRadioRange.getMinIdentifier(), originalTypeDRadioRange.getMaxIdentifier());
copyTypeDRadioRange.setOverlap(originalTypeDRadioRange.overlapProperty().get());
return copyTypeDRadioRange;
case NXDN_TYPE_D_TALKGROUP:
NXDNTypeDTalkgroup originalTypeDTalkgroup = (NXDNTypeDTalkgroup)id;
NXDNTypeDTalkgroup copyTypeDTalkgroup = new NXDNTypeDTalkgroup(originalTypeDTalkgroup.getHomeRepeater(),
originalTypeDTalkgroup.getIdentifier());
copyTypeDTalkgroup.setOverlap(originalTypeDTalkgroup.overlapProperty().get());
return copyTypeDTalkgroup;
case NXDN_TYPE_D_TALKGROUP_RANGE:
NXDNTypeDTalkgroupRange originalTypeDTalkgroupRange = (NXDNTypeDTalkgroupRange)id;
NXDNTypeDTalkgroupRange copyTypeDTalkgroupRange = new NXDNTypeDTalkgroupRange(
originalTypeDTalkgroupRange.getHomeRepeater(), originalTypeDTalkgroupRange.getMinIdentifier(),
originalTypeDTalkgroupRange.getMaxIdentifier());
copyTypeDTalkgroupRange.setOverlap(originalTypeDTalkgroupRange.overlapProperty().get());
return copyTypeDTalkgroupRange;
case P25_FULLY_QUALIFIED_RADIO_ID:
P25FullyQualifiedRadio originalP25 = (P25FullyQualifiedRadio) id;
P25FullyQualifiedRadio copyP25 = new P25FullyQualifiedRadio(originalP25.getWacn(),
originalP25.getSystem(), originalP25.getValue());
copyP25.setOverlap(originalP25.overlapProperty().get());
return copyP25;
case P25_FULLY_QUALIFIED_TALKGROUP:
P25FullyQualifiedTalkgroup originalFqt = (P25FullyQualifiedTalkgroup) id;
P25FullyQualifiedTalkgroup copyFqt = new P25FullyQualifiedTalkgroup(originalFqt.getWacn(),
originalFqt.getSystem(), originalFqt.getValue());
copyFqt.setOverlap(originalFqt.overlapProperty().get());
return copyFqt;
case PRIORITY:
Priority originalPriority = (Priority)id;
Priority copyPriority = new Priority();
@@ -227,7 +187,6 @@ public class AliasFactory
copy.setGroup(original.getGroup());
copy.setColor(original.getColor());
copy.setIconName(original.getIconName());
copy.setRecordable(original.isRecordable());
return copy;
}
@@ -275,4 +234,65 @@ public class AliasFactory
return copy;
}
public static AliasID getAliasID(AliasIDType type)
{
switch(type)
{
case BROADCAST_CHANNEL:
return new BroadcastChannel();
case ESN:
return new Esn();
case FLEETSYNC:
return new FleetsyncID();
case LTR_NET_UID:
return new UniqueID();
case LOJACK:
return new LoJackFunctionAndID();
case MDC1200:
return new MDC1200ID();
case MIN:
return new Min();
case MPT1327:
return new MPT1327ID();
case NON_RECORDABLE:
return new NonRecordable();
case PRIORITY:
return new Priority();
case RADIO_ID:
return new Radio();
case RADIO_ID_RANGE:
return new RadioRange();
case RECORD:
return new Record();
case TALKGROUP:
return new Talkgroup();
case TALKGROUP_RANGE:
return new TalkgroupRange();
case SITE:
return new SiteID();
case STATUS:
return new UserStatusID();
case LEGACY_TALKGROUP:
return new LegacyTalkgroupID();
default:
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
}
}
public static AliasAction getAliasAction(AliasActionType type)
{
switch(type)
{
case BEEP:
return new BeepAction();
case CLIP:
return new ClipAction();
case SCRIPT:
return new ScriptAction();
default:
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -21,34 +21,32 @@ package io.github.dsheirer.alias;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
import io.github.dsheirer.identifier.esn.ESNIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.module.decode.squelch.dcs.DCSCode;
import io.github.dsheirer.protocol.Protocol;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -59,10 +57,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
@@ -73,7 +67,6 @@ public class AliasList
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
private Map<String,Alias> mESNMap = new HashMap<>();
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
@@ -134,7 +127,6 @@ public class AliasList
switch(id.getType())
{
case TALKGROUP:
case NXDN_TYPE_D_TALKGROUP:
Talkgroup talkgroup = (Talkgroup)id;
TalkgroupAliasList talkgroupAliasList = mTalkgroupProtocolMap.get(talkgroup.getProtocol());
@@ -148,7 +140,6 @@ public class AliasList
talkgroupAliasList.add(talkgroup, alias);
break;
case TALKGROUP_RANGE:
case NXDN_TYPE_D_TALKGROUP_RANGE:
TalkgroupRange talkgroupRange = (TalkgroupRange)id;
TalkgroupAliasList talkgroupRangeAliasList = mTalkgroupProtocolMap.get(talkgroupRange.getProtocol());
@@ -161,34 +152,7 @@ public class AliasList
talkgroupRangeAliasList.add(talkgroupRange, alias);
break;
case P25_FULLY_QUALIFIED_RADIO_ID:
P25FullyQualifiedRadio qualifiedRadio = (P25FullyQualifiedRadio) id;
RadioAliasList p25RadioAliasList = mRadioProtocolMap.get(qualifiedRadio.getProtocol());
if(p25RadioAliasList == null)
{
p25RadioAliasList = new RadioAliasList();
mRadioProtocolMap.put(qualifiedRadio.getProtocol(), p25RadioAliasList);
}
p25RadioAliasList.add(qualifiedRadio, alias);
break;
case P25_FULLY_QUALIFIED_TALKGROUP:
P25FullyQualifiedTalkgroup qualifiedTalkgroup = (P25FullyQualifiedTalkgroup) id;
TalkgroupAliasList p25TalkgroupAliasList = mTalkgroupProtocolMap.get(qualifiedTalkgroup.getProtocol());
if(p25TalkgroupAliasList == null)
{
p25TalkgroupAliasList = new TalkgroupAliasList();
mTalkgroupProtocolMap.put(qualifiedTalkgroup.getProtocol(), p25TalkgroupAliasList);
}
p25TalkgroupAliasList.add(qualifiedTalkgroup, alias);
break;
case RADIO_ID:
case NXDN_TYPE_D_RADIO_ID:
Radio radio = (Radio)id;
RadioAliasList radioAliasList = mRadioProtocolMap.get(radio.getProtocol());
@@ -202,7 +166,6 @@ public class AliasList
radioAliasList.add(radio, alias);
break;
case RADIO_ID_RANGE:
case NXDN_TYPE_D_RADIO_ID_RANGE:
RadioRange radioRange = (RadioRange)id;
RadioAliasList radioRangeAliasList = mRadioProtocolMap.get(radioRange.getProtocol());
@@ -215,12 +178,6 @@ public class AliasList
radioRangeAliasList.add(radioRange, alias);
break;
case DCS:
if(id instanceof Dcs dcs)
{
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
}
break;
case ESN:
String esn = ((Esn)id).getEsn();
@@ -422,8 +379,6 @@ public class AliasList
}
break;
case PATCH_GROUP:
List<Alias> aliases = new ArrayList<>();
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
PatchGroup patchGroup = patchGroupIdentifier.getValue();
@@ -431,6 +386,8 @@ public class AliasList
if(patchGroupAliasList != null)
{
List<Alias> aliases = new ArrayList<>();
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
if(alias != null)
@@ -438,36 +395,19 @@ public class AliasList
aliases.add(alias);
}
for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers())
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
{
Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup);
Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup);
if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias))
if(patchedAlias != null && !aliases.contains(patchedAlias))
{
aliases.add(patchedTalkgroupAlias);
aliases.add(patchedAlias);
}
}
return aliases;
}
if(patchGroup.hasPatchedRadios())
{
RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol());
if(radioAliasList != null)
{
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
{
Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio);
if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias))
{
aliases.add(patchedRadioAlias);
}
}
}
}
return aliases;
break;
case RADIO:
RadioIdentifier radio = (RadioIdentifier)identifier;
@@ -499,9 +439,9 @@ public class AliasList
}
break;
case TONE:
if(identifier instanceof ToneIdentifier toneIdentifier)
if(identifier instanceof ToneIdentifier)
{
ToneSequence toneSequence = toneIdentifier.getValue();
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
if(toneSequence != null && toneSequence.hasTones())
{
@@ -514,15 +454,6 @@ public class AliasList
}
}
}
else if(identifier instanceof DCSIdentifier dcsIdentifier)
{
DCSCode dcsCode = dcsIdentifier.getValue();
if(dcsCode != null)
{
return toList(mDCSCodeAliasMap.get(dcsCode));
}
}
break;
}
}
@@ -534,7 +465,9 @@ public class AliasList
{
if(alias != null)
{
return Collections.singletonList(alias);
List<Alias> aliases = new ArrayList<>();
aliases.add(alias);
return aliases;
}
return Collections.emptyList();
@@ -657,7 +590,6 @@ public class AliasList
*/
public class TalkgroupAliasList
{
private Map<String,Alias> mFullyQualifiedTalkgroupAliasMap = new HashMap<>();
private Map<Integer,Alias> mTalkgroupAliasMap = new TreeMap<>();
private Map<TalkgroupRange, Alias> mTalkgroupRangeAliasMap = new HashMap<>();
@@ -667,13 +599,6 @@ public class AliasList
public Alias getAlias(TalkgroupIdentifier identifier)
{
//Attempt to do a fully qualified identifier match only
if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti)
{
return mFullyQualifiedTalkgroupAliasMap.get(fqti.getFullyQualifiedTalkgroupAddress());
}
//Attempt to match the talkgroup value
int value = identifier.getValue();
Alias mapValue = mTalkgroupAliasMap.get(value);
@@ -682,7 +607,6 @@ public class AliasList
return mapValue;
}
//Alternatively, match the talkgroup to any talkgroup ranges
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
{
if(entry.getKey().contains(value))
@@ -696,55 +620,26 @@ public class AliasList
public void add(Talkgroup talkgroup, Alias alias)
{
if(talkgroup instanceof P25FullyQualifiedTalkgroup fqt)
//Detect talkgroup collisions and set overlap flag for both
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
{
//Detect collisions
if(mFullyQualifiedTalkgroupAliasMap.containsKey(fqt.getHashKey()))
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
if(!existing.equals(alias))
{
Alias existing = mFullyQualifiedTalkgroupAliasMap.get(fqt.getHashKey());
talkgroup.setOverlap(true);
if(!existing.equals(alias))
for(AliasID aliasID: existing.getAliasIdentifiers())
{
fqt.setOverlap(true);
for(AliasID aliasID: existing.getAliasIdentifiers())
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
{
if(aliasID instanceof P25FullyQualifiedTalkgroup existingFqt &&
existingFqt.getHashKey().contentEquals(fqt.getHashKey()))
{
aliasID.setOverlap(true);
}
aliasID.setOverlap(true);
}
}
}
else
{
mFullyQualifiedTalkgroupAliasMap.put(fqt.getHashKey(), alias);
}
}
else
{
//Detect talkgroup collisions and set overlap flag for both
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
{
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
if(!existing.equals(alias))
{
talkgroup.setOverlap(true);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
{
aliasID.setOverlap(true);
}
}
}
}
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
}
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
}
public void add(TalkgroupRange talkgroupRange, Alias alias)
@@ -777,7 +672,6 @@ public class AliasList
*/
public class RadioAliasList
{
private Map<String,Alias> mFullyQualifiedRadioAliasMap = new HashMap<>();
private Map<Integer,Alias> mRadioAliasMap = new TreeMap<>();
private Map<RadioRange, Alias> mRadioRangeAliasMap = new HashMap<>();
@@ -787,13 +681,6 @@ public class AliasList
public Alias getAlias(RadioIdentifier identifier)
{
//Match fully qualified identifier only.
if(identifier instanceof FullyQualifiedRadioIdentifier fqri)
{
return mFullyQualifiedRadioAliasMap.get(fqri.getFullyQualifiedRadioAddress());
}
//Attempt to match against the radio identifier
int value = identifier.getValue();
Alias mapValue = mRadioAliasMap.get(value);
@@ -802,7 +689,6 @@ public class AliasList
return mapValue;
}
//Alternatively, attempt to match the radio address against any radio ranges.
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
{
if(entry.getKey().contains(value))
@@ -816,55 +702,26 @@ public class AliasList
public void add(Radio radio, Alias alias)
{
if(radio instanceof P25FullyQualifiedRadio fqr)
//Detect collisions
if(mRadioAliasMap.containsKey(radio.getValue()))
{
//Detect collisions
if(mFullyQualifiedRadioAliasMap.containsKey(fqr.getHashKey()))
Alias existing = mRadioAliasMap.get(radio.getValue());
if(!existing.equals(alias))
{
Alias existing = mFullyQualifiedRadioAliasMap.get(fqr.getHashKey());
radio.setOverlap(true);
if(!existing.equals(alias))
for(AliasID aliasID: existing.getAliasIdentifiers())
{
fqr.setOverlap(true);
for(AliasID aliasID: existing.getAliasIdentifiers())
if(aliasID instanceof Radio && ((Radio)aliasID).getValue() == radio.getValue())
{
if(aliasID instanceof P25FullyQualifiedRadio existingFqr &&
existingFqr.getHashKey().contentEquals(fqr.getHashKey()))
{
aliasID.setOverlap(true);
}
aliasID.setOverlap(true);
}
}
}
else
{
mFullyQualifiedRadioAliasMap.put(fqr.getHashKey(), alias);
}
}
else
{
//Detect collisions
if(mRadioAliasMap.containsKey(radio.getValue()))
{
Alias existing = mRadioAliasMap.get(radio.getValue());
if(!existing.equals(alias))
{
radio.setOverlap(true);
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof Radio existingRadio && (existingRadio.getValue() == radio.getValue()))
{
aliasID.setOverlap(true);
}
}
}
}
mRadioAliasMap.put(radio.getValue(), alias);
}
mRadioAliasMap.put(radio.getValue(), alias);
}
public void add(RadioRange radioRange, Alias alias)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -23,17 +23,18 @@ import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
@@ -63,69 +64,6 @@ public class AliasModel
return mAliasListNames;
}
/**
* Clears and reloads the list of alias list names from the current set of aliases.
*/
public void refreshAliasListNames()
{
mAliasListNames.clear();
for(Alias alias : mAliases)
{
String aliasListName = alias.getAliasListName();
if(aliasListName != null && !aliasListName.isEmpty() && !mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
}
/**
* Renames the alias list across the set of aliases.
* @param oldName currently used by the alias
* @param newName to apply to the alias
*/
public void renameAliasList(String oldName, String newName)
{
if(oldName == null || oldName.isEmpty() || newName == null || newName.isEmpty())
{
return;
}
mAliases.stream().filter(alias -> alias.getAliasListName().equals(oldName)).forEach(alias -> alias.setAliasListName(newName));
}
/**
* Deletes any aliases that have the alias list name
* @param aliasListName to delete
*/
public void deleteAliasList(String aliasListName)
{
if(aliasListName == null || aliasListName.isEmpty())
{
return;
}
mAliases.removeIf(alias -> alias.getAliasListName().equals(aliasListName));
mAliasListMap.remove(aliasListName);
}
/**
* Adds the list of alias list names to the currently managed set of alias list names.
* @param aliasListNames to add
*/
public void addAliasListNames(List<String> aliasListNames)
{
for(String aliasListName : aliasListNames)
{
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
}
/**
* Unmodifiable list of all aliases currently in the model
*/
@@ -246,8 +184,10 @@ public class AliasModel
*/
public void addAliases(List<Alias> aliases)
{
removeAliases(aliases);
mAliases.addAll(aliases);
for(Alias alias : aliases)
{
addAlias(alias);
}
}
/**
@@ -301,26 +241,6 @@ public class AliasModel
}
}
/**
* Removes the list of aliases from this model and any alias lists that might contain each alias.
* @param aliases
*/
public void removeAliases(List<Alias> aliases)
{
if(aliases != null && !aliases.isEmpty())
{
mAliases.removeAll(aliases);
for(Alias alias: aliases)
{
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).removeAlias(alias);
}
}
}
}
/**
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
* @param aliasListName to search

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.
* * ******************************************************************************
* * 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 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.alias.id;
@@ -23,7 +26,6 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
@@ -35,18 +37,11 @@ import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadio;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadioRange;
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
@@ -59,7 +54,6 @@ import javafx.util.Callback;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
@@ -68,18 +62,11 @@ import javafx.util.Callback;
@JsonSubTypes.Type(value = Min.class, name = "min"),
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
@JsonSubTypes.Type(value = NXDNTypeDRadio.class, name = "nxdnTypeDRadio"),
@JsonSubTypes.Type(value = NXDNTypeDRadioRange.class, name = "nxdnTypeDRadioRange"),
@JsonSubTypes.Type(value = NXDNTypeDTalkgroup.class, name = "nxdnTypeDTalkgroup"),
@JsonSubTypes.Type(value = NXDNTypeDTalkgroupRange.class, name = "nxdnTypeDTalkgroupRange"),
@JsonSubTypes.Type(value = P25FullyQualifiedRadio.class, name = "p25FullyQualifiedRadio"),
@JsonSubTypes.Type(value = P25FullyQualifiedTalkgroup.class, name = "p25FullyQualifiedTalkgroup"),
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
@JsonSubTypes.Type(value = Record.class, name = "record"),
@JsonSubTypes.Type(value = SiteID.class, name = "siteID"),
@JsonSubTypes.Type(value = StreamAsTalkgroup.class, name = "streamAsTalkgroup"),
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
@JsonSubTypes.Type(value = TonesID.class, name = "tones"),

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.
* * ******************************************************************************
* * 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 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.alias.id;
@@ -23,18 +26,11 @@ import java.util.EnumSet;
public enum AliasIDType
{
BROADCAST_CHANNEL("Audio Broadcast Channel"),
DCS("Digital Coded Squelch (DCS)"),
ESN("ESN"),
INVERT("Audio Inversion"),
LOJACK("LoJack"),
LTR_NET_UID("LTR-Net UID"),
MIN("Passport MIN"),
NXDN_TYPE_D_RADIO_ID("NXDN Type-D Radio ID"),
NXDN_TYPE_D_RADIO_ID_RANGE("NXDN Type-D Radio ID Range"),
NXDN_TYPE_D_TALKGROUP("NXDN Type-D Talkgroup"),
NXDN_TYPE_D_TALKGROUP_RANGE("NXDN Type-D Talkgroup Range"),
P25_FULLY_QUALIFIED_RADIO_ID("P25 Fully Qualified Radio ID"),
P25_FULLY_QUALIFIED_TALKGROUP("P25 Fully Qualified Talkgroup"),
PRIORITY("Audio Priority"),
RADIO_ID("Radio ID"),
RADIO_ID_RANGE("Radio ID Range"),
@@ -62,8 +58,7 @@ public enum AliasIDType
//Values used by the View-By alias editor
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
NXDN_TYPE_D_RADIO_ID, NXDN_TYPE_D_RADIO_ID_RANGE, NXDN_TYPE_D_TALKGROUP, NXDN_TYPE_D_TALKGROUP_RANGE,
P25_FULLY_QUALIFIED_RADIO_ID, P25_FULLY_QUALIFIED_TALKGROUP, UNIT_STATUS, STATUS, TONES);
UNIT_STATUS, STATUS, TONES);
public String toString()
{

View File

@@ -1,46 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.alias.id;
/**
* NXDN Type-D alias identifier composed of a home repeater and a radio or talkgroup identifier value.
*/
public interface INXDNTypeD
{
/**
* Home repeater for the identifier
*/
int getHomeRepeater();
/**
* Sets the home repeater for the identifier
*/
void setHomeRepeater(int homeRepeater);
/**
* Radio or talkgroup value, without the home repeater.
*/
int getIdentifier();
/**
* Sets the radio or talkgroup value, without the home repeater.
*/
void setIdentifier(int identifier);
}

View File

@@ -1,56 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.alias.id;
/**
* NXDN Type-D alias identifier range of radio or talkgroup values that share a common home repeater.
*/
public interface INXDNTypeDRange
{
/**
* Home repeater for the range
*/
int getHomeRepeater();
/**
* Sets the home repeater for the range
*/
void setHomeRepeater(int homeRepeater);
/**
* Minimum radio or talkgroup value, without the home repeater.
*/
int getMinIdentifier();
/**
* Sets the minimum radio or talkgroup value, without the home repeater.
*/
void setMinIdentifier(int identifier);
/**
* Maximum radio or talkgroup value, without the home repeater.
*/
int getMaxIdentifier();
/**
* Sets the maximum radio or talkgroup value, without the home repeater.
*/
void setMaxIdentifier(int identifier);
}

View File

@@ -1,111 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.alias.id.dcs;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.module.decode.squelch.dcs.DCSCode;
/**
* Digital Coded Squelch (DCS) tone identifier
*/
public class Dcs extends AliasID implements Comparable<Dcs>
{
private DCSCode mDCSCode;
@Override
public AliasIDType getType()
{
return AliasIDType.DCS;
}
@Override
public boolean matches(AliasID id)
{
if(isValid() && id instanceof Dcs other)
{
return other.isValid() && getDCSCode().equals(other.getDCSCode());
}
return false;
}
/**
* DCS code
* @return DCS code or null.
*/
@JacksonXmlProperty(isAttribute = true, localName = "code")
public DCSCode getDCSCode()
{
return mDCSCode;
}
/**
* Sets the DCS code value.
* @param dcsCode to set
*/
public void setDCSCode(DCSCode dcsCode)
{
mDCSCode = dcsCode;
updateValueProperty();
}
@Override
public boolean isValid()
{
return mDCSCode != null;
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@Override
public String toString()
{
if(isValid())
{
return getDCSCode().toString();
}
return "DCS-Invalid - No Tone Selected";
}
@Override
public int compareTo(Dcs o)
{
if(isValid())
{
if(o.isValid())
{
return getDCSCode().compareTo(o.getDCSCode());
}
else
{
return 1;
}
}
return -1;
}
}

View File

@@ -1,96 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.alias.id.radio;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D radio. The value is the over-the-air 16-bit identifier that combines the home repeater and radio so that
* it matches the value of an NXDN Type-D radio identifier.
*/
public class NXDNTypeDRadio extends Radio implements INXDNTypeD
{
public NXDNTypeDRadio()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the radio
* @param radio value, without the home repeater
*/
public NXDNTypeDRadio(int homeRepeater, int radio)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, radio));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_RADIO_ID;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getValue());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, getIdentifier()));
}
@JsonIgnore
@Override
public int getIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getValue());
}
@Override
public void setIdentifier(int identifier)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Radio ID:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,124 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.alias.id.radio;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D radio range for a single home repeater. The min and max values are the over-the-air 16-bit identifiers
* that combine the home repeater and radio.
*/
public class NXDNTypeDRadioRange extends RadioRange implements INXDNTypeDRange
{
public NXDNTypeDRadioRange()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the range
* @param minRadio value, without the home repeater
* @param maxRadio value, without the home repeater
*/
public NXDNTypeDRadioRange(int homeRepeater, int minRadio, int maxRadio)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, minRadio),
NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, maxRadio));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_RADIO_ID_RANGE;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMinRadio());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
int min = getMinIdentifier();
int max = getMaxIdentifier();
setMinRadio(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, min));
setMaxRadio(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, max));
}
@JsonIgnore
@Override
public int getMinIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMinRadio());
}
@Override
public void setMinIdentifier(int identifier)
{
setMinRadio(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@JsonIgnore
@Override
public int getMaxIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMaxRadio());
}
@Override
public void setMaxIdentifier(int identifier)
{
setMaxRadio(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
/**
* Valid when the base range is valid and both ends of the range use the same home repeater.
*/
@Override
public boolean isValid()
{
return super.isValid() && getHomeRepeater() == NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMaxRadio());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Radio ID Range:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMinIdentifier(), true));
sb.append(" to ").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMaxIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,127 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.alias.id.radio;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.protocol.Protocol;
/**
* Fully qualified radio. Note: this is only for P25.
*/
public class P25FullyQualifiedRadio extends Radio
{
private int mWacn;
private int mSystem;
public P25FullyQualifiedRadio()
{
//No-arg JAXB constructor
}
/**
* Constructs an instance
* @param wacn for the radio
* @param system for the radio
* @param id for the radio
*/
public P25FullyQualifiedRadio(int wacn, int system, int id)
{
super(Protocol.APCO25, id);
mWacn = wacn;
mSystem = system;
}
@Override
public AliasIDType getType()
{
return AliasIDType.P25_FULLY_QUALIFIED_RADIO_ID;
}
/**
* WACN for this radio
* @return wacn
*/
@JacksonXmlProperty(isAttribute = true, localName = "wacn")
public int getWacn()
{
return mWacn;
}
/**
* Sets the WACN for this radio
* @param wacn for this radio
*/
public void setWacn(int wacn)
{
mWacn = wacn;
}
/**
* System for this radio
* @return system
*/
@JacksonXmlProperty(isAttribute = true, localName = "system")
public int getSystem()
{
return mSystem;
}
/**
* Sets the system for this radio
* @param system for this radio
*/
public void setSystem(int system)
{
mSystem = system;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Fully Qualified Radio ID:").append(getWacn());
sb.append(".").append(getSystem());
sb.append(".").append(getValue());
sb.append(" Protocol:").append((getProtocol()));
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
/**
* Hashable string key for use in a lookup map
*/
@JsonIgnore
public String getHashKey()
{
StringBuilder sb = new StringBuilder();
sb.append(getWacn());
sb.append(".").append(getSystem());
sb.append(".").append(getValue());
return sb.toString();
}
}

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.
* * ******************************************************************************
* * Copyright (C) 2014-2020 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 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.alias.id.radio;
@@ -35,11 +38,10 @@ public enum RadioFormat
"<html>APCO25 valid range is 0 to 16,777,215"),
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
"<html>DMR unit id valid range is 0 to 16,777,215"),
NXDN("*****", 1, 0xFFFF, "1 to 65,535", "NXDN valid range is 1 to 65,535"),
PASSPORT("********", 0, 0x7FFFFF, "0 to 8,388,607",
"<html>PASSPORT valid range is 0 to 8,388,607"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
"<html>Unknown protocol valid value range is 1-16,777,215");
"Unknown protocol valid value range is 1-16,777,215");
private String mMask;
private int mMinimumValue;
@@ -114,8 +116,6 @@ public enum RadioFormat
return APCO25;
case DMR:
return DMR;
case NXDN:
return NXDN;
case PASSPORT:
return PASSPORT;
default:

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.
* * ******************************************************************************
* * 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 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.alias.id.radio;
@@ -23,14 +26,14 @@ import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.EnumMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Protocol-specific formatting of radio id values and parsing of formatted radio id values.
@@ -44,7 +47,6 @@ public class RadioFormatter
{
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
mFormatterMap.put(Protocol.NXDN, new NXDNTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}

View File

@@ -1,96 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.alias.id.talkgroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D talkgroup. The value is the over-the-air 16-bit identifier that combines the home repeater and talkgroup
* so that it matches the value of an NXDN Type-D talkgroup identifier.
*/
public class NXDNTypeDTalkgroup extends Talkgroup implements INXDNTypeD
{
public NXDNTypeDTalkgroup()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the talkgroup
* @param talkgroup value, without the home repeater
*/
public NXDNTypeDTalkgroup(int homeRepeater, int talkgroup)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, talkgroup));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_TALKGROUP;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getValue());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, getIdentifier()));
}
@JsonIgnore
@Override
public int getIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getValue());
}
@Override
public void setIdentifier(int identifier)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Talkgroup:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,125 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.alias.id.talkgroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D talkgroup range for a single home repeater. The min and max values are the over-the-air 16-bit
* identifiers that combine the home repeater and talkgroup.
*/
public class NXDNTypeDTalkgroupRange extends TalkgroupRange implements INXDNTypeDRange
{
public NXDNTypeDTalkgroupRange()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the range
* @param minTalkgroup value, without the home repeater
* @param maxTalkgroup value, without the home repeater
*/
public NXDNTypeDTalkgroupRange(int homeRepeater, int minTalkgroup, int maxTalkgroup)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, minTalkgroup),
NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, maxTalkgroup));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_TALKGROUP_RANGE;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMinTalkgroup());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
int min = getMinIdentifier();
int max = getMaxIdentifier();
setMinTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, min));
setMaxTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, max));
}
@JsonIgnore
@Override
public int getMinIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMinTalkgroup());
}
@Override
public void setMinIdentifier(int identifier)
{
setMinTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@JsonIgnore
@Override
public int getMaxIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMaxTalkgroup());
}
@Override
public void setMaxIdentifier(int identifier)
{
setMaxTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
/**
* Valid when the base range is valid and both ends of the range use the same home repeater.
*/
@Override
public boolean isValid()
{
return super.isValid() &&
getHomeRepeater() == NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMaxTalkgroup());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Talkgroup Range:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMinIdentifier(), true));
sb.append(" to ").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMaxIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,128 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.alias.id.talkgroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.protocol.Protocol;
/**
* Fully qualified talkgroup. Note: this is only for P25.
*/
public class P25FullyQualifiedTalkgroup extends Talkgroup
{
private int mWacn;
private int mSystem;
public P25FullyQualifiedTalkgroup()
{
//No-arg JAXB constructor
}
/**
* Constructs an instance
* @param wacn for the radio
* @param system for the radio
* @param talkgroup value
*/
public P25FullyQualifiedTalkgroup(int wacn, int system, int talkgroup)
{
super(Protocol.APCO25, talkgroup);
mWacn = wacn;
mSystem = system;
}
@Override
public AliasIDType getType()
{
return AliasIDType.P25_FULLY_QUALIFIED_TALKGROUP;
}
/**
* WACN for this radio
* @return wacn
*/
@JacksonXmlProperty(isAttribute = true, localName = "wacn")
public int getWacn()
{
return mWacn;
}
/**
* Sets the WACN for this radio
* @param wacn for this radio
*/
public void setWacn(int wacn)
{
mWacn = wacn;
}
/**
* System for this radio
* @return system
*/
@JacksonXmlProperty(isAttribute = true, localName = "system")
public int getSystem()
{
return mSystem;
}
/**
* Sets the system for this radio
* @param system for this radio
*/
public void setSystem(int system)
{
mSystem = system;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Fully Qualified Radio ID:").append(getWacn());
sb.append(".").append(getSystem());
sb.append(".").append(getValue());
sb.append(" Protocol:").append((getProtocol()));
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
/**
* Hashable string key for use in a lookup map
* @return
*/
@JsonIgnore
public String getHashKey()
{
StringBuilder sb = new StringBuilder();
sb.append(getWacn());
sb.append(".").append(getSystem());
sb.append(".").append(getValue());
return sb.toString();
}
}

View File

@@ -1,40 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.alias.id.talkgroup;
import io.github.dsheirer.protocol.Protocol;
/**
* Streaming talkgroup alias. For streamed audio metadata, this value is used to replace the decoded talkgroup or TO
* value with an alias value when there are talkgroup collisions resulting from streaming multiple channels to the
* same stream. This is common in P25 conventional repeater setups where each radio channel is using talkgroup 1.
*/
public class StreamAsTalkgroup extends Talkgroup
{
public StreamAsTalkgroup(int talkgroup)
{
super(Protocol.UNKNOWN, talkgroup);
}
public StreamAsTalkgroup()
{
//No arg JAXB constructor
}
}

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.
* * ******************************************************************************
* * Copyright (C) 2014-2020 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 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.alias.id.talkgroup;
@@ -31,8 +34,6 @@ import io.github.dsheirer.protocol.Protocol;
*/
public enum TalkgroupFormat
{
AM("*****", 1, 0xFFFF, "1 to 65,535",
"AM valid value range is 1-65,535"),
APCO25("********", 0, 0xFFFF, "0 to 65,535",
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
@@ -47,8 +48,6 @@ public enum TalkgroupFormat
"<html>MPT-1327 valid ranges are 0-127(prefix)<br>and 1-8192(ident) (ie. 000-0001 to 127-8192)"),
NBFM("*****", 1, 0xFFFF, "1 to 65,535",
"NBFM valid value range is 1-65,535"),
NXDN("*****", 0, 0xFFFF, "0 to 65,535",
"NXDN talkgroup valid range is 0 to 65,535"),
PASSPORT("*****", 1, 0xFFFF, "1 to 65,535",
"Passport valid value range is 1-65,535"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
@@ -123,8 +122,6 @@ public enum TalkgroupFormat
switch(protocol)
{
case AM:
return AM;
case APCO25:
return APCO25;
case FLEETSYNC:
@@ -138,8 +135,6 @@ public enum TalkgroupFormat
return MPT1327;
case NBFM:
return NBFM;
case NXDN:
return NXDN;
case PASSPORT:
return PASSPORT;
default:

View File

@@ -1,6 +1,7 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
* ******************************************************************************
* sdrtrunk
* 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
@@ -14,7 +15,7 @@
*
* 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.alias.id.talkgroup;
@@ -22,21 +23,21 @@ package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AnalogTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.EnumMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
@@ -48,10 +49,6 @@ public class TalkgroupFormatter
static
{
AnalogTalkgroupFormatter analogTalkgroupFormatter = new AnalogTalkgroupFormatter();
mFormatterMap.put(Protocol.AM, analogTalkgroupFormatter);
mFormatterMap.put(Protocol.NBFM, analogTalkgroupFormatter);
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
@@ -60,7 +57,7 @@ public class TalkgroupFormatter
mFormatterMap.put(Protocol.LTR_NET, ltr);
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
mFormatterMap.put(Protocol.NXDN, new NXDNTalkgroupFormatter());
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}

View File

@@ -1,17 +0,0 @@
package io.github.dsheirer.audio;
/**
* Used for defining an array list of filter methods to be implemented in AudioModule
* The filters will be applied in the order given in the list.
* Each audio filter mush implement the filter method.
*/
public abstract class AbstractAudioFilter
{
/**
* To be overridden by each audio filter
* @param audio a buffer of 8 KHz audio samples
* @return float[] filtered buffer of 8 KHz audio samples
*/
public abstract float[] filter(float[] audio);
}

View File

@@ -1,6 +1,7 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 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
@@ -14,7 +15,7 @@
*
* 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;
@@ -35,7 +36,7 @@ import org.slf4j.LoggerFactory;
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
public static final long DEFAULT_MAX_SEGMENT_LENGTH_MILLIS = 60_000; // 1 minute
public static final long DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
public static final int DEFAULT_TIMESLOT = 0;
private final int mMaxSegmentAudioSampleLength;
private Listener<AudioSegment> mAudioSegmentListener;
@@ -51,12 +52,12 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
* Constructs an abstract audio module
*
* @param aliasList for aliasing identifiers
* @param maxSegmentLengthMillis maximum audio segment length in milliseconds
* @param maxSegmentAudioSampleLength in milliseconds
*/
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentLengthMillis)
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentAudioSampleLength)
{
mAliasList = aliasList;
mMaxSegmentAudioSampleLength = (int)(maxSegmentLengthMillis * 8); //Convert milliseconds to samples
mMaxSegmentAudioSampleLength = (int)(maxSegmentAudioSampleLength * 8); //Convert milliseconds to samples
mTimeslot = timeslot;
mIdentifierCollection = new MutableIdentifierCollection(getTimeslot());
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
@@ -67,7 +68,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
*/
public AbstractAudioModule(AliasList aliasList)
{
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_MAX_SEGMENT_LENGTH_MILLIS);
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
}
/**
@@ -105,7 +106,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
* listener(s).
*/
public AudioSegment getAudioSegment()
protected AudioSegment getAudioSegment()
{
synchronized(this)
{
@@ -134,7 +135,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
}
}
public void addAudio(float[] audioBuffer)
protected void addAudio(float[] audioBuffer)
{
AudioSegment audioSegment = getAudioSegment();

View File

@@ -1,44 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.audio;
import io.github.dsheirer.dsp.gain.AudioGainAndDcFilter;
/**
* This is a wrapper for the AudioGainAndDcFilter class so it can be used with the AudioModule filter list.
* Usually instantiated in the DecoderFactory
*/
public class AudioGainFilter extends AbstractAudioFilter
{
AudioGainAndDcFilter mAudioGain;
public AudioGainFilter(float minGain, float maxGain, float objectiveAmplitude)
{
mAudioGain = new AudioGainAndDcFilter(minGain, maxGain, objectiveAmplitude);
mAudioGain.setDecayRate(2); // to remove pumping effect
mAudioGain.reset();
}
@Override
public float[] filter(float[] audio)
{
return mAudioGain.process(audio);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -32,8 +32,6 @@ import io.github.dsheirer.sample.real.IRealBufferListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Provides packaging of demodulated audio sample buffers into audio segments for broadcast to registered listeners.
* Includes audio packet metadata in constructed audio segments.
@@ -44,33 +42,55 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
Listener<float[]>
{
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private List<AbstractAudioFilter>mAudioFilters;
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private static float[] sHighPassFilterCoefficients;
static
{
FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
.sampleRate(8000)
.stopBandCutoff(200)
.stopBandAmplitude(0.0)
.stopBandRipple(0.025)
.passBandStart(300)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.build();
try
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
sHighPassFilterCoefficients = designer.getImpulseResponse();
}
}
catch(FilterDesignException fde)
{
mLog.error("Filter design error", fde);
}
}
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
/**
* Creates an Audio Module.
*
* @param aliasList for aliasing identifiers
* @param timeslot for this audio module
* @param maxAudioSegmentLength in milliseconds
* @param audioFilters a list of audio filters to apply to the incoming audio samples
*/
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, List<AbstractAudioFilter> audioFilters)
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
{
super(aliasList, timeslot, maxAudioSegmentLength);
mAudioFilters = audioFilters;
}
/**
* Creates an Audio Module.
* @param aliasList for aliasing identifiers
* @param audioFilters a list of audio filters to apply to the incoming audio samples
*/
public AudioModule(AliasList aliasList, List<AbstractAudioFilter> audioFilters)
public AudioModule(AliasList aliasList)
{
super(aliasList);
mAudioFilters = audioFilters;
}
@Override
@@ -101,15 +121,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
{
if(mSquelchState == SquelchState.UNSQUELCH)
{
// apply audio filters if any (i.e. deemphasis, high pass, etc.)
if(!mAudioFilters.isEmpty())
{
for(AbstractAudioFilter filter : mAudioFilters)
{
audioBuffer = filter.filter(audioBuffer);
}
}
audioBuffer = mHighPassFilter.filter(audioBuffer);
addAudio(audioBuffer);
}
}
@@ -129,16 +141,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
@Override
public void receive(SquelchStateEvent event)
{
SquelchState squelchState = event.getSquelchState();
mSquelchState = event.getSquelchState();
if(mSquelchState != squelchState)
if(mSquelchState == SquelchState.SQUELCH)
{
mSquelchState = squelchState;
if(mSquelchState == SquelchState.SQUELCH)
{
closeAudioSegment();
}
closeAudioSegment();
}
}
}

View File

@@ -1,21 +1,18 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2020 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 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.
* 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/>
* ****************************************************************************
*/
* 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;
@@ -27,17 +24,8 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
@@ -47,6 +35,15 @@ import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
* a discrete (ie start/stop) audio event, or it may be a time-constrained portion of an ongoing continuous audio
@@ -61,7 +58,7 @@ import org.slf4j.LoggerFactory;
*
* Producers will add audio buffers and update identifiers throughout the life-cycle of an audio segment. The producer
* will signal the completion of an audio segment by setting the complete property to true. This allows consumers the
* option to process the audio buffers throughout the life-cycle of the segment, or to process all the buffers once
* option to process the audio buffers throughout the life-cycle of the segment, or to process all of the buffers once
* the segment is complete.
*/
public class AudioSegment implements Listener<IdentifierUpdateNotification>
@@ -69,7 +66,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
private BooleanProperty mEncrypted = new SimpleBooleanProperty(false);
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
@@ -131,25 +127,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
return (mSampleCount / 8); //8 kHz audio generates 8 samples per millisecond
}
/**
* Indicates if the audio segment contains encrypted audio.
*
* @return encrypted property
*/
public BooleanProperty encryptedProperty()
{
return mEncrypted;
}
/**
* Indicates if this audio segment is encrypted
* @return true if encrypted.
*/
public boolean isEncrypted()
{
return mEncrypted.get();
}
/**
* The complete property is used by the audio segment producer to signal that the segment is complete and no
* additional audio or identifiers will be added to the segment.
@@ -161,15 +138,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
return mComplete;
}
/**
* Indicates if this audio segment is completed.
* @return true if completed.
*/
public boolean isComplete()
{
return mComplete.get();
}
/**
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
* an audio segment is a duplicate.
@@ -407,11 +375,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
throw new IllegalStateException("Can't add audio to an audio segment that is being disposed");
}
if(mAudioBuffers.isEmpty())
{
mStartTimestamp = System.currentTimeMillis() - 20;
}
mAudioBuffers.add(audioBuffer);
mSampleCount += audioBuffer.length;
}
@@ -441,14 +404,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
{
mIdentifierCollection.update(identifier);
/**
* If we have a late-add encryption key, set the encrypted flag to true.
*/
if(identifier instanceof EncryptionKeyIdentifier eki)
{
mEncrypted.set(eki.isEncrypted());
}
List<Alias> aliases = mAliasList.getAliases(identifier);
for(Alias alias: aliases)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2020 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
@@ -28,9 +28,12 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.duplicate.ICallManagementProvider;
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -38,10 +41,7 @@ import java.util.Map;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method.
@@ -52,41 +52,18 @@ import org.slf4j.LoggerFactory;
public class DuplicateCallDetector implements Listener<AudioSegment>
{
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
private ICallManagementProvider mCallManagementProvider;
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
protected Listener<AudioSegment> mDuplicateCallDetectionListener;
/**
* Constructs an instance.
* @param userPreferences to access the duplicate call detection preferences.
*/
public DuplicateCallDetector(UserPreferences userPreferences)
{
this(userPreferences.getCallManagementPreference());
}
/**
* Constructs an instance.
* @param callManagementProvider to provide call management preferences.
*/
public DuplicateCallDetector(ICallManagementProvider callManagementProvider)
{
mCallManagementProvider = callManagementProvider;
}
/**
* Optional listener to be notified each time an audio segment is flagged as duplicate.
* @param listener to register
*/
public void setDuplicateCallDetectionListener(Listener<AudioSegment> listener)
{
mDuplicateCallDetectionListener = listener;
mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference();
}
@Override
public void receive(AudioSegment audioSegment)
{
if(mCallManagementProvider.isDuplicateCallDetectionEnabled())
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled())
{
Identifier identifier = audioSegment.getIdentifierCollection()
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
@@ -101,7 +78,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
if(detector == null)
{
detector = new SystemDuplicateCallDetector(mCallManagementProvider, system);
detector = new SystemDuplicateCallDetector();
mDetectorMap.put(system, detector);
}
@@ -111,99 +88,45 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
}
}
/**
* System level duplicate call detector. Uses a scheduled executor to run every 25 ms to compare all ongoing call
* audio segments to detect duplicates.
*
* All audio segments remain in the queue until they are flagged as complete. While in the queue, each call is
* compared against the others to detect duplicates. Once all calls are either flagged as complete or flagged as
* duplicate and removed, the queue is empty and the monitoring is shutdown until a new audio segment arrives and
* then the monitoring starts again.
*/
public class SystemDuplicateCallDetector
{
private final LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
private final List<AudioSegment> mAudioSegments = new ArrayList<>();
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
private List<AudioSegment> mAudioSegments = new ArrayList<>();
private AtomicBoolean mMonitoring = new AtomicBoolean();
private ScheduledFuture<?> mProcessorFuture;
private Lock mLock = new ReentrantLock();
private boolean mMonitoring = false;
private final ICallManagementProvider mCallManagementProvider;
private String mSystem;
/**
* Constructs an instance
* @param callManagementProvider to check for duplicate monitoring preferences
*/
public SystemDuplicateCallDetector(ICallManagementProvider callManagementProvider, String system)
public SystemDuplicateCallDetector()
{
mCallManagementProvider = callManagementProvider;
mSystem = system;
}
/**
* Adds the audio segment to the monitoring queue.
* @param audioSegment to add
*/
public void add(AudioSegment audioSegment)
{
mLock.lock();
try
//Block on audio segment queue so that we don't interfere with monitoring shutdown
synchronized(mAudioSegmentQueue)
{
mAudioSegmentQueue.add(audioSegment);
startMonitoring();
}
finally
{
mLock.unlock();
}
}
/**
* Starts the call monitoring thread if it's not already running.
*
* Note: this method should only be called from a thread with the lock acquired.
*/
private void startMonitoring()
{
if(!mMonitoring)
if(mMonitoring.compareAndSet(false, true))
{
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(this::process,
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(() -> process(),
0, 25, TimeUnit.MILLISECONDS);
mMonitoring = true;
}
}
/**
* Stops the call monitoring thread if the audio segements queue is empty.
*
* Note: this should only be called from a separate thread and not from within the scheduled monitoring thread
* because cancelling the scheduled timer from within the process() method would kill the thread without
* releasing the lock, causing a deadlock.
*/
private void stopMonitoring()
{
mLock.lock();
try
if(mMonitoring.compareAndSet(true, false))
{
//Recheck the audio segments queue to make sure we didn't slip in another audio segment before we can
//shut down the scheduled monitoring thread.
if(mMonitoring && mAudioSegments.isEmpty())
if(mProcessorFuture != null)
{
if(mProcessorFuture != null)
{
mProcessorFuture.cancel(true);
mProcessorFuture = null;
}
mMonitoring = false;
mProcessorFuture.cancel(true);
}
}
finally
{
mLock.unlock();
}
}
/**
@@ -214,7 +137,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
*/
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
{
if(mCallManagementProvider.isDuplicateCallDetectionByTalkgroupEnabled())
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled())
{
//Step 1 check for duplicate TO values
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
@@ -226,7 +149,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
}
}
if(mCallManagementProvider.isDuplicateCallDetectionByRadioEnabled())
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByRadioEnabled())
{
//Step 2 check for duplicate FROM values
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
@@ -246,51 +169,53 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
* @param identifiers2
* @return
*/
public static boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
private boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
{
for(Identifier identifier1: identifiers1)
{
if(identifier1 instanceof TalkgroupIdentifier tgId1)
if(identifier1 instanceof TalkgroupIdentifier)
{
int tg1 = tgId1.getValue();
int talkgroup1 = ((TalkgroupIdentifier)identifier1).getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == tg1)
if(identifier2 instanceof TalkgroupIdentifier &&
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
{
return true;
}
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
pgId2.getValue().getPatchGroup().getValue() == tg1)
else if(identifier2 instanceof PatchGroupIdentifier &&
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
{
return true;
}
}
}
else if(identifier1 instanceof PatchGroupIdentifier pgId1)
else if(identifier1 instanceof PatchGroupIdentifier)
{
int talkgroup1 = pgId1.getValue().getPatchGroup().getValue();
int talkgroup1 = ((PatchGroupIdentifier)identifier1).getValue().getPatchGroup().getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == talkgroup1)
if(identifier2 instanceof TalkgroupIdentifier &&
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
{
return true;
}
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
pgId2.getValue().getPatchGroup().getValue() == talkgroup1)
else if(identifier2 instanceof PatchGroupIdentifier &&
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
{
return true;
}
}
}
else if(identifier1 instanceof RadioIdentifier raId1)
else if(identifier1 instanceof RadioIdentifier)
{
int radio1 = raId1.getValue();
int radio1 = ((RadioIdentifier)identifier1).getValue();
for(Identifier identifier2: identifiers2)
{
if(identifier2 instanceof RadioIdentifier raId2 && raId2.getValue() == radio1)
if(identifier2 instanceof RadioIdentifier && ((RadioIdentifier)identifier2).getValue() == radio1)
{
return true;
}
@@ -306,8 +231,6 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
*/
private void process()
{
mLock.lock();
try
{
//Transfer in newly arrived audio segments
@@ -325,19 +248,6 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
return complete;
});
//Remove any encrypted audio segments.
mAudioSegments.removeIf(audioSegment -> {
boolean encrypted = audioSegment.isEncrypted();
if(encrypted)
{
audioSegment.decrementConsumerCount();
}
return encrypted;
});
//Only check for duplicates if there is more than one call
if(mAudioSegments.size() > 1)
{
@@ -348,7 +258,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
{
AudioSegment current = mAudioSegments.get(currentIndex);
if(current.hasAudio() && !current.isDuplicate())
if(!current.isDuplicate())
{
int checkIndex = currentIndex + 1;
@@ -363,12 +273,6 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
toCheck.setDuplicate(true);
toCheck.decrementConsumerCount();
duplicates.add(toCheck);
//Notify optional listener that we flagged the call as duplicate.
if(mDuplicateCallDetectionListener != null)
{
mDuplicateCallDetectionListener.receive(toCheck);
}
}
}
@@ -382,12 +286,26 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
mAudioSegments.removeAll(duplicates);
}
//Finally, if the audio segment queue is now empty, shutdown monitoring until a new segment arrives.
//The monitor shutdown method has to be called on a separate thread so that we don't kill our current
// thread and fail to release the lock.
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
if(mAudioSegments.isEmpty())
{
ThreadPool.CACHED.submit(this::stopMonitoring);
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
//allow the add(segment) to restart monitoring as soon as needed.
synchronized(mAudioSegmentQueue)
{
if(mAudioSegmentQueue.isEmpty())
{
try
{
stopMonitoring();
}
catch(Exception e)
{
mLog.error("Unexpected error during duplicate audio segment monitoring shutdown", e);
//Do nothing, we got interrupted
}
}
}
}
}
catch(Throwable t)
@@ -395,10 +313,6 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
mLog.error("Unknown error while processing audio segments for duplicate call detection. Please report " +
"this to the developer.", t);
}
finally
{
mLock.unlock();
}
}
}
}

View File

@@ -1,50 +0,0 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HighPassAudioFilter extends AbstractAudioFilter
{
private static final Logger mLog = LoggerFactory.getLogger(HighPassAudioFilter.class);
private static float[] sHighPassFilterCoefficients;
static
{
FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
.sampleRate(8000)
.stopBandCutoff(200)
.stopBandAmplitude(0.0)
.stopBandRipple(0.025)
.passBandStart(300)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.build();
try
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
sHighPassFilterCoefficients = designer.getImpulseResponse();
}
}
catch(FilterDesignException fde)
{
mLog.error("Filter design error", fde);
}
}
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
@Override
public float[] filter(float[] audio)
{
audio = mHighPassFilter.filter(audio);
return audio;
}
}

View File

@@ -1,27 +1,9 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.audio;
import io.github.dsheirer.audio.playback.AudioChannel;
import io.github.dsheirer.audio.playback.AudioPlaybackDeviceDescriptor;
import io.github.dsheirer.audio.playback.AudioOutput;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
import java.util.List;
/**
@@ -30,13 +12,13 @@ import java.util.List;
public interface IAudioController
{
/* Current Mixer and MixerChannel configuration */
public void setAudioPlaybackDevice(AudioPlaybackDeviceDescriptor device ) throws AudioException;
public AudioPlaybackDeviceDescriptor getAudioPlaybackDevice() throws AudioException;
public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException;
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException;
/* Audio Channel(s) */
public List<AudioChannel> getAudioChannels();
/* Audio Output(s) */
public List<AudioOutput> getAudioOutputs();
/* Controller Audio Event Listener */
public void addAudioEventListener(Listener<AudioEvent> listener );
public void removeAudioEventListener(Listener<AudioEvent> listener );
public void addControllerListener( Listener<AudioEvent> listener );
public void removeControllerListener( Listener<AudioEvent> listener );
}

View File

@@ -34,9 +34,7 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
private Listener<BroadcastEvent> mBroadcastEventListener;
private T mBroadcastConfiguration;
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
protected ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
protected int mStreamedAudioCount = 0;
protected int mDuplicateRejectedAudioCount = 0;
protected int mErrorAudioCount = 0;
protected int mAgedOffAudioCount = 0;
@@ -57,14 +55,6 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
return mBroadcastState;
}
/**
* Observable last bad broadcast state property
*/
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
{
return mLastBadBroadcastState;
}
/**
* Current state of the broadcastAudio connection
*/
@@ -78,26 +68,10 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
*/
public void setBroadcastState(BroadcastState broadcastState)
{
if(broadcastState == BroadcastState.CONNECTED)
{
mLastBadBroadcastState.setValue(null);
}
else if(broadcastState.isErrorState() || broadcastState.isWarningState())
{
mLastBadBroadcastState.setValue(broadcastState);
}
mBroadcastState.setValue(broadcastState);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
}
/**
* Last bad state of the broadcastAudio connection
*/
public BroadcastState getLastBadBroadcastState()
{
return mLastBadBroadcastState.get();
}
/**
* Starts the broadcaster
*/
@@ -164,22 +138,6 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
mStreamedAudioCount++;
}
/**
* Number of audio recordings rejected by the remote service as duplicates.
*/
public int getDuplicateRejectedAudioCount()
{
return mDuplicateRejectedAudioCount;
}
/**
* Increments the duplicate-rejected audio count by one.
*/
public void incrementDuplicateRejectedAudioCount()
{
mDuplicateRejectedAudioCount++;
}
/**
* Number of audio recordings that were removed for exceeding age limit
*/

View File

@@ -211,17 +211,9 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
super.setBroadcastState(state);
if(mBroadcastState.get() != null)
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
{
if(mBroadcastState.get().isErrorState())
{
mLog.error("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
stop();
}
else if(mBroadcastState.get().isWarningState())
{
mLog.warn("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
}
stop();
}
if(!connected())

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -19,34 +19,25 @@
package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.audio.AudioSegment;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.AudioSegmentRecorder;
import io.github.dsheirer.record.RecordFormat;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on
@@ -136,7 +127,7 @@ public class AudioStreamingManager implements Listener<AudioSegment>
{
audioSegment = it.next();
if(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled())
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled())
{
it.remove();
audioSegment.decrementConsumerCount();
@@ -147,60 +138,29 @@ public class AudioStreamingManager implements Listener<AudioSegment>
if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels())
{
IdentifierCollection identifiers =
Path path = getTemporaryRecordingPath();
long length = 0;
for(float[] audioBuffer: audioSegment.getAudioBuffers())
{
length += audioBuffer.length;
}
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
try
{
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
IdentifierCollection identifierCollectionCopy =
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier)
{
if(mUserPreferences.getCallManagementPreference()
.getPatchGroupStreamingOption() == PatchGroupStreamingOption.TALKGROUPS)
{
//Decompose the patch group into the individual (patched) talkgroups and process the audio
//segment for each patched talkgroup.
PatchGroup patchGroup = patchGroupIdentifier.getValue();
List<Identifier> ids = new ArrayList<>();
ids.addAll(patchGroup.getPatchedTalkgroupIdentifiers());
ids.addAll(patchGroup.getPatchedRadioIdentifiers());
//If there are no patched radios/talkgroups, override user preference and stream as a patch group
if(ids.isEmpty() || audioSegment.getAliasList() == null)
{
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
}
else
{
AliasList aliasList = audioSegment.getAliasList();
for(Identifier identifier: ids)
{
List<Alias> aliases = aliasList.getAliases(identifier);
Set<BroadcastChannel> broadcastChannels = new HashSet<>();
for(Alias alias: aliases)
{
broadcastChannels.addAll(alias.getBroadcastChannels());
}
if(!broadcastChannels.isEmpty())
{
MutableIdentifierCollection decomposedIdentifiers =
new MutableIdentifierCollection(identifiers.getIdentifiers());
//Remove patch group TO identifier & replace with the patched talkgroup/radio
decomposedIdentifiers.remove(Role.TO);
decomposedIdentifiers.update(identifier);
processAudioSegment(audioSegment, decomposedIdentifiers, broadcastChannels);
}
}
}
}
else
{
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
}
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
identifierCollectionCopy, audioSegment.getStartTimestamp(), length);
mAudioRecordingListener.receive(audioRecording);
}
else
catch(IOException ioe)
{
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
mLog.error("Error recording temporary stream MP3");
}
}
@@ -209,40 +169,6 @@ public class AudioStreamingManager implements Listener<AudioSegment>
}
}
/**
* Processes an audio segment for streaming by creating a temporary MP3 recording and submitting the recording
* to the specific broadcast channel(s).
* @param audioSegment to process for streaming
* @param identifierCollection to use for the streamed audio recording.
* @param broadcastChannels to receive the audio recording
*/
private void processAudioSegment(AudioSegment audioSegment, IdentifierCollection identifierCollection,
Set<BroadcastChannel> broadcastChannels)
{
Path path = getTemporaryRecordingPath();
long length = 0;
for(float[] audioBuffer: audioSegment.getAudioBuffers())
{
length += audioBuffer.length;
}
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
try
{
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences, identifierCollection);
AudioRecording audioRecording = new AudioRecording(path, broadcastChannels, identifierCollection,
audioSegment.getStartTimestamp(), length);
mAudioRecordingListener.receive(audioRecording);
}
catch(IOException ioe)
{
mLog.error("Error recording temporary stream MP3");
}
}
/**
* Creates a temporary streaming recording file path
*/

View File

@@ -24,8 +24,6 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
@@ -48,8 +46,6 @@ import java.net.SocketAddress;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
@JsonSubTypes.Type(value = RdioScannerConfiguration.class, name="RdioScannerConfiguration"),
@JsonSubTypes.Type(value = OpenMHzConfiguration.class, name="OpenMHzConfiguration"),
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),

View File

@@ -82,7 +82,6 @@ public class BroadcastEvent
BROADCASTER_QUEUE_CHANGE,
BROADCASTER_STATE_CHANGE,
BROADCASTER_STREAMED_COUNT_CHANGE,
BROADCASTER_DUPLICATE_REJECTED_COUNT_CHANGE,
BROADCASTER_AGED_OFF_COUNT_CHANGE,
BROADCASTER_ERROR_COUNT_CHANGE,
BROADCASTER_DELETE,

View File

@@ -22,12 +22,6 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerFeedConfiguration;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzFeedConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
@@ -67,12 +61,6 @@ public class BroadcastFactory
case BROADCASTIFY_CALL:
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
case RDIOSCANNER_CALL:
return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
case OPENMHZ:
return new OpenMHzBroadcaster((OpenMHzConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
case BROADCASTIFY:
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
inputAudioFormat, mp3Setting, aliasModel);
@@ -111,12 +99,8 @@ public class BroadcastFactory
{
case BROADCASTIFY_CALL:
return new BroadcastifyCallConfiguration(format);
case RDIOSCANNER_CALL:
return new RdioScannerConfiguration(format);
case BROADCASTIFY:
return new BroadcastifyFeedConfiguration(format);
case OPENMHZ:
return new OpenMHzConfiguration(format);
case ICECAST_HTTP:
return new IcecastHTTPConfiguration(format);
case ICECAST_TCP:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -62,12 +62,11 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
public static final int COLUMN_BROADCASTER_STATUS = 2;
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
public static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4;
public static final int COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT = 5;
public static final int COLUMN_BROADCASTER_AGED_OFF_COUNT = 6;
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 7;
public static final int COLUMN_BROADCASTER_AGED_OFF_COUNT = 5;
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
public static final String[] COLUMN_NAMES = new String[]
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Duplicate Rejected", "Aged Off", "Upload Error"};
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
@@ -345,13 +344,14 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
deleteBroadcaster(configuredBroadcast);
}
final AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
mAliasModel, mUserPreferences);
if(audioBroadcaster != null)
{
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
audioBroadcaster.setListener(mBroadcastEventListener);
audioBroadcaster.start();
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
@@ -362,7 +362,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
}
broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_ADD));
ThreadPool.CACHED.submit(() -> audioBroadcaster.start());
}
}
}
@@ -532,12 +531,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
fireTableCellUpdated(row, COLUMN_BROADCASTER_STREAMED_COUNT);
}
break;
case BROADCASTER_DUPLICATE_REJECTED_COUNT_CHANGE:
if(row >= 0)
{
fireTableCellUpdated(row, COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT);
}
break;
case BROADCASTER_AGED_OFF_COUNT_CHANGE:
if(row >= 0)
{
@@ -614,12 +607,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
return configuredBroadcast.getAudioBroadcaster().getStreamedAudioCount();
}
break;
case COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT:
if(configuredBroadcast.hasAudioBroadcaster())
{
return configuredBroadcast.getAudioBroadcaster().getDuplicateRejectedAudioCount();
}
break;
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
if(configuredBroadcast.hasAudioBroadcaster())
{
@@ -656,7 +643,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
case COLUMN_BROADCASTER_QUEUE_SIZE:
case COLUMN_BROADCASTER_STREAMED_COUNT:
case COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT:
case COLUMN_BROADCASTER_ERROR_COUNT:
return Integer.class;
case COLUMN_BROADCAST_SERVER_TYPE:

View File

@@ -31,8 +31,6 @@ public enum BroadcastServerType
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
RDIOSCANNER_CALL("Rdio Scanner", "images/rdioscanner.png"),
OPENMHZ("OpenMHz", "images/openmhz.png"),
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),

View File

@@ -23,92 +23,90 @@ public enum BroadcastState
/**
* General error in configuration that causes the remote server to reject the connection
*/
CONFIGURATION_ERROR("Configuration Error", true, false),
CONFIGURATION_ERROR("Configuration Error", true),
/**
* Connected to the broadcastAudio server and capable of streaming
*/
CONNECTED("Connected", false, false),
CONNECTED("Connected", false),
/**
* Connection interrupted, attempting to reconnect.
*/
CONNECTING("Connecting", false, false),
CONNECTING("Connecting", false),
/**
* Indicates the configuration is disabled
*/
DISABLED("Disabled", true, false),
DISABLED("Disabled", true),
/**
* Disconnected from the broadcastAudio server
*/
DISCONNECTED("Disconnected", false, false),
DISCONNECTED("Disconnected", false),
/**
* Invalid credentials - user name or password
*/
INVALID_CREDENTIALS("Invalid User Name/Password", true, false),
INVALID_CREDENTIALS("Invalid User Name/Password", true),
/**
* Invalid mount point
*/
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true, false),
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
/**
* Invalid configuration settings
*/
INVALID_SETTINGS("Invalid Settings", true, false),
INVALID_SETTINGS("Invalid Settings", true),
/**
* Remote server max sources has been exceeded
*/
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", false, true),
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", true),
/**
* Specified mount point is already in use
*/
MOUNT_POINT_IN_USE("Mount Point In Use", false, true),
MOUNT_POINT_IN_USE("Mount Point In Use", false),
/**
* Network is unavailable or the server address cannot be resolved
*/
NETWORK_UNAVAILABLE("Network Unavailable", false, false),
NETWORK_UNAVAILABLE("Network Unavailable", false),
/**
* Server is not known or reachable
*/
NO_SERVER("No Server", false, false),
NO_SERVER("No Server", false),
/**
* Initial state with no connection attempted.
*/
READY("Ready", false, false),
READY("Ready", false),
/**
* Error while broadcasting stream data. Temporary error state to allow connection to be reset.
*/
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false, false),
TEMPORARY_BROADCAST_ERROR("Temporary Broadcast Error", false),
/**
* Unsupported audio format
*/
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true, false),
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true),
/**
* Unspecified error
*/
ERROR("Error", true, false);
ERROR("Error", true);
private String mLabel;
private boolean mErrorState;
private boolean mWarningState;
BroadcastState(String label, boolean errorState, boolean warningState)
BroadcastState(String label, boolean errorState)
{
mLabel = label;
mErrorState = errorState;
mWarningState = warningState;
}
public String toString()
@@ -120,9 +118,4 @@ public enum BroadcastState
{
return mErrorState;
}
public boolean isWarningState()
{
return mWarningState;
}
}

View File

@@ -151,10 +151,7 @@ public class BroadcastStatusPanel extends JPanel
else if(state == BroadcastState.DISABLED)
{
setBackground(table.getBackground());
//Use the LAF's disabled-foreground so the cell reads correctly on any
//theme - LIGHT_GRAY is invisible on a light background.
Color disabled = javax.swing.UIManager.getColor("Label.disabledForeground");
setForeground(disabled != null ? disabled : table.getForeground());
setForeground(Color.LIGHT_GRAY);
}
else if(state == BroadcastState.INVALID_SETTINGS ||
state == BroadcastState.NETWORK_UNAVAILABLE)

View File

@@ -37,7 +37,6 @@ public class ConfiguredBroadcast
private BroadcastConfiguration mBroadcastConfiguration;
private AbstractAudioBroadcaster mAudioBroadcaster;
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
private ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
/**
* Constructs an instance
@@ -90,14 +89,6 @@ public class ConfiguredBroadcast
return mBroadcastState;
}
/**
* Last bad broadcast state of the configured audio broadcaster (optional)
*/
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
{
return mLastBadBroadcastState;
}
/**
* Sets the audio broadcaster
* @param audioBroadcaster to use for this configuration
@@ -105,13 +96,11 @@ public class ConfiguredBroadcast
public void setAudioBroadcaster(AbstractAudioBroadcaster audioBroadcaster)
{
mBroadcastState.unbind();
mLastBadBroadcastState.unbind();
mAudioBroadcaster = audioBroadcaster;
if(audioBroadcaster != null)
{
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
mLastBadBroadcastState.bind(mAudioBroadcaster.lastBadBroadcastStateProperty());
}
else
{
@@ -131,7 +120,6 @@ public class ConfiguredBroadcast
{
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
}
mLastBadBroadcastState.setValue(null);
}
}

View File

@@ -1,48 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.audio.broadcast;
/**
* Options for streaming management of patch groups. These options are used when streaming an audio call that is
* tagged to a patch group. The audio will either be streamed once and identified as the patch group, or it will
* be streamed multiple times, once for each individual patched talkgroup.
*/
public enum PatchGroupStreamingOption
{
PATCH_GROUP("Patch Group"),
TALKGROUPS("Individual Talkgroups");
private String mLabel;
/**
* Constructs an instance
* @param label to display
*/
PatchGroupStreamingOption(String label)
{
mLabel = label;
}
@Override
public String toString()
{
return mLabel;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -20,8 +20,6 @@
package io.github.dsheirer.audio.broadcast.broadcastify;
import com.google.common.net.HttpHeaders;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
@@ -34,14 +32,15 @@ import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
@@ -55,8 +54,6 @@ import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
@@ -81,7 +78,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
final private AliasModel mAliasModel;
/**
* Constructs an instance of the broadcaster
@@ -92,7 +88,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(config);
mAliasModel = aliasModel;
}
/**
@@ -254,8 +249,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
String radioId = getFrom(audioRecording);
float frequency = getFrequency(audioRecording);
String radioIdAlias = getFromAlias(audioRecording);
BroadcastifyCallBuilder bodyBuilder = new BroadcastifyCallBuilder();
bodyBuilder.addPart(FormField.API_KEY, getBroadcastConfiguration().getApiKey())
.addPart(FormField.SYSTEM_ID, getBroadcastConfiguration().getSystemID())
@@ -266,11 +259,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
.addPart(FormField.FREQUENCY, frequency)
.addPart(FormField.ENCODING, ENCODING_TYPE_MP3);
if(radioIdAlias != null && !radioIdAlias.isEmpty())
{
bodyBuilder.addPart(FormField.RADIO_ID_ALIAS, radioIdAlias);
}
try
{
HttpRequest request = HttpRequest.newBuilder()
@@ -370,9 +358,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
else if(urlResponse.startsWith("1 SKIPPED"))
{
//Broadcastify is telling us to skip audio upload - someone already uploaded it
incrementDuplicateRejectedAudioCount();
broadcast(new BroadcastEvent(BroadcastifyCallBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_DUPLICATE_REJECTED_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
else
@@ -457,58 +442,13 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
return "0";
}
/**
* Resolves the over-the-air talker alias for the FROM (source radio) identifier, acquired in real-time from RF
* (e.g. P25 Motorola Talker Alias or DMR Talker Alias). Returns null if no OTA alias was received for this call.
* This is sent as the srcId_alias field in Broadcastify uploads.
*/
private static String getFromAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier talkerAliasIdentifier)
{
if(talkerAliasIdentifier.isValid())
{
return talkerAliasIdentifier.getValue();
}
}
}
return null;
}
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*/
private String getTo(AudioRecording audioRecording)
private static String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
//Alias the TO value when the user specifies a 'Stream As Talkgroup'
if(identifier != null)
{
AliasListConfigurationIdentifier config = audioRecording.getIdentifierCollection().getAliasListConfiguration();
if(config != null)
{
AliasList aliasList = mAliasModel.getAliasList(config.getValue());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
for(Alias a: aliases)
{
if(a.getStreamTalkgroupAlias() != null)
{
return String.valueOf(a.getStreamTalkgroupAlias().getValue());
}
}
}
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return format(patchGroupIdentifier);
@@ -535,11 +475,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
StringBuilder sb = new StringBuilder();
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 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.
* * ******************************************************************************
* * Copyright (C) 2014-2020 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 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.broadcast.broadcastify;
@@ -32,7 +35,6 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
private final static Logger mLog = LoggerFactory.getLogger(BroadcastifyFeedConfiguration.class);
private int mFeedID;
private boolean mVerboseLogging = false;
public BroadcastifyFeedConfiguration()
{
@@ -121,15 +123,4 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
{
mFeedID = feedID;
}
@JacksonXmlProperty(isAttribute = true, localName = "verbose_logging")
public boolean isVerboseLogging()
{
return mVerboseLogging;
}
public void setVerboseLogging(boolean verboseLogging)
{
mVerboseLogging = verboseLogging;
}
}

View File

@@ -30,7 +30,6 @@ public enum FormField
TIMESTAMP("ts"),
TALKGROUP_ID("tg"),
RADIO_ID("src"),
RADIO_ID_ALIAS("srcId_alias"),
FREQUENCY("freq"),
ENCODING("enc"),
TEST("test");

View File

@@ -52,10 +52,8 @@ import org.slf4j.LoggerFactory;
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; // 5 seconds
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
private static final String HTTP_1_0_OK_HEX_DUMP = "48 54 54 50 2F 31 2E 30 20 32 30 30 20 4F 4B";
private static final String HTTP_1_1_CONTINUE_HEX_DUMP = "48 54 54 50 2F 31 2E 31 20 31 30 30";
private NioSocketConnector mSocketConnector;
private IoSession mStreamingSession = null;
@@ -63,7 +61,6 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
private long mLastConnectionAttempt = 0;
private AtomicBoolean mConnecting = new AtomicBoolean();
private AtomicBoolean mConnectionFailureLogged = new AtomicBoolean(false);
/**
* Creates an Icecast 2.4.x compatible broadcaster using HTTP 1.1 protocol. This broadcaster is
@@ -144,8 +141,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
if(mSocketConnector == null)
{
mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutMillis(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS);
mSocketConnector.setConnectTimeoutCheckInterval(1000);
mSocketConnector.setConnectTimeoutCheckInterval(10000);
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
@@ -164,58 +160,37 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
setBroadcastState(BroadcastState.CONNECTING);
try {
try
{
ConnectFuture future = mSocketConnector
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
getBroadcastConfiguration().getPort()));
boolean connected;
try {
connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
if (mConnectionFailureLogged.compareAndSet(false, true)) {
mLog.debug("HTTP connect attempt interrupted");
}
setBroadcastState(BroadcastState.DISCONNECTED);
mLastConnectionAttempt = System.currentTimeMillis();
future.cancel();
disconnect();
return;
}
if (connected && future.isConnected()) {
mStreamingSession = future.getSession();
mConnecting.set(false);
mConnectionFailureLogged.set(false);
return;
} else {
if (mConnectionFailureLogged.compareAndSet(false, true)) {
mLog.debug("HTTP connect attempt timed out ({} ms) or not connected",
CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS);
}
setBroadcastState(BroadcastState.DISCONNECTED);
mLastConnectionAttempt = System.currentTimeMillis();
future.cancel();
disconnect();
}
} catch (RuntimeIoException rie) {
Throwable throwableCause = rie.getCause();
setBroadcastState(BroadcastState.DISCONNECTED);
if (mConnectionFailureLogged.compareAndSet(false, true)) {
if (throwableCause != null) {
mLog.debug("Failed to connect", rie);
} else {
mLog.debug("Failed to connect - no exception is available");
}
}
mLastConnectionAttempt = System.currentTimeMillis();
disconnect();
} finally {
if (!connected()) {
mConnecting.set(false);
}
future.awaitUninterruptibly();
mStreamingSession = future.getSession();
}
catch(RuntimeIoException rie)
{
Throwable throwableCause = rie.getCause();
if(throwableCause instanceof ConnectException)
{
setBroadcastState(BroadcastState.NO_SERVER);
}
else if(throwableCause != null)
{
setBroadcastState(BroadcastState.DISCONNECTED);
mLog.debug("Failed to connect", rie);
}
else
{
setBroadcastState(BroadcastState.DISCONNECTED);
mLog.debug("Failed to connect - no exception is available");
}
disconnect();
}
mConnecting.set(false);
}
};
@@ -235,11 +210,6 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
mStreamingSession.closeNow();
}
mLastConnectionAttempt = System.currentTimeMillis();
if(!getBroadcastState().isErrorState()) {
setBroadcastState(BroadcastState.DISCONNECTED);
}
}
/**
@@ -263,7 +233,6 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
mHTTPHeaders = new HashMap<>();
mHTTPHeaders.put(IcecastHeader.ACCEPT.getValue(), "*/*");
mHTTPHeaders.put(IcecastHeader.EXPECT.getValue(), "100-continue");
mHTTPHeaders.put(IcecastHeader.CONTENT_TYPE.getValue(), getConfiguration().getBroadcastFormat().getValue());
mHTTPHeaders.put(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
mHTTPHeaders.put(IcecastHeader.AUTHORIZATION.getValue(), getConfiguration().getBase64EncodedCredentials());
@@ -319,7 +288,6 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
public void sessionClosed(IoSession session) throws Exception
{
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState())
{
@@ -352,10 +320,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
String hexDump = ((ProtocolDecoderException)throwable).getHexdump();
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP) || hexDump.startsWith(HTTP_1_1_CONTINUE_HEX_DUMP))
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
{
setBroadcastState(BroadcastState.CONNECTED);
mConnectionFailureLogged.set(false);
}
else
{
@@ -374,29 +341,15 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
}
else
{
mLog.error("String [" + getStreamName() + "] - HTTP 403 protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
break;
case 401: //Unauthorized
if(message.toString().contains("Authentication Required"))
{
mLog.error("Stream [" + getStreamName() + "] - unable to connect - invalid credentials");
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
disconnect();
}
else
{
mLog.error("String [" + getStreamName() + "] - HTTP 401 protocol decoder error - message:\n\n" + message);
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
break;
default:
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
}
else
@@ -434,15 +387,10 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
switch(response.getStatus())
{
case INFORMATIONAL_CONTINUE:
//Icecast 2.5+ signals that we can start sending audio with 100-continue
setBroadcastState(BroadcastState.CONNECTED);
mConnecting.set(false);
mConnectionFailureLogged.set(false);
break;
case SUCCESS_OK:
setBroadcastState(BroadcastState.CONNECTED);
mConnecting.set(false);
mConnectionFailureLogged.set(false);
break;
case CLIENT_ERROR_UNAUTHORIZED:
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
@@ -453,16 +401,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
disconnect();
break;
default:
/**
* Only allow a generic error to update state if we've not already experienced a more
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
*/
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.ERROR);
}
mLog.debug("Unspecified error: " + response.toString() + " Class:" + object.getClass());
setBroadcastState(BroadcastState.ERROR);
disconnect();
mLog.debug("Unspecified error: " + response.toString() + " Class:" + object.getClass());
break;
}
}
@@ -506,8 +447,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
mMessage = new String(bytes);
//Icecast 2.4+ responds with HTTP/1.0 and Icecast 2.5+ responds with HTTP/1.1
Pattern pattern = Pattern.compile("HTTP/1\\.[01] (\\d{3})");
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
Matcher m = pattern.matcher(mMessage);
if(m.find())

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.
* * ******************************************************************************
* * Copyright (C) 2014-2020 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 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.broadcast.icecast;
@@ -22,16 +25,21 @@ import com.google.common.base.Joiner;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import java.util.List;
import java.util.Optional;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class IcecastMetadata
{
private final static Logger mLog = LoggerFactory.getLogger(IcecastMetadata.class);
@@ -65,23 +73,13 @@ public class IcecastMetadata
if(to != null)
{
sb.append("TO:").append(to);
List<Alias> aliases = aliasList.getAliases(to);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
if(aliases != null && !aliases.isEmpty())
{
sb.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
}
else
{
sb.append("TO:").append(to);
if(!aliases.isEmpty())
{
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
}
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
}
}
else

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-2022 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
@@ -20,7 +20,6 @@ package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
@@ -40,8 +39,6 @@ import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LogLevel;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,7 +56,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
private IoSession mStreamingSession = null;
private long mLastConnectionAttempt = 0;
private boolean mVerboseLogging = false;
private AtomicBoolean mConnecting = new AtomicBoolean();
/**
@@ -77,11 +73,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
if(configuration instanceof BroadcastifyFeedConfiguration broadcastify)
{
mVerboseLogging = broadcastify.isVerboseLogging();
}
}
/**
@@ -94,6 +85,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
if(mInlineActive)
{
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
if (mInlineRemaining == -1)
{
mInlineRemaining = mInlineInterval;
@@ -104,34 +96,16 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
mInlineRemaining -= chunk.length;
audioOffset += chunk.length;
if(mVerboseLogging)
{
mLog.info("Broadcasting audio data - sending [" + chunk.length + "/" + audio.length + "] bytes");
}
mStreamingSession.write(chunk);
if(mInlineRemaining == 0)
{
mInlineRemaining = mInlineInterval;
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
if(mVerboseLogging)
{
mLog.info("Sending inline metadata: " + new String(metadata));
}
mStreamingSession.write(metadata);
}
}
}
else
{
if(mVerboseLogging)
{
mLog.info("Broadcasting audio data [" + audio.length + "] bytes");
}
mStreamingSession.write(audio);
}
}
@@ -158,12 +132,9 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mSocketConnector = new NioSocketConnector();
mSocketConnector.getSessionConfig().setWriteTimeout(WRITE_TIMEOUT_SECONDS);
if(mVerboseLogging)
{
LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
}
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
mSocketConnector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new IcecastCodecFactory()));
@@ -177,10 +148,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
public void run()
{
if(mVerboseLogging)
{
mLog.info("Attempting connection ...");
}
setBroadcastState(BroadcastState.CONNECTING);
try
@@ -189,42 +156,19 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
getBroadcastConfiguration().getPort()));
if(mVerboseLogging)
{
mLog.info("Socket created - asynchronous connect requested - entering wait period");
}
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
if(connected)
{
if(mVerboseLogging)
{
mLog.info("Connected.");
}
mStreamingSession = future.getSession();
mConnecting.set(false);
return;
}
else
{
if(mVerboseLogging)
{
mLog.info("Not Connected. Connection attempt timeout [" + CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS + "ms] exceeded");
}
}
}
catch(RuntimeIoException rioe)
{
if(rioe.getCause() instanceof SocketException)
{
if(mVerboseLogging)
{
mLog.info("Socket error. This usually indicates sdrtrunk can't reach the server " +
"address over the current network connection. Setting state to " +
"NETWORK UNAVAILABLE", rioe);
}
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
mConnecting.set(false);
return;
@@ -232,22 +176,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
catch(UnresolvedAddressException uae)
{
if(mVerboseLogging)
{
mLog.info("Unresolved Address error. This means the domain name services can't resolve " +
"the server URL to an IP address. Setting state to NETWORK UNAVAILABLE", uae);
}
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
mConnecting.set(false);
return;
}
catch(Exception e)
{
if(mVerboseLogging)
{
mLog.info("Unknown error. An error occurred while attempting to connect to the server.", e);
}
mLog.error("Error", e);
//Disregard ... we'll disconnect and try again
}
@@ -256,11 +190,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mLog.error("Throwable error caught", t);
}
if(mVerboseLogging)
{
mLog.info("Starting disconnect sequence since an error occurred while trying to connect.");
}
disconnect();
mConnecting.set(false);
}
@@ -280,11 +209,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
if(connected() && mStreamingSession != null)
{
if(mVerboseLogging)
{
mLog.info("Routine disconnect requested from a connected state with a non-null streaming session");
}
mStreamingSession.closeNow();
}
else
@@ -293,12 +217,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
//want to preserve the error state that got us here, so the user can see it.
if(!getBroadcastState().isErrorState())
{
if(mVerboseLogging)
{
mLog.info("Disconnect requested - previous non-error state was [" + getBroadcastState() +
"] - changing state to DISCONNECTED");
}
setBroadcastState(BroadcastState.DISCONNECTED);
}
@@ -364,23 +282,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mInlineActive = false;
}
if(mVerboseLogging)
{
mLog.info("Session opened. Sending: " + sb);
}
session.write(sb.toString());
}
@Override
public void sessionClosed(IoSession session) throws Exception
{
if(mVerboseLogging)
{
mLog.info("Session closed. Setting connecting flag to false.");
}
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected
@@ -404,23 +311,15 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
}
if(mVerboseLogging)
{
mLog.info("Session error caught.", cause);
}
disconnect();
}
@Override
public void messageReceived(IoSession session, Object object) throws Exception
{
if(object instanceof String message)
if(object instanceof String)
{
if(mVerboseLogging)
{
mLog.info("Message Received [" + message + "]");
}
String message = (String) object;
if(message != null && !message.trim().isEmpty())
{
@@ -448,15 +347,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
else
{
mLog.error("Unrecognized server response:" + message);
/**
* Only allow a generic error to update state if we've not already experienced a more
* specific error. Otherwise, trailing messages will clear the more meaningful error state.
*/
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.ERROR);
}
setBroadcastState(BroadcastState.ERROR);
}
}
}

View File

@@ -1,51 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.audio.broadcast.openmhz;
/**
* HTTP headers used for posting to Rdio Scanner API
*/
public enum FormField
{
CALL("call"),
FREQ("freq"),
START_TIME("start_time"),
STOP_TIME("stop_time"),
CALL_LENGTH("call_length"),
TALKGROUP_NUM("talkgroup_num"),
EMERGENCY("emergency"),
API_KEY("api_key"),
SOURCE_LIST("source_list"),
FREQ_LIST("freq_list"),
PATCH_LIST("patch_list"),
TALKER_ALIAS("talker_alias");
private String mHeader;
FormField(String header)
{
mHeader = header;
}
public String getHeader()
{
return mHeader;
}
}

View File

@@ -1,681 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.audio.broadcast.openmhz;
import com.google.common.net.HttpHeaders;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio broadcaster to push completed audio recordings to the OpenMHz call upload API.
*
*/
public class OpenMHzBroadcaster extends AbstractAudioBroadcaster<OpenMHzConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzBroadcaster.class);
private static final String ENCODING_TYPE_MP3 = "mp3";
private static final String MULTIPART_TYPE = "multipart";
private static final String DEFAULT_SUBTYPE = "form-data";
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
private HttpClient mHttpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
private AliasModel mAliasModel;
/**
* Constructs an instance of the broadcaster
* @param config to use
* @param aliasModel for access to aliases
*/
public OpenMHzBroadcaster(OpenMHzConfiguration config, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(config);
mAliasModel = aliasModel;
}
/**
* Starts the audio recording processor thread
*/
@Override
public void start()
{
setBroadcastState(BroadcastState.CONNECTING);
String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis();
if(response == "OK")
{
setBroadcastState(BroadcastState.CONNECTED);
}
else if (response == "No Response")
{
setBroadcastState(BroadcastState.NO_SERVER);
mLog.error("Error connecting to OpenMHz server [Server not found or not reachable]");
}
else
{
mLog.error("Error connecting to OpenMHz server on startup [" + response + "]");
setBroadcastState(BroadcastState.ERROR);
}
if(mAudioRecordingProcessorFuture == null)
{
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
0, 500, TimeUnit.MILLISECONDS);
}
}
/**
* Stops the audio recording processor thread
*/
@Override
public void stop()
{
if(mAudioRecordingProcessorFuture != null)
{
mAudioRecordingProcessorFuture.cancel(true);
mAudioRecordingProcessorFuture = null;
dispose();
setBroadcastState(BroadcastState.DISCONNECTED);
}
}
/**
* Prepares for disposal
*/
@Override
public void dispose()
{
AudioRecording audioRecording = mAudioRecordingQueue.poll();
while(audioRecording != null)
{
audioRecording.removePendingReplay();
audioRecording = mAudioRecordingQueue.poll();
}
}
/**
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
* the audio processor thread will persistently invoke this method to attempt a reconnect.
*/
private boolean connected()
{
if(getBroadcastState() != BroadcastState.CONNECTED &&
(System.currentTimeMillis() - mLastConnectionAttempt > mConnectionAttemptInterval))
{
setBroadcastState(BroadcastState.CONNECTING);
String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis();
if(response == "OK")
{
setBroadcastState(BroadcastState.CONNECTED);
}
else if (response == "No Response")
{
setBroadcastState(BroadcastState.NO_SERVER);
mLog.error("Error reconnecting to OpenMHz server [Server not found or not reachable]");
}
else
{
setBroadcastState(BroadcastState.ERROR);
mLog.error("Error reconnecting to OpenMHz server [" + response + "]");
}
}
return getBroadcastState() == BroadcastState.CONNECTED;
}
@Override
public int getAudioQueueSize()
{
return mAudioRecordingQueue.size();
}
@Override
public void receive(AudioRecording audioRecording)
{
mAudioRecordingQueue.offer(audioRecording);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
}
/**
* Indicates if the audio recording is non-null and not too old, meaning that the age of the recording has not
* exceeded the max age value indicated in the broadcast configuration. Audio recordings that are too old will be
* deleted to ensure that the in-memory queue size doesn't blow up.
* @param audioRecording to test
* @return true if the recording is valid
*/
private boolean isValid(AudioRecording audioRecording)
{
return audioRecording != null && System.currentTimeMillis() - audioRecording.getStartTime() <=
getBroadcastConfiguration().getMaximumRecordingAge();
}
/**
* Processes any enqueued audio recordings. This method employs asynchronous
* interaction with the server, so multiple audio recording uploads can occur simultaneously.
*/
private void processRecordingQueue()
{
while(connected() && !mAudioRecordingQueue.isEmpty())
{
final AudioRecording audioRecording = mAudioRecordingQueue.poll();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
if(isValid(audioRecording) && audioRecording.getRecordingLength() > 0)
{
int durationSeconds = (int)(audioRecording.getRecordingLength() / 1E3f);
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
String talkgroup = getTo(audioRecording);
String radioId = getFrom(audioRecording);
String talkerAlias = getTalkerAlias(audioRecording);
Long frequency = getFrequency(audioRecording);
String patches = getPatches(audioRecording);
String talkgroupLabel = getTalkgroupLabel(audioRecording);
String talkgroupGroup = getTalkgroupGroup(audioRecording);
String systemLabel = getSystemLabel(audioRecording);
try
{
byte[] audioBytes = null;
try
{
audioBytes = Files.readAllBytes(audioRecording.getPath());
}
catch(IOException e)
{
mLog.error("OpenMHz - audio recording file not found - ignoring upload");
}
if(audioBytes != null)
{
String uri = getBroadcastConfiguration().getHost() + "/" + getBroadcastConfiguration().getSystemName() + "/upload";
OpenMHzBuilder bodyBuilder = new OpenMHzBuilder();
bodyBuilder
.addFile(audioBytes)
.addPart(FormField.FREQ, frequency)
.addPart(FormField.START_TIME, timestampSeconds)
.addPart(FormField.STOP_TIME, timestampSeconds)
.addPart(FormField.CALL_LENGTH, durationSeconds)
.addPart(FormField.TALKGROUP_NUM, talkgroup)
.addPart(FormField.EMERGENCY, 0)
.addPart(FormField.API_KEY, getBroadcastConfiguration().getApiKey())
.addPart(FormField.PATCH_LIST, patches)
.addPart(FormField.TALKER_ALIAS, talkerAlias)
.addPart(FormField.SOURCE_LIST, "[{ \"pos\": 0.00, \"src\": " + radioId + "}]");
HttpRequest fileRequest = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
.POST(bodyBuilder.build())
.build();
mHttpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
.whenComplete((fileResponse, throwable1) -> {
if(throwable1 != null || fileResponse.statusCode() != 200)
{
if(throwable1 instanceof IOException || throwable1 instanceof CompletionException)
{
//We get socket reset exceptions occasionally when the remote server doesn't
//fully read our request and immediately responds.
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("OpenMHz API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
else
{
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("OpenMHz API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
incrementErrorAudioCount();
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
}
else
{
if(fileResponse.statusCode() == 200)
{
incrementStreamedAudioCount();
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
else
{
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("OpenMHz API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
}
});
}
else
{
//Register an error for the file not found exception
mLog.error("OpenMHz API - upload file not found [" +
audioRecording.getPath().toString() + "]");
incrementErrorAudioCount();
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
}
catch(Exception e)
{
mLog.error("Unknown Error", e);
setBroadcastState(BroadcastState.ERROR);
incrementErrorAudioCount();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
}
}
//If we're not connected and there are recordings in the queue, check the recording at the head of the queue
// and start age-off once the recordings become too old. The recordings should be time ordered in the queue.
AudioRecording audioRecording = mAudioRecordingQueue.peek();
while(audioRecording != null)
{
if(isValid(audioRecording))
{
return;
}
else
{
//Remove the recording from the queue, remove a replay, and peek at the next recording in the queue
mAudioRecordingQueue.poll();
audioRecording.removePendingReplay();
incrementAgedOffAudioCount();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
audioRecording = mAudioRecordingQueue.peek();
}
}
}
/**
* Creates a frequency value from the audio recording identifier collection.
*/
private static Long getFrequency(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
Form.CHANNEL_FREQUENCY, Role.ANY);
if(identifier instanceof ConfigurationLongIdentifier)
{
Long value = ((ConfigurationLongIdentifier)identifier).getValue();
if(value != null)
{
return value;
}
}
return Long.valueOf(0);
}
/**
* Creates a formatted string with the FROM identifier or uses a default of zero(0)
*/
private static String getFrom(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof RadioIdentifier)
{
return ((RadioIdentifier)identifier).getValue().toString();
}
}
return "0";
}
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
private String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier != null)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
}
}
return "0";
}
/**
* Creates a formatted string with the Talkgroup Label from the Audio Recording Alias
* If this is a PatchGroup we return only the first label as the primary talkgroup label.
*
*/
private String getTalkgroupLabel(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
StringBuilder sb = new StringBuilder();
if(identifier != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
if(!aliases.isEmpty())
{
sb.append(aliases.get(0));
}
}
return sb.toString();
}
/**
* Creates a formatted string with the Talkgroup Group from the Audio Recording Alias
* If this is a PatchGroup we return only the first group as the primary talkgroup group.
*
*/
private String getTalkgroupGroup(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
StringBuilder sb = new StringBuilder();
if(identifier != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
if(!aliases.isEmpty())
{
sb.append(aliases.get(0).getGroup());
}
}
return sb.toString();
}
/**
* Creates a formatted string with the System Label from the Audio Recording Alias
* If this is a PatchGroup we return only the first sytem as the primary talkgroup system.
*
*/
private String getSystemLabel(AudioRecording audioRecording)
{
List<Identifier> systems = audioRecording.getIdentifierCollection().getIdentifiers(Form.SYSTEM);
StringBuilder sb = new StringBuilder();
if(!systems.isEmpty())
{
sb.append(systems.get(0));
}
return sb.toString();
}
/**
* Creates a formatted string with the patched talkgroups
*/
public static String getPatches(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
PatchGroup patchGroup = patchGroupIdentifier.getValue();
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}
sb.append("]");
return sb.toString();
}
return "[]";
}
/**
* Creates a formatted string with talkerAlias identifier
*/
private static String getTalkerAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier)
{
TalkerAliasIdentifier talkerID = (TalkerAliasIdentifier)identifier;
if(talkerID.isValid())
{
return talkerID.getValue();
}
}
}
return "";
}
/**
* Tests both the connection and configuration against the OpenMHz Call API service
* @param configuration containing API key and system id
* @return error string or null if test is successful
*/
public static String testConnection(OpenMHzConfiguration configuration)
{
String uri = configuration.getHost() + "/" + configuration.getSystemName() + "/authorize";
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_FORM_URLENCODED)
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
.POST(HttpRequest.BodyPublishers.ofString("api_key="+configuration.getApiKey()))
.build();
HttpResponse.BodyHandler<String> responseHandler = HttpResponse.BodyHandlers.ofString();
try
{
HttpResponse<String> response = httpClient.send(request, responseHandler);
if (response.statusCode() == 200)
{
return "OK";
}
else if(response.statusCode() == 403)
{
return "Invalid API Key";
}
else if(response.statusCode() == 500)
{
return "Invalid System Name";
}
return "No Response";
}
catch(Exception e)
{
Throwable throwableCause = e.getCause();
if(throwableCause instanceof ConnectException)
{
return "No Response";
}
mLog.error("Exception connecting to OpenMHz server [" + e.toString() + "]");
return "Uknown Exception";
}
}
public class AudioRecordingProcessor implements Runnable
{
@Override
public void run()
{
processRecordingQueue();
}
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
OpenMHzConfiguration config = new OpenMHzConfiguration();
config.setHost("https://api.OpenMHz.com/call-upload-dev");
config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
config.setSystemName("systemx");
String response = testConnection(config);
mLog.error("Response: " + response);
if(response == "OK")
{
mLog.debug("Test Successful!");
}
else
{
if(response.contains("Invalid API Key"))
{
mLog.error("Invalid API Key");
}
else if(response.contains("Invalid System Name"))
{
mLog.error("Invalid System Name");
}
else
{
mLog.debug("Response: " + response);
}
}
mLog.debug("Finished!");
}
}

View File

@@ -1,216 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.audio.broadcast.openmhz;
import java.net.http.HttpRequest;
import java.util.ArrayList;
import java.util.List;
import java.lang.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* Builder for an HTTP body publisher to produce a OpenMHz call event
*/
public class OpenMHzBuilder
{
private static final String DASH_DASH = "--";
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
private List<Part> mParts = new ArrayList<>();
private byte[] audioBytes = null;
/**
* Constructs an instance
*/
public OpenMHzBuilder()
{
}
/**
* Access the static multi-part boundary string
*/
public String getBoundary()
{
return BOUNDARY;
}
/**
* Adds a Audio bytes part to the call
*/
public OpenMHzBuilder addFile(byte[] value)
{
audioBytes = value;
return this;
}
/**
* Adds a string part to the call
*/
public OpenMHzBuilder addPart(FormField key, String value)
{
if(key != null && value != null)
{
mParts.add(new Part(key.getHeader(), value));
}
return this;
}
/**
* Adds a number part to the call
*/
public OpenMHzBuilder addPart(FormField key, Number value)
{
if(key != null && value != null)
{
mParts.add(new Part(key.getHeader(), value.toString()));
}
return this;
}
/**
* Creates a form-data item
*/
private static String formatPart(Part part, String boundary)
{
StringBuilder sb = new StringBuilder();
sb.append(DASH_DASH).append(boundary).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"").append(part.mKey).append("\"\r\n\r\n");
sb.append(part.getValue()).append("\r\n");
return sb.toString();
}
/**
* Creates the audio file item
*/
private String formatFilePart(String boundary)
{
if(audioBytes == null)
{
return "";
}
StringBuilder sb= new StringBuilder();
sb.append(DASH_DASH).append(boundary).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"").append("call").append("\"; filename=\"audio.mp3\"\r\n\r\n");
return sb.toString();
}
/**
* Creates the boundary closing item
*/
private static String getClosingBoundary(String boundary)
{
StringBuilder sb = new StringBuilder();
sb.append(DASH_DASH).append(boundary).append(DASH_DASH).append("\r\n");
return sb.toString();
}
/**
* Creates a BodyPublisher for accessing the call form data
*/
public HttpRequest.BodyPublisher build()
{
StringBuilder sb = new StringBuilder();
for(Part part: mParts)
{
sb.append(formatPart(part, BOUNDARY));
}
sb.append(formatFilePart(BOUNDARY));
/**
* We need to create a ByteArray consisting of the Sting "parts" and the audio file bytes
*/
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try
{
outputStream.write(sb.toString().getBytes());
if(audioBytes != null)
{
outputStream.write(audioBytes);
}
sb = new StringBuilder();
sb.append("\r\n");
sb.append(getClosingBoundary(BOUNDARY));
outputStream.write(sb.toString().getBytes());
}
catch(IOException e)
{
//mLog.error("Rdio Scanner API - unable to create POST reqeust.");
}
return HttpRequest.BodyPublishers.ofByteArray(outputStream.toByteArray());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
for(Part part: mParts)
{
sb.append(formatPart(part, BOUNDARY));
}
sb.append(getClosingBoundary(BOUNDARY));
return sb.toString();
}
/**
* Key:Value pair holder
*/
public class Part
{
private String mKey;
private String mValue;
/**
* Constructs a new part
* @param key value
* @param value item
*/
public Part(String key, String value)
{
mKey = key;
mValue = value;
}
public String getKey()
{
return mKey;
}
public String getValue()
{
return mValue;
}
}
}

View File

@@ -1,139 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.audio.broadcast.openmhz;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Streaming configuration for Rdio Scanner API.
*
* Note: this API is not a streaming audio service, rather a completed call push service. However, it fits nicely
* with the structure of the audio streaming subsystem in sdrtrunk
*/
public class OpenMHzConfiguration extends BroadcastConfiguration
{
public static final String PRODUCTION_ENDPOINT = "https://api.openmhz.com";
private StringProperty mSystemName = new SimpleStringProperty();
private StringProperty mApiKey = new SimpleStringProperty();
/**
* Constructor for faster jackson
*/
public OpenMHzConfiguration()
{
this(BroadcastFormat.MP3);
}
/**
* Public constructor.
* @param format to use for audio recording (MP3)
*/
public OpenMHzConfiguration(BroadcastFormat format)
{
super(format);
if(getHost() == null)
{
setHost(PRODUCTION_ENDPOINT);
}
//The parent class binds this property, so we unbind it and rebind it here
mValid.unbind();
mValid.bind(Bindings.and(Bindings.and(Bindings.isNotNull(mSystemName), Bindings.isNotNull(mApiKey)),
Bindings.isNotNull(mHost)));
}
/**
* System ID as a property
*/
public StringProperty systemIDProperty()
{
return mSystemName;
}
/**
* API key as a property
*/
public StringProperty apiKeyProperty()
{
return mApiKey;
}
/**
* API Key
*/
@JacksonXmlProperty(isAttribute = true, localName = "api_key")
public String getApiKey()
{
return mApiKey.get();
}
/**
* Sets the api key
* @param apiKey
*/
public void setApiKey(String apiKey)
{
mApiKey.setValue(apiKey);
}
/**
* System name as provided by OpenMHz.com
*/
@JacksonXmlProperty(isAttribute = true, localName = "system_name")
public String getSystemName()
{
return mSystemName.get();
}
/**
* Sets the system name provided by OpenMHz.com
*/
public void setSystemName(String systemName)
{
mSystemName.set(systemName);
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.OPENMHZ;
}
@Override
public BroadcastConfiguration copyOf()
{
OpenMHzConfiguration copy = new OpenMHzConfiguration();
copy.setSystemName(getSystemName());
return copy;
}
}

View File

@@ -1,234 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.audio.broadcast.openmhz;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.gui.playlist.streaming.AbstractBroadcastEditor;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.controlsfx.control.ToggleSwitch;
/**
* OpenMHz calls API configuration editor
*/
public class OpenMHzEditor extends AbstractBroadcastEditor<OpenMHzConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzEditor.class);
private TextField mSystemNameTextField;
private IntegerTextField mMaxAgeTextField;
private TextField mApiKeyTextField;
private TextField mHostTextField;
private GridPane mEditorPane;
/**
* Constructs an instance
* @param playlistManager for accessing the broadcast model
*/
public OpenMHzEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
@Override
public void setItem(OpenMHzConfiguration item)
{
super.setItem(item);
getSystemNameTextField().setDisable(item == null);
getApiKeyTextField().setDisable(item == null);
getHostTextField().setDisable(item == null);
getMaxAgeTextField().setDisable(item == null);
if(item != null)
{
getSystemNameTextField().setText(item.getSystemName());
getApiKeyTextField().setText(item.getApiKey());
getHostTextField().setText(item.getHost());
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000));
}
else
{
getSystemNameTextField().setText(null);
getApiKeyTextField().setText(null);
getHostTextField().setText(null);
getMaxAgeTextField().set(0);
}
modifiedProperty().set(false);
}
@Override
public void dispose()
{
}
@Override
public void save()
{
if(getItem() != null)
{
getItem().setSystemName(getSystemNameTextField().getText());
getItem().setHost(getHostTextField().getText());
getItem().setApiKey(getApiKeyTextField().getText());
getItem().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
}
super.save();
}
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.OPENMHZ;
}
protected GridPane getEditorPane()
{
if(mEditorPane == null)
{
mEditorPane = new GridPane();
mEditorPane.setPadding(new Insets(10, 5, 10,10));
mEditorPane.setVgap(10);
mEditorPane.setHgap(5);
int row = 0;
Label formatLabel = new Label("Format");
GridPane.setHalignment(formatLabel, HPos.RIGHT);
GridPane.setConstraints(formatLabel, 0, row);
mEditorPane.getChildren().add(formatLabel);
GridPane.setConstraints(getFormatField(), 1, row);
mEditorPane.getChildren().add(getFormatField());
Label enabledLabel = new Label("Enabled");
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
GridPane.setConstraints(enabledLabel, 2, row);
mEditorPane.getChildren().add(enabledLabel);
GridPane.setConstraints(getEnabledSwitch(), 3, row);
mEditorPane.getChildren().add(getEnabledSwitch());
Label systemLabel = new Label("Name");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, ++row);
mEditorPane.getChildren().add(systemLabel);
GridPane.setConstraints(getNameTextField(), 1, row);
mEditorPane.getChildren().add(getNameTextField());
Label apiKeyLabel = new Label("API Key");
GridPane.setHalignment(apiKeyLabel, HPos.RIGHT);
GridPane.setConstraints(apiKeyLabel, 0, ++row);
mEditorPane.getChildren().add(apiKeyLabel);
GridPane.setConstraints(getApiKeyTextField(), 1, row);
mEditorPane.getChildren().add(getApiKeyTextField());
Label systemNameLabel = new Label("System Short Name");
GridPane.setHalignment(systemNameLabel, HPos.RIGHT);
GridPane.setConstraints(systemNameLabel, 0, ++row);
mEditorPane.getChildren().add(systemNameLabel);
GridPane.setConstraints(getSystemNameTextField(), 1, row);
mEditorPane.getChildren().add(getSystemNameTextField());
Label hostLabel = new Label("OpenMHz Upload Server (API)");
GridPane.setHalignment(hostLabel, HPos.RIGHT);
GridPane.setConstraints(hostLabel, 0, ++row);
mEditorPane.getChildren().add(hostLabel);
GridPane.setConstraints(getHostTextField(), 1, row);
mEditorPane.getChildren().add(getHostTextField());
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, ++row);
mEditorPane.getChildren().add(maxAgeLabel);
GridPane.setConstraints(getMaxAgeTextField(), 1, row);
mEditorPane.getChildren().add(getMaxAgeTextField());
}
return mEditorPane;
}
private IntegerTextField getMaxAgeTextField()
{
if(mMaxAgeTextField == null)
{
mMaxAgeTextField = new IntegerTextField();
mMaxAgeTextField.setDisable(true);
mMaxAgeTextField.textProperty().addListener(mEditorModificationListener);
}
return mMaxAgeTextField;
}
private TextField getHostTextField()
{
if(mHostTextField == null)
{
mHostTextField = new TextField();
mHostTextField.setDisable(true);
mHostTextField.textProperty().addListener(mEditorModificationListener);
}
return mHostTextField;
}
private TextField getApiKeyTextField()
{
if(mApiKeyTextField == null)
{
mApiKeyTextField = new TextField();
mApiKeyTextField.setDisable(true);
mApiKeyTextField.textProperty().addListener(mEditorModificationListener);
}
return mApiKeyTextField;
}
private TextField getSystemNameTextField()
{
if(mSystemNameTextField == null)
{
mSystemNameTextField = new TextField();
mSystemNameTextField.setDisable(true);
mSystemNameTextField.textProperty().addListener(mEditorModificationListener);
}
return mSystemNameTextField;
}
}

View File

@@ -1,126 +0,0 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.audio.broadcast.openmhz;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OpenMHzFeedConfiguration extends IcecastTCPConfiguration
{
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzFeedConfiguration.class);
private int mFeedID;
public OpenMHzFeedConfiguration()
{
//No-arg constructor for JAXB
this(BroadcastFormat.MP3);
}
/**
* OpenMHz configuration for Icecast 2.3.2 compatible servers
*
* @param format of output audio (MP3)
*/
public OpenMHzFeedConfiguration(BroadcastFormat format)
{
super(format);
setBitRate(16);
setChannels(1);
setSampleRate(8000);
setInline(true);
}
public static OpenMHzFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
{
OpenMHzFeedConfiguration config = new OpenMHzFeedConfiguration(BroadcastFormat.MP3);
config.setName(userFeedBroadcast.getDescription());
config.setHost(userFeedBroadcast.getHostname());
config.setMountPoint(userFeedBroadcast.getMount());
config.setFeedID(userFeedBroadcast.getFeedId());
config.setPassword(userFeedBroadcast.getPassword());
try
{
config.setPort(Integer.parseInt(userFeedBroadcast.getPort()));
}
catch(Exception e)
{
mLog.error("Error creating Rdio Scanner configuration from radio reference user feed instance");
}
return config;
}
@Override
public BroadcastConfiguration copyOf()
{
OpenMHzFeedConfiguration copy = new OpenMHzFeedConfiguration(getBroadcastFormat());
//Broadcast Configuration Parameters
copy.setName(getName());
copy.setHost(getHost());
copy.setPort(getPort());
copy.setInline(getInline());
copy.setPassword(getPassword());
copy.setDelay(getDelay());
copy.setEnabled(false);
//Icecast Configuration Parameters
copy.setUserName(getUserName());
copy.setMountPoint(getMountPoint());
copy.setDescription(getDescription());
copy.setGenre(getGenre());
copy.setPublic(isPublic());
copy.setURL(getURL());
//OpenMHz Configuration Parameters
copy.setFeedID(getFeedID());
return copy;
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.OPENMHZ;
}
@JacksonXmlProperty(isAttribute = true, localName = "feed_id")
public int getFeedID()
{
return mFeedID;
}
public void setFeedID(int feedID)
{
mFeedID = feedID;
}
}

View File

@@ -1,57 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.audio.broadcast.rdioscanner;
/**
* HTTP headers used for posting to Rdio Scanner API
*/
public enum FormField
{
AUDIO("audio"),
AUDIO_NAME("audioName"),
AUDIO_TYPE("audioType"),
DATE_TIME("dateTime"),
FREQUENCIES("frequencies"),
FREQUENCY("frequency"),
KEY("key"),
PATCHES("patches"),
SOURCE("source"),
SOURCES("sources"),
SYSTEM("system"),
SYSTEM_LABEL("systemLabel"),
TALKER_ALIAS("talkerAlias"),
TALKGROUP_ID("talkgroup"),
TALKGROUP_GROUP("talkgroupGroup"),
TALKGROUP_LABEL("talkgroupLabel"),
TALKGROUP_TAG("talkgroupTag"),
TEST("test");
private String mHeader;
FormField(String header)
{
mHeader = header;
}
public String getHeader()
{
return mHeader;
}
}

View File

@@ -1,658 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 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.audio.broadcast.rdioscanner;
import com.google.common.net.HttpHeaders;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Audio broadcaster to push completed audio recordings to the Rdio Scanner call upload API.
*
*/
public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScannerConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerBroadcaster.class);
private static final String ENCODING_TYPE_MP3 = "mp3";
private static final String MULTIPART_TYPE = "multipart";
private static final String DEFAULT_SUBTYPE = "form-data";
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
private HttpClient mHttpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
private AliasModel mAliasModel;
/**
* Constructs an instance of the broadcaster
* @param config to use
* @param aliasModel for access to aliases
*/
public RdioScannerBroadcaster(RdioScannerConfiguration config, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(config);
mAliasModel = aliasModel;
}
/**
* Starts the audio recording processor thread
*/
@Override
public void start()
{
setBroadcastState(BroadcastState.CONNECTING);
String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis();
/**
* Rdio Scanner API does not currently expose a test method.
*/
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
{
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
mLog.error("Error connecting to Rdio Scanner server on startup [" + response + "]");
setBroadcastState(BroadcastState.ERROR);
}
if(mAudioRecordingProcessorFuture == null)
{
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
0, 500, TimeUnit.MILLISECONDS);
}
}
/**
* Stops the audio recording processor thread
*/
@Override
public void stop()
{
if(mAudioRecordingProcessorFuture != null)
{
mAudioRecordingProcessorFuture.cancel(true);
mAudioRecordingProcessorFuture = null;
dispose();
setBroadcastState(BroadcastState.DISCONNECTED);
}
}
/**
* Prepares for disposal
*/
@Override
public void dispose()
{
AudioRecording audioRecording = mAudioRecordingQueue.poll();
while(audioRecording != null)
{
audioRecording.removePendingReplay();
audioRecording = mAudioRecordingQueue.poll();
}
}
/**
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
* the audio processor thread will persistently invoke this method to attempt a reconnect.
*
* Rdio Scanner does not have a test API endpoint, so we look for the incomplete call response.
*/
private boolean connected()
{
if(getBroadcastState() != BroadcastState.CONNECTED &&
(System.currentTimeMillis() - mLastConnectionAttempt > mConnectionAttemptInterval))
{
setBroadcastState(BroadcastState.CONNECTING);
String response = testConnection(getBroadcastConfiguration());
mLastConnectionAttempt = System.currentTimeMillis();
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
{
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
setBroadcastState(BroadcastState.ERROR);
}
}
return getBroadcastState() == BroadcastState.CONNECTED;
}
@Override
public int getAudioQueueSize()
{
return mAudioRecordingQueue.size();
}
@Override
public void receive(AudioRecording audioRecording)
{
mAudioRecordingQueue.offer(audioRecording);
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
}
/**
* Indicates if the audio recording is non-null and not too old, meaning that the age of the recording has not
* exceeded the max age value indicated in the broadcast configuration. Audio recordings that are too old will be
* deleted to ensure that the in-memory queue size doesn't blow up.
* @param audioRecording to test
* @return true if the recording is valid
*/
private boolean isValid(AudioRecording audioRecording)
{
return audioRecording != null && System.currentTimeMillis() - audioRecording.getStartTime() <=
getBroadcastConfiguration().getMaximumRecordingAge();
}
/**
* Processes any enqueued audio recordings. This method employs asynchronous
* interaction with the server, so multiple audio recording uploads can occur simultaneously.
*/
private void processRecordingQueue()
{
while(connected() && !mAudioRecordingQueue.isEmpty())
{
final AudioRecording audioRecording = mAudioRecordingQueue.poll();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
if(isValid(audioRecording) && audioRecording.getRecordingLength() > 0)
{
float durationSeconds = (float)(audioRecording.getRecordingLength() / 1E3f);
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
String talkgroup = getTo(audioRecording);
String radioId = getFrom(audioRecording);
String talkerAlias = getTalkerAlias(audioRecording);
Long frequency = getFrequency(audioRecording);
String patches = getPatches(audioRecording);
String talkgroupLabel = getTalkgroupLabel(audioRecording);
String talkgroupGroup = getTalkgroupGroup(audioRecording);
String systemLabel = getSystemLabel(audioRecording);
String path = audioRecording.getPath().toString();
// Remove TEMPORARY_STREAM_FILE_SUFFIX
String audioName = path.substring(path.substring(0, path.lastIndexOf("_")).lastIndexOf("_") + 1);
try
{
byte[] audioBytes = null;
try
{
audioBytes = Files.readAllBytes(audioRecording.getPath());
}
catch(IOException e)
{
mLog.error("Rdio Scanner API - audio recording file not found - ignoring upload");
}
if(audioBytes != null)
{
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
bodyBuilder.addPart(FormField.KEY, getBroadcastConfiguration().getApiKey())
.addPart(FormField.SYSTEM, getBroadcastConfiguration().getSystemID())
.addAudioName(audioName)
.addFile(audioBytes)
.addPart(FormField.DATE_TIME, timestampSeconds)
.addPart(FormField.TALKGROUP_ID, talkgroup)
.addPart(FormField.SOURCE, radioId)
.addPart(FormField.FREQUENCY, frequency)
.addPart(FormField.TALKER_ALIAS, talkerAlias)
.addPart(FormField.TALKGROUP_LABEL, talkgroupLabel)
.addPart(FormField.TALKGROUP_GROUP, talkgroupGroup)
.addPart(FormField.SYSTEM_LABEL, systemLabel)
.addPart(FormField.PATCHES, patches);
HttpRequest fileRequest = HttpRequest.newBuilder()
.uri(URI.create(getBroadcastConfiguration().getHost()))
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
.POST(bodyBuilder.build())
.build();
mHttpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
.whenComplete((fileResponse, throwable1) -> {
if(throwable1 != null || fileResponse.statusCode() != 200)
{
if(throwable1 instanceof IOException || throwable1 instanceof CompletionException)
{
//We get socket reset exceptions occasionally when the remote server doesn't
//fully read our request and immediately responds.
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("Rdio Scanner API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
else
{
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("Rdio Scanner API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
incrementErrorAudioCount();
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
}
else
{
String fileResponseString = fileResponse.body();
if(fileResponseString.contains("Call imported successfully."))
{
incrementStreamedAudioCount();
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
else if(fileResponseString.contains("duplicate call rejected"))
{
//Rdio Scanner is telling us to skip audio upload - someone already uploaded it
audioRecording.removePendingReplay();
}
else
{
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
mLog.error("Rdio Scanner API file upload fail [" +
fileResponse.statusCode() + "] response [" +
fileResponse.body() + "]");
}
}
});
}
else
{
//Register an error for the file not found exception
mLog.error("Rdio Scanner API - upload file not found [" +
audioRecording.getPath().toString() + "]");
incrementErrorAudioCount();
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
}
catch(Exception e)
{
mLog.error("Unknown Error", e);
setBroadcastState(BroadcastState.ERROR);
incrementErrorAudioCount();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
}
}
//If we're not connected and there are recordings in the queue, check the recording at the head of the queue
// and start age-off once the recordings become too old. The recordings should be time ordered in the queue.
AudioRecording audioRecording = mAudioRecordingQueue.peek();
while(audioRecording != null)
{
if(isValid(audioRecording))
{
return;
}
else
{
//Remove the recording from the queue, remove a replay, and peek at the next recording in the queue
mAudioRecordingQueue.poll();
audioRecording.removePendingReplay();
incrementAgedOffAudioCount();
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
audioRecording = mAudioRecordingQueue.peek();
}
}
}
/**
* Creates a frequency value from the audio recording identifier collection.
*/
private static Long getFrequency(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
Form.CHANNEL_FREQUENCY, Role.ANY);
if(identifier instanceof ConfigurationLongIdentifier)
{
Long value = ((ConfigurationLongIdentifier)identifier).getValue();
if(value != null)
{
return value;
}
}
return Long.valueOf(0);
}
/**
* Creates a formatted string with the FROM identifier or uses a default of zero(0)
*/
private static String getFrom(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof RadioIdentifier)
{
return ((RadioIdentifier)identifier).getValue().toString();
}
}
return "0";
}
private static String getTalkerAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier)
{
TalkerAliasIdentifier talkerID = (TalkerAliasIdentifier)identifier;
if(talkerID.isValid())
{
return talkerID.getValue();
}
}
}
return "";
}
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
private String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier != null)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
}
}
return "0";
}
/**
* Creates a formatted string with the Talkgroup Label from the Audio Recording Alias
* If this is a PatchGroup we return only the first label as the primary talkgroup label.
*
*/
private String getTalkgroupLabel(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
StringBuilder sb = new StringBuilder();
if(identifier != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
if(!aliases.isEmpty())
{
sb.append(aliases.get(0));
}
}
return sb.toString();
}
/**
* Creates a formatted string with the Talkgroup Group from the Audio Recording Alias
* If this is a PatchGroup we return only the first group as the primary talkgroup group.
*
*/
private String getTalkgroupGroup(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
StringBuilder sb = new StringBuilder();
if(identifier != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
if(!aliases.isEmpty())
{
sb.append(aliases.get(0).getGroup());
}
}
return sb.toString();
}
/**
* Creates a formatted string with the System Label from the Audio Recording Alias
* If this is a PatchGroup we return only the first sytem as the primary talkgroup system.
*
*/
private String getSystemLabel(AudioRecording audioRecording)
{
List<Identifier> systems = audioRecording.getIdentifierCollection().getIdentifiers(Form.SYSTEM);
StringBuilder sb = new StringBuilder();
if(!systems.isEmpty())
{
sb.append(systems.get(0));
}
return sb.toString();
}
/**
* Creates a formatted string with the patched talkgroups
*/
public static String getPatches(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
PatchGroup patchGroup = patchGroupIdentifier.getValue();
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}
sb.append("]");
return sb.toString();
}
return "[]";
}
/**
* Tests both the connection and configuration against the RdioScanner Call API service
* @param configuration containing API key and system id
* @return error string or null if test is successful
*/
public static String testConnection(RdioScannerConfiguration configuration)
{
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
bodyBuilder.addPart(FormField.KEY, configuration.getApiKey())
.addPart(FormField.SYSTEM, configuration.getSystemID())
.addPart(FormField.TEST, 1);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(configuration.getHost()))
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
.header(HttpHeaders.ACCEPT, "*/*")
.POST(bodyBuilder.build())
.build();
HttpResponse.BodyHandler<String> responseHandler = HttpResponse.BodyHandlers.ofString();
try
{
HttpResponse<String> response = httpClient.send(request, responseHandler);
String responseBody = response.body();
return (responseBody != null ? responseBody : "(no response)") + " Status Code:" + response.statusCode();
}
catch(Exception e)
{
return e.getLocalizedMessage();
}
}
public class AudioRecordingProcessor implements Runnable
{
@Override
public void run()
{
processRecordingQueue();
}
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
RdioScannerConfiguration config = new RdioScannerConfiguration();
config.setHost("https://api.RdioScanner.com/call-upload-dev");
config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
config.setSystemID(11);
String response = testConnection(config);
if(response == null)
{
mLog.debug("Test Successful!");
}
else
{
if(response.contains("1 Invalid-API-Key"))
{
mLog.error("Invalid API Key");
}
else if(response.contains("1 API-Key-Access-Denied"))
{
mLog.error("System ID not valid for API Key");
}
else
{
mLog.debug("Response: " + response);
}
}
mLog.debug("Finished!");
}
}

Some files were not shown because too many files have changed in this diff Show More