Compare commits

..

3 Commits

Author SHA1 Message Date
Dennis Sheirer
5dc12fd257 1723 Adds database folder as a user directory preference. 2024-01-27 08:32:30 -05:00
Dennis Sheirer
93f7122f57 1723 Adds call event management and deletion by age or directory size, both options in the user preferences dialog. 2024-01-27 08:08:10 -05:00
Dennis Sheirer
af2b80a066 #1723 Initial commit - audio call management feature.
-Implements Spring Boot and spring dependency injection - significant code refactoring.
-Creates database repository with Calls table
-Implements AudioManager to update database and manage audio recordings.
-Calls paging and searching
-Adds audio recording to wave file and places file in the calls database table.
-Audio spectrum view with 64 bins.
-Updates audio playback manager to process the incoming calls queue and support auto, auto-locked, replay, and mute playback modes.
-Adds channel lock/unlock and mute/unmute features.
-Adds column configuration editor to change column order & visibility and adds table state monitoring to save any user customization of the column ordering.
-Adds call table column sort ordering to the saved table state and restores the sort on application launch.
-Adds audio playback channel view options for: Complete, Standard and Minimal.  Updates user preferences Audio | Playback section to allow user to select preferred view style.
2024-01-23 17:21:56 -05:00
2025 changed files with 89688 additions and 139583 deletions

View File

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

View File

@@ -0,0 +1,16 @@
---
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

View File

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

View File

