mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
2 Commits
v0.6.1-bet
...
v0.5.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
421da1c1ce | ||
|
|
2412833899 |
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -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.
|
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: How-To Question or Does The Software Support xxx
|
|
||||||
about: End user question or support discussion
|
|
||||||
title: STOP - Don't Use Issue Reporting to Post End-User Questions!!!
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
STOP !!!!
|
|
||||||
|
|
||||||
GitHub issues are for reporting software bugs or requesting enhancements.
|
|
||||||
|
|
||||||
If you have a question about how to use the software, or if you want to know if sdrtrunk supports a protocol, tuner, or other aspect, please post your question in one of the Support forums.
|
|
||||||
|
|
||||||
Visit: https://github.com/DSheirer/sdrtrunk/wiki
|
|
||||||
10
.github/workflows/gradle.yml
vendored
10
.github/workflows/gradle.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 23
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '23'
|
java-version: '17'
|
||||||
distribution: 'liberica'
|
distribution: 'liberica'
|
||||||
java-package: 'jdk+fx'
|
java-package: 'jdk+fx'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
|
||||||
with:
|
with:
|
||||||
arguments: clean build
|
arguments: clean build
|
||||||
|
|||||||
47
.github/workflows/nightly.yml
vendored
47
.github/workflows/nightly.yml
vendored
@@ -1,47 +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 23
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '23'
|
|
||||||
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: pyTooling/Actions/releaser@main
|
|
||||||
with:
|
|
||||||
tag: nightly
|
|
||||||
rm: false
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
files: |
|
|
||||||
build/image/*.zip
|
|
||||||
build/image/build_info.txt
|
|
||||||
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -29,7 +29,6 @@
|
|||||||
<w>talkgroups</w>
|
<w>talkgroups</w>
|
||||||
<w>tgid</w>
|
<w>tgid</w>
|
||||||
<w>ultravox</w>
|
<w>ultravox</w>
|
||||||
<w>unsynchronized</w>
|
|
||||||
<w>wacn</w>
|
<w>wacn</w>
|
||||||
<w>xtea</w>
|
<w>xtea</w>
|
||||||
</words>
|
</words>
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23_PREVIEW" project-jdk-name="liberica-23" 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" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,10 +1,8 @@
|
|||||||

|

|
||||||

|
|
||||||
|
|
||||||
# sdrtrunk
|
# sdrtrunk
|
||||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
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)
|
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
||||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
||||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||||
@@ -20,13 +18,6 @@ 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.
|
* **(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.
|
* **(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
|
## Minimum System Requirements
|
||||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 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
|
* **CPU:** 4-core
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
309
build.gradle
309
build.gradle
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,33 +16,6 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instructions for building/compiling the sdrtrunk application.
|
|
||||||
*
|
|
||||||
* Prerequisites:
|
|
||||||
* Install and configure an OpenJDK version 22+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
|
|
||||||
* - Optional: install and configure Gradle 8.10+
|
|
||||||
*
|
|
||||||
* 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 {
|
plugins {
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'java'
|
id 'java'
|
||||||
@@ -55,15 +28,8 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
|
version = '0.5.2'
|
||||||
version = project.findProperty('projectVersion')
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
//Java 23 is required for this version of the Project Panama preview/incubator feature
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion = JavaLanguageVersion.of(23)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs 'src/main'
|
main.java.srcDirs 'src/main'
|
||||||
@@ -77,11 +43,12 @@ test {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// JUnit Tests
|
// JUnit Tests
|
||||||
testImplementation(platform("org.junit:junit-bom:5.11.0"))
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.0'
|
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")
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||||
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
|
|
||||||
|
|
||||||
//Jitpack imports
|
//Jitpack imports
|
||||||
implementation 'com.github.dnault:libresample4j:master'
|
implementation 'com.github.dnault:libresample4j:master'
|
||||||
@@ -89,8 +56,8 @@ dependencies {
|
|||||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||||
|
|
||||||
//mavenCentral/jcenter imports
|
//mavenCentral/jcenter imports
|
||||||
implementation 'ch.qos.logback:logback-classic:1.4.12'
|
implementation 'ch.qos.logback:logback-classic:1.4.5'
|
||||||
implementation 'ch.qos.logback:logback-core:1.4.14'
|
implementation 'ch.qos.logback:logback-core:1.4.5'
|
||||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
|
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
|
||||||
implementation 'com.fazecast:jSerialComm:2.9.3'
|
implementation 'com.fazecast:jSerialComm:2.9.3'
|
||||||
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
||||||
@@ -98,16 +65,16 @@ dependencies {
|
|||||||
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||||
implementation 'com.github.wendykierp:JTransforms:3.1'
|
implementation 'com.github.wendykierp:JTransforms:3.1'
|
||||||
implementation 'com.google.code.gson:gson:2.10'
|
implementation 'com.google.code.gson:gson:2.10'
|
||||||
implementation 'com.google.guava:guava:33.3.0-jre'
|
implementation 'com.google.guava:guava:31.1-jre'
|
||||||
implementation 'com.jidesoft:jide-oss:3.6.18'
|
implementation 'com.jidesoft:jide-oss:3.6.18'
|
||||||
implementation 'com.miglayout:miglayout-swing:11.0'
|
implementation 'com.miglayout:miglayout-swing:11.0'
|
||||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||||
implementation 'commons-io:commons-io:2.11.0'
|
implementation 'commons-io:commons-io:2.11.0'
|
||||||
implementation 'eu.hansolo:charts:1.0.5'
|
implementation 'eu.hansolo:charts:1.0.5'
|
||||||
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
|
||||||
implementation 'javax.usb:usb-api:1.0.2'
|
implementation 'javax.usb:usb-api:1.0.2'
|
||||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||||
implementation 'org.apache.commons:commons-compress:1.26.0'
|
implementation 'org.apache.commons:commons-compress:1.21'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||||
implementation 'org.apache.commons:commons-csv:1.9.0'
|
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||||
@@ -123,45 +90,19 @@ dependencies {
|
|||||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||||
}
|
}
|
||||||
|
|
||||||
def os = org.gradle.nativeplatform.platform.internal.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) {
|
tasks.withType(JavaCompile) {
|
||||||
options.compilerArgs.add("--enable-preview") //Project Panama Vector API is still in incubator
|
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
|
||||||
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Project Panama Vector API is still 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
|
|
||||||
'-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', //Needed while Project Panama API is still in incubator
|
|
||||||
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
|
|
||||||
'--enable-native-access=ALL-UNNAMED'] //SDRPlay API library path
|
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||||
|
applicationDefaultJvmArgs =
|
||||||
if(os.isWindows()) {
|
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||||
applicationDefaultJvmArgs = jvmArgsWindows
|
'--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
|
||||||
else {
|
|
||||||
applicationDefaultJvmArgs = jvmArgsLinux
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@@ -170,7 +111,7 @@ jar {
|
|||||||
'Implementation-Title' : 'sdrtrunk project',
|
'Implementation-Title' : 'sdrtrunk project',
|
||||||
'Implementation-Version' : archiveVersion,
|
'Implementation-Version' : archiveVersion,
|
||||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
'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-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']}"
|
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
||||||
)
|
)
|
||||||
@@ -198,158 +139,90 @@ String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
|||||||
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
* 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_base = '/home/denny/java_jdks/'
|
||||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-22.0.2-full'
|
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
|
||||||
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-22.0.2-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-22.0.2-full.jdk'
|
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
||||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-22.0.2-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-22.0.2-full'
|
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/23+38/bellsoft-jdk23+38-"
|
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
|
||||||
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"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the runtime zip task with additional options/settings.
|
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
|
||||||
* @param rt task to configure
|
*
|
||||||
* @param jvmArgs to use (windows or linux)
|
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
|
||||||
* @return configured task.
|
* 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(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
|
runtime {
|
||||||
//Use the correct set of JVM arguments
|
//Build by automatically downloading JDKs for the OS and CPU targets
|
||||||
rt.extension.launcherData.get().jvmArgs = jvmArgs
|
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.
|
//Additional modules to include with auto-detected modules.
|
||||||
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
|
//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.accessibility is used with assistive technologies like screen readers
|
//jdk.accessibility is used with assistive technologies like screen readers
|
||||||
//java.management for JVM resource monitoring
|
modules = ['jdk.crypto.ec','jdk.accessibility']
|
||||||
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management')
|
|
||||||
|
|
||||||
//Use auto-detected modules and 'add' any specified modules.
|
//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
|
//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.
|
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||||
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
|
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
|
||||||
}
|
file("$buildDir/image/sdr-trunk-" + version + ".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', org.beryx.runtime.RuntimeZipTask) {rt ->
|
|
||||||
def arch = System.properties.getProperty("os.arch")
|
|
||||||
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
|
|
||||||
|
|
||||||
if(os.isWindows()) {
|
|
||||||
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
|
|
||||||
configure(rt, jvmArgsWindows)
|
|
||||||
}
|
|
||||||
else if(os.isLinux())
|
|
||||||
{
|
|
||||||
if(arch.equals("amd64")) {
|
|
||||||
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
|
|
||||||
}
|
|
||||||
else if(arch.equals("aarch64")) {
|
|
||||||
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
|
|
||||||
}
|
|
||||||
configure(rt, jvmArgsLinux)
|
|
||||||
}
|
|
||||||
else if(os.isMacOsX())
|
|
||||||
{
|
|
||||||
if(arch.equals("amd64")) {
|
|
||||||
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
|
|
||||||
}
|
|
||||||
else if(arch.equals("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', org.beryx.runtime.RuntimeZipTask) {rt ->
|
|
||||||
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', org.beryx.runtime.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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
#
|
|
||||||
# ******************************************************************************
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# 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.1-beta-2
|
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,8 +19,7 @@
|
|||||||
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
14
gradlew
vendored
14
gradlew
vendored
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#
|
#
|
||||||
# ******************************************************************************
|
# ******************************************************************************
|
||||||
# Copyright (C) 2014-2024 Dennis Sheirer
|
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -88,6 +88,9 @@ done
|
|||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || 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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
||||||
@@ -133,13 +136,10 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
if ! command -v java >/dev/null 2>&1
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
then
|
|
||||||
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
@@ -200,10 +200,6 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
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;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,32 +19,38 @@
|
|||||||
package io.github.dsheirer.alias;
|
package io.github.dsheirer.alias;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.action.AliasAction;
|
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.beep.BeepAction;
|
||||||
import io.github.dsheirer.alias.action.clip.ClipAction;
|
import io.github.dsheirer.alias.action.clip.ClipAction;
|
||||||
import io.github.dsheirer.alias.action.script.ScriptAction;
|
import io.github.dsheirer.alias.action.script.ScriptAction;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
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.broadcast.BroadcastChannel;
|
||||||
import io.github.dsheirer.alias.id.dcs.Dcs;
|
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
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.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.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.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
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.Radio;
|
||||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||||
import io.github.dsheirer.alias.id.record.Record;
|
import io.github.dsheirer.alias.id.record.Record;
|
||||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
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.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class AliasFactory
|
public class AliasFactory
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class);
|
||||||
@@ -58,11 +64,6 @@ public class AliasFactory
|
|||||||
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
||||||
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
||||||
return copyBroadcast;
|
return copyBroadcast;
|
||||||
case DCS:
|
|
||||||
Dcs originalDcs = (Dcs)id;
|
|
||||||
Dcs copyDcs = new Dcs();
|
|
||||||
copyDcs.setDCSCode(originalDcs.getDCSCode());
|
|
||||||
return copyDcs;
|
|
||||||
case ESN:
|
case ESN:
|
||||||
Esn originalESN = (Esn)id;
|
Esn originalESN = (Esn)id;
|
||||||
Esn copyESN = new Esn();
|
Esn copyESN = new Esn();
|
||||||
@@ -79,18 +80,6 @@ public class AliasFactory
|
|||||||
Min copyMin = new Min();
|
Min copyMin = new Min();
|
||||||
copyMin.setMin(originalMin.getMin());
|
copyMin.setMin(originalMin.getMin());
|
||||||
return copyMin;
|
return copyMin;
|
||||||
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:
|
case PRIORITY:
|
||||||
Priority originalPriority = (Priority)id;
|
Priority originalPriority = (Priority)id;
|
||||||
Priority copyPriority = new Priority();
|
Priority copyPriority = new Priority();
|
||||||
@@ -198,7 +187,6 @@ public class AliasFactory
|
|||||||
copy.setGroup(original.getGroup());
|
copy.setGroup(original.getGroup());
|
||||||
copy.setColor(original.getColor());
|
copy.setColor(original.getColor());
|
||||||
copy.setIconName(original.getIconName());
|
copy.setIconName(original.getIconName());
|
||||||
copy.setRecordable(original.isRecordable());
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,4 +234,65 @@ public class AliasFactory
|
|||||||
|
|
||||||
return copy;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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
|
* 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 com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
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.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
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.Radio;
|
||||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
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.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
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.esn.ESNIdentifier;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
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.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
|
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
|
||||||
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
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.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneSequence;
|
import io.github.dsheirer.identifier.tone.ToneSequence;
|
||||||
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -59,10 +57,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
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
|
* 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 final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
||||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = 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<String,Alias> mESNMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
||||||
@@ -159,32 +152,6 @@ public class AliasList
|
|||||||
|
|
||||||
talkgroupRangeAliasList.add(talkgroupRange, alias);
|
talkgroupRangeAliasList.add(talkgroupRange, alias);
|
||||||
break;
|
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 RADIO_ID:
|
||||||
Radio radio = (Radio)id;
|
Radio radio = (Radio)id;
|
||||||
|
|
||||||
@@ -211,12 +178,6 @@ public class AliasList
|
|||||||
|
|
||||||
radioRangeAliasList.add(radioRange, alias);
|
radioRangeAliasList.add(radioRange, alias);
|
||||||
break;
|
break;
|
||||||
case DCS:
|
|
||||||
if(id instanceof Dcs dcs)
|
|
||||||
{
|
|
||||||
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ESN:
|
case ESN:
|
||||||
String esn = ((Esn)id).getEsn();
|
String esn = ((Esn)id).getEsn();
|
||||||
|
|
||||||
@@ -418,8 +379,6 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PATCH_GROUP:
|
case PATCH_GROUP:
|
||||||
List<Alias> aliases = new ArrayList<>();
|
|
||||||
|
|
||||||
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
||||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
@@ -427,6 +386,8 @@ public class AliasList
|
|||||||
|
|
||||||
if(patchGroupAliasList != null)
|
if(patchGroupAliasList != null)
|
||||||
{
|
{
|
||||||
|
List<Alias> aliases = new ArrayList<>();
|
||||||
|
|
||||||
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
||||||
|
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
@@ -434,36 +395,19 @@ public class AliasList
|
|||||||
aliases.add(alias);
|
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;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
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;
|
|
||||||
case RADIO:
|
case RADIO:
|
||||||
RadioIdentifier radio = (RadioIdentifier)identifier;
|
RadioIdentifier radio = (RadioIdentifier)identifier;
|
||||||
|
|
||||||
@@ -495,9 +439,9 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TONE:
|
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())
|
if(toneSequence != null && toneSequence.hasTones())
|
||||||
{
|
{
|
||||||
@@ -510,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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,7 +465,9 @@ public class AliasList
|
|||||||
{
|
{
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
{
|
{
|
||||||
return Collections.singletonList(alias);
|
List<Alias> aliases = new ArrayList<>();
|
||||||
|
aliases.add(alias);
|
||||||
|
return aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
@@ -653,7 +590,6 @@ public class AliasList
|
|||||||
*/
|
*/
|
||||||
public class TalkgroupAliasList
|
public class TalkgroupAliasList
|
||||||
{
|
{
|
||||||
private Map<String,Alias> mFullyQualifiedTalkgroupAliasMap = new HashMap<>();
|
|
||||||
private Map<Integer,Alias> mTalkgroupAliasMap = new TreeMap<>();
|
private Map<Integer,Alias> mTalkgroupAliasMap = new TreeMap<>();
|
||||||
private Map<TalkgroupRange, Alias> mTalkgroupRangeAliasMap = new HashMap<>();
|
private Map<TalkgroupRange, Alias> mTalkgroupRangeAliasMap = new HashMap<>();
|
||||||
|
|
||||||
@@ -663,13 +599,6 @@ public class AliasList
|
|||||||
|
|
||||||
public Alias getAlias(TalkgroupIdentifier identifier)
|
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();
|
int value = identifier.getValue();
|
||||||
|
|
||||||
Alias mapValue = mTalkgroupAliasMap.get(value);
|
Alias mapValue = mTalkgroupAliasMap.get(value);
|
||||||
@@ -678,7 +607,6 @@ public class AliasList
|
|||||||
return mapValue;
|
return mapValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Alternatively, match the talkgroup to any talkgroup ranges
|
|
||||||
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
|
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getKey().contains(value))
|
if(entry.getKey().contains(value))
|
||||||
@@ -692,55 +620,26 @@ public class AliasList
|
|||||||
|
|
||||||
public void add(Talkgroup talkgroup, Alias alias)
|
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
|
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
|
||||||
if(mFullyQualifiedTalkgroupAliasMap.containsKey(fqt.getHashKey()))
|
|
||||||
|
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);
|
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
|
||||||
|
|
||||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
|
||||||
{
|
{
|
||||||
if(aliasID instanceof P25FullyQualifiedTalkgroup existingFqt &&
|
aliasID.setOverlap(true);
|
||||||
existingFqt.getHashKey().contentEquals(fqt.getHashKey()))
|
|
||||||
{
|
|
||||||
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))
|
mTalkgroupAliasMap.put(talkgroup.getValue(), 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(TalkgroupRange talkgroupRange, Alias alias)
|
public void add(TalkgroupRange talkgroupRange, Alias alias)
|
||||||
@@ -773,7 +672,6 @@ public class AliasList
|
|||||||
*/
|
*/
|
||||||
public class RadioAliasList
|
public class RadioAliasList
|
||||||
{
|
{
|
||||||
private Map<String,Alias> mFullyQualifiedRadioAliasMap = new HashMap<>();
|
|
||||||
private Map<Integer,Alias> mRadioAliasMap = new TreeMap<>();
|
private Map<Integer,Alias> mRadioAliasMap = new TreeMap<>();
|
||||||
private Map<RadioRange, Alias> mRadioRangeAliasMap = new HashMap<>();
|
private Map<RadioRange, Alias> mRadioRangeAliasMap = new HashMap<>();
|
||||||
|
|
||||||
@@ -783,13 +681,6 @@ public class AliasList
|
|||||||
|
|
||||||
public Alias getAlias(RadioIdentifier identifier)
|
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();
|
int value = identifier.getValue();
|
||||||
|
|
||||||
Alias mapValue = mRadioAliasMap.get(value);
|
Alias mapValue = mRadioAliasMap.get(value);
|
||||||
@@ -798,7 +689,6 @@ public class AliasList
|
|||||||
return mapValue;
|
return mapValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Alternatively, attempt to match the radio address against any radio ranges.
|
|
||||||
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
|
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getKey().contains(value))
|
if(entry.getKey().contains(value))
|
||||||
@@ -812,55 +702,26 @@ public class AliasList
|
|||||||
|
|
||||||
public void add(Radio radio, Alias alias)
|
public void add(Radio radio, Alias alias)
|
||||||
{
|
{
|
||||||
if(radio instanceof P25FullyQualifiedRadio fqr)
|
//Detect collisions
|
||||||
|
if(mRadioAliasMap.containsKey(radio.getValue()))
|
||||||
{
|
{
|
||||||
//Detect collisions
|
Alias existing = mRadioAliasMap.get(radio.getValue());
|
||||||
if(mFullyQualifiedRadioAliasMap.containsKey(fqr.getHashKey()))
|
|
||||||
|
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);
|
if(aliasID instanceof Radio && ((Radio)aliasID).getValue() == radio.getValue())
|
||||||
|
|
||||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
|
||||||
{
|
{
|
||||||
if(aliasID instanceof P25FullyQualifiedRadio existingFqr &&
|
aliasID.setOverlap(true);
|
||||||
existingFqr.getHashKey().contentEquals(fqr.getHashKey()))
|
|
||||||
{
|
|
||||||
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))
|
mRadioAliasMap.put(radio.getValue(), 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(RadioRange radioRange, Alias alias)
|
public void add(RadioRange radioRange, Alias alias)
|
||||||
|
|||||||
@@ -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
|
* 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
|
* 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.alias.id.broadcast.BroadcastChannel;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
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.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
* 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
|
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
|
||||||
@@ -183,8 +184,10 @@ public class AliasModel
|
|||||||
*/
|
*/
|
||||||
public void addAliases(List<Alias> aliases)
|
public void addAliases(List<Alias> aliases)
|
||||||
{
|
{
|
||||||
removeAliases(aliases);
|
for(Alias alias : aliases)
|
||||||
mAliases.addAll(aliases);
|
{
|
||||||
|
addAlias(alias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,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
|
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
|
||||||
* @param aliasListName to search
|
* @param aliasListName to search
|
||||||
|
|||||||
@@ -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
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
* 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.
|
||||||
|
* *
|
||||||
|
* * 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;
|
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.annotation.JsonTypeInfo;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
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.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
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.mdc.MDC1200ID;
|
||||||
@@ -35,13 +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.legacy.uniqueID.UniqueID;
|
||||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
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.Radio;
|
||||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||||
import io.github.dsheirer.alias.id.record.Record;
|
import io.github.dsheirer.alias.id.record.Record;
|
||||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
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.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
@@ -54,7 +54,6 @@ import javafx.util.Callback;
|
|||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
||||||
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
|
|
||||||
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
||||||
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
||||||
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
||||||
@@ -63,8 +62,6 @@ import javafx.util.Callback;
|
|||||||
@JsonSubTypes.Type(value = Min.class, name = "min"),
|
@JsonSubTypes.Type(value = Min.class, name = "min"),
|
||||||
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
|
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
|
||||||
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
|
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
|
||||||
@JsonSubTypes.Type(value = P25FullyQualifiedRadio.class, name = "p25FullyQualifiedRadio"),
|
|
||||||
@JsonSubTypes.Type(value = P25FullyQualifiedTalkgroup.class, name = "p25FullyQualifiedTalkgroup"),
|
|
||||||
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
||||||
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
|
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
|
||||||
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
|
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
|
||||||
|
|||||||
@@ -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
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
* 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.
|
||||||
|
* *
|
||||||
|
* * 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;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -23,14 +26,11 @@ import java.util.EnumSet;
|
|||||||
public enum AliasIDType
|
public enum AliasIDType
|
||||||
{
|
{
|
||||||
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
||||||
DCS("Digital Coded Squelch (DCS)"),
|
|
||||||
ESN("ESN"),
|
ESN("ESN"),
|
||||||
INVERT("Audio Inversion"),
|
INVERT("Audio Inversion"),
|
||||||
LOJACK("LoJack"),
|
LOJACK("LoJack"),
|
||||||
LTR_NET_UID("LTR-Net UID"),
|
LTR_NET_UID("LTR-Net UID"),
|
||||||
MIN("Passport MIN"),
|
MIN("Passport MIN"),
|
||||||
P25_FULLY_QUALIFIED_RADIO_ID("P25 Fully Qualified Radio ID"),
|
|
||||||
P25_FULLY_QUALIFIED_TALKGROUP("P25 Fully Qualified Talkgroup"),
|
|
||||||
PRIORITY("Audio Priority"),
|
PRIORITY("Audio Priority"),
|
||||||
RADIO_ID("Radio ID"),
|
RADIO_ID("Radio ID"),
|
||||||
RADIO_ID_RANGE("Radio ID Range"),
|
RADIO_ID_RANGE("Radio ID Range"),
|
||||||
@@ -58,7 +58,7 @@ public enum AliasIDType
|
|||||||
|
|
||||||
//Values used by the View-By alias editor
|
//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,
|
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
|
||||||
P25_FULLY_QUALIFIED_RADIO_ID, P25_FULLY_QUALIFIED_TALKGROUP, UNIT_STATUS, STATUS, TONES);
|
UNIT_STATUS, STATUS, TONES);
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,8 +34,6 @@ import io.github.dsheirer.protocol.Protocol;
|
|||||||
*/
|
*/
|
||||||
public enum TalkgroupFormat
|
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",
|
APCO25("********", 0, 0xFFFF, "0 to 65,535",
|
||||||
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
||||||
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||||
@@ -124,8 +122,6 @@ public enum TalkgroupFormat
|
|||||||
|
|
||||||
switch(protocol)
|
switch(protocol)
|
||||||
{
|
{
|
||||||
case AM:
|
|
||||||
return AM;
|
|
||||||
case APCO25:
|
case APCO25:
|
||||||
return APCO25;
|
return APCO25;
|
||||||
case FLEETSYNC:
|
case FLEETSYNC:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* ******************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* *****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.alias.id.talkgroup;
|
package io.github.dsheirer.alias.id.talkgroup;
|
||||||
@@ -22,20 +23,21 @@ package io.github.dsheirer.alias.id.talkgroup;
|
|||||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
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.DMRTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
||||||
|
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
|
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
|
||||||
@@ -47,10 +49,6 @@ public class TalkgroupFormatter
|
|||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
AnalogTalkgroupFormatter analogTalkgroupFormatter = new AnalogTalkgroupFormatter();
|
|
||||||
mFormatterMap.put(Protocol.AM, analogTalkgroupFormatter);
|
|
||||||
mFormatterMap.put(Protocol.NBFM, analogTalkgroupFormatter);
|
|
||||||
|
|
||||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
||||||
@@ -59,6 +57,7 @@ public class TalkgroupFormatter
|
|||||||
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
||||||
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
||||||
|
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* *****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio;
|
||||||
@@ -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
|
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
||||||
* listener(s).
|
* listener(s).
|
||||||
*/
|
*/
|
||||||
public AudioSegment getAudioSegment()
|
protected AudioSegment getAudioSegment()
|
||||||
{
|
{
|
||||||
synchronized(this)
|
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();
|
AudioSegment audioSegment = getAudioSegment();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -43,7 +43,6 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||||
private static float[] sHighPassFilterCoefficients;
|
private static float[] sHighPassFilterCoefficients;
|
||||||
private final boolean mAudioFilterEnable;
|
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
@@ -71,33 +70,27 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||||
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
*
|
*
|
||||||
* @param aliasList for aliasing identifiers
|
* @param aliasList for aliasing identifiers
|
||||||
* @param timeslot for this audio module
|
|
||||||
* @param maxAudioSegmentLength in milliseconds
|
* @param maxAudioSegmentLength in milliseconds
|
||||||
* @param audioFilterEnable to enable or disable high-pass audio filter
|
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, boolean audioFilterEnable)
|
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
|
||||||
{
|
{
|
||||||
super(aliasList, timeslot, maxAudioSegmentLength);
|
super(aliasList, timeslot, maxAudioSegmentLength);
|
||||||
mAudioFilterEnable = audioFilterEnable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
* @param aliasList for aliasing identifiers
|
|
||||||
* @param audioFilterEnable to enable or disable high-pass audio filter
|
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList, boolean audioFilterEnable)
|
public AudioModule(AliasList aliasList)
|
||||||
{
|
{
|
||||||
super(aliasList);
|
super(aliasList);
|
||||||
mAudioFilterEnable = audioFilterEnable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -128,11 +121,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||||
{
|
{
|
||||||
if(mAudioFilterEnable)
|
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||||
{
|
|
||||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
addAudio(audioBuffer);
|
addAudio(audioBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,16 +141,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
@Override
|
@Override
|
||||||
public void receive(SquelchStateEvent event)
|
public void receive(SquelchStateEvent event)
|
||||||
{
|
{
|
||||||
SquelchState squelchState = event.getSquelchState();
|
mSquelchState = event.getSquelchState();
|
||||||
|
|
||||||
if(mSquelchState != squelchState)
|
if(mSquelchState == SquelchState.SQUELCH)
|
||||||
{
|
{
|
||||||
mSquelchState = squelchState;
|
closeAudioSegment();
|
||||||
|
|
||||||
if(mSquelchState == SquelchState.SQUELCH)
|
|
||||||
{
|
|
||||||
closeAudioSegment();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
/*
|
/*******************************************************************************
|
||||||
* *****************************************************************************
|
* sdr-trunk
|
||||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||||
* it under the terms of the GNU General Public License as published by
|
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* later version.
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
* 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
|
* You should have received a copy of the GNU General Public License along with this program.
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
*
|
||||||
*/
|
******************************************************************************/
|
||||||
|
|
||||||
package io.github.dsheirer.audio;
|
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.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
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.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
@@ -47,6 +35,15 @@ import javafx.collections.ObservableSet;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
* 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
|
* a discrete (ie start/stop) audio event, or it may be a time-constrained portion of an ongoing continuous audio
|
||||||
@@ -69,7 +66,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AudioSegment.class);
|
||||||
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
|
private BooleanProperty mComplete = new SimpleBooleanProperty(false);
|
||||||
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
|
private BooleanProperty mDuplicate = new SimpleBooleanProperty(false);
|
||||||
private BooleanProperty mEncrypted = new SimpleBooleanProperty(false);
|
|
||||||
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
|
private BooleanProperty mRecordAudio = new SimpleBooleanProperty(false);
|
||||||
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
private IntegerProperty mMonitorPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||||
private ObservableSet<BroadcastChannel> mBroadcastChannels = FXCollections.observableSet(new HashSet<>());
|
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
|
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
|
* 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.
|
* additional audio or identifiers will be added to the segment.
|
||||||
@@ -161,15 +138,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
return mComplete;
|
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
|
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
||||||
* an audio segment is a duplicate.
|
* 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");
|
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);
|
mAudioBuffers.add(audioBuffer);
|
||||||
mSampleCount += audioBuffer.length;
|
mSampleCount += audioBuffer.length;
|
||||||
}
|
}
|
||||||
@@ -441,14 +404,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
{
|
{
|
||||||
mIdentifierCollection.update(identifier);
|
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);
|
List<Alias> aliases = mAliasList.getAliases(identifier);
|
||||||
|
|
||||||
for(Alias alias: aliases)
|
for(Alias alias: aliases)
|
||||||
|
|||||||
@@ -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
|
* 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
|
* 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.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
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.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -38,10 +41,7 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method.
|
* 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>
|
public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
||||||
private ICallManagementProvider mCallManagementProvider;
|
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
|
||||||
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
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)
|
public DuplicateCallDetector(UserPreferences userPreferences)
|
||||||
{
|
{
|
||||||
this(userPreferences.getCallManagementPreference());
|
mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive(AudioSegment audioSegment)
|
public void receive(AudioSegment audioSegment)
|
||||||
{
|
{
|
||||||
if(mCallManagementProvider.isDuplicateCallDetectionEnabled())
|
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled())
|
||||||
{
|
{
|
||||||
Identifier identifier = audioSegment.getIdentifierCollection()
|
Identifier identifier = audioSegment.getIdentifierCollection()
|
||||||
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||||
@@ -101,7 +78,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
|
|
||||||
if(detector == null)
|
if(detector == null)
|
||||||
{
|
{
|
||||||
detector = new SystemDuplicateCallDetector(mCallManagementProvider, system);
|
detector = new SystemDuplicateCallDetector();
|
||||||
mDetectorMap.put(system, detector);
|
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
|
public class SystemDuplicateCallDetector
|
||||||
{
|
{
|
||||||
private final LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||||
private final List<AudioSegment> mAudioSegments = new ArrayList<>();
|
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||||
|
private AtomicBoolean mMonitoring = new AtomicBoolean();
|
||||||
private ScheduledFuture<?> mProcessorFuture;
|
private ScheduledFuture<?> mProcessorFuture;
|
||||||
private Lock mLock = new ReentrantLock();
|
|
||||||
private boolean mMonitoring = false;
|
|
||||||
private final ICallManagementProvider mCallManagementProvider;
|
|
||||||
private String mSystem;
|
|
||||||
|
|
||||||
/**
|
public SystemDuplicateCallDetector()
|
||||||
* Constructs an instance
|
|
||||||
* @param callManagementProvider to check for duplicate monitoring preferences
|
|
||||||
*/
|
|
||||||
public SystemDuplicateCallDetector(ICallManagementProvider callManagementProvider, String system)
|
|
||||||
{
|
{
|
||||||
mCallManagementProvider = callManagementProvider;
|
|
||||||
mSystem = system;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the audio segment to the monitoring queue.
|
|
||||||
* @param audioSegment to add
|
|
||||||
*/
|
|
||||||
public void add(AudioSegment audioSegment)
|
public void add(AudioSegment audioSegment)
|
||||||
{
|
{
|
||||||
mLock.lock();
|
//Block on audio segment queue so that we don't interfere with monitoring shutdown
|
||||||
|
synchronized(mAudioSegmentQueue)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
mAudioSegmentQueue.add(audioSegment);
|
mAudioSegmentQueue.add(audioSegment);
|
||||||
startMonitoring();
|
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()
|
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);
|
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()
|
private void stopMonitoring()
|
||||||
{
|
{
|
||||||
mLock.lock();
|
if(mMonitoring.compareAndSet(true, false))
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
//Recheck the audio segments queue to make sure we didn't slip in another audio segment before we can
|
if(mProcessorFuture != null)
|
||||||
//shut down the scheduled monitoring thread.
|
|
||||||
if(mMonitoring && mAudioSegments.isEmpty())
|
|
||||||
{
|
{
|
||||||
if(mProcessorFuture != null)
|
mProcessorFuture.cancel(true);
|
||||||
{
|
|
||||||
mProcessorFuture.cancel(true);
|
|
||||||
mProcessorFuture = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
mMonitoring = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
mLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,7 +137,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
*/
|
*/
|
||||||
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
||||||
{
|
{
|
||||||
if(mCallManagementProvider.isDuplicateCallDetectionByTalkgroupEnabled())
|
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
||||||
{
|
{
|
||||||
//Step 1 check for duplicate TO values
|
//Step 1 check for duplicate TO values
|
||||||
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
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
|
//Step 2 check for duplicate FROM values
|
||||||
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||||
@@ -246,51 +169,53 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
* @param identifiers2
|
* @param identifiers2
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
|
private boolean isDuplicate(List<Identifier> identifiers1, List<Identifier> identifiers2)
|
||||||
{
|
{
|
||||||
for(Identifier identifier1: identifiers1)
|
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)
|
for(Identifier identifier2: identifiers2)
|
||||||
{
|
{
|
||||||
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == tg1)
|
if(identifier2 instanceof TalkgroupIdentifier &&
|
||||||
|
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
|
else if(identifier2 instanceof PatchGroupIdentifier &&
|
||||||
pgId2.getValue().getPatchGroup().getValue() == tg1)
|
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
||||||
{
|
{
|
||||||
return true;
|
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)
|
for(Identifier identifier2: identifiers2)
|
||||||
{
|
{
|
||||||
if(identifier2 instanceof TalkgroupIdentifier tgId2 && tgId2.getValue() == talkgroup1)
|
if(identifier2 instanceof TalkgroupIdentifier &&
|
||||||
|
((TalkgroupIdentifier)identifier2).getValue() == talkgroup1)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if(identifier2 instanceof PatchGroupIdentifier pgId2 &&
|
else if(identifier2 instanceof PatchGroupIdentifier &&
|
||||||
pgId2.getValue().getPatchGroup().getValue() == talkgroup1)
|
((PatchGroupIdentifier)identifier2).getValue().getPatchGroup().getValue() == talkgroup1)
|
||||||
{
|
{
|
||||||
return true;
|
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)
|
for(Identifier identifier2: identifiers2)
|
||||||
{
|
{
|
||||||
if(identifier2 instanceof RadioIdentifier raId2 && raId2.getValue() == radio1)
|
if(identifier2 instanceof RadioIdentifier && ((RadioIdentifier)identifier2).getValue() == radio1)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -306,8 +231,6 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
*/
|
*/
|
||||||
private void process()
|
private void process()
|
||||||
{
|
{
|
||||||
mLock.lock();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Transfer in newly arrived audio segments
|
//Transfer in newly arrived audio segments
|
||||||
@@ -325,19 +248,6 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
return complete;
|
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
|
//Only check for duplicates if there is more than one call
|
||||||
if(mAudioSegments.size() > 1)
|
if(mAudioSegments.size() > 1)
|
||||||
{
|
{
|
||||||
@@ -348,7 +258,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
{
|
{
|
||||||
AudioSegment current = mAudioSegments.get(currentIndex);
|
AudioSegment current = mAudioSegments.get(currentIndex);
|
||||||
|
|
||||||
if(current.hasAudio() && !current.isDuplicate())
|
if(!current.isDuplicate())
|
||||||
{
|
{
|
||||||
int checkIndex = currentIndex + 1;
|
int checkIndex = currentIndex + 1;
|
||||||
|
|
||||||
@@ -363,12 +273,6 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
toCheck.setDuplicate(true);
|
toCheck.setDuplicate(true);
|
||||||
toCheck.decrementConsumerCount();
|
toCheck.decrementConsumerCount();
|
||||||
duplicates.add(toCheck);
|
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);
|
mAudioSegments.removeAll(duplicates);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Finally, if the audio segment queue is now empty, shutdown monitoring until a new segment arrives.
|
//Finally, if the audio segment queue is empty, shutdown montitoring 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.
|
|
||||||
if(mAudioSegments.isEmpty())
|
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)
|
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 " +
|
mLog.error("Unknown error while processing audio segments for duplicate call detection. Please report " +
|
||||||
"this to the developer.", t);
|
"this to the developer.", t);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
mLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
|
|||||||
private Listener<BroadcastEvent> mBroadcastEventListener;
|
private Listener<BroadcastEvent> mBroadcastEventListener;
|
||||||
private T mBroadcastConfiguration;
|
private T mBroadcastConfiguration;
|
||||||
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
|
protected ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>(BroadcastState.READY);
|
||||||
protected ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
|
|
||||||
protected int mStreamedAudioCount = 0;
|
protected int mStreamedAudioCount = 0;
|
||||||
protected int mErrorAudioCount = 0;
|
protected int mErrorAudioCount = 0;
|
||||||
protected int mAgedOffAudioCount = 0;
|
protected int mAgedOffAudioCount = 0;
|
||||||
@@ -56,14 +55,6 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
|
|||||||
return mBroadcastState;
|
return mBroadcastState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable last bad broadcast state property
|
|
||||||
*/
|
|
||||||
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
|
|
||||||
{
|
|
||||||
return mLastBadBroadcastState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current state of the broadcastAudio connection
|
* Current state of the broadcastAudio connection
|
||||||
*/
|
*/
|
||||||
@@ -77,26 +68,10 @@ public abstract class AbstractAudioBroadcaster<T extends BroadcastConfiguration>
|
|||||||
*/
|
*/
|
||||||
public void setBroadcastState(BroadcastState broadcastState)
|
public void setBroadcastState(BroadcastState broadcastState)
|
||||||
{
|
{
|
||||||
if(broadcastState == BroadcastState.CONNECTED)
|
|
||||||
{
|
|
||||||
mLastBadBroadcastState.setValue(null);
|
|
||||||
}
|
|
||||||
else if(broadcastState.isErrorState() || broadcastState.isWarningState())
|
|
||||||
{
|
|
||||||
mLastBadBroadcastState.setValue(broadcastState);
|
|
||||||
}
|
|
||||||
mBroadcastState.setValue(broadcastState);
|
mBroadcastState.setValue(broadcastState);
|
||||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));
|
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
|
* Starts the broadcaster
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -211,17 +211,9 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
|||||||
|
|
||||||
super.setBroadcastState(state);
|
super.setBroadcastState(state);
|
||||||
|
|
||||||
if(mBroadcastState.get() != null)
|
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
|
||||||
{
|
{
|
||||||
if(mBroadcastState.get().isErrorState())
|
stop();
|
||||||
{
|
|
||||||
mLog.error("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
else if(mBroadcastState.get().isWarningState())
|
|
||||||
{
|
|
||||||
mLog.warn("[" + getStreamName() + "] status: " + mBroadcastState.get().toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!connected())
|
if(!connected())
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,34 +19,25 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.audio.broadcast;
|
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.audio.AudioSegment;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
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.preference.UserPreferences;
|
||||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||||
import io.github.dsheirer.record.RecordFormat;
|
import io.github.dsheirer.record.RecordFormat;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import io.github.dsheirer.util.TimeStamp;
|
import io.github.dsheirer.util.TimeStamp;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
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
|
* 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();
|
audioSegment = it.next();
|
||||||
|
|
||||||
if(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled())
|
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled())
|
||||||
{
|
{
|
||||||
it.remove();
|
it.remove();
|
||||||
audioSegment.decrementConsumerCount();
|
audioSegment.decrementConsumerCount();
|
||||||
@@ -147,60 +138,29 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
|||||||
|
|
||||||
if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels())
|
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());
|
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||||
|
|
||||||
if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier)
|
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
|
||||||
{
|
identifierCollectionCopy, audioSegment.getStartTimestamp(), length);
|
||||||
if(mUserPreferences.getCallManagementPreference()
|
mAudioRecordingListener.receive(audioRecording);
|
||||||
.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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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
|
* Creates a temporary streaming recording file path
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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.JacksonXmlProperty;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
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.icecast.IcecastConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
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")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
|
@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 = IcecastConfiguration.class, name="icecastConfiguration"),
|
||||||
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
||||||
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
||||||
|
|||||||
@@ -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.BroadcastifyCallBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
|
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.IcecastHTTPAudioBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
||||||
@@ -67,12 +61,6 @@ public class BroadcastFactory
|
|||||||
case BROADCASTIFY_CALL:
|
case BROADCASTIFY_CALL:
|
||||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
||||||
inputAudioFormat, mp3Setting, aliasModel);
|
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:
|
case BROADCASTIFY:
|
||||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
||||||
inputAudioFormat, mp3Setting, aliasModel);
|
inputAudioFormat, mp3Setting, aliasModel);
|
||||||
@@ -111,12 +99,8 @@ public class BroadcastFactory
|
|||||||
{
|
{
|
||||||
case BROADCASTIFY_CALL:
|
case BROADCASTIFY_CALL:
|
||||||
return new BroadcastifyCallConfiguration(format);
|
return new BroadcastifyCallConfiguration(format);
|
||||||
case RDIOSCANNER_CALL:
|
|
||||||
return new RdioScannerConfiguration(format);
|
|
||||||
case BROADCASTIFY:
|
case BROADCASTIFY:
|
||||||
return new BroadcastifyFeedConfiguration(format);
|
return new BroadcastifyFeedConfiguration(format);
|
||||||
case OPENMHZ:
|
|
||||||
return new OpenMHzConfiguration(format);
|
|
||||||
case ICECAST_HTTP:
|
case ICECAST_HTTP:
|
||||||
return new IcecastHTTPConfiguration(format);
|
return new IcecastHTTPConfiguration(format);
|
||||||
case ICECAST_TCP:
|
case ICECAST_TCP:
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -344,13 +344,14 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
deleteBroadcaster(configuredBroadcast);
|
deleteBroadcaster(configuredBroadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
|
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
|
||||||
mAliasModel, mUserPreferences);
|
mAliasModel, mUserPreferences);
|
||||||
|
|
||||||
if(audioBroadcaster != null)
|
if(audioBroadcaster != null)
|
||||||
{
|
{
|
||||||
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
|
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
|
||||||
audioBroadcaster.setListener(mBroadcastEventListener);
|
audioBroadcaster.setListener(mBroadcastEventListener);
|
||||||
|
audioBroadcaster.start();
|
||||||
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
|
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
|
||||||
|
|
||||||
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
|
||||||
@@ -361,7 +362,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
}
|
}
|
||||||
|
|
||||||
broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_ADD));
|
broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_ADD));
|
||||||
ThreadPool.CACHED.submit(audioBroadcaster::start);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ public enum BroadcastServerType
|
|||||||
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||||
|
|
||||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.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"),
|
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||||
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||||
|
|||||||
@@ -23,92 +23,90 @@ public enum BroadcastState
|
|||||||
/**
|
/**
|
||||||
* General error in configuration that causes the remote server to reject the connection
|
* 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 to the broadcastAudio server and capable of streaming
|
||||||
*/
|
*/
|
||||||
CONNECTED("Connected", false, false),
|
CONNECTED("Connected", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection interrupted, attempting to reconnect.
|
* Connection interrupted, attempting to reconnect.
|
||||||
*/
|
*/
|
||||||
CONNECTING("Connecting", false, false),
|
CONNECTING("Connecting", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the configuration is disabled
|
* Indicates the configuration is disabled
|
||||||
*/
|
*/
|
||||||
DISABLED("Disabled", true, false),
|
DISABLED("Disabled", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnected from the broadcastAudio server
|
* Disconnected from the broadcastAudio server
|
||||||
*/
|
*/
|
||||||
DISCONNECTED("Disconnected", false, false),
|
DISCONNECTED("Disconnected", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid credentials - user name or password
|
* 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_POINT("Invalid Mount/Stream ID", true, false),
|
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid configuration settings
|
* Invalid configuration settings
|
||||||
*/
|
*/
|
||||||
INVALID_SETTINGS("Invalid Settings", true, false),
|
INVALID_SETTINGS("Invalid Settings", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote server max sources has been exceeded
|
* 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
|
* 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 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
|
* Server is not known or reachable
|
||||||
*/
|
*/
|
||||||
NO_SERVER("No Server", false, false),
|
NO_SERVER("No Server", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial state with no connection attempted.
|
* 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.
|
* 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_FORMAT("Unsupported Audio Type", true, false),
|
UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unspecified error
|
* Unspecified error
|
||||||
*/
|
*/
|
||||||
ERROR("Error", true, false);
|
ERROR("Error", true);
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
private boolean mErrorState;
|
private boolean mErrorState;
|
||||||
private boolean mWarningState;
|
|
||||||
|
|
||||||
BroadcastState(String label, boolean errorState, boolean warningState)
|
BroadcastState(String label, boolean errorState)
|
||||||
{
|
{
|
||||||
mLabel = label;
|
mLabel = label;
|
||||||
mErrorState = errorState;
|
mErrorState = errorState;
|
||||||
mWarningState = warningState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
@@ -120,9 +118,4 @@ public enum BroadcastState
|
|||||||
{
|
{
|
||||||
return mErrorState;
|
return mErrorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWarningState()
|
|
||||||
{
|
|
||||||
return mWarningState;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ public class ConfiguredBroadcast
|
|||||||
private BroadcastConfiguration mBroadcastConfiguration;
|
private BroadcastConfiguration mBroadcastConfiguration;
|
||||||
private AbstractAudioBroadcaster mAudioBroadcaster;
|
private AbstractAudioBroadcaster mAudioBroadcaster;
|
||||||
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
|
private ObjectProperty<BroadcastState> mBroadcastState = new SimpleObjectProperty<>();
|
||||||
private ObjectProperty<BroadcastState> mLastBadBroadcastState = new SimpleObjectProperty<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -90,14 +89,6 @@ public class ConfiguredBroadcast
|
|||||||
return mBroadcastState;
|
return mBroadcastState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Last bad broadcast state of the configured audio broadcaster (optional)
|
|
||||||
*/
|
|
||||||
public ObjectProperty<BroadcastState> lastBadBroadcastStateProperty()
|
|
||||||
{
|
|
||||||
return mLastBadBroadcastState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the audio broadcaster
|
* Sets the audio broadcaster
|
||||||
* @param audioBroadcaster to use for this configuration
|
* @param audioBroadcaster to use for this configuration
|
||||||
@@ -105,13 +96,11 @@ public class ConfiguredBroadcast
|
|||||||
public void setAudioBroadcaster(AbstractAudioBroadcaster audioBroadcaster)
|
public void setAudioBroadcaster(AbstractAudioBroadcaster audioBroadcaster)
|
||||||
{
|
{
|
||||||
mBroadcastState.unbind();
|
mBroadcastState.unbind();
|
||||||
mLastBadBroadcastState.unbind();
|
|
||||||
mAudioBroadcaster = audioBroadcaster;
|
mAudioBroadcaster = audioBroadcaster;
|
||||||
|
|
||||||
if(audioBroadcaster != null)
|
if(audioBroadcaster != null)
|
||||||
{
|
{
|
||||||
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
|
mBroadcastState.bind(mAudioBroadcaster.broadcastStateProperty());
|
||||||
mLastBadBroadcastState.bind(mAudioBroadcaster.lastBadBroadcastStateProperty());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -131,7 +120,6 @@ public class ConfiguredBroadcast
|
|||||||
{
|
{
|
||||||
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
|
mBroadcastState.setValue(BroadcastState.CONFIGURATION_ERROR);
|
||||||
}
|
}
|
||||||
mLastBadBroadcastState.setValue(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,6 +38,9 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -45,13 +48,12 @@ import java.net.http.HttpClient;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.
|
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
||||||
@@ -473,11 +475,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
|||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
||||||
{
|
|
||||||
sb.append(",").append(patched.getValue());
|
|
||||||
}
|
|
||||||
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
|
||||||
{
|
{
|
||||||
sb.append(",").append(patched.getValue());
|
sb.append(",").append(patched.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,29 +341,15 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.error("String [" + getStreamName() + "] - HTTP 403 protocol decoder error - message:\n\n" + message);
|
mLog.error("String [" + getStreamName() + "] - HTTP 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);
|
|
||||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -415,16 +401,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
disconnect();
|
disconnect();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/**
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
* 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());
|
|
||||||
disconnect();
|
disconnect();
|
||||||
|
mLog.debug("Unspecified error: " + response.toString() + " Class:" + object.getClass());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,15 +347,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.error("Unrecognized server response:" + message);
|
mLog.error("Unrecognized server response:" + message);
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +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"),
|
|
||||||
PATCHES("patches");
|
|
||||||
|
|
||||||
private String mHeader;
|
|
||||||
|
|
||||||
FormField(String header)
|
|
||||||
{
|
|
||||||
mHeader = header;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHeader()
|
|
||||||
{
|
|
||||||
return mHeader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,650 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* 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/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.broadcast.openmhz;
|
|
||||||
|
|
||||||
import com.google.common.net.HttpHeaders;
|
|
||||||
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.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.MutableIdentifierCollection;
|
|
||||||
import io.github.dsheirer.identifier.Role;
|
|
||||||
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.File;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.CompletionException;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
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())
|
|
||||||
// For future use if OpenMHz supports patch information
|
|
||||||
//.addPart(FormField.PATCHES, patches)
|
|
||||||
.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 static String getTo(AudioRecording audioRecording)
|
|
||||||
{
|
|
||||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
|
||||||
|
|
||||||
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 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!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,233 +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.gui.playlist.streaming;
|
|
||||||
|
|
||||||
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.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +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"),
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,618 +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.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.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.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);
|
|
||||||
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.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";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private static String getTo(AudioRecording audioRecording)
|
|
||||||
{
|
|
||||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
|
||||||
|
|
||||||
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!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,227 +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;
|
|
||||||
|
|
||||||
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 RdioScanner call event
|
|
||||||
*/
|
|
||||||
public class RdioScannerBuilder
|
|
||||||
{
|
|
||||||
private static final String DASH_DASH = "--";
|
|
||||||
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
|
|
||||||
private List<Part> mParts = new ArrayList<>();
|
|
||||||
private byte[] audioBytes = null;
|
|
||||||
private String audioName = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
*/
|
|
||||||
public RdioScannerBuilder()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access the static multi-part boundary string
|
|
||||||
*/
|
|
||||||
public String getBoundary()
|
|
||||||
{
|
|
||||||
return BOUNDARY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a Audio file name part to the call
|
|
||||||
*/
|
|
||||||
public RdioScannerBuilder addAudioName(String value)
|
|
||||||
{
|
|
||||||
audioName = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a Audio bytes part to the call
|
|
||||||
*/
|
|
||||||
public RdioScannerBuilder addFile(byte[] value)
|
|
||||||
{
|
|
||||||
audioBytes = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a string part to the call
|
|
||||||
*/
|
|
||||||
public RdioScannerBuilder 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 RdioScannerBuilder 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; filename=\"").append(audioName);
|
|
||||||
sb.append("\"; name=\"").append("audio").append("\"\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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +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.rdioscanner;
|
|
||||||
|
|
||||||
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.IntegerProperty;
|
|
||||||
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 RdioScannerConfiguration extends BroadcastConfiguration
|
|
||||||
{
|
|
||||||
private IntegerProperty mSystemID = new SimpleIntegerProperty();
|
|
||||||
private StringProperty mApiKey = new SimpleStringProperty();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for faster jackson
|
|
||||||
*/
|
|
||||||
public RdioScannerConfiguration()
|
|
||||||
{
|
|
||||||
this(BroadcastFormat.MP3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public constructor.
|
|
||||||
* @param format to use for audio recording (MP3)
|
|
||||||
*/
|
|
||||||
public RdioScannerConfiguration(BroadcastFormat format)
|
|
||||||
{
|
|
||||||
super(format);
|
|
||||||
|
|
||||||
//The parent class binds this property, so we unbind it and rebind it here
|
|
||||||
mValid.unbind();
|
|
||||||
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
|
|
||||||
Bindings.isNotNull(mHost)));
|
|
||||||
|
|
||||||
if(mHost.getValue() == null || mHost.getValue().isEmpty())
|
|
||||||
{
|
|
||||||
mHost.set("http://localhost");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* System ID as a property
|
|
||||||
*/
|
|
||||||
public IntegerProperty systemIDProperty()
|
|
||||||
{
|
|
||||||
return mSystemID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ID as provided by RdioScanner.com
|
|
||||||
*/
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "system_id")
|
|
||||||
public int getSystemID()
|
|
||||||
{
|
|
||||||
return mSystemID.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the system ID provided by RdioScanner.com
|
|
||||||
*/
|
|
||||||
public void setSystemID(int systemID)
|
|
||||||
{
|
|
||||||
mSystemID.set(systemID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
|
||||||
@Override
|
|
||||||
public BroadcastServerType getBroadcastServerType()
|
|
||||||
{
|
|
||||||
return BroadcastServerType.RDIOSCANNER_CALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BroadcastConfiguration copyOf()
|
|
||||||
{
|
|
||||||
RdioScannerConfiguration copy = new RdioScannerConfiguration();
|
|
||||||
copy.setSystemID(getSystemID());
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.rdioscanner;
|
|
||||||
|
|
||||||
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 RdioScannerFeedConfiguration extends IcecastTCPConfiguration
|
|
||||||
{
|
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerFeedConfiguration.class);
|
|
||||||
|
|
||||||
private int mFeedID;
|
|
||||||
|
|
||||||
public RdioScannerFeedConfiguration()
|
|
||||||
{
|
|
||||||
//No-arg constructor for JAXB
|
|
||||||
this(BroadcastFormat.MP3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RdioScanner configuration for Icecast 2.3.2 compatible servers
|
|
||||||
*
|
|
||||||
* @param format of output audio (MP3)
|
|
||||||
*/
|
|
||||||
public RdioScannerFeedConfiguration(BroadcastFormat format)
|
|
||||||
{
|
|
||||||
super(format);
|
|
||||||
|
|
||||||
setBitRate(16);
|
|
||||||
setChannels(1);
|
|
||||||
setSampleRate(8000);
|
|
||||||
setInline(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RdioScannerFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
|
|
||||||
{
|
|
||||||
RdioScannerFeedConfiguration config = new RdioScannerFeedConfiguration(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()
|
|
||||||
{
|
|
||||||
RdioScannerFeedConfiguration copy = new RdioScannerFeedConfiguration(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());
|
|
||||||
|
|
||||||
//RdioScanner 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.RDIOSCANNER_CALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "feed_id")
|
|
||||||
public int getFeedID()
|
|
||||||
{
|
|
||||||
return mFeedID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFeedID(int feedID)
|
|
||||||
{
|
|
||||||
mFeedID = feedID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
* 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.
|
||||||
|
* *
|
||||||
|
* * 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.codec.mbe;
|
package io.github.dsheirer.audio.codec.mbe;
|
||||||
@@ -29,6 +32,11 @@ import io.github.dsheirer.message.IMessageListener;
|
|||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import jmbe.iface.IAudioCodec;
|
||||||
|
import jmbe.iface.IAudioCodecLibrary;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -36,20 +44,15 @@ import java.net.URLClassLoader;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import jmbe.iface.IAudioCodec;
|
|
||||||
import jmbe.iface.IAudioCodecLibrary;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
|
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
|
||||||
ISquelchStateListener
|
ISquelchStateListener
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
||||||
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
|
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
|
||||||
private static final List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
private static List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
||||||
private IAudioCodec mAudioCodec;
|
private IAudioCodec mAudioCodec;
|
||||||
private final UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private static Class sLoadedJmbeAudioConverterClass;
|
|
||||||
|
|
||||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||||
{
|
{
|
||||||
@@ -67,7 +70,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
mAudioCodec = null;
|
mAudioCodec = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAudioCodec getAudioCodec()
|
protected IAudioCodec getAudioCodec()
|
||||||
{
|
{
|
||||||
return mAudioCodec;
|
return mAudioCodec;
|
||||||
}
|
}
|
||||||
@@ -107,67 +110,38 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
protected abstract String getCodecName();
|
protected abstract String getCodecName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads JMBE audio converter library class and then instantiates new converter instances from the loaded class.
|
* Loads audio frame processing chain. Constructs an imbe targetdataline
|
||||||
|
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
|
||||||
|
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
|
||||||
*/
|
*/
|
||||||
protected void loadConverter()
|
protected void loadConverter()
|
||||||
{
|
{
|
||||||
IAudioCodec audioConverter = null;
|
IAudioCodec audioConverter = null;
|
||||||
|
|
||||||
if(sLoadedJmbeAudioConverterClass == null)
|
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||||
{
|
|
||||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
|
||||||
|
|
||||||
if(path != null)
|
if(path != null)
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.info("Loading JMBE library from [" + path + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
|
||||||
this.getClass().getClassLoader());
|
|
||||||
|
|
||||||
sLoadedJmbeAudioConverterClass = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
|
||||||
}
|
|
||||||
catch(IllegalArgumentException iae)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(MalformedURLException mue)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(ClassNotFoundException e1)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sLoadedJmbeAudioConverterClass != null)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Object instance = sLoadedJmbeAudioConverterClass.getDeclaredConstructor().newInstance();
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
|
||||||
if(instance instanceof IAudioCodecLibrary library)
|
|
||||||
{
|
{
|
||||||
|
mLog.info("Loading JMBE library from [" + path.toString() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
||||||
|
this.getClass().getClassLoader());
|
||||||
|
|
||||||
|
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
||||||
|
|
||||||
|
Object instance = classToLoad.getDeclaredConstructor().newInstance();
|
||||||
|
|
||||||
|
if(instance instanceof IAudioCodecLibrary)
|
||||||
|
{
|
||||||
|
IAudioCodecLibrary library = (IAudioCodecLibrary)instance;
|
||||||
|
|
||||||
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
||||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||||
{
|
{
|
||||||
audioConverter = library.getAudioConverter(getCodecName());
|
audioConverter = library.getAudioConverter(getCodecName());
|
||||||
|
|
||||||
@@ -195,6 +169,38 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(IllegalArgumentException iae)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(NoSuchMethodException nsme)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(MalformedURLException mue)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e1)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
catch(InvocationTargetException ite)
|
catch(InvocationTargetException ite)
|
||||||
{
|
{
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
@@ -219,14 +225,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(NoSuchMethodException nsme)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -237,6 +235,13 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mAudioCodec = audioConverter;
|
if(audioConverter != null)
|
||||||
|
{
|
||||||
|
mAudioCodec = audioConverter;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mAudioCodec = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,27 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
|
||||||
* 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
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
* 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.
|
||||||
|
* *
|
||||||
|
* * 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.codec.mbe;
|
package io.github.dsheirer.audio.codec.mbe;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.github.dsheirer.alias.AliasList;
|
|
||||||
import io.github.dsheirer.audio.AudioSegment;
|
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
|
||||||
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
|
||||||
import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder;
|
|
||||||
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
|
||||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Collection;
|
|
||||||
import jmbe.iface.IAudioCodec;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.commons.io.filefilter.DirectoryFileFilter;
|
|
||||||
import org.apache.commons.io.filefilter.SuffixFileFilter;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -51,130 +32,79 @@ public class MBECallSequenceConverter
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
||||||
|
|
||||||
/**
|
// public static void convert(Path input, Path output) throws IOException
|
||||||
* Converts the input MBE file to PCM audio and writes to the output wave file.
|
// {
|
||||||
* @param input path to the MBE file.
|
// InputStream inputStream = Files.newInputStream(input);
|
||||||
* @param output path to write the WAVE recording.
|
// ObjectMapper mapper = new ObjectMapper();
|
||||||
* @throws IOException if there is an error.
|
// MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
||||||
*/
|
// convert(sequence, output);
|
||||||
public static void convert(Path input, Path output) throws IOException
|
// }
|
||||||
{
|
//
|
||||||
InputStream inputStream = Files.newInputStream(input);
|
// public static void convert(MBECallSequence callSequence, Path outputPath)
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
// {
|
||||||
MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
// if(callSequence == null || callSequence.isEncrypted())
|
||||||
convert(sequence, output);
|
// {
|
||||||
}
|
// throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
||||||
|
// }
|
||||||
public static void convert(MBECallSequence callSequence, Path outputPath)
|
//
|
||||||
{
|
// if(callSequence != null && !callSequence.isEncrypted())
|
||||||
if(callSequence == null || callSequence.isEncrypted())
|
// {
|
||||||
{
|
// ThumbDv.AudioProtocol protocol = ThumbDv.AudioProtocol.P25_PHASE2;
|
||||||
throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
//
|
||||||
}
|
// AudioPacketWaveRecorder recorder = new AudioPacketWaveRecorder(outputPath);
|
||||||
|
// recorder.start();
|
||||||
if(callSequence != null)
|
//
|
||||||
{
|
// long delayMillis = 0;
|
||||||
if(callSequence.getProtocol().equals(P25P1CallSequenceRecorder.PROTOCOL))
|
//
|
||||||
{
|
// try(ThumbDv thumbDv = new ThumbDv(protocol, recorder))
|
||||||
P25P1AudioModule audioModule = new P25P1AudioModule(new UserPreferences(), new AliasList("mbe generator"));
|
// {
|
||||||
audioModule.setRecordAudio(true);
|
// thumbDv.start();
|
||||||
audioModule.start();
|
// for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
||||||
|
// {
|
||||||
if(callSequence.getFromIdentifier() != null)
|
// mLog.debug("Frame [" + voiceFrame.getFrame() + "] + Hex [" + AmbeResponse.toHex(voiceFrame.getFrameBytes()) + "]");
|
||||||
{
|
// thumbDv.decode(voiceFrame.getFrameBytes());
|
||||||
int from = 0;
|
// delayMillis += 30;
|
||||||
|
// }
|
||||||
try
|
//
|
||||||
{
|
// if(delayMillis > 0)
|
||||||
from = Integer.parseInt(callSequence.getFromIdentifier());
|
// {
|
||||||
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25RadioIdentifier.createFrom(from),
|
// delayMillis += 1000;
|
||||||
IdentifierUpdateNotification.Operation.ADD, 0));
|
// try
|
||||||
}
|
// {
|
||||||
catch(Exception e)
|
// Thread.sleep(delayMillis);
|
||||||
{
|
// }
|
||||||
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
// catch(InterruptedException ie)
|
||||||
}
|
// {
|
||||||
}
|
//
|
||||||
|
// }
|
||||||
if(callSequence.getToIdentifier() != null)
|
// }
|
||||||
{
|
// }
|
||||||
int to = 0;
|
// catch(IOException ioe)
|
||||||
|
// {
|
||||||
try
|
// mLog.error("Error", ioe);
|
||||||
{
|
// }
|
||||||
to = Integer.parseInt(callSequence.getToIdentifier());
|
//
|
||||||
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25Talkgroup.create(to),
|
// recorder.stop(Paths.get(outputPath.toString().replace(".tmp", ".wav")), new WaveMetadata());
|
||||||
IdentifierUpdateNotification.Operation.ADD, 0));
|
// }
|
||||||
}
|
// }
|
||||||
catch(Exception e)
|
//
|
||||||
{
|
// public static void main(String[] args)
|
||||||
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
// {
|
||||||
}
|
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_3_TS0_65084_6591007.mbe";
|
||||||
}
|
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_2_TS1_65035.mbe";
|
||||||
|
//
|
||||||
IAudioCodec codec = audioModule.getAudioCodec();
|
// Path input = Paths.get(mbe);
|
||||||
|
// Path output = Paths.get(mbe.replace(".mbe", ".tmp"));
|
||||||
for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
//
|
||||||
{
|
// mLog.info("Converting: " + mbe);
|
||||||
byte[] frameBytes = voiceFrame.getFrameBytes();
|
//
|
||||||
float[] audio = codec.getAudio(frameBytes);
|
// try
|
||||||
audioModule.addAudio(audio);
|
// {
|
||||||
}
|
// MBECallSequenceConverter.convert(input, output);
|
||||||
|
// }
|
||||||
AudioSegment audioSegment = audioModule.getAudioSegment();
|
// catch(IOException ioe)
|
||||||
|
// {
|
||||||
try
|
// mLog.error("Error", ioe);
|
||||||
{
|
// }
|
||||||
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath, audioSegment.getIdentifierCollection());
|
// }
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
|
||||||
mLog.error("Error writing audio segment, ioe");
|
|
||||||
}
|
|
||||||
|
|
||||||
audioModule.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
boolean all = true;
|
|
||||||
|
|
||||||
String path = "/home/denny/SDRTrunk/recordings";
|
|
||||||
Path input = Paths.get(path);
|
|
||||||
|
|
||||||
if(all)
|
|
||||||
{
|
|
||||||
Collection<File> mbeFiles = FileUtils.listFiles(input.toFile(), new SuffixFileFilter(".mbe"), DirectoryFileFilter.DIRECTORY);
|
|
||||||
|
|
||||||
for(File inputFile: mbeFiles)
|
|
||||||
{
|
|
||||||
Path output = Paths.get(inputFile.getAbsolutePath().replace(".mbe", ".wav"));
|
|
||||||
mLog.info("Converting: " + inputFile);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MBECallSequenceConverter.convert(inputFile.toPath(), output);
|
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
|
||||||
mLog.error("Error", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Path output = Paths.get(path.replace(".mbe", ".wav"));
|
|
||||||
mLog.info("Converting: " + path);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MBECallSequenceConverter.convert(input, output);
|
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
|
||||||
mLog.error("Error", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
|
||||||
* 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
|
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
* 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.
|
||||||
|
* *
|
||||||
|
* * 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.playback;
|
package io.github.dsheirer.audio.playback;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
import io.github.dsheirer.audio.AudioEvent;
|
import io.github.dsheirer.audio.AudioEvent;
|
||||||
import io.github.dsheirer.audio.AudioSegment;
|
import io.github.dsheirer.audio.AudioSegment;
|
||||||
import io.github.dsheirer.controller.NamingThreadFactory;
|
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
import io.github.dsheirer.log.LoggingSuppressor;
|
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||||
import java.nio.ByteBuffer;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import java.util.concurrent.Executors;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -51,6 +51,10 @@ import javax.sound.sampled.LineListener;
|
|||||||
import javax.sound.sampled.LineUnavailableException;
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.sound.sampled.SourceDataLine;
|
import javax.sound.sampled.SourceDataLine;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio output/playback channel for a single audio mixer channel. Providers support for playback of audio segments
|
* Audio output/playback channel for a single audio mixer channel. Providers support for playback of audio segments
|
||||||
@@ -59,11 +63,11 @@ import javax.sound.sampled.SourceDataLine;
|
|||||||
public abstract class AudioOutput implements LineListener, Listener<IdentifierUpdateNotification>
|
public abstract class AudioOutput implements LineListener, Listener<IdentifierUpdateNotification>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(AudioOutput.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AudioOutput.class);
|
||||||
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog);
|
|
||||||
private int mBufferStartThreshold;
|
private int mBufferStartThreshold;
|
||||||
private int mBufferStopThreshold;
|
private int mBufferStopThreshold;
|
||||||
private Listener<IdentifierCollection> mIdentifierCollectionListener;
|
private Listener<IdentifierCollection> mIdentifierCollectionListener;
|
||||||
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||||
|
private ScheduledFuture<?> mProcessorFuture;
|
||||||
private SourceDataLine mOutput;
|
private SourceDataLine mOutput;
|
||||||
private Mixer mMixer;
|
private Mixer mMixer;
|
||||||
private MixerChannel mMixerChannel;
|
private MixerChannel mMixerChannel;
|
||||||
@@ -72,21 +76,17 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
private AudioEvent mAudioStartEvent;
|
private AudioEvent mAudioStartEvent;
|
||||||
private AudioEvent mAudioStopEvent;
|
private AudioEvent mAudioStopEvent;
|
||||||
private boolean mCanProcessAudio = false;
|
private boolean mCanProcessAudio = false;
|
||||||
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
|
||||||
private AudioSegment mCurrentAudioSegment;
|
private AudioSegment mCurrentAudioSegment;
|
||||||
|
private AudioSegment mNextAudioSegment;
|
||||||
|
private ReentrantLock mLock = new ReentrantLock();
|
||||||
private int mCurrentBufferIndex = 0;
|
private int mCurrentBufferIndex = 0;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
|
private BooleanProperty mEmptyProperty = new SimpleBooleanProperty(true);
|
||||||
|
private IntegerProperty mAudioPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||||
private ByteBuffer mAudioSegmentStartTone;
|
private ByteBuffer mAudioSegmentStartTone;
|
||||||
|
private ByteBuffer mAudioSegmentPreemptTone;
|
||||||
private ByteBuffer mAudioSegmentDropTone;
|
private ByteBuffer mAudioSegmentDropTone;
|
||||||
private boolean mRunning = false;
|
private boolean mRunning = false;
|
||||||
private ScheduledExecutorService mScheduledExecutorService;
|
|
||||||
private ScheduledFuture<?> mProcessorFuture;
|
|
||||||
private boolean mDropDuplicates;
|
|
||||||
private long mOutputLastTimestamp = 0;
|
|
||||||
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
|
|
||||||
private AudioFormat mAudioFormat;
|
|
||||||
private Line.Info mLineInfo;
|
|
||||||
private int mRequestedBufferSize;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single audio channel playback with automatic starting and stopping of the
|
* Single audio channel playback with automatic starting and stopping of the
|
||||||
@@ -107,17 +107,11 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
mMixer = mixer;
|
mMixer = mixer;
|
||||||
mMixerChannel = mixerChannel;
|
mMixerChannel = mixerChannel;
|
||||||
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
|
|
||||||
"sdrtrunk audio output " + mixerChannel.name()));
|
|
||||||
mUserPreferences = userPreferences;
|
mUserPreferences = userPreferences;
|
||||||
mDropDuplicates = mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled();
|
|
||||||
mAudioFormat = audioFormat;
|
|
||||||
mLineInfo = lineInfo;
|
|
||||||
mRequestedBufferSize = requestedBufferSize;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mOutput = (SourceDataLine) mMixer.getLine(mLineInfo);
|
mOutput = (SourceDataLine) mMixer.getLine(lineInfo);
|
||||||
|
|
||||||
if(mOutput != null)
|
if(mOutput != null)
|
||||||
{
|
{
|
||||||
@@ -156,7 +150,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Run the queue processor task every 100 milliseconds or 10 times a second
|
//Run the queue processor task every 100 milliseconds or 10 times a second
|
||||||
mProcessorFuture = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||||
0, 100, TimeUnit.MILLISECONDS);
|
0, 100, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,22 +167,31 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
|
|
||||||
updateToneInsertionAudioClips();
|
updateToneInsertionAudioClips();
|
||||||
|
|
||||||
//Register to receive preference update notifications so we can update the preference items
|
//Register to receive directory preference update notifications so we can update the preference items
|
||||||
MyEventBus.getGlobalEventBus().register(this);
|
MyEventBus.getGlobalEventBus().register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this audio output doesn't currently have any audio segments queued for playback.
|
* Boolean property that indicates if this audio output has an audio segment queued or in process of playback.
|
||||||
* @return true if empty
|
|
||||||
*/
|
*/
|
||||||
public boolean isEmpty()
|
public BooleanProperty emptyProperty()
|
||||||
{
|
{
|
||||||
return mAudioSegmentQueue.isEmpty();
|
return mEmptyProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules the audio segment for playback. The audio segment user count should already be incremented by the
|
* Audio playback priority of the current audio segment or default audio priority if nothing is currently loaded.
|
||||||
* calling entity.
|
*/
|
||||||
|
public IntegerProperty audioPriorityProperty()
|
||||||
|
{
|
||||||
|
return mAudioPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules the audio segment for playback.
|
||||||
|
*
|
||||||
|
* Note: if a segment is currently playing and another audio segment is already queued for playback, invoking
|
||||||
|
* this method will overwrite the queued segment with the argument.
|
||||||
*
|
*
|
||||||
* @param audioSegment to schedule for playback.
|
* @param audioSegment to schedule for playback.
|
||||||
*/
|
*/
|
||||||
@@ -196,8 +199,23 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
if(audioSegment != null)
|
if(audioSegment != null)
|
||||||
{
|
{
|
||||||
//Audio segment use count has already been incremented by the external caller.
|
mLock.lock();
|
||||||
mAudioSegmentQueue.add(audioSegment);
|
|
||||||
|
if(mNextAudioSegment != null)
|
||||||
|
{
|
||||||
|
mNextAudioSegment.decrementConsumerCount();
|
||||||
|
mNextAudioSegment = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mNextAudioSegment = audioSegment;
|
||||||
|
mEmptyProperty.set(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,10 +254,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
updateToneInsertionAudioClips();
|
updateToneInsertionAudioClips();
|
||||||
}
|
}
|
||||||
else if(preferenceType == PreferenceType.DUPLICATE_CALL_DETECTION)
|
|
||||||
{
|
|
||||||
mDropDuplicates = mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,6 +262,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
private void updateToneInsertionAudioClips()
|
private void updateToneInsertionAudioClips()
|
||||||
{
|
{
|
||||||
mAudioSegmentStartTone = null;
|
mAudioSegmentStartTone = null;
|
||||||
|
mAudioSegmentPreemptTone = null;
|
||||||
mAudioSegmentDropTone = null;
|
mAudioSegmentDropTone = null;
|
||||||
|
|
||||||
float[] start = mUserPreferences.getPlaybackPreference().getStartTone();
|
float[] start = mUserPreferences.getPlaybackPreference().getStartTone();
|
||||||
@@ -257,6 +272,13 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
mAudioSegmentStartTone = convert(start);
|
mAudioSegmentStartTone = convert(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float[] preempt = mUserPreferences.getPlaybackPreference().getPreemptTone();
|
||||||
|
|
||||||
|
if(preempt != null)
|
||||||
|
{
|
||||||
|
mAudioSegmentPreemptTone = convert(preempt);
|
||||||
|
}
|
||||||
|
|
||||||
float[] drop = mUserPreferences.getPlaybackPreference().getDropTone();
|
float[] drop = mUserPreferences.getPlaybackPreference().getDropTone();
|
||||||
|
|
||||||
if(drop != null)
|
if(drop != null)
|
||||||
@@ -265,6 +287,48 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of a processed audio segment and notifies the originator that processing of the segment is complete
|
||||||
|
* by decrementing the consumer count.
|
||||||
|
* @param audioSegment to dispose
|
||||||
|
*/
|
||||||
|
private void dispose(AudioSegment audioSegment)
|
||||||
|
{
|
||||||
|
mAudioPriority.unbind();
|
||||||
|
|
||||||
|
if(audioSegment != null)
|
||||||
|
{
|
||||||
|
audioSegment.decrementConsumerCount();
|
||||||
|
audioSegment.removeIdentifierUpdateNotificationListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a tone indicating that a new audio segment is starting
|
||||||
|
*/
|
||||||
|
private ByteBuffer getAudioSegmentStartTone()
|
||||||
|
{
|
||||||
|
return mAudioSegmentStartTone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a tone indicating that the current audio segment playback has been preempted for a higher priority
|
||||||
|
* audio segment that is now starting.
|
||||||
|
*/
|
||||||
|
private ByteBuffer getAudioSegmentPreemptionTone()
|
||||||
|
{
|
||||||
|
return mAudioSegmentPreemptTone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a tone indicating that the current audio segment playback has been dropped because the audio segment
|
||||||
|
* has been flagged as Do Not Monitor after playback has started.
|
||||||
|
*/
|
||||||
|
private ByteBuffer getAudioSegmentDropTone()
|
||||||
|
{
|
||||||
|
return mAudioSegmentDropTone;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the audio buffer data to the source data line. If the data line is not currently playing, write as
|
* Writes the audio buffer data to the source data line. If the data line is not currently playing, write as
|
||||||
* much of the buffer to the data line as will fit, start the dataline, and then finish writing the residual
|
* much of the buffer to the data line as will fit, start the dataline, and then finish writing the residual
|
||||||
@@ -274,12 +338,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
*/
|
*/
|
||||||
private void playAudio(ByteBuffer buffer)
|
private void playAudio(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
if(mOutput == null)
|
|
||||||
{
|
|
||||||
LOGGING_SUPPRESSOR.error("null output", 5, "Audio Output is null - ignoring audio playback request");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(buffer != null)
|
if(buffer != null)
|
||||||
{
|
{
|
||||||
int wrote = 0;
|
int wrote = 0;
|
||||||
@@ -297,252 +355,157 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
|
|
||||||
//Top off the buffer and check if we can start it
|
//Top off the buffer and check if we can start it
|
||||||
wrote += mOutput.write(buffer.array(), 0, toWrite);
|
wrote += mOutput.write(buffer.array(), 0, toWrite);
|
||||||
mOutputLastTimestamp = System.currentTimeMillis();
|
|
||||||
|
|
||||||
checkStart();
|
checkStart();
|
||||||
|
|
||||||
//Something is causing the source data line to fail to accept audio byte data via the write() method
|
|
||||||
//and this seems to have started around JDK22, maybe. We can detect when this happens and close() and
|
|
||||||
//reopen the data line to clear the error.
|
|
||||||
if(!mOutput.isRunning() && wrote <= 0 && buffer.array().length > 0)
|
|
||||||
{
|
|
||||||
mOutput.close();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mOutput = (SourceDataLine)mMixer.getLine(mLineInfo);
|
|
||||||
|
|
||||||
if(mOutput != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control gain = mOutput.getControl(FloatControl.Type.MASTER_GAIN);
|
|
||||||
mGainControl = (FloatControl) gain;
|
|
||||||
}
|
|
||||||
catch(IllegalArgumentException iae)
|
|
||||||
{
|
|
||||||
mLog.warn("Couldn't obtain MASTER GAIN control for stereo line [" +
|
|
||||||
mMixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control mute = mOutput.getControl(BooleanControl.Type.MUTE);
|
|
||||||
mMuteControl = (BooleanControl) mute;
|
|
||||||
}
|
|
||||||
catch(IllegalArgumentException iae)
|
|
||||||
{
|
|
||||||
mLog.warn("Couldn't obtain MUTE control for stereo line [" +
|
|
||||||
mMixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGING_SUPPRESSOR.info("reopen audio output success", 5,
|
|
||||||
"Closed and reopened audio output - success - mOutput is not null");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOGGING_SUPPRESSOR.info("reopen audio output fail", 5,
|
|
||||||
"Closed and reopened audio output - fail - mOutput is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(LineUnavailableException lue)
|
|
||||||
{
|
|
||||||
LOGGING_SUPPRESSOR.error("reopen fail for lua", 3, "Attempt to reopen " +
|
|
||||||
"audio source data line failed", lue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mOutput.open(mAudioFormat, mRequestedBufferSize);
|
|
||||||
|
|
||||||
//Start threshold: buffer is full with 10% or less of capacity remaining
|
|
||||||
mBufferStartThreshold = (int) (mOutput.getBufferSize() * 0.10);
|
|
||||||
|
|
||||||
//Stop threshold: buffer is empty with 90% or more capacity available
|
|
||||||
mBufferStopThreshold = (int) (mOutput.getBufferSize() * 0.90);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control gain = mOutput.getControl(FloatControl.Type.MASTER_GAIN);
|
|
||||||
mGainControl = (FloatControl) gain;
|
|
||||||
}
|
|
||||||
catch(IllegalArgumentException iae)
|
|
||||||
{
|
|
||||||
mLog.warn("Couldn't obtain MASTER GAIN control for stereo line [" + getChannelName() +
|
|
||||||
"] after reopening the source data line");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control mute = mOutput.getControl(BooleanControl.Type.MUTE);
|
|
||||||
mMuteControl = (BooleanControl) mute;
|
|
||||||
}
|
|
||||||
catch(IllegalArgumentException iae)
|
|
||||||
{
|
|
||||||
mLog.warn("Couldn't obtain MUTE control for stereo line [" + getChannelName() +
|
|
||||||
"] after reopening the source data line");
|
|
||||||
}
|
|
||||||
|
|
||||||
mAudioStartEvent = new AudioEvent(AudioEvent.Type.AUDIO_STARTED, getChannelName());
|
|
||||||
mAudioStopEvent = new AudioEvent(AudioEvent.Type.AUDIO_STOPPED, getChannelName());
|
|
||||||
mCanProcessAudio = true;
|
|
||||||
}
|
|
||||||
catch(LineUnavailableException e)
|
|
||||||
{
|
|
||||||
LOGGING_SUPPRESSOR.error("failed reopen source data line lua", 3,
|
|
||||||
"Failed to (re)open the source data line for audio output - mixer [" +
|
|
||||||
mMixer.getMixerInfo().getName() + "]");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mOutput.isRunning() && wrote < buffer.array().length)
|
if(mOutput.isRunning() && wrote < buffer.array().length)
|
||||||
{
|
{
|
||||||
//This will block until the buffer is fully written to the data line
|
//This will block until the buffer is fully written to the data line
|
||||||
mOutput.write(buffer.array(), wrote, buffer.array().length - wrote);
|
mOutput.write(buffer.array(), wrote, buffer.array().length - wrote);
|
||||||
mOutputLastTimestamp = System.currentTimeMillis();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispose of the currently assigned audio segment.
|
|
||||||
*/
|
|
||||||
private void disposeCurrentAudioSegment()
|
|
||||||
{
|
|
||||||
if(mCurrentAudioSegment != null)
|
|
||||||
{
|
|
||||||
mCurrentAudioSegment.decrementConsumerCount();
|
|
||||||
mCurrentAudioSegment.removeIdentifierUpdateNotificationListener(this);
|
|
||||||
mCurrentAudioSegment = null;
|
|
||||||
broadcast(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the next audio segment from the queue.
|
|
||||||
*/
|
|
||||||
private void loadNextAudioSegment()
|
|
||||||
{
|
|
||||||
AudioSegment audioSegment = mAudioSegmentQueue.poll();
|
|
||||||
|
|
||||||
boolean verificationInProgress = (audioSegment != null);
|
|
||||||
|
|
||||||
while(verificationInProgress)
|
|
||||||
{
|
|
||||||
if(audioSegment != null)
|
|
||||||
{
|
|
||||||
//Throw away the audio segment if it has been flagged as do not monitor or is duplicate
|
|
||||||
if(isThrowaway(audioSegment))
|
|
||||||
{
|
|
||||||
audioSegment.decrementConsumerCount();
|
|
||||||
audioSegment = mAudioSegmentQueue.poll();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
verificationInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
verificationInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentAudioSegment = audioSegment;
|
|
||||||
|
|
||||||
if(audioSegment != null)
|
|
||||||
{
|
|
||||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
|
||||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentBufferIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the audio segment should be thrown away because it is marked as duplicate or do-not-monitor.
|
|
||||||
* @param audioSegment to evaluate
|
|
||||||
* @return true if the audio should be thrown away.
|
|
||||||
*/
|
|
||||||
private boolean isThrowaway(AudioSegment audioSegment)
|
|
||||||
{
|
|
||||||
return audioSegment != null && (audioSegment.isDoNotMonitor() || mDropDuplicates && (audioSegment.isDuplicate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage audio segment playback and process audio segment buffers. This method is designed to be called
|
* Manage audio segment playback and process audio segment buffers. This method is designed to be called
|
||||||
* by a threaded processor repeatedly to playback the current audio segment and check for and start newly
|
* by a threaded processor repeatedly to playback the current audio segment and check for and start a newly
|
||||||
* added audio segments. It also handles starting and stopping the playback source data line to avoid audio
|
* assigned audio segment. It also handles starting and stopping the playback source data line to avoid audio
|
||||||
* discontinuities due to buffer underruns.
|
* discontinuities due to buffer underruns.
|
||||||
*/
|
*/
|
||||||
private void processAudio()
|
private void processAudio()
|
||||||
{
|
{
|
||||||
if(mCurrentAudioSegment == null)
|
if(mNextAudioSegment != null)
|
||||||
{
|
{
|
||||||
loadNextAudioSegment();
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Remove an assigned segment that's subsequently flagged as duplicate or do not monitor before playback
|
||||||
|
if(mNextAudioSegment.isDoNotMonitor() || (mNextAudioSegment.isDuplicate() &&
|
||||||
|
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||||
|
{
|
||||||
|
mNextAudioSegment = null;
|
||||||
|
|
||||||
|
if(mCurrentAudioSegment == null)
|
||||||
|
{
|
||||||
|
mEmptyProperty.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//For linked audio segments, allow the linked segment to complete first before assigning the next
|
||||||
|
else if(mNextAudioSegment.isLinked())
|
||||||
|
{
|
||||||
|
if(mCurrentAudioSegment == null)
|
||||||
|
{
|
||||||
|
mCurrentAudioSegment = mNextAudioSegment;
|
||||||
|
mNextAudioSegment = null;
|
||||||
|
mCurrentBufferIndex = 0;
|
||||||
|
|
||||||
|
if(mCurrentAudioSegment != null)
|
||||||
|
{
|
||||||
|
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
||||||
|
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||||
|
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Insert audio segment start or audio priority preemption bonk tone
|
||||||
|
if(mCurrentAudioSegment == null)
|
||||||
|
{
|
||||||
|
playAudio(getAudioSegmentStartTone());
|
||||||
|
}
|
||||||
|
else if(mCurrentBufferIndex > 0 &&
|
||||||
|
(!mCurrentAudioSegment.completeProperty().get() ||
|
||||||
|
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount()))
|
||||||
|
{
|
||||||
|
playAudio(getAudioSegmentPreemptionTone());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playAudio(getAudioSegmentStartTone());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close current audio segment
|
||||||
|
dispose(mCurrentAudioSegment);
|
||||||
|
mCurrentAudioSegment = mNextAudioSegment;
|
||||||
|
mNextAudioSegment = null;
|
||||||
|
mCurrentBufferIndex = 0;
|
||||||
|
|
||||||
|
if(mCurrentAudioSegment != null)
|
||||||
|
{
|
||||||
|
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
||||||
|
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||||
|
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Evaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
if(mCurrentAudioSegment != null)
|
||||||
while(isThrowaway(mCurrentAudioSegment))
|
|
||||||
{
|
{
|
||||||
if(mCurrentBufferIndex > 0)
|
//Check for completed audio segment or a segment flagged as duplicate or Do Not Monitor
|
||||||
|
if(mCurrentAudioSegment.isDoNotMonitor() ||
|
||||||
|
(mCurrentAudioSegment.isDuplicate() &&
|
||||||
|
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()) ||
|
||||||
|
(mCurrentAudioSegment.completeProperty().get() && mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||||
{
|
{
|
||||||
playAudio(mAudioSegmentDropTone);
|
if(mCurrentAudioSegment.isDoNotMonitor())
|
||||||
}
|
|
||||||
|
|
||||||
disposeCurrentAudioSegment();
|
|
||||||
loadNextAudioSegment();
|
|
||||||
}
|
|
||||||
|
|
||||||
while(mCurrentAudioSegment != null && mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount())
|
|
||||||
{
|
|
||||||
//Continuously evaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
|
||||||
if(isThrowaway(mCurrentAudioSegment))
|
|
||||||
{
|
|
||||||
if(mCurrentBufferIndex > 0)
|
|
||||||
{
|
{
|
||||||
playAudio(mAudioSegmentDropTone);
|
playAudio(getAudioSegmentDropTone());
|
||||||
}
|
}
|
||||||
|
|
||||||
disposeCurrentAudioSegment();
|
dispose(mCurrentAudioSegment);
|
||||||
}
|
mCurrentAudioSegment = null;
|
||||||
else
|
|
||||||
{
|
mLock.lock();
|
||||||
if(mCurrentBufferIndex == 0)
|
|
||||||
{
|
|
||||||
playAudio(mAudioSegmentStartTone);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
if(mNextAudioSegment == null)
|
||||||
|
|
||||||
if(audioBuffer != null)
|
|
||||||
{
|
{
|
||||||
ByteBuffer audio = convert(audioBuffer);
|
mEmptyProperty.set(true);
|
||||||
//This call blocks until all audio bytes are dumped into the data line.
|
|
||||||
playAudio(audio);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
finally
|
||||||
{
|
{
|
||||||
mLog.error("Error while processing audio for [" + mMixerChannel.name() + "]", e);
|
mLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Process any new buffers that have been added to the audio segment. If a next audio segment gets assigned
|
||||||
|
//while processing, exit the loop so that we can evaluate the next for higher priority preempt. If the next
|
||||||
|
//segment is a linked segment, ignore it so that we can close out the current segment.
|
||||||
|
while(mCurrentAudioSegment != null && (mNextAudioSegment == null || mNextAudioSegment.isLinked()) &&
|
||||||
|
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount() &&
|
||||||
|
!mCurrentAudioSegment.isDoNotMonitor() && !(mCurrentAudioSegment.isDuplicate() &&
|
||||||
|
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||||
|
{
|
||||||
|
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
||||||
|
|
||||||
|
if(audioBuffer != null)
|
||||||
|
{
|
||||||
|
ByteBuffer audio = convert(audioBuffer);
|
||||||
|
playAudio(audio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check for completed and fully-played audio segment to closeout
|
|
||||||
if(mCurrentAudioSegment != null &&
|
|
||||||
mCurrentAudioSegment.isComplete() &&
|
|
||||||
(mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
|
||||||
{
|
|
||||||
disposeCurrentAudioSegment();
|
|
||||||
}
|
|
||||||
|
|
||||||
checkStop();
|
checkStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,7 +523,25 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
}
|
}
|
||||||
|
|
||||||
mProcessorFuture = null;
|
mProcessorFuture = null;
|
||||||
disposeCurrentAudioSegment();
|
|
||||||
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(mNextAudioSegment != null)
|
||||||
|
{
|
||||||
|
mNextAudioSegment.decrementConsumerCount();
|
||||||
|
mNextAudioSegment = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(mCurrentAudioSegment);
|
||||||
|
mCurrentAudioSegment = null;
|
||||||
|
|
||||||
mAudioEventBroadcaster.clear();
|
mAudioEventBroadcaster.clear();
|
||||||
mIdentifierCollectionListener = null;
|
mIdentifierCollectionListener = null;
|
||||||
|
|
||||||
@@ -669,28 +650,14 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
if(mRunning)
|
if(mRunning)
|
||||||
{
|
{
|
||||||
//If the output buffer falls below the threshold then drain the output and stop playback
|
//If the output buffer falls below the threshold then drain the output and stop playback
|
||||||
if(mOutput.isRunning())
|
if(mOutput.isRunning() && mOutput.available() >= mBufferStopThreshold)
|
||||||
{
|
{
|
||||||
if(mOutput.available() >= mBufferStopThreshold)
|
mOutput.drain();
|
||||||
{
|
mOutput.stop();
|
||||||
mOutput.drain();
|
mRunning = false;
|
||||||
mOutput.stop();
|
|
||||||
mRunning = false;
|
|
||||||
}
|
|
||||||
//Detect when we have a partially filled output that will neither start nor stop and the last write
|
|
||||||
//timestamp was more than the staleness threshold (500 ms) -- stop the output and discard stale contents
|
|
||||||
else if(mOutput.available() < mBufferStopThreshold &&
|
|
||||||
mOutput.available() > mBufferStartThreshold &&
|
|
||||||
(System.currentTimeMillis() - mOutputLastTimestamp >= STALE_PLAYBACK_THRESHOLD_MS))
|
|
||||||
{
|
|
||||||
mOutput.stop();
|
|
||||||
//Discard the stale buffer contents.
|
|
||||||
mOutput.flush();
|
|
||||||
mRunning = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//If output playback stopped on its own because the buffer emptied, then cleanup
|
//If output playback stopped on its own because the buffer emptied, then cleanup
|
||||||
else
|
else if(!mOutput.isRunning())
|
||||||
{
|
{
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
}
|
}
|
||||||
@@ -768,23 +735,16 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
*/
|
*/
|
||||||
public class AudioSegmentProcessor implements Runnable
|
public class AudioSegmentProcessor implements Runnable
|
||||||
{
|
{
|
||||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
if(mProcessing.compareAndSet(false, true))
|
try
|
||||||
{
|
{
|
||||||
try
|
processAudio();
|
||||||
{
|
}
|
||||||
processAudio();
|
catch(Throwable t)
|
||||||
}
|
{
|
||||||
catch(Throwable t)
|
mLog.error("Error while processing audio buffers", t);
|
||||||
{
|
|
||||||
mLog.error("Error while processing audio buffers", t);
|
|
||||||
}
|
|
||||||
|
|
||||||
mProcessing.set(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* ******************************************************************************
|
||||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* *****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.playback;
|
package io.github.dsheirer.audio.playback;
|
||||||
|
|
||||||
@@ -23,7 +24,6 @@ import io.github.dsheirer.audio.AudioEvent;
|
|||||||
import io.github.dsheirer.audio.AudioException;
|
import io.github.dsheirer.audio.AudioException;
|
||||||
import io.github.dsheirer.audio.AudioSegment;
|
import io.github.dsheirer.audio.AudioSegment;
|
||||||
import io.github.dsheirer.audio.IAudioController;
|
import io.github.dsheirer.audio.IAudioController;
|
||||||
import io.github.dsheirer.controller.NamingThreadFactory;
|
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
@@ -31,18 +31,17 @@ import io.github.dsheirer.sample.Broadcaster;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||||
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages scheduling and playback of audio segments to the local users audio system.
|
* Manages scheduling and playback of audio segments to the local users audio system.
|
||||||
@@ -51,6 +50,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioPlaybackManager.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioPlaybackManager.class);
|
||||||
|
|
||||||
|
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
|
||||||
|
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
|
||||||
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
||||||
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null);
|
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null);
|
||||||
public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
|
public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
|
||||||
@@ -63,8 +64,6 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||||
private List<AudioSegment> mPendingAudioSegments = new ArrayList<>();
|
private List<AudioSegment> mPendingAudioSegments = new ArrayList<>();
|
||||||
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||||
private ScheduledExecutorService mScheduledExecutorService =
|
|
||||||
Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk audio manager"));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
@@ -120,7 +119,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
while(newSegment != null)
|
while(newSegment != null)
|
||||||
{
|
{
|
||||||
if(newSegment.isDuplicate() &&
|
if(newSegment.isDuplicate() &&
|
||||||
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled())
|
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
|
||||||
{
|
{
|
||||||
newSegment.decrementConsumerCount();
|
newSegment.decrementConsumerCount();
|
||||||
}
|
}
|
||||||
@@ -148,7 +147,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
audioSegment = it.next();
|
audioSegment = it.next();
|
||||||
|
|
||||||
if(audioSegment.isDuplicate() &&
|
if(audioSegment.isDuplicate() &&
|
||||||
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled())
|
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled())
|
||||||
{
|
{
|
||||||
it.remove();
|
it.remove();
|
||||||
audioSegment.decrementConsumerCount();
|
audioSegment.decrementConsumerCount();
|
||||||
@@ -182,7 +181,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
audioSegment = it.next();
|
audioSegment = it.next();
|
||||||
|
|
||||||
if(audioSegment.isDoNotMonitor() || (audioSegment.isDuplicate() &&
|
if(audioSegment.isDoNotMonitor() || (audioSegment.isDuplicate() &&
|
||||||
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled()))
|
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||||
{
|
{
|
||||||
it.remove();
|
it.remove();
|
||||||
audioSegment.decrementConsumerCount();
|
audioSegment.decrementConsumerCount();
|
||||||
@@ -203,13 +202,29 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
//Sort audio segments by playback priority and assign to empty audio outputs
|
//Sort audio segments by playback priority and assign to empty audio outputs
|
||||||
if(!mAudioSegments.isEmpty())
|
if(!mAudioSegments.isEmpty())
|
||||||
{
|
{
|
||||||
//TODO: change this to take audio segment start time into account also
|
|
||||||
mAudioSegments.sort(Comparator.comparingInt(o -> o.monitorPriorityProperty().get()));
|
mAudioSegments.sort(Comparator.comparingInt(o -> o.monitorPriorityProperty().get()));
|
||||||
|
|
||||||
//Assign empty audio outputs first
|
//Assign empty audio outputs first
|
||||||
for(AudioOutput audioOutput: mAudioOutputs)
|
for(AudioOutput audioOutput: mAudioOutputs)
|
||||||
{
|
{
|
||||||
if(audioOutput.isEmpty())
|
if(audioOutput.emptyProperty().get())
|
||||||
|
{
|
||||||
|
audioOutput.play(mAudioSegments.remove(0));
|
||||||
|
|
||||||
|
if(mAudioSegments.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Preempt ongoing audio segment playback when higher priority audio segments are available
|
||||||
|
if(!mAudioSegments.isEmpty())
|
||||||
|
{
|
||||||
|
for(AudioOutput audioOutput: mAudioOutputs)
|
||||||
|
{
|
||||||
|
if(mAudioSegments.get(0).monitorPriorityProperty().get() < audioOutput.audioPriorityProperty().get())
|
||||||
{
|
{
|
||||||
audioOutput.play(mAudioSegments.remove(0));
|
audioOutput.play(mAudioSegments.remove(0));
|
||||||
|
|
||||||
@@ -228,7 +243,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
audioSegment = it.next();
|
audioSegment = it.next();
|
||||||
|
|
||||||
if(audioSegment.completeProperty().get() || (audioSegment.isDuplicate() &&
|
if(audioSegment.completeProperty().get() || (audioSegment.isDuplicate() &&
|
||||||
mUserPreferences.getCallManagementPreference().isDuplicatePlaybackSuppressionEnabled()))
|
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||||
{
|
{
|
||||||
it.remove();
|
it.remove();
|
||||||
audioSegment.decrementConsumerCount();
|
audioSegment.decrementConsumerCount();
|
||||||
@@ -269,7 +284,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
}
|
}
|
||||||
catch(AudioException ae)
|
catch(AudioException ae)
|
||||||
{
|
{
|
||||||
mLog.error("Error changing audio output to [" + configuration + "]", ae);
|
mLog.error("Error changing audio output to [" + configuration.toString() + "]", ae);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +332,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
|
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
|
||||||
}
|
}
|
||||||
|
|
||||||
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
mProcessingTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||||
0, 100, TimeUnit.MILLISECONDS);
|
0, 100, TimeUnit.MILLISECONDS);
|
||||||
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
|
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
|
||||||
mMixerChannelConfiguration = entry;
|
mMixerChannelConfiguration = entry;
|
||||||
@@ -328,7 +343,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
* Current audio playback mixer channel configuration setting.
|
* Current audio playback mixer channel configuration setting.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public MixerChannelConfiguration getMixerChannelConfiguration()
|
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
||||||
{
|
{
|
||||||
return mMixerChannelConfiguration;
|
return mMixerChannelConfiguration;
|
||||||
}
|
}
|
||||||
@@ -369,23 +384,16 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
*/
|
*/
|
||||||
public class AudioSegmentProcessor implements Runnable
|
public class AudioSegmentProcessor implements Runnable
|
||||||
{
|
{
|
||||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
if(mProcessing.compareAndSet(false, true))
|
try
|
||||||
{
|
{
|
||||||
try
|
processAudioSegments();
|
||||||
{
|
}
|
||||||
processAudioSegments();
|
catch(Throwable t)
|
||||||
}
|
{
|
||||||
catch(Throwable t)
|
mLog.error("Encountered error while processing audio segments", t);
|
||||||
{
|
|
||||||
mLog.error("Encountered error while processing audio segments", t);
|
|
||||||
}
|
|
||||||
|
|
||||||
mProcessing.set(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.bits;
|
|||||||
|
|
||||||
import io.github.dsheirer.edac.CRC;
|
import io.github.dsheirer.edac.CRC;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
@@ -34,8 +33,6 @@ public class BinaryMessage extends BitSet
|
|||||||
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
|
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||||
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
private static final String UTF_8 = "UTF-8";
|
private static final String UTF_8 = "UTF-8";
|
||||||
private static final String GB2312 = "GB2312";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical (ie constructed) size of this bitset, despite the actual size of
|
* Logical (ie constructed) size of this bitset, despite the actual size of
|
||||||
@@ -277,7 +274,8 @@ public class BinaryMessage extends BitSet
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new BitSetFullException("bitset is full -- contains [" + mPointer + "/" + size() + "] bits");
|
throw new BitSetFullException("bitset is full -- contains " +
|
||||||
|
(mPointer + 1) + "bits");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,8 +305,8 @@ public class BinaryMessage extends BitSet
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the parent method which returns a BitSet so that we can return a BinaryMessage
|
* Overrides the parent method which returns a BitSet so that we can return a BinaryMessage
|
||||||
* @param from inclusive
|
* @param from
|
||||||
* @param to exclusive
|
* @param to
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -451,203 +449,6 @@ public class BinaryMessage extends BitSet
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the message field described by the field argument.
|
|
||||||
* @param intField with start and end indices (inclusive).
|
|
||||||
* @return integer field value.
|
|
||||||
*/
|
|
||||||
public int getInt(IntField intField)
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
for(int index = intField.start(); index <= intField.end(); index++)
|
|
||||||
{
|
|
||||||
value = Integer.rotateLeft(value, 1);
|
|
||||||
|
|
||||||
if(get(index))
|
|
||||||
{
|
|
||||||
value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the message field described by the field argument.
|
|
||||||
* @param fragmentedField with an array of message indices
|
|
||||||
* @return integer field value.
|
|
||||||
*/
|
|
||||||
public int getInt(FragmentedIntField fragmentedField)
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
for(int index: fragmentedField.indices())
|
|
||||||
{
|
|
||||||
value = Integer.rotateLeft(value, 1);
|
|
||||||
|
|
||||||
if(get(index))
|
|
||||||
{
|
|
||||||
value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
||||||
* @param intField to inspect
|
|
||||||
* @return true if any of the bits are set, indicating a non-zero value.
|
|
||||||
*/
|
|
||||||
public boolean hasInt(IntField intField)
|
|
||||||
{
|
|
||||||
return nextSetBit(intField.start()) <= intField.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
||||||
* @param fragmentedField to inspect
|
|
||||||
* @return true if any of the bits are set, indicating a non-zero value.
|
|
||||||
*/
|
|
||||||
public boolean hasInt(FragmentedIntField fragmentedField)
|
|
||||||
{
|
|
||||||
for(int index: fragmentedField.indices())
|
|
||||||
{
|
|
||||||
if(get(index))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the message field described by the field argument where the message start index is
|
|
||||||
* offset within this binary message.
|
|
||||||
* @param intField with start and end indices (inclusive).
|
|
||||||
* @param offset to the start of the first message index.
|
|
||||||
* @return integer field value.
|
|
||||||
*/
|
|
||||||
public int getInt(IntField intField, int offset)
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
|
|
||||||
{
|
|
||||||
value = Integer.rotateLeft(value, 1);
|
|
||||||
|
|
||||||
if(get(index))
|
|
||||||
{
|
|
||||||
value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the message field described by the field argument where the message start index is
|
|
||||||
* offset within this binary message.
|
|
||||||
* @param fragmentedField with an array of field indices
|
|
||||||
* @param offset to the start of the first message index.
|
|
||||||
* @return integer field value.
|
|
||||||
*/
|
|
||||||
public int getInt(FragmentedIntField fragmentedField, int offset)
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
for(int index: fragmentedField.indices())
|
|
||||||
{
|
|
||||||
value = Integer.rotateLeft(value, 1);
|
|
||||||
|
|
||||||
if(get(index + offset))
|
|
||||||
{
|
|
||||||
value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
||||||
* @param intField to inspect
|
|
||||||
* @param offset to the first bit of the message.
|
|
||||||
* @return true if any of the bits are set, indicating a non-zero value.
|
|
||||||
*/
|
|
||||||
public boolean hasInt(IntField intField, int offset)
|
|
||||||
{
|
|
||||||
return nextSetBit(intField.start() + offset) <= (intField.end() + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
|
||||||
* @param intField to inspect
|
|
||||||
* @param offset to the first bit of the message.
|
|
||||||
* @return true if any of the bits are set, indicating a non-zero value.
|
|
||||||
*/
|
|
||||||
public boolean hasInt(FragmentedIntField fragmentedField, int offset)
|
|
||||||
{
|
|
||||||
for(int index: fragmentedField.indices())
|
|
||||||
{
|
|
||||||
if(get(index + offset))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the long value of the message field described by the field argument.
|
|
||||||
* @param intField with start and end indices (inclusive).
|
|
||||||
* @return integer field value.
|
|
||||||
*/
|
|
||||||
public long getLong(LongField intField)
|
|
||||||
{
|
|
||||||
long value = 0;
|
|
||||||
|
|
||||||
for(int index = intField.start(); index <= intField.end(); index++)
|
|
||||||
{
|
|
||||||
value = Long.rotateLeft(value, 1);
|
|
||||||
|
|
||||||
if(get(index))
|
|
||||||
{
|
|
||||||
value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the long value of the message field described by the field argument where the message start index is
|
|
||||||
* offset within this binary message.
|
|
||||||
* @param intField with start and end indices (inclusive).
|
|
||||||
* @param offset to the start of the first message index.
|
|
||||||
* @return field value.
|
|
||||||
*/
|
|
||||||
public long getLong(LongField intField, int offset)
|
|
||||||
{
|
|
||||||
long value = 0;
|
|
||||||
|
|
||||||
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
|
|
||||||
{
|
|
||||||
value = Long.rotateLeft(value, 1);
|
|
||||||
|
|
||||||
if(get(index))
|
|
||||||
{
|
|
||||||
value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the integer value represented by the bit array
|
* Returns the integer value represented by the bit array
|
||||||
*
|
*
|
||||||
@@ -939,18 +740,6 @@ public class BinaryMessage extends BitSet
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer field formatted as a hex value using zero prefixes to pad the hex character count to fully
|
|
||||||
* represent the size (width) of the field.
|
|
||||||
* @param field to parse as hex
|
|
||||||
* @return hex value.
|
|
||||||
*/
|
|
||||||
public String getHex(IntField field)
|
|
||||||
{
|
|
||||||
int width = Math.ceilDiv(field.width(), 4);
|
|
||||||
return String.format("%0" + width + "X", getInt(field));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less
|
* Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less
|
||||||
* than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in
|
* than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in
|
||||||
@@ -1054,18 +843,13 @@ public class BinaryMessage extends BitSet
|
|||||||
*/
|
*/
|
||||||
public int getTwosComplement(int start, int end)
|
public int getTwosComplement(int start, int end)
|
||||||
{
|
{
|
||||||
if(get(start))
|
BinaryMessage sub = getSubMessage(start, end);
|
||||||
{
|
boolean negative = sub.get(start);
|
||||||
//Negative value - flip and add one
|
sub.flip(0, sub.size());
|
||||||
BinaryMessage fragment = getSubMessage(start, end + 1);
|
|
||||||
fragment.flip(0, fragment.size());
|
int value = sub.getInt(0, sub.size()) + 1;
|
||||||
return -(fragment.getInt(0, fragment.size() - 1) + 1);
|
value *= (negative ? -1 : 1);
|
||||||
}
|
return value;
|
||||||
else
|
|
||||||
{
|
|
||||||
//Positive value - return the contents.
|
|
||||||
return getInt(start, end);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1407,22 +1191,9 @@ public class BinaryMessage extends BitSet
|
|||||||
{
|
{
|
||||||
byte[] bytes = new byte[characterCount];
|
byte[] bytes = new byte[characterCount];
|
||||||
|
|
||||||
int index;
|
|
||||||
for(int x = 0; x < characterCount; x++)
|
for(int x = 0; x < characterCount; x++)
|
||||||
{
|
{
|
||||||
index = x * 7 + offset;
|
bytes[x] = getByte(CHARACTER_7_BIT, x * 7 + offset);
|
||||||
|
|
||||||
if(index == 0)
|
|
||||||
{
|
|
||||||
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
|
||||||
bytes[x] = (byte)((bytes[x] >> 1) & 0x7F);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
index--;
|
|
||||||
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
|
||||||
bytes[x] &= (byte)0x7F; //mask off the high bit since we have to parse it as an 8-bit byte.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new String(bytes);
|
return new String(bytes);
|
||||||
@@ -1465,26 +1236,6 @@ public class BinaryMessage extends BitSet
|
|||||||
return new String(bytes, UTF_8);
|
return new String(bytes, UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse GB2312 Simplified Chinese Characters from the message starting at the offset.
|
|
||||||
* @param offset where to start parsing.
|
|
||||||
* @param characterCount number of characters to parse
|
|
||||||
* @return parsed message.
|
|
||||||
* @throws UnsupportedEncodingException if GB2312 encoding is not supported.
|
|
||||||
*/
|
|
||||||
public String parseGB2312(int offset, int characterCount) throws UnsupportedEncodingException
|
|
||||||
{
|
|
||||||
int length = characterCount * 2;
|
|
||||||
byte[] bytes = new byte[length];
|
|
||||||
|
|
||||||
for(int x = 0; x < length; x++)
|
|
||||||
{
|
|
||||||
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String(bytes, GB2312);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse 16-bit unicode characters from the message starting at the offset.
|
* Parse 16-bit unicode characters from the message starting at the offset.
|
||||||
* @param offset where to start parsing.
|
* @param offset where to start parsing.
|
||||||
@@ -1501,7 +1252,7 @@ public class BinaryMessage extends BitSet
|
|||||||
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new String(bytes, Charset.forName("UTF-16"));
|
return new String(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
* 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.
|
||||||
|
* *
|
||||||
|
* * 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.bits;
|
package io.github.dsheirer.bits;
|
||||||
|
|
||||||
@@ -81,9 +84,9 @@ public class CorrectedBinaryMessage extends BinaryMessage
|
|||||||
/**
|
/**
|
||||||
* Returns a new binary message containing the bits from (inclusive) to end (exclusive).
|
* Returns a new binary message containing the bits from (inclusive) to end (exclusive).
|
||||||
*
|
*
|
||||||
* @param start bit inclusive
|
* @param start bit
|
||||||
* @param end bit exclusive
|
* @param end bit
|
||||||
* @return message with sub message bits
|
* @return message
|
||||||
*/
|
*/
|
||||||
public CorrectedBinaryMessage getSubMessage(int start, int end)
|
public CorrectedBinaryMessage getSubMessage(int start, int end)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.bits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a fragmented or non-contiguous bit field within a binary message.
|
|
||||||
* @param indices for the bits in the field.
|
|
||||||
*/
|
|
||||||
public record FragmentedIntField(int... indices)
|
|
||||||
{
|
|
||||||
public FragmentedIntField
|
|
||||||
{
|
|
||||||
if(indices.length > 32)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Integer field indices size [" + indices.length + "] cannot exceed 32-bits for an integer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method.
|
|
||||||
* @param indices (inclusive)
|
|
||||||
* @return constructed fragmented integer field.
|
|
||||||
*/
|
|
||||||
public static FragmentedIntField of(int... indices)
|
|
||||||
{
|
|
||||||
return new FragmentedIntField(indices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.bits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a contiguous bit field within a binary message.
|
|
||||||
* @param start bit index, inclusive.
|
|
||||||
* @param end bit index, inclusive.
|
|
||||||
*/
|
|
||||||
public record IntField(int start, int end)
|
|
||||||
{
|
|
||||||
public IntField
|
|
||||||
{
|
|
||||||
if(end - start > 32)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Integer field length [" + (end - start) + "] cannot exceed 32-bits for an integer");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(end < start)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Integer field start index must be less than end index");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Width of the field in bit positions
|
|
||||||
* @return width
|
|
||||||
*/
|
|
||||||
public int width()
|
|
||||||
{
|
|
||||||
return end() - start() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @param end index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField range(int start, int end)
|
|
||||||
{
|
|
||||||
return new IntField(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with one four bits of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField length4(int start)
|
|
||||||
{
|
|
||||||
return new IntField(start, (start + 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with one octet of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField length8(int start)
|
|
||||||
{
|
|
||||||
return new IntField(start, (start + 7));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with 12 bits of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField length12(int start)
|
|
||||||
{
|
|
||||||
return new IntField(start, (start + 11));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with two octets of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField length16(int start)
|
|
||||||
{
|
|
||||||
return new IntField(start, (start + 15));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with 20 bits of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField length20(int start)
|
|
||||||
{
|
|
||||||
return new IntField(start, (start + 19));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with two octets of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField length24(int start)
|
|
||||||
{
|
|
||||||
return new IntField(start, (start + 23));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with three octets of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static IntField length32(int start)
|
|
||||||
{
|
|
||||||
return new IntField(start, (start + 31));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* 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.bits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a contiguous bit field within a binary message.
|
|
||||||
* @param start bit index, inclusive.
|
|
||||||
* @param end bit index, inclusive.
|
|
||||||
*/
|
|
||||||
public record LongField(int start, int end)
|
|
||||||
{
|
|
||||||
public LongField
|
|
||||||
{
|
|
||||||
if(end - start > 64)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Long field length [" + (end - start) + "] cannot exceed 64-bits for a long");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(end < start)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Long field start index must be less than end index");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @param end index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField range(int start, int end)
|
|
||||||
{
|
|
||||||
return new LongField(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with one four bits of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField length4(int start)
|
|
||||||
{
|
|
||||||
return new LongField(start, (start + 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with one octet of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField length8(int start)
|
|
||||||
{
|
|
||||||
return new LongField(start, (start + 7));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with 12 bits of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField length12(int start)
|
|
||||||
{
|
|
||||||
return new LongField(start, (start + 11));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with two octets of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField length16(int start)
|
|
||||||
{
|
|
||||||
return new LongField(start, (start + 15));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with 20 bits of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField length20(int start)
|
|
||||||
{
|
|
||||||
return new LongField(start, (start + 19));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with two octets of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField length24(int start)
|
|
||||||
{
|
|
||||||
return new LongField(start, (start + 23));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility constructor method for a field with three octets of length.
|
|
||||||
* @param start index (inclusive)
|
|
||||||
* @return constructed bit field.
|
|
||||||
*/
|
|
||||||
public static LongField length32(int start)
|
|
||||||
{
|
|
||||||
return new LongField(start, (start + 31));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -132,10 +132,12 @@ public enum SyncPattern
|
|||||||
true, true, false, false //1100 0xC
|
true, true, false, false //1100 0xC
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/* Sync (0x158) = 101011000 */
|
/* Sync (0x158) = 1101011000 */
|
||||||
PASSPORT( new boolean[]
|
PASSPORT( new boolean[]
|
||||||
{
|
{
|
||||||
true,false,true,false,true,true,false,false,false
|
true, //0001 0x1
|
||||||
|
false, true, false, true, //0101 0x5
|
||||||
|
true, false, false, false //1000 0x8
|
||||||
} ),
|
} ),
|
||||||
|
|
||||||
LTR_STANDARD_OSW( new boolean[]
|
LTR_STANDARD_OSW( new boolean[]
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -84,7 +84,7 @@ public class DcCorrectionManager
|
|||||||
{
|
{
|
||||||
mDcCalculationsRemaining--;
|
mDcCalculationsRemaining--;
|
||||||
}
|
}
|
||||||
|
//
|
||||||
// mLog.info("DC: " + mDecimalFormat.format(mAverageDc) +
|
// mLog.info("DC: " + mDecimalFormat.format(mAverageDc) +
|
||||||
// " RES:" + mDecimalFormat.format(residualOffsetToRemove) +
|
// " RES:" + mDecimalFormat.format(residualOffsetToRemove) +
|
||||||
// " REM:" + mDcCalculationsRemaining);
|
// " REM:" + mDcCalculationsRemaining);
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,16 +21,21 @@ package io.github.dsheirer.buffer;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Circular sample buffer - allocates a buffer and stores samples in a circular fashion, overwriting older samples with
|
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
||||||
* newly arrived samples. Initially fills buffer with 0-valued (default) samples or with a specified value.
|
* fashion, overwriting older samples with newly arrived samples. Initially
|
||||||
|
* fills buffer with 0-valued samples
|
||||||
|
*
|
||||||
|
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
||||||
*/
|
*/
|
||||||
public class FloatCircularBuffer
|
public class FloatCircularBuffer
|
||||||
{
|
{
|
||||||
private float[] mBuffer;
|
private float[] mBuffer;
|
||||||
private int mBufferPointer = 0;
|
private int mBufferPointer = 0;
|
||||||
|
private float mOldestSample;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
||||||
|
*
|
||||||
* @param size of the buffer
|
* @param size of the buffer
|
||||||
* @param initialFillValue to fill the buffer entries
|
* @param initialFillValue to fill the buffer entries
|
||||||
*/
|
*/
|
||||||
@@ -42,6 +47,7 @@ public class FloatCircularBuffer
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
|
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
|
||||||
|
*
|
||||||
* @param size of the buffer
|
* @param size of the buffer
|
||||||
*/
|
*/
|
||||||
public FloatCircularBuffer(int size)
|
public FloatCircularBuffer(int size)
|
||||||
@@ -49,34 +55,33 @@ public class FloatCircularBuffer
|
|||||||
this(size, 0.0f);
|
this(size, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public float[] getBuffer()
|
||||||
* Resets the delay buffer to an all zeros state
|
|
||||||
*/
|
|
||||||
public void reset(float fillValue)
|
|
||||||
{
|
{
|
||||||
Arrays.fill(mBuffer, fillValue);
|
return mBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts the new value into the buffer and returns the oldest buffer value that it replaced
|
* Gets the current buffer value and overwrites that value position in the buffer with the new value.
|
||||||
*
|
*
|
||||||
* @param newValue to add to the buffer
|
* @param newValue to add to the buffer
|
||||||
* @return oldest value that was overwritten by the new value
|
* @return oldest value that was overwritten by the new value
|
||||||
*/
|
*/
|
||||||
public float get(float newValue)
|
public float getAndPut(float newValue)
|
||||||
{
|
{
|
||||||
float oldestSample = mBuffer[mBufferPointer];
|
mOldestSample = mBuffer[mBufferPointer];
|
||||||
|
put(newValue);
|
||||||
|
return mOldestSample;
|
||||||
|
}
|
||||||
|
|
||||||
mBuffer[mBufferPointer] = newValue;
|
/**
|
||||||
|
* Puts the value into the buffer, updates the pointer and returns the buffer value at the pointer position.
|
||||||
mBufferPointer++;
|
* @param sample to add
|
||||||
|
* @return next pointed sample.
|
||||||
if(mBufferPointer >= mBuffer.length)
|
*/
|
||||||
{
|
public float putAndGet(float sample)
|
||||||
mBufferPointer = 0;
|
{
|
||||||
}
|
put(sample);
|
||||||
|
return mBuffer[mBufferPointer];
|
||||||
return oldestSample;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] getAll()
|
public float[] getAll()
|
||||||
@@ -100,70 +105,13 @@ public class FloatCircularBuffer
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Places the new value into the buffer, overwriting the oldest value
|
* Places the new value into the buffer, overwriting the oldest value
|
||||||
|
*
|
||||||
* @param newValue to add to the buffer
|
* @param newValue to add to the buffer
|
||||||
*/
|
*/
|
||||||
public void put(float newValue)
|
public void put(float newValue)
|
||||||
{
|
{
|
||||||
mBuffer[mBufferPointer] = newValue;
|
mBuffer[mBufferPointer] = newValue;
|
||||||
|
|
||||||
mBufferPointer++;
|
mBufferPointer++;
|
||||||
|
mBufferPointer %= mBuffer.length;
|
||||||
if(mBufferPointer >= mBuffer.length)
|
|
||||||
{
|
|
||||||
mBufferPointer = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the maximum from the values currently in the buffer
|
|
||||||
*/
|
|
||||||
public float max(float referenceValue)
|
|
||||||
{
|
|
||||||
float max = referenceValue;
|
|
||||||
|
|
||||||
for(int x = 0; x < mBuffer.length; x++)
|
|
||||||
{
|
|
||||||
if(mBuffer[x] > max)
|
|
||||||
{
|
|
||||||
max = mBuffer[x];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// System.out.println("Max [" + max + "] from " + Arrays.toString(mBuffer));
|
|
||||||
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the minimum from the values currently in the buffer
|
|
||||||
*/
|
|
||||||
public float min()
|
|
||||||
{
|
|
||||||
float min = 0.0f;
|
|
||||||
|
|
||||||
for(int x = 0; x < mBuffer.length; x++)
|
|
||||||
{
|
|
||||||
if(mBuffer[x] < min)
|
|
||||||
{
|
|
||||||
min = mBuffer[x];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the average/mean of the values contained in this buffer
|
|
||||||
*/
|
|
||||||
public float mean()
|
|
||||||
{
|
|
||||||
double accumulator = 0.0;
|
|
||||||
|
|
||||||
for(float value: mBuffer)
|
|
||||||
{
|
|
||||||
accumulator += value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (float)(accumulator / mBuffer.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ package io.github.dsheirer.buffer;
|
|||||||
import io.github.dsheirer.sample.SampleUtils;
|
import io.github.dsheirer.sample.SampleUtils;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +30,6 @@ import java.util.Iterator;
|
|||||||
public class FloatNativeBuffer extends AbstractNativeBuffer
|
public class FloatNativeBuffer extends AbstractNativeBuffer
|
||||||
{
|
{
|
||||||
private float[] mInterleavedComplexSamples;
|
private float[] mInterleavedComplexSamples;
|
||||||
private static final int BUFFER_SIZE = 2048;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -84,51 +81,48 @@ public class FloatNativeBuffer extends AbstractNativeBuffer
|
|||||||
|
|
||||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||||
{
|
{
|
||||||
private int mBufferPointer = 0;
|
private boolean mEmpty;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
return mBufferPointer < mInterleavedComplexSamples.length;
|
return !mEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComplexSamples next()
|
public ComplexSamples next()
|
||||||
{
|
{
|
||||||
if(!hasNext())
|
if(mEmpty)
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("No more samples");
|
throw new IllegalStateException("No more samples");
|
||||||
}
|
}
|
||||||
|
|
||||||
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
mEmpty = true;
|
||||||
mBufferPointer += BUFFER_SIZE * 2;
|
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
|
||||||
|
|
||||||
return SampleUtils.deinterleave(chunk, getTimestamp());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||||
{
|
{
|
||||||
private int mBufferPointer = 0;
|
private boolean mEmpty;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
return mBufferPointer < mInterleavedComplexSamples.length;
|
return !mEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InterleavedComplexSamples next()
|
public InterleavedComplexSamples next()
|
||||||
{
|
{
|
||||||
if(!hasNext())
|
if(mEmpty)
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("No more samples");
|
throw new IllegalStateException("No more samples");
|
||||||
}
|
}
|
||||||
|
|
||||||
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
mEmpty = true;
|
||||||
mBufferPointer += BUFFER_SIZE * 2;
|
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
|
||||||
|
|
||||||
return new InterleavedComplexSamples(chunk, getTimestamp());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
src/main/java/io/github/dsheirer/buffer/RealCircularBuffer.java
Normal file
124
src/main/java/io/github/dsheirer/buffer/RealCircularBuffer.java
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
package io.github.dsheirer.buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
||||||
|
* fashion, overwriting older samples with newly arrived samples.
|
||||||
|
*
|
||||||
|
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
||||||
|
*/
|
||||||
|
public class RealCircularBuffer
|
||||||
|
{
|
||||||
|
float[] mBuffer;
|
||||||
|
int mBufferPointer = 0;
|
||||||
|
|
||||||
|
public RealCircularBuffer(int size)
|
||||||
|
{
|
||||||
|
mBuffer = new float[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize()
|
||||||
|
{
|
||||||
|
return mBuffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getBuffer()
|
||||||
|
{
|
||||||
|
return mBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the new value into the buffer and returns the oldest buffer value
|
||||||
|
* that it replaced
|
||||||
|
*
|
||||||
|
* @param newValue
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public float putAndGet(float newValue)
|
||||||
|
{
|
||||||
|
float oldestSample = mBuffer[mBufferPointer];
|
||||||
|
|
||||||
|
put(newValue);
|
||||||
|
|
||||||
|
return oldestSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the new value to this buffer.
|
||||||
|
*/
|
||||||
|
public void put(float newValue)
|
||||||
|
{
|
||||||
|
mBuffer[mBufferPointer] = newValue;
|
||||||
|
|
||||||
|
mBufferPointer++;
|
||||||
|
|
||||||
|
if(mBufferPointer >= mBuffer.length)
|
||||||
|
{
|
||||||
|
mBufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sample at the specified index, where index 0 is the oldest sample and index (length - 1) is the
|
||||||
|
* newest sample.
|
||||||
|
* @param index in range of 0 to length-1
|
||||||
|
* @return value at indexed position
|
||||||
|
*/
|
||||||
|
|
||||||
|
public float get(int index)
|
||||||
|
{
|
||||||
|
if(index < mBuffer.length)
|
||||||
|
{
|
||||||
|
int pointer = mBufferPointer + index;
|
||||||
|
|
||||||
|
if(pointer >= mBuffer.length)
|
||||||
|
{
|
||||||
|
pointer -= mBuffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mBuffer[pointer];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArrayIndexOutOfBoundsException("Index [" + index + "] is not valid for the buffer size of [" +
|
||||||
|
mBuffer.length + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of the circular buffer unwrapped where index 0 is the oldest sample and index length-1
|
||||||
|
* is the newest sample.
|
||||||
|
*
|
||||||
|
* @return array of samples
|
||||||
|
*/
|
||||||
|
public float[] get()
|
||||||
|
{
|
||||||
|
float[] samples = new float[mBuffer.length];
|
||||||
|
|
||||||
|
System.arraycopy(mBuffer, mBufferPointer, samples, 0, mBuffer.length - mBufferPointer);
|
||||||
|
|
||||||
|
if(mBufferPointer != 0)
|
||||||
|
{
|
||||||
|
System.arraycopy(mBuffer, 0, samples, mBuffer.length - mBufferPointer, mBufferPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,132 +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.buffer.airspy.hf;
|
|
||||||
|
|
||||||
import io.github.dsheirer.buffer.AbstractNativeBuffer;
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
|
||||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Native buffer implementation for Airspy HF+ & Discovery tuners.
|
|
||||||
*/
|
|
||||||
public class AirspyHfNativeBuffer extends AbstractNativeBuffer
|
|
||||||
{
|
|
||||||
public static final float SCALE = 1.0f / 32768.0f;
|
|
||||||
private short[] mInterleavedSamples;
|
|
||||||
private float mAverageDc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance.
|
|
||||||
* @param timestamp of the first sample of the buffer
|
|
||||||
* @param interleavedSamples pairs of 16-bit complex samples.
|
|
||||||
*/
|
|
||||||
public AirspyHfNativeBuffer(long timestamp, float samplesPerMillisecond, float averageDc, short[] interleavedSamples)
|
|
||||||
{
|
|
||||||
super(timestamp, samplesPerMillisecond);
|
|
||||||
mInterleavedSamples = interleavedSamples;
|
|
||||||
mAverageDc = averageDc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<ComplexSamples> iterator()
|
|
||||||
{
|
|
||||||
return new IteratorComplexSamples();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
|
||||||
{
|
|
||||||
return new IteratorInterleaved();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int sampleCount()
|
|
||||||
{
|
|
||||||
return mInterleavedSamples.length / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scalar implementation of complex samples buffer iterator
|
|
||||||
*/
|
|
||||||
public class IteratorComplexSamples implements Iterator<ComplexSamples>
|
|
||||||
{
|
|
||||||
private boolean mHasNext = true;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext()
|
|
||||||
{
|
|
||||||
return mHasNext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComplexSamples next()
|
|
||||||
{
|
|
||||||
int length = mInterleavedSamples.length / 2;
|
|
||||||
float[] i = new float[length];
|
|
||||||
float[] q = new float[length];
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
for(int x = 0; x < mInterleavedSamples.length; x += 2)
|
|
||||||
{
|
|
||||||
index = x / 2;
|
|
||||||
//Native ordering is in Q, I, Q, I ... reverse it to I, Q, I, Q
|
|
||||||
i[index] = (mInterleavedSamples[x + 1] * SCALE) - mAverageDc;
|
|
||||||
q[index] = (mInterleavedSamples[x] * SCALE) - mAverageDc;
|
|
||||||
}
|
|
||||||
|
|
||||||
mHasNext = false;
|
|
||||||
|
|
||||||
return new ComplexSamples(i, q, getTimestamp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scalar implementation of interleaved sample buffer iterator.
|
|
||||||
*/
|
|
||||||
public class IteratorInterleaved implements Iterator<InterleavedComplexSamples>
|
|
||||||
{
|
|
||||||
private boolean mHasNext = true;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext()
|
|
||||||
{
|
|
||||||
return mHasNext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InterleavedComplexSamples next()
|
|
||||||
{
|
|
||||||
float[] samples = new float[mInterleavedSamples.length];
|
|
||||||
|
|
||||||
for(int x = 0; x < mInterleavedSamples.length; x += 2)
|
|
||||||
{
|
|
||||||
//Native ordering is in Q, I, Q, I ... reverse it to I, Q, I, Q
|
|
||||||
samples[x] = (mInterleavedSamples[x + 1] * SCALE) - mAverageDc;
|
|
||||||
samples[x + 1] = (mInterleavedSamples[x] * SCALE) - mAverageDc;
|
|
||||||
}
|
|
||||||
|
|
||||||
mHasNext = false;
|
|
||||||
|
|
||||||
return new InterleavedComplexSamples(samples, getTimestamp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +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.buffer.airspy.hf;
|
|
||||||
|
|
||||||
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
|
|
||||||
import io.github.dsheirer.buffer.DcCorrectionManager;
|
|
||||||
import io.github.dsheirer.buffer.INativeBuffer;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.ShortBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Airspy HF+ native buffer factory.
|
|
||||||
*
|
|
||||||
* Note: the tuner provides 1024 samples in each transfer where samples are 16-bit interleaved complex.
|
|
||||||
*/
|
|
||||||
public class AirspyHfNativeBufferFactory extends AbstractNativeBufferFactory
|
|
||||||
{
|
|
||||||
private DcCorrectionManager mDcCorrectionManager = new DcCorrectionManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
*/
|
|
||||||
public AirspyHfNativeBufferFactory()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the samples byte buffer into a native buffer and calculates DC offset
|
|
||||||
* @param samples byte array copied from native memory
|
|
||||||
* @param timestamp of the samples
|
|
||||||
* @return constructed native buffer
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
|
|
||||||
{
|
|
||||||
ShortBuffer shortBuffer = samples.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
|
||||||
short[] converted = new short[shortBuffer.capacity()];
|
|
||||||
shortBuffer.get(converted);
|
|
||||||
|
|
||||||
if(mDcCorrectionManager.shouldCalculateDc())
|
|
||||||
{
|
|
||||||
calculateDc(converted);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AirspyHfNativeBuffer(timestamp, getSamplesPerMillisecond(), mDcCorrectionManager.getAverageDc(), converted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
|
|
||||||
* native buffer is used.
|
|
||||||
* @param samples containing DC offset
|
|
||||||
*/
|
|
||||||
private void calculateDc(short[] samples)
|
|
||||||
{
|
|
||||||
float dcAccumulator = 0;
|
|
||||||
|
|
||||||
for(short sample: samples)
|
|
||||||
{
|
|
||||||
dcAccumulator += sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
dcAccumulator /= samples.length;
|
|
||||||
mDcCorrectionManager.adjust(dcAccumulator * AirspyHfNativeBuffer.SCALE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,7 +22,6 @@ package io.github.dsheirer.channel.metadata;
|
|||||||
import io.github.dsheirer.alias.Alias;
|
import io.github.dsheirer.alias.Alias;
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.identifier.Form;
|
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
@@ -36,11 +35,12 @@ import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier
|
|||||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel metadata containing details about the channel configuration, decoder state and current
|
* Channel metadata containing details about the channel configuration, decoder state and current
|
||||||
* set of decoded user identifiers (ie TO and FROM).
|
* set of decoded user identifiers (ie TO and FROM).
|
||||||
@@ -58,7 +58,6 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
|
|||||||
private DecoderTypeConfigurationIdentifier mDecoderTypeConfigurationIdentifier;
|
private DecoderTypeConfigurationIdentifier mDecoderTypeConfigurationIdentifier;
|
||||||
private Identifier mFromIdentifier;
|
private Identifier mFromIdentifier;
|
||||||
private List<Alias> mFromIdentifierAliases;
|
private List<Alias> mFromIdentifierAliases;
|
||||||
private Identifier mTalkerAliasIdentifier;
|
|
||||||
private Identifier mToIdentifier;
|
private Identifier mToIdentifier;
|
||||||
private List<Alias> mToIdentifierAliases;
|
private List<Alias> mToIdentifierAliases;
|
||||||
private Integer mTimeslot;
|
private Integer mTimeslot;
|
||||||
@@ -67,51 +66,17 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
|
|||||||
private AliasModel mAliasModel;
|
private AliasModel mAliasModel;
|
||||||
private AliasList mAliasList;
|
private AliasList mAliasList;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
* @param aliasModel for alias lookups
|
|
||||||
* @param timeslot for this metadata
|
|
||||||
*/
|
|
||||||
public ChannelMetadata(AliasModel aliasModel, Integer timeslot)
|
public ChannelMetadata(AliasModel aliasModel, Integer timeslot)
|
||||||
{
|
{
|
||||||
mAliasModel = aliasModel;
|
mAliasModel = aliasModel;
|
||||||
mTimeslot = timeslot;
|
mTimeslot = timeslot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
* @param aliasModel for alias lookups
|
|
||||||
*/
|
|
||||||
public ChannelMetadata(AliasModel aliasModel)
|
public ChannelMetadata(AliasModel aliasModel)
|
||||||
{
|
{
|
||||||
this(aliasModel, null);
|
this(aliasModel, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a textual description of this channel metadata.
|
|
||||||
*/
|
|
||||||
public String getDescription()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("Channel Metadata Description\n");
|
|
||||||
sb.append("\tTimeslot: ").append(mTimeslot).append("\n");
|
|
||||||
Identifier decoder = getDecoderTypeConfigurationIdentifier();
|
|
||||||
sb.append("\tDecoder: ").append(decoder != null ? decoder : "(null)").append("\n");
|
|
||||||
Identifier state = getChannelStateIdentifier();
|
|
||||||
sb.append("\tState: ").append(state != null ? state : "(null)").append("\n");
|
|
||||||
Identifier system = getSystemConfigurationIdentifier();
|
|
||||||
sb.append("\tSystem: ").append(system != null ? system : "(null)").append("\n");
|
|
||||||
Identifier site = getSiteConfigurationIdentifier();
|
|
||||||
sb.append("\tSite: ").append(site != null ? site : "(null)").append("\n");
|
|
||||||
Identifier channel = getChannelNameConfigurationIdentifier();
|
|
||||||
sb.append("\tChannel: ").append(channel != null ? channel : "(null)").append("\n");
|
|
||||||
Identifier frequency = getFrequencyConfigurationIdentifier();
|
|
||||||
sb.append("\tFrequency: ").append(frequency != null ? frequency : "(null)").append("\n");
|
|
||||||
Identifier logical = getDecoderLogicalChannelNameIdentifier();
|
|
||||||
sb.append("\tLogical Channel: ").append(logical != null ? logical : "(null)").append("\n");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getTimeslot()
|
public Integer getTimeslot()
|
||||||
{
|
{
|
||||||
return mTimeslot;
|
return mTimeslot;
|
||||||
@@ -240,22 +205,6 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
|
|||||||
return mFromIdentifierAliases;
|
return mFromIdentifierAliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional talker alias identifier
|
|
||||||
*/
|
|
||||||
public Identifier getTalkerAliasIdentifier()
|
|
||||||
{
|
|
||||||
return mTalkerAliasIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if we have a non-null talker alias identifier
|
|
||||||
*/
|
|
||||||
public boolean hasTalkerAliasIdentifier()
|
|
||||||
{
|
|
||||||
return mTalkerAliasIdentifier != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current call event TO identifier
|
* Current call event TO identifier
|
||||||
*/
|
*/
|
||||||
@@ -427,22 +376,15 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
|
|||||||
case USER:
|
case USER:
|
||||||
if(identifier.getRole() == Role.FROM)
|
if(identifier.getRole() == Role.FROM)
|
||||||
{
|
{
|
||||||
if(identifier.getForm() == Form.TALKER_ALIAS)
|
mFromIdentifier = update.isAdd() ? identifier : null;
|
||||||
|
|
||||||
|
if(mAliasList != null && mFromIdentifier != null)
|
||||||
{
|
{
|
||||||
mTalkerAliasIdentifier = identifier;
|
mFromIdentifierAliases = mAliasList.getAliases(mFromIdentifier);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mFromIdentifier = update.isAdd() ? identifier : null;
|
mFromIdentifierAliases = Collections.EMPTY_LIST;
|
||||||
|
|
||||||
if(mAliasList != null && mFromIdentifier != null)
|
|
||||||
{
|
|
||||||
mFromIdentifierAliases = mAliasList.getAliases(mFromIdentifier);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mFromIdentifierAliases = Collections.EMPTY_LIST;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastUpdate(ChannelMetadataField.USER_FROM);
|
broadcastUpdate(ChannelMetadataField.USER_FROM);
|
||||||
|
|||||||
@@ -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
|
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
* 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.
|
||||||
|
* *
|
||||||
|
* * 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.channel.metadata;
|
package io.github.dsheirer.channel.metadata;
|
||||||
|
|
||||||
@@ -26,16 +29,16 @@ import io.github.dsheirer.identifier.Identifier;
|
|||||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
|
||||||
|
|
||||||
public class ChannelMetadataModel extends AbstractTableModel implements IChannelMetadataUpdateListener
|
public class ChannelMetadataModel extends AbstractTableModel implements IChannelMetadataUpdateListener
|
||||||
{
|
{
|
||||||
@@ -81,47 +84,6 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a diagnostic description of the channel metadata and metadata-channel map.
|
|
||||||
* @return diagnostic description.
|
|
||||||
*/
|
|
||||||
public String getDiagnosticInformation()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
Map<ChannelMetadata,Channel> mapCopy = new HashMap<>(mMetadataChannelMap);
|
|
||||||
List<ChannelMetadata> metadataCopy = new ArrayList<>(mChannelMetadata);
|
|
||||||
|
|
||||||
sb.append("Channel Metadata Model Diagnostics\n");
|
|
||||||
|
|
||||||
for(ChannelMetadata channelMetadata: metadataCopy)
|
|
||||||
{
|
|
||||||
sb.append("\n--------------- CHANNEL METADATA ITEM --------------------\n\n");
|
|
||||||
if(mapCopy.containsKey(channelMetadata))
|
|
||||||
{
|
|
||||||
Channel channel = mapCopy.get(channelMetadata);
|
|
||||||
sb.append("\tMapped Channel:").append(channel).append("\n");
|
|
||||||
sb.append("\t\tSource Configuration: ").append(channel.getSourceConfiguration()).append("\n");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.append("\tMapped Channel: **Channel Metadata Is Not In the Metadata:Channel Map**\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(channelMetadata.getDescription());
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for orphaned map entries that don't match the current channel metadata list.
|
|
||||||
for(Map.Entry<ChannelMetadata,Channel> entry: mapCopy.entrySet())
|
|
||||||
{
|
|
||||||
if(!metadataCopy.contains(entry.getKey()))
|
|
||||||
{
|
|
||||||
sb.append("\n\tWARNING: orphaned Metadata Channel map entry - channel: " + entry.getValue()).append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
MyEventBus.getGlobalEventBus().unregister(this);
|
MyEventBus.getGlobalEventBus().unregister(this);
|
||||||
@@ -279,8 +241,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
String value = id.getValue().replace(" TS1", "")
|
String value = id.getValue().replace(" TS0", "").replace(" TS1", "");
|
||||||
.replace(" TS2", "");
|
|
||||||
return value + " TS:" + channelMetadata.getTimeslot();
|
return value + " TS:" + channelMetadata.getTimeslot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,14 +38,6 @@ import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
|
|||||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -60,6 +52,14 @@ import javax.swing.SwingConstants;
|
|||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.event.ListSelectionEvent;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
|
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
|
||||||
{
|
{
|
||||||
@@ -294,10 +294,6 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
|||||||
{
|
{
|
||||||
text = EMPTY_VALUE;
|
text = EMPTY_VALUE;
|
||||||
}
|
}
|
||||||
else if(hasAdditionalIdentifier(channelMetadata))
|
|
||||||
{
|
|
||||||
text = text + " " + getAdditionalIdentifier(channelMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
label.setText(text);
|
label.setText(text);
|
||||||
}
|
}
|
||||||
@@ -310,8 +306,6 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract Identifier getIdentifier(ChannelMetadata channelMetadata);
|
public abstract Identifier getIdentifier(ChannelMetadata channelMetadata);
|
||||||
public abstract boolean hasAdditionalIdentifier(ChannelMetadata channelMetadata);
|
|
||||||
public abstract Identifier getAdditionalIdentifier(ChannelMetadata channelMetadata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -329,18 +323,6 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
|||||||
{
|
{
|
||||||
return channelMetadata.getFromIdentifier();
|
return channelMetadata.getFromIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Identifier getAdditionalIdentifier(ChannelMetadata channelMetadata)
|
|
||||||
{
|
|
||||||
return channelMetadata.getTalkerAliasIdentifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasAdditionalIdentifier(ChannelMetadata channelMetadata)
|
|
||||||
{
|
|
||||||
return channelMetadata.hasTalkerAliasIdentifier();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -358,11 +340,6 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
|||||||
{
|
{
|
||||||
return channelMetadata.getToIdentifier();
|
return channelMetadata.getToIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Identifier getAdditionalIdentifier(ChannelMetadata channelMetadata) {return null;}
|
|
||||||
@Override
|
|
||||||
public boolean hasAdditionalIdentifier(ChannelMetadata channelMetadata) {return false;}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ColoredStateCellRenderer extends DefaultTableCellRenderer
|
public class ColoredStateCellRenderer extends DefaultTableCellRenderer
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user