@@ -13,36 +13,35 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Set up JDK 27
uses: actions/setup-java@v6
- name: Set up JDK 20
uses: actions/setup-java@v3
with:
# Must match the JDK version downloaded in build.gradle (jdk_download_base) - jlink requires an exact version match
java-version: '27.0.0'
java-version: '20'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
- name: Build Linux and Mac releases with Gradle
run: ./gradlew runtimeZipOthers -PprojectVersion=nightly
uses: gradle/gradle-build-action@v2
with:
arguments: runtimeZipOthers -PprojectVersion=nightly
- name: Build Windows release with Gradle
run: ./gradlew runtimeZipWindows -PprojectVersion=nightly
uses: gradle/gradle-build-action@v2
with:
arguments: runtimeZipWindows -PprojectVersion=nightly
- name: Create Build Info file
run: |
echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" > build/image/build_info.txt
- name: Add build artifacts to nightly release
uses: softprops/action-gh-release@v3
uses: pyTooling/Actions/releaser@main
with:
tag_name: nightly
prerelease: true
tag: nightly
rm: false
token: ${{ secrets.GITHUB_TOKEN }}
files: |
build/image/*.zip
build/image/build_info.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,130 +0,0 @@
# Publishes a release when a version tag is pushed. The tag has to match
# projectVersion in gradle.properties with a leading 'v': v0.6.2 for a final
# release, or v0.6.2-beta-1 / v0.6.2-alpha-1 for a pre-release.
name: Release
on:
push:
tags: ['v[0-9]+.[0-9]+.[0-9]+*']
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
previous_tag: ${{ steps.previous.outputs.tag }}
steps:
- uses: actions/checkout@v7
with:
# Full history and tags, to find the previous release tag for the release notes
fetch-depth: 0
- id: version
run: |
expected="v$(sed -n 's/^projectVersion=//p' gradle.properties | tr -d '[:space:]')"
if [ "$GITHUB_REF_NAME" != "$expected" ]; then
echo "::error::Tag $GITHUB_REF_NAME does not match projectVersion in gradle.properties ($expected)"
exit 1
fi
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
# Release notes list the PRs since the previous version tag (never 'nightly'). A final release
# starts from the previous final release, so it covers every alpha and beta in between.
- id: previous
run: |
exclude=(--exclude "$GITHUB_REF_NAME")
if [[ "$GITHUB_REF_NAME" != *-* ]]; then
exclude+=(--exclude '*-*')
fi
tag="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "${exclude[@]}" HEAD 2>/dev/null || true)"
echo "Release notes start from: ${tag:-(GitHub default)}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- name: Set up JDK 27
uses: actions/setup-java@v6
with:
# Must match the JDK version downloaded in build.gradle (jdk_download_base) - jlink requires an exact version match
java-version: '27.0.0'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
- name: Build and test
run: ./gradlew clean build --console=plain
# The runtime plugin can't build Windows and the other platforms in the same invocation.
- name: Build Linux and Mac releases
run: ./gradlew runtimeZipOthers --console=plain
- name: Build Windows release
run: ./gradlew runtimeZipWindows --console=plain
- name: Check release zips
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
missing=0
for platform in linux-aarch64 linux-x86_64 osx-aarch64 osx-x86_64 windows-aarch64 windows-x86_64; do
zip="build/image/sdr-trunk-${platform}-v${VERSION}.zip"
if [ ! -f "$zip" ]; then
echo "::error::Missing $zip"
missing=1
fi
done
ls -l build/image/*.zip
exit $missing
- uses: actions/upload-artifact@v7
with:
name: release-zips
path: build/image/*.zip
if-no-files-found: error
retention-days: 7
publish:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v8
with:
name: release-zips
path: zips
# Publishing is split into steps that can all be repeated, so a failed
# upload or a re-run finishes the draft left behind rather than failing on
# it: gh retries uploads, and a retry of an upload that had in fact
# succeeded fails with "ReleaseAsset.name already exists" unless clobbering.
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
VERSION: ${{ needs.build.outputs.version }}
PREVIOUS_TAG: ${{ needs.build.outputs.previous_tag }}
run: |
(cd zips && sha256sum *.zip > SHA256SUMS)
prerelease=()
if [[ "$GITHUB_REF_NAME" == *-* ]]; then
prerelease=(--prerelease)
fi
notes_start=()
if [ -n "$PREVIOUS_TAG" ]; then
notes_start=(--notes-start-tag "$PREVIOUS_TAG")
fi
if gh release view "$GITHUB_REF_NAME" --json isDraft --jq .isDraft 2>/dev/null | grep -qx false; then
echo "::error::Release $GITHUB_REF_NAME is already published"
exit 1
elif gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
echo "Reusing the draft release $GITHUB_REF_NAME"
else
gh release create "$GITHUB_REF_NAME" --draft "${prerelease[@]}" \
--verify-tag --generate-notes "${notes_start[@]}" --title "Version $VERSION"
fi
gh release upload "$GITHUB_REF_NAME" zips/* --clobber
gh release edit "$GITHUB_REF_NAME" --draft=false

View File

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

45
.idea/misc.xml generated
View File

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

View File

@@ -1,24 +1,6 @@
![Gradle Build](https://github.com/dsheirer/sdrtrunk/actions/workflows/gradle.yml/badge.svg)
![Nightly Release](https://github.com/dsheirer/sdrtrunk/actions/workflows/nightly.yml/badge.svg)
# MacOS Tahoe 26.1 Users - Attention:
Changes to USB support in Tahoe version 26.x cause sdrtrunk to fail to launch. Do the following to install the latest libusb and create a symbolic link and then use the nightly build which includes an updated usb4java native library for Tahoe with ARM processor. There may still be issue(s) with MacOS accessing your USB SDR tuners.
```
brew install libusb --HEAD
cd /opt
sudo mkdir local
cd local
sudo mkdir lib
```
Next, find where brew installed the libusb library, for example: ```/opt/homebrew/Cellar/libusb/HEAD-9ceaa52/lib/libusb-1.0.0.dylib``` Note: the folder "HEAD-9ceaa52" is the version stamp for HEAD when you installed from it.
Finally, create a symbolic link from the installed library to the place where usb4java is expecting to find libusb (/opt/local/lib/libusb-1.0.0.dylib)
```
sudo ln -s /opt/homebrew/Cellar/libusb/HEAD-9ceaa52/lib/libusb-1.0.0.dylib /opt/local/lib/libusb-1.0.0.dylib
```
# sdrtrunk
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
@@ -46,31 +28,6 @@ playlist(s) before you use the nightly builds.** Note: the nightly release is u
committed to the code base, so it's not really 'nightly' as much as it is 'current'.
## Minimum System Requirements
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac (64-bit, 12.x or higher)
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
* **CPU:** 4-core
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
## Creating a Release (Maintainers)
Releases are built and published automatically by the [Release](.github/workflows/release.yml) GitHub workflow
whenever a version tag is pushed to the repository.
1. Update `projectVersion` in `gradle.properties` to the new version, following the naming convention described in
that file (e.g. `0.6.2-alpha-1`, `0.6.2-beta-1` or `0.6.2`).
2. Commit and push the change to `master`.
3. Create and push a tag that matches the version with a leading `v`:
```
git tag v0.6.2-beta-1
git push origin v0.6.2-beta-1
```
The workflow then:
* Verifies that the tag matches `projectVersion` in `gradle.properties` and fails if it does not.
* Builds and tests the code, then creates the release zips for Linux, Mac and Windows (x86-64 and aarch64).
* Creates a GitHub release titled `Version <version>` with auto-generated release notes (a "What's Changed" list of
the PRs merged since the previous version tag), and attaches the six zip
files plus a `SHA256SUMS` checksum file.
* Marks tags containing a dash (alpha and beta versions) as pre-releases. Tags without a dash become full releases.
If the workflow fails part way through, fix the problem and re-run it from the Actions tab. It resumes from the
draft release left behind. It won't modify a release that has already been published. To rebuild a published
release, delete the release and the tag on GitHub, then push the tag again.

View File

@@ -0,0 +1,36 @@
Auto-generating Java code from SDRPlay API using jextract.
Note: the api headers in the 3.11 folder are equivalent to the 3.08 headers. The only
difference from the 3.07 headers is the addition of the boolean 'valid' flag to DeviceT structure.
1. Download JDK19 compatible build of jextract JDK: https://jdk.java.net/jextract/
2. If generating on windows platform, modify the sdrplay_api.h to remove the windows
pieces of the file. Specifically:
2a. Comment out these lines but leave the *HANDLE typedef:
//#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
2b. Remove the '_SDRPLAY_DLL_QUALIFIER' from in front of the method declarations
2c. Change the 3.11 version string to 3.08 either in the header file, or in the generated java class
3. Open command prompt to the root source code where the generated files should be placed:
..\SDRtrunk\sdrplay-api\src\main\java
4. Generate the code:
c:\Users\Denny\Downloads\jdk-19-ea2-panama\bin\jextract "C:\Users\Denny\git\sdrtrunk\sdrplay-api\api\v3_11\sdrplay_api.h" -l libsdrplay_api --source
5. Do this for all versions of the API.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,230 @@
#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.11)
// 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_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, double newSampleRate);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
import java.text.SimpleDateFormat
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
* 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
@@ -17,18 +19,12 @@
* ****************************************************************************
*/
import org.beryx.runtime.RuntimeZipTask
import org.gradle.internal.jvm.Jvm
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import java.text.SimpleDateFormat
/**
* Instructions for building/compiling the sdrtrunk application.
*
* Prerequisites:
* Install and configure an OpenJDK version 27+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
* - Optional: install and configure Gradle 9.8+
* Install and configure an OpenJDK version 20 that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
* - Optional: install and configure Gradle 8.2+
*
* Scenario 1: run the application via gradle command line from the source code root directory:
* command: ./gradlew run
@@ -49,43 +45,29 @@ plugins {
id 'application'
id 'java'
id 'idea'
id 'org.beryx.runtime' version '2.0.1'
id 'org.beryx.runtime' version '1.12.7'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
}
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
}
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
version = project.findProperty('projectVersion')
//Java 27 is required for this version of the Project Panama preview/incubator feature
//Java 20 is required for this version of the Project Panama preview/incubator feature
java {
toolchain {
languageVersion = JavaLanguageVersion.of(27)
vendor = JvmVendorSpec.BELLSOFT
languageVersion = JavaLanguageVersion.of(20)
}
}
/**
* Verifies that the selected toolchain JDK includes the JavaFX modules (e.g. Bellsoft Liberica 'Full JDK').
*
* Gradle's toolchain spec can only select by language version, vendor and implementation, so it can't distinguish
* between the Liberica JDK packages that do and don't bundle JavaFX. This task fails the build with a clear message
* instead of allowing compilation to fail with missing javafx packages.
*/
tasks.register('verifyToolchainJavaFx') {
def jdkHome = javaToolchains.launcherFor(java.toolchain).map { it.metadata.installationPath.asFile }
doLast {
def release = new File(jdkHome.get(), 'release')
if(!release.exists() || !release.text.contains('javafx.base')) {
throw new GradleException("The JDK at [${jdkHome.get()}] does not include the JavaFX modules. Install " +
"the Bellsoft Liberica JDK 27 'Full' package (includes LibericaFX), or use " +
"-Porg.gradle.java.installations.paths=<jdk path> to specify the JDK that Gradle should use.")
}
}
}
tasks.named('compileJava') {
dependsOn 'verifyToolchainJavaFx'
sourceSets {
main.java.srcDirs 'src/main'
test.java.srcDirs 'src/test'
}
test {
@@ -95,66 +77,66 @@ test {
dependencies {
// JUnit Tests
testImplementation(platform("org.junit:junit-bom:6.0.1"))
testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.1'
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.0')
testImplementation 'org.mockito:mockito-core:4.8.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
//Jitpack imports
implementation 'com.github.dnault:libresample4j:master'
implementation 'com.github.DSheirer.jmbe:api:v2.1.0'
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
//mavenCentral/jcenter imports
implementation 'ch.qos.logback:logback-classic:1.6.3'
implementation 'ch.qos.logback:logback-core:1.5.38'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.22.4'
implementation 'com.fazecast:jSerialComm:2.11.4'
implementation 'com.formdev:flatlaf:3.7.2'
implementation 'com.formdev:flatlaf-intellij-themes:3.7.2'
implementation 'ch.qos.logback:logback-classic:1.4.5'
implementation 'ch.qos.logback:logback-core:1.4.5'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
implementation 'com.fazecast:jSerialComm:2.9.3'
implementation 'com.github.jiconfont:jiconfont-elusive:2.0.3'
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
implementation 'com.github.wendykierp:JTransforms:3.2'
implementation 'com.google.code.gson:gson:2.14.0'
implementation 'com.google.guava:guava:33.6.0-jre'
implementation 'com.github.wendykierp:JTransforms:3.1'
implementation 'com.google.code.gson:gson:2.10'
implementation 'com.google.guava:guava:31.1-jre'
implementation 'com.jidesoft:jide-oss:3.6.18'
implementation 'com.miglayout:miglayout-swing:11.4.3'
implementation 'com.miglayout:miglayout-swing:11.0'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation 'commons-io:commons-io:2.22.0'
implementation 'commons-io:commons-io:2.11.0'
implementation 'eu.hansolo:charts:1.0.5'
implementation 'io.github.dsheirer:jairspy:1.0.12.1' //Airspy tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jairspyhf:1.8.1.1' //Airspy HF+ tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jbladerf:2.6.0.1' //bladeRF tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jhackrf:2026.01.3.1' //HackRF tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jlibusb:1.0.30.1' //libusb and USB hotplug for the tuner libraries - GitHub release; not pulled in transitively
implementation 'io.github.dsheirer:jhydrasdr:1.1.4.1' //HydraSDR tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jlimesdr:25.1.0.2' //LimeSDR tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jrtl:1.4.0.2' //RTL-SDR tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP tuners - GitHub release, see settings.gradle
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
implementation 'javax.usb:usb-api:1.0.2'
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
implementation 'org.apache.commons:commons-compress:1.28.0'
implementation 'org.apache.commons:commons-lang3:3.20.0'
implementation 'org.apache.commons:commons-compress:1.21'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-csv:1.14.1'
implementation 'org.apache.mina:mina-core:2.2.9'
implementation 'org.apache.mina:mina-http:2.2.9'
implementation 'org.controlsfx:controlsfx:11.2.3'
implementation 'org.apache.commons:commons-csv:1.9.0'
implementation 'org.apache.derby:derby'
implementation 'org.apache.derby:derbytools'
implementation 'org.apache.mina:mina-core:2.2.1'
implementation 'org.apache.mina:mina-http:2.2.1'
implementation 'org.controlsfx:controlsfx:11.1.2'
implementation 'org.rauschig:jarchivelib:1.2.0'
implementation 'org.slf4j:slf4j-api:2.0.17'
implementation 'org.slf4j:slf4j-api:2.0.5'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.usb4java:libusb4java:1.3.0'
implementation 'org.usb4java:usb4java:1.3.0'
implementation 'org.usb4java:usb4java-javax:1.3.0'
implementation 'pl.edu.icm:JLargeArrays:1.6'
}
def os = DefaultNativePlatform.currentOperatingSystem
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 JDK19 panama and vector API ... until it moves out of incubation or preview
*/
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("--enable-preview") //Project Panama Vector API is still in incubator
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Project Panama Vector API is still in incubator
tasks.withType(JavaCompile) {
options.compilerArgs.add("--enable-preview") //Panama Foreign Function/Memory is preview in JDK 19
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
}
/**
@@ -163,25 +145,23 @@ tasks.withType(JavaCompile).configureEach {
def jvmArgsWindows =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', //windows users for JIDE
'--add-modules=jdk.incubator.vector', //Project Panama vector API is still in incubator
'--enable-preview', //Project Panama Vector API is still in incubator
'--enable-native-access=ALL-UNNAMED', //Suppress unsafe warnings for FFM API Foreign Memory calls
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
'--sun-misc-unsafe-memory-access=allow'] //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
'--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',
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //Allows Project Panama Foreign Function access to system library
/**
* JVM Arguments for running the application on Linux.
*/
def jvmArgsLinux =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Project Panama vector API is still in incubator
'--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
'--sun-misc-unsafe-memory-access=allow', //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
'--enable-native-access=ALL-UNNAMED'] //Suppress warnings for FFM API native calls (tuner libraries)
'--enable-native-access=ALL-UNNAMED',
'-Djava.library.path=/usr/local/lib'] //Allows Project Panama Foreign Function access to system library
application {
mainClass = "io.github.dsheirer.gui.SDRTrunk"
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
if(os.isWindows()) {
applicationDefaultJvmArgs = jvmArgsWindows
@@ -191,15 +171,6 @@ application {
}
}
test {
if(os.isWindows()) {
jvmArgs = jvmArgsWindows
}
else {
jvmArgs = jvmArgsLinux
}
}
jar {
manifest {
attributes (
@@ -220,11 +191,6 @@ idea {
}
}
javadoc {
options.addStringOption('-add-modules', 'jdk.incubator.vector')
options.addStringOption('Xdoclint:none', '-quiet')
}
/**
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
*/
@@ -233,31 +199,28 @@ String targetLinuxX86_64 = 'linux-x86_64-v' + version
String targetOsxAarch64 = 'osx-aarch64-v' + version
String targetOsxX86_64 = 'osx-x86_64-v' + version
String targetWindowsX86_64 = 'windows-x86_64-v' + version
String targetWindowsAarch64 = 'windows-aarch64-v' + version
/**
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
* author's development environment, but can be changed to your environment if you want to use this build pattern.
*/
String jdk_base = '/home/denny/java_jdks/'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-25.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-25.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-25.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-25.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-25.0.1-full'
String jdk_windows_aarch64 = jdk_base + 'windows-arm64/jdk-25.0.1-full'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-20.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-20.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-20.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-20.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-20.0.1-full'
/**
* Download URLs to download the JDK as part of the gradle build packaging process
*/
def jdk_download_base = "https://download.bell-sw.com/java/27+36/bellsoft-jdk27+36-"
def jdk_download_base = "https://download.bell-sw.com/java/20.0.1+10/bellsoft-jdk20.0.1+10-"
def jdk_download_suffix = "-full.tar.gz"
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
def jdk_download_osx_x86_64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
def jdk_download_osx_aarch64 = jdk_download_base + "macos-aarch64" + jdk_download_suffix
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
def jdk_download_windows_aarch64 = jdk_download_base + "windows-aarch64-full.zip"
/**
* Configures the runtime zip task with additional options/settings.
@@ -265,17 +228,16 @@ def jdk_download_windows_aarch64 = jdk_download_base + "windows-aarch64-full.zip
* @param jvmArgs to use (windows or linux)
* @return configured task.
*/
def configure(RuntimeZipTask rt, List<String> jvmArgs) {
def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
//Use the correct set of JVM arguments
rt.extension.launcherData.get().jvmArgs = jvmArgs
//Additional modules to include with auto-detected modules.
//jdk.charsets - NXDN BIG5 encoding support
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
//jdk.accessibility is used with assistive technologies like screen readers
//java.management for JVM resource monitoring
rt.extension.addModules('jdk.charsets', 'jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management')
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management', 'java.instrument')
//Use auto-detected modules and 'add' any specified modules.
rt.extension.additive.set(true)
@@ -284,8 +246,7 @@ def configure(RuntimeZipTask rt, List<String> jvmArgs) {
rt.extension.addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
// rt.extension.imageDir=layout.buildDirectory.dir('image')
rt.extension.imageZip.set(file(layout.buildDirectory.dir('image').get().file('sdr-trunk.zip')))
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
}
/**
@@ -297,35 +258,30 @@ def configure(RuntimeZipTask rt, List<String> jvmArgs) {
* Usage: gradlew runtimeZipCurrent (windows - using gradlew)
* Usage: ./gradlew runtimeZipCurrent (linux - using gradlew)
*/
tasks.register('runtimeZipCurrent', RuntimeZipTask) {rt ->
tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
def arch = System.properties.getProperty("os.arch")
def javaHome = Jvm.current().getJavaHome().toString()
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
if(os.isWindows()) {
if(arch == "amd64") {
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
}
else if(arch == "aarch64") {
rt.extension.targetPlatform(targetWindowsAarch64, javaHome)
}
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
configure(rt, jvmArgsWindows)
}
else if(os.isLinux())
{
if(arch == "amd64") {
if(arch.equals("amd64")) {
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
}
else if(arch == "aarch64") {
else if(arch.equals("aarch64")) {
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
}
else if(os.isMacOsX())
{
if(arch == "amd64") {
if(arch.equals("amd64")) {
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
}
else if(arch == "aarch64") {
else if(arch.equals("aarch64")) {
rt.extension.targetPlatform(targetOsxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
@@ -342,17 +298,7 @@ tasks.register('runtimeZipCurrent', RuntimeZipTask) {rt ->
* Usage: gradlew runtimeZipWindows (windows - using gradlew)
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
*/
tasks.register('runtimeZipWindows', RuntimeZipTask) {rt ->
//Windows aarch64
if(file(jdk_windows_aarch64).exists()) {
rt.extension.targetPlatform(targetWindowsAarch64, jdk_windows_aarch64, [])
}
else {
rt.extension.targetPlatform(targetWindowsAarch64) {
jdkHome = jdkDownload(jdk_download_windows_aarch64)
}
}
//Windows x86-64
tasks.register('runtimeZipWindows', org.beryx.runtime.RuntimeZipTask) {rt ->
if(file(jdk_windows_x86_64).exists()) {
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
}
@@ -374,7 +320,7 @@ tasks.register('runtimeZipWindows', RuntimeZipTask) {rt ->
* Usage: gradlew runtimeZipOthers (windows - using gradlew)
* Usage: ./gradlew runtimeZipOthers (linux - using gradlew)
*/
tasks.register('runtimeZipOthers', RuntimeZipTask) {rt ->
tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
//Linux aarch64
if(file(jdk_linux_aarch64).exists()) {
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
@@ -412,5 +358,5 @@ tasks.register('runtimeZipOthers', RuntimeZipTask) {rt ->
}
}
configure(rt, jvmArgsLinux)
configure(rt, jvmArgsLinux);
}

View File

@@ -1,6 +1,6 @@
#
# ******************************************************************************
# Copyright (C) 2014-2026 Dennis Sheirer
# 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
@@ -28,4 +28,4 @@
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
projectVersion=1.0.0-beta-1
projectVersion=0.7.0-alpha-1

Binary file not shown.

View File

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

65
gradlew vendored
View File

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

58
gradlew.bat vendored
View File

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

View File

@@ -1,86 +1 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
rootProject.name = 'sdr-trunk'
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
//jAirspy is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jAirspy/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jairspy' }
}
//jAirspyHF is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jAirspyHF/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jairspyhf' }
}
//jRTL is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jRTL/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jrtl' }
}
//jlibusb is published as a GitHub release, with libusb for all platforms in the jar. Tuner libraries built on
//jlibusb (jAirspy, jAirspyHF, jBladeRF, jHackRF, jHydraSDR, jLimeSDR, jRTL) load their native library next to its libusb, so the process shares one copy.
ivy {
url = 'https://github.com/DSheirer/jlibusb/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jlibusb' }
}
//jHackRF is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jHackRF/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jhackrf' }
}
//jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jHydraSDR/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jhydrasdr' }
}
//jLimeSDR is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jLimeSDR/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jlimesdr' }
}
//jsdrplay is published as a GitHub release. The jar does not contain the SDRplay API native library, which
//users install separately from sdrplay.com
ivy {
url = 'https://github.com/DSheirer/jsdrplay/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jsdrplay' }
}
//jBladeRF is published as a GitHub release, with the native libraries for all platforms in the jar
ivy {
url = 'https://github.com/DSheirer/jBladeRF/releases/download'
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
metadataSources { artifact() }
content { includeModule 'io.github.dsheirer', 'jbladerf' }
}
}
}
rootProject.name = 'sdr-trunk'

View File

@@ -1,81 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package com.sun.java.swing.plaf.windows;
import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
/**
* No-op stub for the JDK's Windows look-and-feel.
*
* <p>JIDE 3.6.18's {@code LookAndFeelFactory} contains compile-time
* {@code instanceof com.sun.java.swing.plaf.windows.WindowsLookAndFeel} checks throughout its
* dispatch logic. On Linux and macOS JDK builds that class is genuinely absent from
* {@code java.desktop}, so the JVM throws {@link NoClassDefFoundError} the first time JIDE's
* dispatch code executes the {@code instanceof} bytecode. That happens during
* {@link com.jidesoft.swing.JideSplitPane#updateUI()} on every LAF change.
*
* <p>This stub lives in the classpath so the class reference resolves on non-Windows JDKs.
* On Windows JDK builds the real {@code WindowsLookAndFeel} is loaded from the
* {@code java.desktop} module via the boot class loader and shadows this class.
*
* <p>The stub reports itself as not native and not supported - JIDE never instantiates this
* class, it only uses it for {@code instanceof} comparisons against the active LAF. Since
* sdrtrunk never installs this LAF, the {@code instanceof} check always returns false and
* JIDE proceeds to its next branch.
*/
public class WindowsLookAndFeel extends LookAndFeel
{
@Override
public String getName()
{
return "Windows (stub)";
}
@Override
public String getID()
{
return "WindowsStub";
}
@Override
public String getDescription()
{
return "Compatibility stub for JIDE's instanceof checks on non-Windows JDKs.";
}
@Override
public boolean isNativeLookAndFeel()
{
return false;
}
@Override
public boolean isSupportedLookAndFeel()
{
return false;
}
@Override
public UIDefaults getDefaults()
{
return new UIDefaults();
}
}

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias;
@@ -27,22 +30,13 @@ import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
@@ -52,6 +46,13 @@ import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* Alias provides an aliasing (e.g. name, color, etc) container that is linked to multiple alias identifiers and
* provides a corresponding set of actions related to the alias.
@@ -73,7 +74,6 @@ public class Alias
private StringProperty mName = new SimpleStringProperty();
private ObservableList<AliasID> mAliasIDs = FXCollections.observableArrayList();
private ObservableList<AliasAction> mAliasActions = FXCollections.observableArrayList();
private ObjectProperty<StreamAsTalkgroup> mStreamTalkgroupAlias = new SimpleObjectProperty<>();
/**
* Constructs an instance and sets the specified name.
@@ -180,12 +180,6 @@ public class Alias
return mColor;
}
@JsonIgnore
public ObjectProperty streamTalkgroupAliasProperty()
{
return mStreamTalkgroupAlias;
}
/**
* Icon name property
*/
@@ -235,24 +229,6 @@ public class Alias
mName.set(name);
}
/**
* Alias name
*/
@JacksonXmlProperty(isAttribute = true, localName = "stream_talkgroup_alias")
public StreamAsTalkgroup getStreamTalkgroupAlias()
{
return mStreamTalkgroupAlias.get();
}
/**
* Sets the stream as talkgroup value that will be used in the TO field for streamed audio calls.
* @param streamTalkgroupAlias with a talkgroup value.
*/
public void setStreamTalkgroupAlias(StreamAsTalkgroup streamTalkgroupAlias)
{
mStreamTalkgroupAlias.set(streamTalkgroupAlias);
}
/**
* Alias list name for the alias list that this alias belongs to
*/
@@ -734,7 +710,6 @@ public class Alias
{
return (Alias a) -> new Observable[] {a.recordableProperty(), a.streamableProperty(), a.colorProperty(),
a.aliasListNameProperty(), a.groupProperty(), a.iconNameProperty(), a.nameProperty(), a.aliasIds(),
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty(), a.priorityProperty(),
a.streamTalkgroupAliasProperty()};
a.aliasActions(), a.nonAudioIdentifierCountProperty(), a.overlapProperty()};
}
}

View File

@@ -30,16 +30,12 @@ import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadio;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadioRange;
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
@@ -83,31 +79,6 @@ public class AliasFactory
Min copyMin = new Min();
copyMin.setMin(originalMin.getMin());
return copyMin;
case NXDN_TYPE_D_RADIO_ID:
NXDNTypeDRadio originalTypeDRadio = (NXDNTypeDRadio)id;
NXDNTypeDRadio copyTypeDRadio = new NXDNTypeDRadio(originalTypeDRadio.getHomeRepeater(),
originalTypeDRadio.getIdentifier());
copyTypeDRadio.setOverlap(originalTypeDRadio.overlapProperty().get());
return copyTypeDRadio;
case NXDN_TYPE_D_RADIO_ID_RANGE:
NXDNTypeDRadioRange originalTypeDRadioRange = (NXDNTypeDRadioRange)id;
NXDNTypeDRadioRange copyTypeDRadioRange = new NXDNTypeDRadioRange(originalTypeDRadioRange.getHomeRepeater(),
originalTypeDRadioRange.getMinIdentifier(), originalTypeDRadioRange.getMaxIdentifier());
copyTypeDRadioRange.setOverlap(originalTypeDRadioRange.overlapProperty().get());
return copyTypeDRadioRange;
case NXDN_TYPE_D_TALKGROUP:
NXDNTypeDTalkgroup originalTypeDTalkgroup = (NXDNTypeDTalkgroup)id;
NXDNTypeDTalkgroup copyTypeDTalkgroup = new NXDNTypeDTalkgroup(originalTypeDTalkgroup.getHomeRepeater(),
originalTypeDTalkgroup.getIdentifier());
copyTypeDTalkgroup.setOverlap(originalTypeDTalkgroup.overlapProperty().get());
return copyTypeDTalkgroup;
case NXDN_TYPE_D_TALKGROUP_RANGE:
NXDNTypeDTalkgroupRange originalTypeDTalkgroupRange = (NXDNTypeDTalkgroupRange)id;
NXDNTypeDTalkgroupRange copyTypeDTalkgroupRange = new NXDNTypeDTalkgroupRange(
originalTypeDTalkgroupRange.getHomeRepeater(), originalTypeDTalkgroupRange.getMinIdentifier(),
originalTypeDTalkgroupRange.getMaxIdentifier());
copyTypeDTalkgroupRange.setOverlap(originalTypeDTalkgroupRange.overlapProperty().get());
return copyTypeDTalkgroupRange;
case P25_FULLY_QUALIFIED_RADIO_ID:
P25FullyQualifiedRadio originalP25 = (P25FullyQualifiedRadio) id;
P25FullyQualifiedRadio copyP25 = new P25FullyQualifiedRadio(originalP25.getWacn(),

View File

@@ -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
* it under the terms of the GNU General Public License as published by
@@ -47,7 +47,7 @@ import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.module.decode.squelch.dcs.DCSCode;
import io.github.dsheirer.module.decode.dcs.DCSCode;
import io.github.dsheirer.protocol.Protocol;
import java.util.ArrayList;
import java.util.Collection;
@@ -134,7 +134,6 @@ public class AliasList
switch(id.getType())
{
case TALKGROUP:
case NXDN_TYPE_D_TALKGROUP:
Talkgroup talkgroup = (Talkgroup)id;
TalkgroupAliasList talkgroupAliasList = mTalkgroupProtocolMap.get(talkgroup.getProtocol());
@@ -148,7 +147,6 @@ public class AliasList
talkgroupAliasList.add(talkgroup, alias);
break;
case TALKGROUP_RANGE:
case NXDN_TYPE_D_TALKGROUP_RANGE:
TalkgroupRange talkgroupRange = (TalkgroupRange)id;
TalkgroupAliasList talkgroupRangeAliasList = mTalkgroupProtocolMap.get(talkgroupRange.getProtocol());
@@ -188,7 +186,6 @@ public class AliasList
p25TalkgroupAliasList.add(qualifiedTalkgroup, alias);
break;
case RADIO_ID:
case NXDN_TYPE_D_RADIO_ID:
Radio radio = (Radio)id;
RadioAliasList radioAliasList = mRadioProtocolMap.get(radio.getProtocol());
@@ -202,7 +199,6 @@ public class AliasList
radioAliasList.add(radio, alias);
break;
case RADIO_ID_RANGE:
case NXDN_TYPE_D_RADIO_ID_RANGE:
RadioRange radioRange = (RadioRange)id;
RadioAliasList radioRangeAliasList = mRadioProtocolMap.get(radioRange.getProtocol());
@@ -667,13 +663,11 @@ public class AliasList
public Alias getAlias(TalkgroupIdentifier identifier)
{
//Attempt to do a fully qualified identifier match only
if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti)
{
return mFullyQualifiedTalkgroupAliasMap.get(fqti.getFullyQualifiedTalkgroupAddress());
return mFullyQualifiedTalkgroupAliasMap.get(fqti.toString());
}
//Attempt to match the talkgroup value
int value = identifier.getValue();
Alias mapValue = mTalkgroupAliasMap.get(value);
@@ -682,7 +676,6 @@ public class AliasList
return mapValue;
}
//Alternatively, match the talkgroup to any talkgroup ranges
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
{
if(entry.getKey().contains(value))
@@ -787,13 +780,11 @@ public class AliasList
public Alias getAlias(RadioIdentifier identifier)
{
//Match fully qualified identifier only.
if(identifier instanceof FullyQualifiedRadioIdentifier fqri)
{
return mFullyQualifiedRadioAliasMap.get(fqri.getFullyQualifiedRadioAddress());
return mFullyQualifiedRadioAliasMap.get(fqri.toString());
}
//Attempt to match against the radio identifier
int value = identifier.getValue();
Alias mapValue = mRadioAliasMap.get(value);
@@ -802,7 +793,6 @@ public class AliasList
return mapValue;
}
//Alternatively, attempt to match the radio address against any radio ranges.
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
{
if(entry.getKey().contains(value))

View File

@@ -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
* it under the terms of the GNU General Public License as published by
@@ -33,12 +33,14 @@ import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 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
* identifiers produced by channel decoder(s).
*/
@Component("aliasModel")
public class AliasModel
{
private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class);
@@ -63,69 +65,6 @@ public class AliasModel
return mAliasListNames;
}
/**
* Clears and reloads the list of alias list names from the current set of aliases.
*/
public void refreshAliasListNames()
{
mAliasListNames.clear();
for(Alias alias : mAliases)
{
String aliasListName = alias.getAliasListName();
if(aliasListName != null && !aliasListName.isEmpty() && !mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
}
/**
* Renames the alias list across the set of aliases.
* @param oldName currently used by the alias
* @param newName to apply to the alias
*/
public void renameAliasList(String oldName, String newName)
{
if(oldName == null || oldName.isEmpty() || newName == null || newName.isEmpty())
{
return;
}
mAliases.stream().filter(alias -> alias.getAliasListName().equals(oldName)).forEach(alias -> alias.setAliasListName(newName));
}
/**
* Deletes any aliases that have the alias list name
* @param aliasListName to delete
*/
public void deleteAliasList(String aliasListName)
{
if(aliasListName == null || aliasListName.isEmpty())
{
return;
}
mAliases.removeIf(alias -> alias.getAliasListName().equals(aliasListName));
mAliasListMap.remove(aliasListName);
}
/**
* Adds the list of alias list names to the currently managed set of alias list names.
* @param aliasListNames to add
*/
public void addAliasListNames(List<String> aliasListNames)
{
for(String aliasListName : aliasListNames)
{
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
}
}
}
/**
* Unmodifiable list of all aliases currently in the model
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -24,19 +24,19 @@ import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.audio.AudioFormats;
import io.github.dsheirer.gui.preference.playback.ToneFrequency;
import io.github.dsheirer.gui.preference.playback.ToneUtil;
import io.github.dsheirer.gui.preference.playback.ToneVolume;
import io.github.dsheirer.gui.preference.audio.playback.ToneFrequency;
import io.github.dsheirer.gui.preference.audio.playback.ToneUtil;
import io.github.dsheirer.gui.preference.audio.playback.ToneVolume;
import io.github.dsheirer.message.IMessage;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
public class BeepAction extends RecurringAction
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
* 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
@@ -35,18 +35,13 @@ import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadio;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadioRange;
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.record.Record;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
@@ -68,10 +63,6 @@ import javafx.util.Callback;
@JsonSubTypes.Type(value = Min.class, name = "min"),
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
@JsonSubTypes.Type(value = NXDNTypeDRadio.class, name = "nxdnTypeDRadio"),
@JsonSubTypes.Type(value = NXDNTypeDRadioRange.class, name = "nxdnTypeDRadioRange"),
@JsonSubTypes.Type(value = NXDNTypeDTalkgroup.class, name = "nxdnTypeDTalkgroup"),
@JsonSubTypes.Type(value = NXDNTypeDTalkgroupRange.class, name = "nxdnTypeDTalkgroupRange"),
@JsonSubTypes.Type(value = P25FullyQualifiedRadio.class, name = "p25FullyQualifiedRadio"),
@JsonSubTypes.Type(value = P25FullyQualifiedTalkgroup.class, name = "p25FullyQualifiedTalkgroup"),
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
@@ -79,7 +70,6 @@ import javafx.util.Callback;
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
@JsonSubTypes.Type(value = Record.class, name = "record"),
@JsonSubTypes.Type(value = SiteID.class, name = "siteID"),
@JsonSubTypes.Type(value = StreamAsTalkgroup.class, name = "streamAsTalkgroup"),
@JsonSubTypes.Type(value = Talkgroup.class, name = "talkgroup"),
@JsonSubTypes.Type(value = TalkgroupRange.class, name = "talkgroupRange"),
@JsonSubTypes.Type(value = TonesID.class, name = "tones"),

View File

@@ -29,10 +29,6 @@ public enum AliasIDType
LOJACK("LoJack"),
LTR_NET_UID("LTR-Net UID"),
MIN("Passport MIN"),
NXDN_TYPE_D_RADIO_ID("NXDN Type-D Radio ID"),
NXDN_TYPE_D_RADIO_ID_RANGE("NXDN Type-D Radio ID Range"),
NXDN_TYPE_D_TALKGROUP("NXDN Type-D Talkgroup"),
NXDN_TYPE_D_TALKGROUP_RANGE("NXDN Type-D Talkgroup Range"),
P25_FULLY_QUALIFIED_RADIO_ID("P25 Fully Qualified Radio ID"),
P25_FULLY_QUALIFIED_TALKGROUP("P25 Fully Qualified Talkgroup"),
PRIORITY("Audio Priority"),
@@ -62,7 +58,6 @@ public enum AliasIDType
//Values used by the View-By alias editor
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
NXDN_TYPE_D_RADIO_ID, NXDN_TYPE_D_RADIO_ID_RANGE, NXDN_TYPE_D_TALKGROUP, NXDN_TYPE_D_TALKGROUP_RANGE,
P25_FULLY_QUALIFIED_RADIO_ID, P25_FULLY_QUALIFIED_TALKGROUP, UNIT_STATUS, STATUS, TONES);
public String toString()

View File

@@ -1,46 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id;
/**
* NXDN Type-D alias identifier composed of a home repeater and a radio or talkgroup identifier value.
*/
public interface INXDNTypeD
{
/**
* Home repeater for the identifier
*/
int getHomeRepeater();
/**
* Sets the home repeater for the identifier
*/
void setHomeRepeater(int homeRepeater);
/**
* Radio or talkgroup value, without the home repeater.
*/
int getIdentifier();
/**
* Sets the radio or talkgroup value, without the home repeater.
*/
void setIdentifier(int identifier);
}

View File

@@ -1,56 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id;
/**
* NXDN Type-D alias identifier range of radio or talkgroup values that share a common home repeater.
*/
public interface INXDNTypeDRange
{
/**
* Home repeater for the range
*/
int getHomeRepeater();
/**
* Sets the home repeater for the range
*/
void setHomeRepeater(int homeRepeater);
/**
* Minimum radio or talkgroup value, without the home repeater.
*/
int getMinIdentifier();
/**
* Sets the minimum radio or talkgroup value, without the home repeater.
*/
void setMinIdentifier(int identifier);
/**
* Maximum radio or talkgroup value, without the home repeater.
*/
int getMaxIdentifier();
/**
* Sets the maximum radio or talkgroup value, without the home repeater.
*/
void setMaxIdentifier(int identifier);
}

View File

@@ -22,7 +22,7 @@ package io.github.dsheirer.alias.id.dcs;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.module.decode.squelch.dcs.DCSCode;
import io.github.dsheirer.module.decode.dcs.DCSCode;
/**
* Digital Coded Squelch (DCS) tone identifier

View File

@@ -1,96 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.radio;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D radio. The value is the over-the-air 16-bit identifier that combines the home repeater and radio so that
* it matches the value of an NXDN Type-D radio identifier.
*/
public class NXDNTypeDRadio extends Radio implements INXDNTypeD
{
public NXDNTypeDRadio()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the radio
* @param radio value, without the home repeater
*/
public NXDNTypeDRadio(int homeRepeater, int radio)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, radio));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_RADIO_ID;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getValue());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, getIdentifier()));
}
@JsonIgnore
@Override
public int getIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getValue());
}
@Override
public void setIdentifier(int identifier)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Radio ID:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,124 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.radio;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D radio range for a single home repeater. The min and max values are the over-the-air 16-bit identifiers
* that combine the home repeater and radio.
*/
public class NXDNTypeDRadioRange extends RadioRange implements INXDNTypeDRange
{
public NXDNTypeDRadioRange()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the range
* @param minRadio value, without the home repeater
* @param maxRadio value, without the home repeater
*/
public NXDNTypeDRadioRange(int homeRepeater, int minRadio, int maxRadio)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, minRadio),
NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, maxRadio));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_RADIO_ID_RANGE;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMinRadio());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
int min = getMinIdentifier();
int max = getMaxIdentifier();
setMinRadio(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, min));
setMaxRadio(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, max));
}
@JsonIgnore
@Override
public int getMinIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMinRadio());
}
@Override
public void setMinIdentifier(int identifier)
{
setMinRadio(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@JsonIgnore
@Override
public int getMaxIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMaxRadio());
}
@Override
public void setMaxIdentifier(int identifier)
{
setMaxRadio(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
/**
* Valid when the base range is valid and both ends of the range use the same home repeater.
*/
@Override
public boolean isValid()
{
return super.isValid() && getHomeRepeater() == NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMaxRadio());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Radio ID Range:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMinIdentifier(), true));
sb.append(" to ").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMaxIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.radio;
@@ -35,11 +38,10 @@ public enum RadioFormat
"<html>APCO25 valid range is 0 to 16,777,215"),
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
"<html>DMR unit id valid range is 0 to 16,777,215"),
NXDN("*****", 1, 0xFFFF, "1 to 65,535", "NXDN valid range is 1 to 65,535"),
PASSPORT("********", 0, 0x7FFFFF, "0 to 8,388,607",
"<html>PASSPORT valid range is 0 to 8,388,607"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
"<html>Unknown protocol valid value range is 1-16,777,215");
"Unknown protocol valid value range is 1-16,777,215");
private String mMask;
private int mMinimumValue;
@@ -114,8 +116,6 @@ public enum RadioFormat
return APCO25;
case DMR:
return DMR;
case NXDN:
return NXDN;
case PASSPORT:
return PASSPORT;
default:

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.radio;
@@ -23,14 +26,14 @@ import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.util.EnumMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Protocol-specific formatting of radio id values and parsing of formatted radio id values.
@@ -44,7 +47,6 @@ public class RadioFormatter
{
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
mFormatterMap.put(Protocol.NXDN, new NXDNTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}

View File

@@ -1,96 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D talkgroup. The value is the over-the-air 16-bit identifier that combines the home repeater and talkgroup
* so that it matches the value of an NXDN Type-D talkgroup identifier.
*/
public class NXDNTypeDTalkgroup extends Talkgroup implements INXDNTypeD
{
public NXDNTypeDTalkgroup()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the talkgroup
* @param talkgroup value, without the home repeater
*/
public NXDNTypeDTalkgroup(int homeRepeater, int talkgroup)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, talkgroup));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_TALKGROUP;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getValue());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, getIdentifier()));
}
@JsonIgnore
@Override
public int getIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getValue());
}
@Override
public void setIdentifier(int identifier)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Talkgroup:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,125 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D talkgroup range for a single home repeater. The min and max values are the over-the-air 16-bit
* identifiers that combine the home repeater and talkgroup.
*/
public class NXDNTypeDTalkgroupRange extends TalkgroupRange implements INXDNTypeDRange
{
public NXDNTypeDTalkgroupRange()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the range
* @param minTalkgroup value, without the home repeater
* @param maxTalkgroup value, without the home repeater
*/
public NXDNTypeDTalkgroupRange(int homeRepeater, int minTalkgroup, int maxTalkgroup)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, minTalkgroup),
NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, maxTalkgroup));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_TALKGROUP_RANGE;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMinTalkgroup());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
int min = getMinIdentifier();
int max = getMaxIdentifier();
setMinTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, min));
setMaxTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, max));
}
@JsonIgnore
@Override
public int getMinIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMinTalkgroup());
}
@Override
public void setMinIdentifier(int identifier)
{
setMinTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@JsonIgnore
@Override
public int getMaxIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMaxTalkgroup());
}
@Override
public void setMaxIdentifier(int identifier)
{
setMaxTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
/**
* Valid when the base range is valid and both ends of the range use the same home repeater.
*/
@Override
public boolean isValid()
{
return super.isValid() &&
getHomeRepeater() == NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMaxTalkgroup());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Talkgroup Range:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMinIdentifier(), true));
sb.append(" to ").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMaxIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -1,40 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
import io.github.dsheirer.protocol.Protocol;
/**
* Streaming talkgroup alias. For streamed audio metadata, this value is used to replace the decoded talkgroup or TO
* value with an alias value when there are talkgroup collisions resulting from streaming multiple channels to the
* same stream. This is common in P25 conventional repeater setups where each radio channel is using talkgroup 1.
*/
public class StreamAsTalkgroup extends Talkgroup
{
public StreamAsTalkgroup(int talkgroup)
{
super(Protocol.UNKNOWN, talkgroup);
}
public StreamAsTalkgroup()
{
//No arg JAXB constructor
}
}

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.talkgroup;
@@ -47,8 +50,6 @@ public enum TalkgroupFormat
"<html>MPT-1327 valid ranges are 0-127(prefix)<br>and 1-8192(ident) (ie. 000-0001 to 127-8192)"),
NBFM("*****", 1, 0xFFFF, "1 to 65,535",
"NBFM valid value range is 1-65,535"),
NXDN("*****", 0, 0xFFFF, "0 to 65,535",
"NXDN talkgroup valid range is 0 to 65,535"),
PASSPORT("*****", 1, 0xFFFF, "1 to 65,535",
"Passport valid value range is 1-65,535"),
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
@@ -138,8 +139,6 @@ public enum TalkgroupFormat
return MPT1327;
case NBFM:
return NBFM;
case NXDN:
return NXDN;
case PASSPORT:
return PASSPORT;
default:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
* 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
@@ -28,7 +28,6 @@ import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupForm
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
@@ -60,7 +59,6 @@ public class TalkgroupFormatter
mFormatterMap.put(Protocol.LTR_NET, ltr);
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
mFormatterMap.put(Protocol.NXDN, new NXDNTalkgroupFormatter());
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
}

View File

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

View File

@@ -20,6 +20,7 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.audio.call.AudioSegment;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
@@ -35,7 +36,7 @@ import org.slf4j.LoggerFactory;
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
{
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
public static final long DEFAULT_MAX_SEGMENT_LENGTH_MILLIS = 60_000; // 1 minute
public static final long DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
public static final int DEFAULT_TIMESLOT = 0;
private final int mMaxSegmentAudioSampleLength;
private Listener<AudioSegment> mAudioSegmentListener;
@@ -51,12 +52,12 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
* Constructs an abstract audio module
*
* @param aliasList for aliasing identifiers
* @param maxSegmentLengthMillis maximum audio segment length in milliseconds
* @param maxSegmentAudioSampleLength in milliseconds
*/
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentLengthMillis)
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentAudioSampleLength)
{
mAliasList = aliasList;
mMaxSegmentAudioSampleLength = (int)(maxSegmentLengthMillis * 8); //Convert milliseconds to samples
mMaxSegmentAudioSampleLength = (int)(maxSegmentAudioSampleLength * 8); //Convert milliseconds to samples
mTimeslot = timeslot;
mIdentifierCollection = new MutableIdentifierCollection(getTimeslot());
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
@@ -67,7 +68,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
*/
public AbstractAudioModule(AliasList aliasList)
{
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_MAX_SEGMENT_LENGTH_MILLIS);
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
}
/**
@@ -89,7 +90,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
{
mAudioSegment.completeProperty().set(true);
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
mAudioSegment.decrementConsumerCount();
mAudioSegment.removeLease(getClass().toString());
mAudioSegment = null;
}
}
@@ -112,7 +113,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
if(mAudioSegment == null)
{
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
mAudioSegment.incrementConsumerCount();
mAudioSegment.addLease(getClass().toString());
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
@@ -123,7 +124,6 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
if(mAudioSegmentListener != null)
{
mAudioSegment.incrementConsumerCount();
mAudioSegmentListener.receive(mAudioSegment);
}
@@ -136,26 +136,33 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
public void addAudio(float[] audioBuffer)
{
AudioSegment audioSegment = getAudioSegment();
if(audioBuffer != null)
{
AudioSegment audioSegment = getAudioSegment();
//If the current segment exceeds the max samples length, close it so that a new segment gets generated
//and then link the segments together
if(mAudioSampleCount >= mMaxSegmentAudioSampleLength)
{
AudioSegment previous = getAudioSegment();
closeAudioSegment();
audioSegment = getAudioSegment();
audioSegment.linkTo(previous);
}
//If the current segment exceeds the max samples length, close it so that a new segment gets generated
//and then link the segments together
if(mAudioSampleCount >= mMaxSegmentAudioSampleLength)
{
AudioSegment previous = getAudioSegment();
closeAudioSegment();
audioSegment = getAudioSegment();
audioSegment.linkTo(previous);
}
try
{
audioSegment.addAudio(audioBuffer);
mAudioSampleCount += audioBuffer.length;
try
{
audioSegment.addAudio(audioBuffer);
mAudioSampleCount += audioBuffer.length;
}
catch(Exception e)
{
closeAudioSegment();
}
}
catch(Exception e)
else
{
closeAudioSegment();
mLog.info("Attempt to add null audio from " + getClass());
}
}

View File

@@ -0,0 +1,503 @@
/*
* *****************************************************************************
* 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.audio;
import io.github.dsheirer.audio.broadcast.AudioStreamingManager;
import io.github.dsheirer.audio.call.ActiveAudioSegment;
import io.github.dsheirer.audio.call.AudioSegment;
import io.github.dsheirer.audio.call.Call;
import io.github.dsheirer.audio.call.CallRepository;
import io.github.dsheirer.audio.call.ICallEventListener;
import io.github.dsheirer.audio.playback.AudioPlaybackManager;
import io.github.dsheirer.audio.playbackfx.AudioPlaybackController;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.retention.AgeUnits;
import io.github.dsheirer.preference.retention.RetentionPolicy;
import io.github.dsheirer.preference.retention.RetentionPreference;
import io.github.dsheirer.record.AudioRecordingManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
/**
* Manages handling and distribution of audio segments and calls across the application.
*/
@Component("audioManager")
public class AudioManager implements Listener<AudioSegment>
{
private static final Logger LOGGER = LoggerFactory.getLogger(AudioManager.class);
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LOGGER);
private static final long GIGABYTES = 1024l * 1024l * 1024l;
@Resource
private AudioPlaybackManager mAudioPlaybackManager;
@Resource
private AudioPlaybackController mAudioPlaybackController;
@Resource
private AudioRecordingManager mAudioRecordingManager;
@Resource
private AudioStreamingManager mAudioStreamingManager;
@Resource
private DuplicateCallDetector mDuplicateCallDetector;
@Resource
private UserPreferences mUserPreferences;
private CallRepository mCallRepository;
private List<ICallEventListener> mCallListeners = new CopyOnWriteArrayList<>();
private ActiveAudioSegmentProcessor mAudioSegmentProcessor = new ActiveAudioSegmentProcessor();
private CallAgeOffProcessor mCallAgeOffProcessor = new CallAgeOffProcessor();
private ScheduledFuture<?> mAudioSegmentProcessorFuture;
private ScheduledFuture<?> mCallRetentionProcessorFuture;
/**
* Constructs an instance
*/
@Autowired
public AudioManager(CallRepository callRepository)
{
mCallRepository = callRepository;
}
@PostConstruct
public void postConstruct()
{
LOGGER.info("Post constructing ......");
mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mAudioSegmentProcessor, 100,
500, TimeUnit.MILLISECONDS);
//Delay first run until 1-minute after startup.
mCallRetentionProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mCallAgeOffProcessor,
30, 30, TimeUnit.SECONDS);
//Register call event listeners
add(mAudioRecordingManager);
add(mAudioPlaybackController);
add(mAudioStreamingManager);
}
@PreDestroy
public void preDestroy()
{
LOGGER.info("AudioManager shutting down");
if(mAudioSegmentProcessorFuture != null)
{
mAudioSegmentProcessorFuture.cancel(true);
mAudioSegmentProcessorFuture = null;
}
mAudioSegmentProcessor.shutdown();
if(mCallRetentionProcessorFuture != null)
{
mCallRetentionProcessorFuture.cancel(true);
mCallRetentionProcessorFuture = null;
}
}
/**
* Adds the call listener to be notified of call events.
* @param listener to add
*/
public void add(ICallEventListener listener)
{
if(!mCallListeners.contains(listener))
{
mCallListeners.add(listener);
}
}
/**
* Broadcasts a call added event to registered listeners.
* @param call that was added
*/
private void broadcastAdded(Call call)
{
for(ICallEventListener listener: mCallListeners)
{
listener.added(call);
}
}
/**
* Broadcasts a call updated event to registered listeners.
* @param call that was updated
*/
private void broadcastUpdated(Call call)
{
for(ICallEventListener listener: mCallListeners)
{
listener.updated(call);
}
}
/**
* Broadcasts a call completed event to registered listeners.
* @param call that was completed
* @param audioSegment associated with the call.
*/
private void broadcastCompleted(Call call, AudioSegment audioSegment)
{
for(ICallEventListener listener: mCallListeners)
{
audioSegment.addLease(listener.getClass().toString());
listener.completed(call, audioSegment);
}
}
/**
* Broadcasts a call deleted event to registered listeners.
* @param call that was deleted
*/
private void broadcastDeleted(Call call)
{
for(ICallEventListener listener: mCallListeners)
{
listener.deleted(call);
}
}
/**
* Removes the listener from receiving call event notifications.
* @param listener to remove
*/
public void remove(ICallEventListener listener)
{
mCallListeners.remove(listener);
}
@Override
public void receive(AudioSegment audioSegment)
{
//Let the duplicate call detector process the segment first to detect duplicates.
audioSegment.addLease(mDuplicateCallDetector.getClass().toString());
mDuplicateCallDetector.receive(audioSegment);
audioSegment.addLease(mAudioSegmentProcessor.getClass().toString());
mAudioSegmentProcessor.add(audioSegment);
//TODO: remove for cleanup
// audioSegment.addLease(mAudioPlaybackManager.getClass().toString());
// mAudioPlaybackManager.receive(audioSegment);
}
/**
* Active audio segment processor. Single thread processor that runs as a scheduled task to process incoming
* audio segments, update existing active audio segments, and process completed audio segments.
*/
private class ActiveAudioSegmentProcessor implements Runnable
{
private LinkedTransferQueue<AudioSegment> mQueuedAudioSegments = new LinkedTransferQueue<>();
private List<AudioSegment> mAudioSegmentsToProcess = new ArrayList<>();
private Map<AudioSegment,ActiveAudioSegment> mActiveAudioSegmentsMap = new HashMap<>();
private AtomicBoolean mProcessing = new AtomicBoolean();
/**
* Adds the new audio segment to be processed
* @param audioSegment to process
*/
public void add(AudioSegment audioSegment)
{
mQueuedAudioSegments.add(audioSegment);
}
/**
* Performs shutdown and cleanup of any active audio segments that remain after the threaded processing
* is terminated.
*/
public void shutdown()
{
for(Map.Entry<AudioSegment,ActiveAudioSegment> entry: mActiveAudioSegmentsMap.entrySet())
{
//Invoke dispose() method to remove the lease on the audio segment
entry.getValue().dispose();
}
mActiveAudioSegmentsMap.clear();
for(AudioSegment audioSegment: mQueuedAudioSegments)
{
audioSegment.removeLease(getClass().toString());
}
mQueuedAudioSegments.clear();
}
@Override
public void run()
{
if(mProcessing.compareAndSet(false, true))
{
//Process existing active audio segments
try
{
Iterator<Map.Entry<AudioSegment,ActiveAudioSegment>> it = mActiveAudioSegmentsMap.entrySet().iterator();
while(it.hasNext())
{
ActiveAudioSegment activeAudioSegment = it.next().getValue();
if(activeAudioSegment.update())
{
broadcastUpdated(activeAudioSegment.getCall());
}
if(activeAudioSegment.isComplete())
{
//Process residual audio and close the wave recording file.
activeAudioSegment.closeAudio();
Call call = activeAudioSegment.getCall();
call.setComplete(true);
try
{
mCallRepository.save(call);
}
catch(Exception e)
{
LOGGER.error("Couldn't update call in database");
}
broadcastCompleted(call, activeAudioSegment.getAudioSegment());
activeAudioSegment.dispose();
it.remove();
}
}
}
catch(Throwable t)
{
LOGGER.error("Error while processing existing active audio segments", t);
}
//Process new audio segments
try
{
mQueuedAudioSegments.drainTo(mAudioSegmentsToProcess);
for(AudioSegment audioSegment: mAudioSegmentsToProcess)
{
if(mActiveAudioSegmentsMap.containsKey(audioSegment))
{
ActiveAudioSegment activeAudioSegment = mActiveAudioSegmentsMap.get(audioSegment);
if(activeAudioSegment.update())
{
broadcastUpdated(activeAudioSegment.getCall());
}
}
else
{
Call call = ActiveAudioSegment.create(audioSegment);
ActiveAudioSegment activeAudioSegment = new ActiveAudioSegment(audioSegment, call, mUserPreferences);
mActiveAudioSegmentsMap.put(audioSegment, activeAudioSegment);
mCallRepository.save(call);
broadcastAdded(call);
}
}
mAudioSegmentsToProcess.clear();
}
catch(Throwable t)
{
LOGGER.error("Error while processing existing active audio segments", t);
}
mProcessing.set(false);
}
}
}
/**
* Call event retention and age-off processor.
*/
public class CallAgeOffProcessor implements Runnable
{
@Override
public void run()
{
RetentionPreference retentionPreference = mUserPreferences.getRetentionPreference();
RetentionPolicy policy = retentionPreference.getRetentionPolicy();
try
{
if(policy.isAgePolicy())
{
AgeUnits ageUnits = retentionPreference.getAgeUnits();
int ageValue = retentionPreference.getAgeValue();
long threshold = System.currentTimeMillis() - ageUnits.getDuration(ageValue);
PageRequest requestPage1 = PageRequest.of(0, 100);
long start = 0;
Page<Call> page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, threshold, requestPage1);
while(page.getTotalElements() > 0)
{
List<Call> calls = page.get().collect(Collectors.toList());
for(Call call: calls)
{
//Keep adjusting the start time to 1 millisecond more than the current call so that
//if there's an error deleting any given call, we don't get stuck processing it every time
start = call.getEventTime() + 1;
//Only evaluate calls that are complete
if(call.isComplete())
{
boolean deleteFailed = false;
File file = new File(call.getFile());
if(file.exists())
{
try
{
File parentDirectory = file.getParentFile();
Files.delete(file.toPath());
//Delete parent directory if it is now empty
if(parentDirectory.exists() && parentDirectory.isDirectory())
{
if(!Files.list(parentDirectory.toPath()).findFirst().isPresent())
{
Files.delete(parentDirectory.toPath());
}
}
}
catch(Exception io)
{
LOGGING_SUPPRESSOR.error("Call Event Delete", 5,
"Error deleting call event recording: " + call.getFile() +
" - " + io.getLocalizedMessage());
deleteFailed = true;
}
}
if(!deleteFailed)
{
mCallRepository.delete(call);
broadcastDeleted(call);
}
}
}
page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, threshold, requestPage1);
}
}
if(policy.isSizePolicy())
{
Path callsDirectory = mUserPreferences.getDirectoryPreference().getDirectoryCalls();
if(Files.exists(callsDirectory))
{
long currentSize = FileUtils.sizeOfDirectory(callsDirectory.toFile());
long objectiveSize = retentionPreference.getObjectiveDirectorySize();
if(currentSize >= objectiveSize)
{
long start = 0;
long now = System.currentTimeMillis();
PageRequest pageable = PageRequest.of(0, 100);
Page<Call> page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, now, pageable);
while(currentSize >= objectiveSize && page.getTotalElements() > 0)
{
List<Call> calls = page.get().collect(Collectors.toList());
for(Call call: calls)
{
//Keep adjusting the start time to 1 millisecond more than the current call so that
//if there's an error deleting any given call, we don't get stuck processing it every time
start = call.getEventTime() + 1;
if(currentSize < objectiveSize)
{
start = now - 1;
break;
}
//Only evaluate calls that are complete
if(call.isComplete())
{
boolean deleteFailed = false;
File file = new File(call.getFile());
if(file.exists())
{
try
{
File parentDirectory = file.getParentFile();
long fileSize = FileUtils.sizeOf(file);
Files.delete(file.toPath());
currentSize -= fileSize;
//Delete parent directory if it is now empty
if(parentDirectory.exists() && parentDirectory.isDirectory())
{
if(!Files.list(parentDirectory.toPath()).findFirst().isPresent())
{
Files.delete(parentDirectory.toPath());
}
}
}
catch(Exception io)
{
LOGGING_SUPPRESSOR.error("Call Event Delete", 5,
"Error deleting call event recording: " + call.getFile() +
" - " + io.getLocalizedMessage());
deleteFailed = true;
}
}
if(!deleteFailed)
{
mCallRepository.delete(call);
broadcastDeleted(call);
}
}
}
page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, now, pageable);
}
}
}
}
}
catch(Throwable t)
{
LOGGER.error("Unexpected error while deleting call events using retention policy: " + policy, t);
}
}
}
}

View File

@@ -32,8 +32,6 @@ import io.github.dsheirer.sample.real.IRealBufferListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Provides packaging of demodulated audio sample buffers into audio segments for broadcast to registered listeners.
* Includes audio packet metadata in constructed audio segments.
@@ -44,7 +42,36 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
Listener<float[]>
{
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private List<AbstractAudioFilter>mAudioFilters;
private static float[] sHighPassFilterCoefficients;
private final boolean mAudioFilterEnable;
static
{
FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
.sampleRate(8000)
.stopBandCutoff(200)
.stopBandAmplitude(0.0)
.stopBandRipple(0.025)
.passBandStart(300)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.build();
try
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
sHighPassFilterCoefficients = designer.getImpulseResponse();
}
}
catch(FilterDesignException fde)
{
mLog.error("Filter design error", fde);
}
}
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
@@ -54,23 +81,23 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
* @param aliasList for aliasing identifiers
* @param timeslot for this audio module
* @param maxAudioSegmentLength in milliseconds
* @param audioFilters a list of audio filters to apply to the incoming audio samples
* @param audioFilterEnable to enable or disable high-pass audio filter
*/
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, List<AbstractAudioFilter> audioFilters)
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, boolean audioFilterEnable)
{
super(aliasList, timeslot, maxAudioSegmentLength);
mAudioFilters = audioFilters;
mAudioFilterEnable = audioFilterEnable;
}
/**
* Creates an Audio Module.
* @param aliasList for aliasing identifiers
* @param audioFilters a list of audio filters to apply to the incoming audio samples
* @param audioFilterEnable to enable or disable high-pass audio filter
*/
public AudioModule(AliasList aliasList, List<AbstractAudioFilter> audioFilters)
public AudioModule(AliasList aliasList, boolean audioFilterEnable)
{
super(aliasList);
mAudioFilters = audioFilters;
mAudioFilterEnable = audioFilterEnable;
}
@Override
@@ -101,13 +128,9 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
{
if(mSquelchState == SquelchState.UNSQUELCH)
{
// apply audio filters if any (i.e. deemphasis, high pass, etc.)
if(!mAudioFilters.isEmpty())
if(mAudioFilterEnable)
{
for(AbstractAudioFilter filter : mAudioFilters)
{
audioBuffer = filter.filter(audioBuffer);
}
audioBuffer = mHighPassFilter.filter(audioBuffer);
}
addAudio(audioBuffer);

View File

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

View File

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

View File

@@ -1,27 +1,9 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio;
import io.github.dsheirer.audio.playback.AudioChannel;
import io.github.dsheirer.audio.playback.AudioPlaybackDeviceDescriptor;
import io.github.dsheirer.audio.playback.AudioOutput;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
import java.util.List;
/**
@@ -30,13 +12,13 @@ import java.util.List;
public interface IAudioController
{
/* Current Mixer and MixerChannel configuration */
public void setAudioPlaybackDevice(AudioPlaybackDeviceDescriptor device ) throws AudioException;
public AudioPlaybackDeviceDescriptor getAudioPlaybackDevice() throws AudioException;
public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException;
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException;
/* Audio Channel(s) */
public List<AudioChannel> getAudioChannels();
/* Audio Output(s) */
public List<AudioOutput> getAudioOutputs();
/* Controller Audio Event Listener */
public void addAudioEventListener(Listener<AudioEvent> listener );
public void removeAudioEventListener(Listener<AudioEvent> listener );
public void addControllerListener( Listener<AudioEvent> listener );
public void removeControllerListener( Listener<AudioEvent> listener );
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -19,6 +19,7 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.audio.call.AudioSegment;
import io.github.dsheirer.sample.Listener;
/**

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -19,6 +19,7 @@
package io.github.dsheirer.audio;
import io.github.dsheirer.audio.call.AudioSegment;
import io.github.dsheirer.sample.Listener;
/**

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
* Copyright (C) 2014-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
@@ -22,7 +22,10 @@ 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.call.AudioSegment;
import io.github.dsheirer.audio.call.Call;
import io.github.dsheirer.audio.call.CompletedCall;
import io.github.dsheirer.audio.call.ICallEventListener;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
@@ -32,14 +35,15 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.AudioSegmentRecorder;
import io.github.dsheirer.record.RecordFormat;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedTransferQueue;
@@ -47,59 +51,50 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on
* disk and enqueues the temporary recording for streaming.
*/
public class AudioStreamingManager implements Listener<AudioSegment>
@Component("audioStreamingManager")
public class AudioStreamingManager implements ICallEventListener
{
private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingManager.class);
private LinkedTransferQueue<AudioSegment> mNewAudioSegments = new LinkedTransferQueue<>();
private List<AudioSegment> mAudioSegments = new ArrayList<>();
private Listener<AudioRecording> mAudioRecordingListener;
private BroadcastFormat mBroadcastFormat;
@Resource
private BroadcastModel mBroadcastModel;
@Resource
private UserPreferences mUserPreferences;
private LinkedTransferQueue<CompletedCall> mCompletedCallQueue = new LinkedTransferQueue<>();
private List<CompletedCall> mCompletedCalls = new ArrayList<>();
private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3;
private ScheduledFuture<?> mAudioSegmentProcessorFuture;
private int mNextRecordingNumber = 1;
/**
* Constructs an instance
* @param listener to receive completed audio recordings
* @param broadcastFormat for temporary recordings
* @param userPreferences to manage recording directories
*/
public AudioStreamingManager(Listener<AudioRecording> listener, BroadcastFormat broadcastFormat, UserPreferences userPreferences)
public AudioStreamingManager()
{
mAudioRecordingListener = listener;
mBroadcastFormat = broadcastFormat;
mUserPreferences = userPreferences;
}
/**
* Primary receive method
*/
@Override
public void receive(AudioSegment audioSegment)
{
mNewAudioSegments.add(audioSegment);
}
/**
* Starts the scheduled audio segment processor
*/
@PostConstruct
public void start()
{
if(mAudioSegmentProcessorFuture == null)
{
mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
0, 250, TimeUnit.MILLISECONDS);
mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new CompletedCallProcessor(),
0, 250, TimeUnit.MILLISECONDS);
}
}
/**
* Stops the scheduled audio segment processor
*/
@PreDestroy
public void stop()
{
if(mAudioSegmentProcessorFuture != null)
@@ -108,44 +103,53 @@ public class AudioStreamingManager implements Listener<AudioSegment>
mAudioSegmentProcessorFuture = null;
}
for(AudioSegment audioSegment: mNewAudioSegments)
for(CompletedCall completedCall: mCompletedCallQueue)
{
audioSegment.decrementConsumerCount();
completedCall.audioSegment().removeLease(getClass().toString());
}
mNewAudioSegments.clear();
mCompletedCallQueue.clear();
for(AudioSegment audioSegment: mAudioSegments)
for(CompletedCall completedCall: mCompletedCalls)
{
audioSegment.decrementConsumerCount();
completedCall.audioSegment().removeLease(getClass().toString());
}
mAudioSegments.clear();
mCompletedCalls.clear();
}
/**
* Implements the ICallEventListener interface to receive and enqueue completed calls to process for streaming.
* @param call that was completed.
* @param audioSegment for the call
*/
@Override
public void completed(Call call, AudioSegment audioSegment)
{
mCompletedCallQueue.add(new CompletedCall(call, audioSegment));
}
@Override
public void added(Call call) {} //Not implemented
@Override
public void updated(Call call) {} //Not implemented
@Override
public void deleted(Call call) {} //Not implemented
/**
* Main processing method to process audio segments
*/
private void processAudioSegments()
private void processCompletedCalls()
{
mNewAudioSegments.drainTo(mAudioSegments);
CompletedCall completedCall = mCompletedCallQueue.poll();
Iterator<AudioSegment> it = mAudioSegments.iterator();
AudioSegment audioSegment;
while(it.hasNext())
while(completedCall != null)
{
audioSegment = it.next();
AudioSegment audioSegment = completedCall.audioSegment();
if(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled())
if(!(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled()))
{
it.remove();
audioSegment.decrementConsumerCount();
}
else if(audioSegment.completeProperty().get())
{
it.remove();
if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels())
if(mBroadcastModel != null && audioSegment.hasBroadcastChannels())
{
IdentifierCollection identifiers =
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
@@ -203,9 +207,10 @@ public class AudioStreamingManager implements Listener<AudioSegment>
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
}
}
audioSegment.decrementConsumerCount();
}
audioSegment.removeLease(getClass().toString());
completedCall = mCompletedCallQueue.poll();
}
}
@@ -235,7 +240,7 @@ public class AudioStreamingManager implements Listener<AudioSegment>
AudioRecording audioRecording = new AudioRecording(path, broadcastChannels, identifierCollection,
audioSegment.getStartTimestamp(), length);
mAudioRecordingListener.receive(audioRecording);
mBroadcastModel.receive(audioRecording);
}
catch(IOException ioe)
{
@@ -269,20 +274,20 @@ public class AudioStreamingManager implements Listener<AudioSegment>
}
/**
* Scheduled runnable to process audio segments.
* Scheduled runnable to process completed calls.
*/
public class AudioSegmentProcessor implements Runnable
public class CompletedCallProcessor implements Runnable
{
@Override
public void run()
{
try
{
processAudioSegments();
processCompletedCalls();
}
catch(Throwable t)
{
mLog.error("Error processing audio segments for streaming", t);
mLog.error("Error processing completed calls for streaming", t);
}
}
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -22,16 +22,14 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerFeedConfiguration;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzFeedConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcaster;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster;
@@ -65,29 +63,26 @@ public class BroadcastFactory
switch(configuration.getBroadcastServerType())
{
case BROADCASTIFY_CALL:
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration);
case RDIOSCANNER_CALL:
return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration);
case OPENMHZ:
return new OpenMHzBroadcaster((OpenMHzConfiguration)configuration,
inputAudioFormat, mp3Setting, aliasModel);
return new OpenMHzBroadcaster((OpenMHzConfiguration)configuration);
case BROADCASTIFY:
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
inputAudioFormat, mp3Setting, aliasModel);
inputAudioFormat, mp3Setting);
case ICECAST_TCP:
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
mp3Setting);
case ICECAST_HTTP:
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
mp3Setting);
case SHOUTCAST_V1:
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
mp3Setting);
case SHOUTCAST_V2:
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
inputAudioFormat, mp3Setting, aliasModel);
inputAudioFormat, mp3Setting);
case UNKNOWN:
default:
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());

View File

@@ -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
* it under the terms of the GNU General Public License as published by
@@ -20,12 +20,13 @@ package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@@ -45,9 +46,11 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.swing.table.AbstractTableModel;
@Component("broadcastModel")
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
{
private final static Logger mLog = LoggerFactory.getLogger(BroadcastModel.class);
@@ -62,35 +65,36 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
public static final int COLUMN_BROADCASTER_STATUS = 2;
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
public static final int COLUMN_BROADCASTER_STREAMED_COUNT = 4;
public static final int COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT = 5;
public static final int COLUMN_BROADCASTER_AGED_OFF_COUNT = 6;
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 7;
public static final int COLUMN_BROADCASTER_AGED_OFF_COUNT = 5;
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
public static final String[] COLUMN_NAMES = new String[]
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Duplicate Rejected", "Aged Off", "Upload Error"};
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
@Resource
private AliasModel mAliasModel;
@Resource
private UserPreferences mUserPreferences;
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
private List<AudioRecording> mRecordingQueue = new CopyOnWriteArrayList<>();
private Map<Integer,AbstractAudioBroadcaster> mBroadcasterMap = new HashMap<>();
private IconModel mIconModel;
private AliasModel mAliasModel;
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
private UserPreferences mUserPreferences;
/**
* Model for managing Broadcast configurations and any associated broadcaster instances.
*/
public BroadcastModel(AliasModel aliasModel, IconModel iconModel, UserPreferences userPreferences)
public BroadcastModel()
{
mAliasModel = aliasModel;
mIconModel = iconModel;
mUserPreferences = userPreferences;
}
@PostConstruct
public void postConstruct()
{
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
removeOrphanedTemporaryRecordings();
}
@@ -345,13 +349,14 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
deleteBroadcaster(configuredBroadcast);
}
final AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
mAliasModel, mUserPreferences);
if(audioBroadcaster != null)
{
configuredBroadcast.setAudioBroadcaster(audioBroadcaster);
audioBroadcaster.setListener(mBroadcastEventListener);
audioBroadcaster.start();
mBroadcasterMap.put(audioBroadcaster.getBroadcastConfiguration().getId(), audioBroadcaster);
int index = mConfiguredBroadcasts.indexOf(configuredBroadcast);
@@ -362,7 +367,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
}
broadcast(new BroadcastEvent(audioBroadcaster, BroadcastEvent.Event.BROADCASTER_ADD));
ThreadPool.CACHED.submit(() -> audioBroadcaster.start());
}
}
}
@@ -532,12 +536,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
fireTableCellUpdated(row, COLUMN_BROADCASTER_STREAMED_COUNT);
}
break;
case BROADCASTER_DUPLICATE_REJECTED_COUNT_CHANGE:
if(row >= 0)
{
fireTableCellUpdated(row, COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT);
}
break;
case BROADCASTER_AGED_OFF_COUNT_CHANGE:
if(row >= 0)
{
@@ -614,12 +612,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
return configuredBroadcast.getAudioBroadcaster().getStreamedAudioCount();
}
break;
case COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT:
if(configuredBroadcast.hasAudioBroadcaster())
{
return configuredBroadcast.getAudioBroadcaster().getDuplicateRejectedAudioCount();
}
break;
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
if(configuredBroadcast.hasAudioBroadcaster())
{
@@ -656,7 +648,6 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
case COLUMN_BROADCASTER_AGED_OFF_COUNT:
case COLUMN_BROADCASTER_QUEUE_SIZE:
case COLUMN_BROADCASTER_STREAMED_COUNT:
case COLUMN_BROADCASTER_DUPLICATE_REJECTED_COUNT:
case COLUMN_BROADCASTER_ERROR_COUNT:
return Integer.class;
case COLUMN_BROADCAST_SERVER_TYPE:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -31,9 +31,9 @@ public enum BroadcastServerType
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
RDIOSCANNER_CALL("Rdio Scanner", "images/rdioscanner.png"),
OPENMHZ("OpenMHz", "images/openmhz.png"),
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
OPENMHZ("OpenMHz", "images/openmhz.png"),
RDIOSCANNER_CALL("Rdio Scanner", "images/rdioscanner.png"),
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
UNKNOWN("Unknown", null);

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -20,10 +20,12 @@ package io.github.dsheirer.audio.broadcast;
import io.github.dsheirer.icon.Icon;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import net.miginfocom.swing.MigLayout;
import javax.swing.ImageIcon;
@@ -42,23 +44,15 @@ public class BroadcastStatusPanel extends JPanel
private JTable mTable;
private JTableColumnWidthMonitor mColumnWidthMonitor;
private JScrollPane mScrollPane;
@Resource
private BroadcastModel mBroadcastModel;
private UserPreferences mUserPreferences;
private String mPreferenceKey;
private String mPreferenceKey = "application.broadcast.status.panel";
/**
* Constructs an instance
* @param broadcastModel to access the streams
* @param userPreferences for configuring the panel
* @param preferenceKey to store column preferences for this panel.
*/
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
public BroadcastStatusPanel()
{
mBroadcastModel = broadcastModel;
mUserPreferences = userPreferences;
mPreferenceKey = preferenceKey;
init();
}
public JTable getTable()
@@ -66,21 +60,19 @@ public class BroadcastStatusPanel extends JPanel
return mTable;
}
private void init()
@PostConstruct
private void postConstruct()
{
setPreferredSize(new Dimension(880, 70));
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
mTable = new JTable(mBroadcastModel);
mTable.setEnabled(false);
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)mTable.getDefaultRenderer(String.class);
renderer.setHorizontalAlignment(SwingConstants.CENTER);
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
mColumnWidthMonitor = new JTableColumnWidthMonitor(mTable, mPreferenceKey);
mScrollPane = new JScrollPane(mTable);
add(mScrollPane);
}
@@ -151,10 +143,7 @@ public class BroadcastStatusPanel extends JPanel
else if(state == BroadcastState.DISABLED)
{
setBackground(table.getBackground());
//Use the LAF's disabled-foreground so the cell reads correctly on any
//theme - LIGHT_GRAY is invisible on a light background.
Color disabled = javax.swing.UIManager.getColor("Label.disabledForeground");
setForeground(disabled != null ? disabled : table.getForeground());
setForeground(Color.LIGHT_GRAY);
}
else if(state == BroadcastState.INVALID_SETTINGS ||
state == BroadcastState.NETWORK_UNAVAILABLE)

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
* 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
@@ -20,22 +20,15 @@
package io.github.dsheirer.audio.broadcast.broadcastify;
import com.google.common.net.HttpHeaders;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
@@ -49,7 +42,6 @@ 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;
@@ -81,18 +73,14 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
final private AliasModel mAliasModel;
/**
* Constructs an instance of the broadcaster
* @param config to use
* @param aliasModel for access to aliases
*/
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config)
{
super(config);
mAliasModel = aliasModel;
}
/**
@@ -254,8 +242,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
String radioId = getFrom(audioRecording);
float frequency = getFrequency(audioRecording);
String radioIdAlias = getFromAlias(audioRecording);
BroadcastifyCallBuilder bodyBuilder = new BroadcastifyCallBuilder();
bodyBuilder.addPart(FormField.API_KEY, getBroadcastConfiguration().getApiKey())
.addPart(FormField.SYSTEM_ID, getBroadcastConfiguration().getSystemID())
@@ -266,11 +252,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
.addPart(FormField.FREQUENCY, frequency)
.addPart(FormField.ENCODING, ENCODING_TYPE_MP3);
if(radioIdAlias != null && !radioIdAlias.isEmpty())
{
bodyBuilder.addPart(FormField.RADIO_ID_ALIAS, radioIdAlias);
}
try
{
HttpRequest request = HttpRequest.newBuilder()
@@ -370,9 +351,6 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
else if(urlResponse.startsWith("1 SKIPPED"))
{
//Broadcastify is telling us to skip audio upload - someone already uploaded it
incrementDuplicateRejectedAudioCount();
broadcast(new BroadcastEvent(BroadcastifyCallBroadcaster.this,
BroadcastEvent.Event.BROADCASTER_DUPLICATE_REJECTED_COUNT_CHANGE));
audioRecording.removePendingReplay();
}
else
@@ -457,58 +435,13 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
return "0";
}
/**
* Resolves the over-the-air talker alias for the FROM (source radio) identifier, acquired in real-time from RF
* (e.g. P25 Motorola Talker Alias or DMR Talker Alias). Returns null if no OTA alias was received for this call.
* This is sent as the srcId_alias field in Broadcastify uploads.
*/
private static String getFromAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier talkerAliasIdentifier)
{
if(talkerAliasIdentifier.isValid())
{
return talkerAliasIdentifier.getValue();
}
}
}
return null;
}
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*/
private String getTo(AudioRecording audioRecording)
private static String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
//Alias the TO value when the user specifies a 'Stream As Talkgroup'
if(identifier != null)
{
AliasListConfigurationIdentifier config = audioRecording.getIdentifierCollection().getAliasListConfiguration();
if(config != null)
{
AliasList aliasList = mAliasModel.getAliasList(config.getValue());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
for(Alias a: aliases)
{
if(a.getStreamTalkgroupAlias() != null)
{
return String.valueOf(a.getStreamTalkgroupAlias().getValue());
}
}
}
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return format(patchGroupIdentifier);

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast.broadcastify;
@@ -32,7 +35,6 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
private final static Logger mLog = LoggerFactory.getLogger(BroadcastifyFeedConfiguration.class);
private int mFeedID;
private boolean mVerboseLogging = false;
public BroadcastifyFeedConfiguration()
{
@@ -121,15 +123,4 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
{
mFeedID = feedID;
}
@JacksonXmlProperty(isAttribute = true, localName = "verbose_logging")
public boolean isVerboseLogging()
{
return mVerboseLogging;
}
public void setVerboseLogging(boolean verboseLogging)
{
mVerboseLogging = verboseLogging;
}
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -18,7 +18,6 @@
*/
package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
@@ -28,13 +27,11 @@ import io.github.dsheirer.audio.convert.MP3Setting;
public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
{
private IBroadcastMetadataUpdater mMetadataUpdater;
protected AliasModel mAliasModel;
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
MP3Setting mp3Setting)
{
super(broadcastConfiguration, inputAudioFormat, mp3Setting);
mAliasModel = aliasModel;
}
/**
@@ -50,7 +47,7 @@ public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
{
if(mMetadataUpdater == null)
{
mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration(), mAliasModel);
mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration());
}
return mMetadataUpdater;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -18,23 +18,12 @@
*/
package io.github.dsheirer.audio.broadcast.icecast;
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.IBroadcastMetadataUpdater;
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@@ -42,7 +31,8 @@ import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdater
{
@@ -50,6 +40,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
private final static String UTF8 = "UTF-8";
private HttpClient mHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
private IcecastConfiguration mIcecastConfiguration;
@Resource
private AliasModel mAliasModel;
private boolean mConnectionLoggingSuppressed = false;
@@ -59,10 +50,9 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
* broadcaster. When multiple metadata updates are received prior to completion of the current ongoing update
* sequence, those updates will be queued and processed in the order received.
*/
public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration, AliasModel aliasModel)
public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration)
{
mIcecastConfiguration = icecastConfiguration;
mAliasModel = aliasModel;
}
/**

View File

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

View File

@@ -1,20 +1,23 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast.icecast;
@@ -22,16 +25,21 @@ import com.google.common.base.Joiner;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import java.util.List;
import java.util.Optional;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class IcecastMetadata
{
private final static Logger mLog = LoggerFactory.getLogger(IcecastMetadata.class);
@@ -65,23 +73,13 @@ public class IcecastMetadata
if(to != null)
{
sb.append("TO:").append(to);
List<Alias> aliases = aliasList.getAliases(to);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
if(aliases != null && !aliases.isEmpty())
{
sb.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
}
else
{
sb.append("TO:").append(to);
if(!aliases.isEmpty())
{
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
}
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
}
}
else

View File

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

View File

@@ -34,8 +34,7 @@
API_KEY("api_key"),
SOURCE_LIST("source_list"),
FREQ_LIST("freq_list"),
PATCH_LIST("patch_list"),
TALKER_ALIAS("talker_alias");
PATCHES("patches");
private String mHeader;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
* 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
@@ -27,20 +27,21 @@ import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URI;
@@ -50,14 +51,11 @@ import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,18 +81,16 @@ import org.slf4j.LoggerFactory;
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
@Resource
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)
public OpenMHzBroadcaster(OpenMHzConfiguration config)
{
super(config);
mAliasModel = aliasModel;
}
/**
@@ -239,7 +235,6 @@ import org.slf4j.LoggerFactory;
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
String talkgroup = getTo(audioRecording);
String radioId = getFrom(audioRecording);
String talkerAlias = getTalkerAlias(audioRecording);
Long frequency = getFrequency(audioRecording);
String patches = getPatches(audioRecording);
String talkgroupLabel = getTalkgroupLabel(audioRecording);
@@ -273,8 +268,8 @@ import org.slf4j.LoggerFactory;
.addPart(FormField.TALKGROUP_NUM, talkgroup)
.addPart(FormField.EMERGENCY, 0)
.addPart(FormField.API_KEY, getBroadcastConfiguration().getApiKey())
.addPart(FormField.PATCH_LIST, patches)
.addPart(FormField.TALKER_ALIAS, talkerAlias)
// 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()
@@ -417,40 +412,22 @@ import org.slf4j.LoggerFactory;
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
private String getTo(AudioRecording audioRecording)
private static String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier != null)
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
}
return 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";
@@ -556,28 +533,6 @@ import org.slf4j.LoggerFactory;
return "[]";
}
/**
* Creates a formatted string with talkerAlias identifier
*/
private static String getTalkerAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier)
{
TalkerAliasIdentifier talkerID = (TalkerAliasIdentifier)identifier;
if(talkerID.isValid())
{
return talkerID.getValue();
}
}
}
return "";
}
/**
* Tests both the connection and configuration against the OpenMHz Call API service
* @param configuration containing API key and system id
@@ -622,7 +577,7 @@ import org.slf4j.LoggerFactory;
return "No Response";
}
catch(Exception e)
{
{
Throwable throwableCause = e.getCause();
if(throwableCause instanceof ConnectException)

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* 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
@@ -19,23 +19,16 @@
package io.github.dsheirer.audio.broadcast.openmhz;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster;
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.gui.playlist.streaming.AbstractBroadcastEditor;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.controlsfx.control.ToggleSwitch;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* OpenMHz calls API configuration editor
@@ -51,11 +44,9 @@ import io.github.dsheirer.playlist.PlaylistManager;
/**
* Constructs an instance
* @param playlistManager for accessing the broadcast model
*/
public OpenMHzEditor(PlaylistManager playlistManager)
public OpenMHzEditor()
{
super(playlistManager);
}
@Override

View File

@@ -36,7 +36,6 @@ public enum FormField
SOURCES("sources"),
SYSTEM("system"),
SYSTEM_LABEL("systemLabel"),
TALKER_ALIAS("talkerAlias"),
TALKGROUP_ID("talkgroup"),
TALKGROUP_GROUP("talkgroupGroup"),
TALKGROUP_LABEL("talkgroupLabel"),

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
* 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
@@ -27,20 +27,18 @@ import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.util.ThreadPool;
import jakarta.annotation.Resource;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
@@ -49,7 +47,6 @@ import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedTransferQueue;
@@ -80,18 +77,16 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
.build();
private long mLastConnectionAttempt;
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
@Resource
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)
public RdioScannerBroadcaster(RdioScannerConfiguration config)
{
super(config);
mAliasModel = aliasModel;
}
/**
@@ -229,7 +224,6 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
String talkgroup = getTo(audioRecording);
String radioId = getFrom(audioRecording);
String talkerAlias = getTalkerAlias(audioRecording);
Long frequency = getFrequency(audioRecording);
String patches = getPatches(audioRecording);
String talkgroupLabel = getTalkgroupLabel(audioRecording);
@@ -264,7 +258,6 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
.addPart(FormField.TALKGROUP_ID, talkgroup)
.addPart(FormField.SOURCE, radioId)
.addPart(FormField.FREQUENCY, frequency)
.addPart(FormField.TALKER_ALIAS, talkerAlias)
.addPart(FormField.TALKGROUP_LABEL, talkgroupLabel)
.addPart(FormField.TALKGROUP_GROUP, talkgroupGroup)
.addPart(FormField.SYSTEM_LABEL, systemLabel)
@@ -412,62 +405,26 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
return "0";
}
private static String getTalkerAlias(AudioRecording audioRecording)
{
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
{
if(identifier instanceof TalkerAliasIdentifier)
{
TalkerAliasIdentifier talkerID = (TalkerAliasIdentifier)identifier;
if(talkerID.isValid())
{
return talkerID.getValue();
}
}
}
return "";
}
/**
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
*
*/
private String getTo(AudioRecording audioRecording)
private static String getTo(AudioRecording audioRecording)
{
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
if(identifier != null)
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
if(aliasList != null)
{
List<Alias> aliases = aliasList.getAliases(identifier);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
return String.valueOf(streamAs.get().getStreamTalkgroupAlias().getValue());
}
}
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
{
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
}
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
{
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
talkgroupIdentifier.getProtocol()));
}
else if(identifier instanceof RadioIdentifier radioIdentifier)
{
return radioIdentifier.getValue().toString();
}
return 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";
@@ -480,7 +437,6 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
*/
private String getTalkgroupLabel(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
@@ -505,7 +461,6 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
*/
private String getTalkgroupGroup(AudioRecording audioRecording)
{
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* 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
@@ -18,7 +18,6 @@
*/
package io.github.dsheirer.audio.broadcast.shoutcast.v1;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
@@ -26,6 +25,10 @@ import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.util.ThreadPool;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.ConnectFuture;
@@ -37,11 +40,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicBoolean;
public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
{
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1AudioBroadcaster.class);
@@ -50,7 +48,6 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
private NioSocketConnector mSocketConnector;
private IoSession mStreamingSession = null;
private IBroadcastMetadataUpdater mMetadataUpdater;
private AliasModel mAliasModel;
private long mLastConnectionAttempt = 0;
private AtomicBoolean mConnecting = new AtomicBoolean();
@@ -65,10 +62,9 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
* @param configuration for the Shoutcast stream
*/
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
MP3Setting mp3Setting)
{
super(configuration, inputAudioFormat, mp3Setting);
mAliasModel = aliasModel;
}
/**
@@ -84,7 +80,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
{
if(mMetadataUpdater == null)
{
mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getConfiguration(), mAliasModel);
mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getConfiguration());
}
return mMetadataUpdater;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2025 Dennis Sheirer
* 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
@@ -29,6 +29,7 @@ import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.util.ThreadPool;
import jakarta.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
@@ -36,7 +37,6 @@ import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -58,6 +58,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
private final static String UTF8 = "UTF-8";
private ShoutcastV1Configuration mShoutcastV1Configuration;
@Resource
private AliasModel mAliasModel;
private NioSocketConnector mSocketConnector;
private AtomicBoolean mUpdating = new AtomicBoolean();
@@ -70,10 +71,9 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
* broadcaster. When multiple metadata updates are received prior to completion of the current ongoing update
* sequence, those updates will be queued and processed in the order received.
*/
public ShoutcastV1BroadcastMetadataUpdater(ShoutcastV1Configuration shoutcastV1Configuration, AliasModel aliasModel)
public ShoutcastV1BroadcastMetadataUpdater(ShoutcastV1Configuration shoutcastV1Configuration)
{
mShoutcastV1Configuration = shoutcastV1Configuration;
mAliasModel = aliasModel;
}
/**
@@ -212,20 +212,10 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
if(to != null)
{
sb.append("TO:").append(to);
List<Alias> aliases = aliasList.getAliases(to);
//Check for 'Stream As Talkgroup' alias and use this instead of the decoded TO value.
Optional<Alias> streamAs = aliases.stream().filter(alias -> alias.getStreamTalkgroupAlias() != null).findFirst();
if(streamAs.isPresent())
{
sb.append("TO:").append(streamAs.get().getStreamTalkgroupAlias().getValue());
}
else
{
sb.append("TO:").append(to);
}
if(!aliases.isEmpty())
{
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));

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