mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 01:57:39 -05:00
Compare commits
209 Commits
v0.5.0
...
1723-audio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dc12fd257 | ||
|
|
93f7122f57 | ||
|
|
af2b80a066 | ||
|
|
42e7a49d37 | ||
|
|
686bddf7d5 | ||
|
|
1c420b8eee | ||
|
|
e2a0325d07 | ||
|
|
9d03c151af | ||
|
|
3c18bfdc81 | ||
|
|
d89ba65238 | ||
|
|
d2cf2ee7b5 | ||
|
|
7a03331955 | ||
|
|
7c5ef3803a | ||
|
|
6f3e7c86e5 | ||
|
|
fc51de2b4f | ||
|
|
e1e75710cb | ||
|
|
87ce08e9b9 | ||
|
|
1a5c7521a5 | ||
|
|
b54e94dfbe | ||
|
|
19977d5f4d | ||
|
|
0492f19a95 | ||
|
|
098b039154 | ||
|
|
5c6e6059f8 | ||
|
|
72d45543df | ||
|
|
e96fc46315 | ||
|
|
f0cfbb80dd | ||
|
|
a199f03a60 | ||
|
|
526c9609ef | ||
|
|
ec9ceeefb3 | ||
|
|
b6c6505824 | ||
|
|
93c61c48f9 | ||
|
|
9b89e80bb5 | ||
|
|
e0ecaa6e08 | ||
|
|
a7c89b5d1a | ||
|
|
834846d339 | ||
|
|
45c8bb73fa | ||
|
|
d2580f8a99 | ||
|
|
804c2e6359 | ||
|
|
3f2ec612d5 | ||
|
|
8bead998f5 | ||
|
|
2ecdaba183 | ||
|
|
82cb10a03d | ||
|
|
8717ce38eb | ||
|
|
5858a85a50 | ||
|
|
65d16da997 | ||
|
|
b1b5ae5fd2 | ||
|
|
b5ec6d8877 | ||
|
|
8599aebed9 | ||
|
|
2b9e753033 | ||
|
|
dba50744a9 | ||
|
|
490569e023 | ||
|
|
e79f2af759 | ||
|
|
ec7ee1ce91 | ||
|
|
672207c27c | ||
|
|
a1a6b3e988 | ||
|
|
2347135614 | ||
|
|
93438ddd97 | ||
|
|
ae31c41643 | ||
|
|
16aed8741d | ||
|
|
e878ca0c7f | ||
|
|
ca61dd2543 | ||
|
|
6187253589 | ||
|
|
b51c80742f | ||
|
|
15352b9216 | ||
|
|
a848b21ae8 | ||
|
|
279aca7287 | ||
|
|
88879c956a | ||
|
|
729b7e27a8 | ||
|
|
feaed168ae | ||
|
|
50edb18db3 | ||
|
|
7302e2806d | ||
|
|
8845306b4a | ||
|
|
09e19e1486 | ||
|
|
0f5478925c | ||
|
|
09dce0b0d1 | ||
|
|
ad98b1cb5e | ||
|
|
8effd97749 | ||
|
|
9dbed72c32 | ||
|
|
b4dd3692b8 | ||
|
|
030cbb856c | ||
|
|
511101e9b7 | ||
|
|
60dd2b2df7 | ||
|
|
218dc94e02 | ||
|
|
2440f578b1 | ||
|
|
09c87237cb | ||
|
|
3d974ddd91 | ||
|
|
1489c970ba | ||
|
|
9e0afca3d1 | ||
|
|
99eda4013e | ||
|
|
a62f81f890 | ||
|
|
66f0731731 | ||
|
|
dc239801c6 | ||
|
|
d4ba0988bc | ||
|
|
ad0f953b2a | ||
|
|
e1bc9eabd6 | ||
|
|
d3dc1a8667 | ||
|
|
bf278120de | ||
|
|
2f52f6e009 | ||
|
|
02363c3113 | ||
|
|
cfaac0fcbc | ||
|
|
bee0405e00 | ||
|
|
50bdf0ee81 | ||
|
|
a27ddbe0d0 | ||
|
|
0d652b9153 | ||
|
|
9468fd189b | ||
|
|
215737ad9b | ||
|
|
123c25b142 | ||
|
|
af56198f0e | ||
|
|
3c73fdfd6e | ||
|
|
b391ebbb5f | ||
|
|
bad5a1f9f0 | ||
|
|
a7470b463f | ||
|
|
d6c706cb35 | ||
|
|
a0d6950c75 | ||
|
|
2495200b36 | ||
|
|
bfe7ae0b1b | ||
|
|
4912abbf07 | ||
|
|
2f2998d02e | ||
|
|
c96349fa1c | ||
|
|
f640576cc3 | ||
|
|
77d725a488 | ||
|
|
2c0305f928 | ||
|
|
21fd61d507 | ||
|
|
8fb56a5342 | ||
|
|
5d7b73f876 | ||
|
|
85b2a38058 | ||
|
|
c78517ee75 | ||
|
|
ae116d3dfe | ||
|
|
93252f9a40 | ||
|
|
3c03a4db2e | ||
|
|
d0fcd617aa | ||
|
|
6ca0decbd6 | ||
|
|
ddd9ca933e | ||
|
|
b2cc29569e | ||
|
|
411735772b | ||
|
|
2ffb2963af | ||
|
|
227c88c8a2 | ||
|
|
cbe9c79498 | ||
|
|
1b8c100b43 | ||
|
|
30c5fd64f2 | ||
|
|
3a6b486340 | ||
|
|
ded7335dbf | ||
|
|
005a4f8119 | ||
|
|
1ac49bfa7f | ||
|
|
c302c4d121 | ||
|
|
b0a524cf62 | ||
|
|
d246ffff30 | ||
|
|
adfc8b2d6f | ||
|
|
ad2ab08466 | ||
|
|
f3369410b2 | ||
|
|
bac38a5bd8 | ||
|
|
e5f5df74ad | ||
|
|
b3837f581a | ||
|
|
b2ab6f58e9 | ||
|
|
e04b59ba15 | ||
|
|
366eacd234 | ||
|
|
f792876c0a | ||
|
|
19f7a5171b | ||
|
|
5b6377cc11 | ||
|
|
9502ca3069 | ||
|
|
77629000b2 | ||
|
|
ca7916f992 | ||
|
|
9de83843a3 | ||
|
|
cbab7bb0c9 | ||
|
|
3df8d60eed | ||
|
|
b39dd1a19f | ||
|
|
0af5963d08 | ||
|
|
4f8b8e978b | ||
|
|
5654254cbb | ||
|
|
11f37e896a | ||
|
|
e8f563deeb | ||
|
|
f877354a11 | ||
|
|
df3d3b5ddc | ||
|
|
59e74d70b4 | ||
|
|
8d6b7c3ac8 | ||
|
|
372d2ff870 | ||
|
|
32a8363b45 | ||
|
|
87f1f8986f | ||
|
|
b3c08053fb | ||
|
|
a5208dcf1e | ||
|
|
2782953518 | ||
|
|
366055145e | ||
|
|
39ebdb47b1 | ||
|
|
c788292db8 | ||
|
|
f0dc80437c | ||
|
|
2d6c5c2331 | ||
|
|
bc4e6cb5b0 | ||
|
|
d69ad46c10 | ||
|
|
ba7b616f88 | ||
|
|
8355b7e3b9 | ||
|
|
22ffb656be | ||
|
|
de80d2a6c1 | ||
|
|
a320b4a24f | ||
|
|
fc13777211 | ||
|
|
75347f9d0a | ||
|
|
9abf64c6ab | ||
|
|
1702103f5d | ||
|
|
8f68a989b9 | ||
|
|
b51a0102ef | ||
|
|
e177c2c6b0 | ||
|
|
3567fdf51d | ||
|
|
323097ba65 | ||
|
|
33f2bab7ac | ||
|
|
c063cfb201 | ||
|
|
e99aa3d3b4 | ||
|
|
ec162ff84c | ||
|
|
f0db2865f2 | ||
|
|
f97b2150b9 | ||
|
|
8ce8aaddf9 |
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Create a report to help us improve sdrtrunk
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**sdrtrunk Version**
|
||||||
|
Indicate which sdrtrunk version you are using. Use 'master-branch' if you are running the latest code.
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Application Log**
|
||||||
|
If applicable, include either the full application log,or a snippet of the error logging.
|
||||||
|
|
||||||
|
**Desktop (optional - complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- CPU Cores: [e.g. 4 cores]
|
||||||
|
- RAM:[e.g. 16GB]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
16
.github/ISSUE_TEMPLATE/how-to-question-or-does-the-software-support-xxx.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/how-to-question-or-does-the-software-support-xxx.md
vendored
Normal 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
|
||||||
10
.github/workflows/gradle.yml
vendored
10
.github/workflows/gradle.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 20
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '20'
|
||||||
distribution: 'liberica'
|
distribution: 'liberica'
|
||||||
java-package: 'jdk+fx'
|
java-package: 'jdk+fx'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: clean build
|
arguments: clean build
|
||||||
|
|||||||
47
.github/workflows/nightly.yml
vendored
Normal file
47
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Create nightly release for Linux, Mac and Windows using the Linux runner.
|
||||||
|
name: Nightly Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
# Don't run nightly build for pull requests - uncomment to test changes to this action within scope of the PR review
|
||||||
|
# pull_request:
|
||||||
|
# branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nightly:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 20
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '20'
|
||||||
|
distribution: 'liberica'
|
||||||
|
java-package: 'jdk+fx'
|
||||||
|
|
||||||
|
- name: Build Linux and Mac releases with Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2
|
||||||
|
with:
|
||||||
|
arguments: runtimeZipOthers -PprojectVersion=nightly
|
||||||
|
|
||||||
|
- name: Build Windows release with Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2
|
||||||
|
with:
|
||||||
|
arguments: runtimeZipWindows -PprojectVersion=nightly
|
||||||
|
|
||||||
|
- name: Create Build Info file
|
||||||
|
run: |
|
||||||
|
echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" > build/image/build_info.txt
|
||||||
|
|
||||||
|
- name: Add build artifacts to nightly release
|
||||||
|
uses: pyTooling/Actions/releaser@main
|
||||||
|
with:
|
||||||
|
tag: nightly
|
||||||
|
rm: false
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
files: |
|
||||||
|
build/image/*.zip
|
||||||
|
build/image/build_info.txt
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" 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" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,8 +1,10 @@
|
|||||||

|

|
||||||
|

|
||||||
|
|
||||||
# sdrtrunk
|
# sdrtrunk
|
||||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||||
|
|
||||||
|
* [Help/Wiki Home Page](https://github.com/DSheirer/sdrtrunk/wiki)
|
||||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
||||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
||||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||||
@@ -18,6 +20,13 @@ All release versions of sdrtrunk are available from the [releases](https://githu
|
|||||||
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
|
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
|
||||||
* **(final)** These versions have been tested and are the current release version.
|
* **(final)** These versions have been tested and are the current release version.
|
||||||
|
|
||||||
|
## Download Nightly Software Build
|
||||||
|
The [nightly](https://github.com/DSheirer/sdrtrunk/releases/tag/nightly) release contains current builds of the software
|
||||||
|
for all supported operating systems. This version of the software may contain bugs and may not run correctly. However,
|
||||||
|
it let's you preview the most recent changes and fixes before the next software release. **Always backup your
|
||||||
|
playlist(s) before you use the nightly builds.** Note: the nightly release is updated each time code changes are
|
||||||
|
committed to the code base, so it's not really 'nightly' as much as it is 'current'.
|
||||||
|
|
||||||
## Minimum System Requirements
|
## Minimum System Requirements
|
||||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
||||||
* **CPU:** 4-core
|
* **CPU:** 4-core
|
||||||
|
|||||||
36
artifacts/sdrplay-api-headers/README.txt
Normal file
36
artifacts/sdrplay-api-headers/README.txt
Normal 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.
|
||||||
|
|
||||||
229
artifacts/sdrplay-api-headers/v3_07/sdrplay_api.h
Normal file
229
artifacts/sdrplay-api-headers/v3_07/sdrplay_api.h
Normal 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
|
||||||
78
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_callback.h
Normal file
78
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_callback.h
Normal 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
|
||||||
55
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_control.h
Normal 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
|
||||||
51
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_dev.h
Normal 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
|
||||||
22
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp1a.h
Normal 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
|
||||||
35
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp2.h
Normal 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
|
||||||
40
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDuo.h
Normal 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
|
||||||
45
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDx.h
Normal file
45
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDx.h
Normal 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
|
||||||
21
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rx_channel.h
Normal 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
|
||||||
93
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_tuner.h
Normal 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
|
||||||
230
artifacts/sdrplay-api-headers/v3_08/sdrplay_api.h
Normal file
230
artifacts/sdrplay-api-headers/v3_08/sdrplay_api.h
Normal 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
|
||||||
78
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_callback.h
Normal file
78
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_callback.h
Normal 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
|
||||||
55
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_control.h
Normal 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
|
||||||
51
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_dev.h
Normal 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
|
||||||
22
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp1a.h
Normal 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
|
||||||
35
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp2.h
Normal 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
|
||||||
40
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDuo.h
Normal 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
|
||||||
45
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDx.h
Normal file
45
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDx.h
Normal 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
|
||||||
21
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rx_channel.h
Normal 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
|
||||||
93
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_tuner.h
Normal 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
|
||||||
230
artifacts/sdrplay-api-headers/v3_11/sdrplay_api.h
Normal file
230
artifacts/sdrplay-api-headers/v3_11/sdrplay_api.h
Normal 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
|
||||||
79
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_callback.h
Normal file
79
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_callback.h
Normal 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
|
||||||
55
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_control.h
Normal 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
|
||||||
51
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_dev.h
Normal 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
|
||||||
22
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp1a.h
Normal 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
|
||||||
35
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp2.h
Normal 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
|
||||||
40
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDuo.h
Normal 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
|
||||||
46
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDx.h
Normal file
46
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDx.h
Normal 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
|
||||||
21
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rx_channel.h
Normal 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
|
||||||
93
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_tuner.h
Normal 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
|
||||||
296
build.gradle
296
build.gradle
@@ -1,6 +1,8 @@
|
|||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,11 +18,36 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions for building/compiling the sdrtrunk application.
|
||||||
|
*
|
||||||
|
* Prerequisites:
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* Scenario 2: create an application release for the current operating system
|
||||||
|
* command: ./gradlew runtimeZipCurrent
|
||||||
|
* Note: release image is located in the /build/image/ directory
|
||||||
|
*
|
||||||
|
* Scenario 3: create releases for Linux and OSX operating systems using downloaded JDKs
|
||||||
|
* command: ./gradlew runtimeZipOthers
|
||||||
|
* Note: release images are located in the /build/image/ directory
|
||||||
|
*
|
||||||
|
* Scenario 4: create release for Windows operating system using downloaded JDK
|
||||||
|
* command: ./gradlew runtimeZipWindows
|
||||||
|
* Note: release image is located in the /build/image/ directory
|
||||||
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'idea'
|
id 'idea'
|
||||||
id 'org.beryx.runtime' version '1.12.7'
|
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 {
|
repositories {
|
||||||
@@ -28,8 +55,15 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.5.0'
|
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
|
||||||
sourceCompatibility = '17'
|
version = project.findProperty('projectVersion')
|
||||||
|
|
||||||
|
//Java 20 is required for this version of the Project Panama preview/incubator feature
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs 'src/main'
|
main.java.srcDirs 'src/main'
|
||||||
@@ -60,6 +94,7 @@ dependencies {
|
|||||||
implementation 'ch.qos.logback:logback-core: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.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
|
||||||
implementation 'com.fazecast:jSerialComm:2.9.3'
|
implementation 'com.fazecast:jSerialComm:2.9.3'
|
||||||
|
implementation 'com.github.jiconfont:jiconfont-elusive:2.0.3'
|
||||||
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
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-javafx:1.0.0'
|
||||||
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||||
@@ -71,6 +106,7 @@ dependencies {
|
|||||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||||
implementation 'commons-io:commons-io:2.11.0'
|
implementation 'commons-io:commons-io:2.11.0'
|
||||||
implementation 'eu.hansolo:charts:1.0.5'
|
implementation 'eu.hansolo:charts:1.0.5'
|
||||||
|
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
|
||||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
|
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
|
||||||
implementation 'javax.usb:usb-api:1.0.2'
|
implementation 'javax.usb:usb-api:1.0.2'
|
||||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||||
@@ -78,31 +114,61 @@ dependencies {
|
|||||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||||
implementation 'org.apache.commons:commons-csv:1.9.0'
|
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||||
|
implementation 'org.apache.derby:derby'
|
||||||
|
implementation 'org.apache.derby:derbytools'
|
||||||
implementation 'org.apache.mina:mina-core:2.2.1'
|
implementation 'org.apache.mina:mina-core:2.2.1'
|
||||||
implementation 'org.apache.mina:mina-http:2.2.1'
|
implementation 'org.apache.mina:mina-http:2.2.1'
|
||||||
implementation 'org.controlsfx:controlsfx:11.1.2'
|
implementation 'org.controlsfx:controlsfx:11.1.2'
|
||||||
implementation 'org.rauschig:jarchivelib:1.2.0'
|
implementation 'org.rauschig:jarchivelib:1.2.0'
|
||||||
implementation 'org.slf4j:slf4j-api:2.0.5'
|
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:libusb4java:1.3.0'
|
||||||
implementation 'org.usb4java:usb4java:1.3.0'
|
implementation 'org.usb4java:usb4java:1.3.0'
|
||||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||||
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
|
|
||||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is needed for the JDK17 vector API ... until it moves out of incubation
|
* This is needed for the JDK19 panama and vector API ... until it moves out of incubation or preview
|
||||||
*/
|
*/
|
||||||
tasks.withType(JavaCompile) {
|
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
|
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM Arguments for running the application on Windows.
|
||||||
|
*/
|
||||||
|
def jvmArgsWindows =
|
||||||
|
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||||
|
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', //windows users for JIDE
|
||||||
|
'--add-modules=jdk.incubator.vector', //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', //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=/usr/local/lib'] //Allows Project Panama Foreign Function access to system library
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||||
applicationDefaultJvmArgs =
|
|
||||||
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
if(os.isWindows()) {
|
||||||
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
|
applicationDefaultJvmArgs = jvmArgsWindows
|
||||||
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
|
}
|
||||||
|
else {
|
||||||
|
applicationDefaultJvmArgs = jvmArgsLinux
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@@ -111,7 +177,7 @@ jar {
|
|||||||
'Implementation-Title' : 'sdrtrunk project',
|
'Implementation-Title' : 'sdrtrunk project',
|
||||||
'Implementation-Version' : archiveVersion,
|
'Implementation-Version' : archiveVersion,
|
||||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||||
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
'Build-Timestamp' : new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||||
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
||||||
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
||||||
)
|
)
|
||||||
@@ -139,88 +205,158 @@ String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
|||||||
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
||||||
*/
|
*/
|
||||||
String jdk_base = '/home/denny/java_jdks/'
|
String jdk_base = '/home/denny/java_jdks/'
|
||||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.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-19.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-19.0.1-full.jdk'
|
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-20.0.1-full.jdk'
|
||||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.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-19.0.1-full'
|
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-20.0.1-full'
|
||||||
|
|
||||||
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
|
|
||||||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
|
* Download URLs to download the JDK as part of the gradle build packaging process
|
||||||
* including this option: '-PdownloadJdks=true'
|
|
||||||
*/
|
*/
|
||||||
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
|
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"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
|
* Configures the runtime zip task with additional options/settings.
|
||||||
*
|
* @param rt task to configure
|
||||||
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
|
* @param jvmArgs to use (windows or linux)
|
||||||
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
|
* @return configured task.
|
||||||
*
|
|
||||||
* >: gradle runtimeZip
|
|
||||||
*
|
|
||||||
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
|
|
||||||
*
|
|
||||||
* >: gradle runtimeZip -PdownloadJdks=true
|
|
||||||
*/
|
*/
|
||||||
runtime {
|
def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
|
||||||
//Build by automatically downloading JDKs for the OS and CPU targets
|
//Use the correct set of JVM arguments
|
||||||
if(downloadJdks) {
|
rt.extension.launcherData.get().jvmArgs = jvmArgs
|
||||||
println("Building all runtime images")
|
|
||||||
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
|
|
||||||
targetPlatform(targetLinuxAarch64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetLinuxX86_64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetOsxAarch64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetOsxX86_64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetWindowsX86_64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Build using pre-downloaded JDKs
|
|
||||||
else if(hasTargetJdks) {
|
|
||||||
println("Building all runtime images using pre-downloaded JDKs")
|
|
||||||
if(file(jdk_linux_aarch64).exists()) {
|
|
||||||
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
|
|
||||||
}
|
|
||||||
if(file(jdk_linux_x86_64).exists()) {
|
|
||||||
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
|
|
||||||
}
|
|
||||||
if(file(jdk_osx_aarch64).exists()) {
|
|
||||||
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
|
|
||||||
}
|
|
||||||
if(file(jdk_osx_x86_64).exists()) {
|
|
||||||
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
|
|
||||||
}
|
|
||||||
if(file(jdk_windows_x86_64).exists()) {
|
|
||||||
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Build using locally installed JDK only
|
|
||||||
else {
|
|
||||||
println("Building a single runtime image using locally installed JDK")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Additional modules to include with auto-detected modules.
|
//Additional modules to include with auto-detected modules.
|
||||||
modules = ['jdk.crypto.ec'] //jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
|
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
|
||||||
|
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
|
||||||
|
//jdk.accessibility is used with assistive technologies like screen readers
|
||||||
|
//java.management for JVM resource monitoring
|
||||||
|
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management', 'java.instrument')
|
||||||
|
|
||||||
//Use auto-detected modules and 'add' any specified modules.
|
//Use auto-detected modules and 'add' any specified modules.
|
||||||
additive = true
|
rt.extension.additive.set(true)
|
||||||
|
|
||||||
//Recommended options for reducing/shrinking runtime target image size
|
//Recommended options for reducing/shrinking runtime target image size
|
||||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
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.
|
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||||
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
|
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
|
||||||
file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a release package for the current operating system.
|
||||||
|
*
|
||||||
|
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||||
|
*
|
||||||
|
* Usage: gradle runtimeZipCurrent
|
||||||
|
* Usage: gradlew runtimeZipCurrent (windows - using gradlew)
|
||||||
|
* Usage: ./gradlew runtimeZipCurrent (linux - using gradlew)
|
||||||
|
*/
|
||||||
|
tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||||
|
def arch = System.properties.getProperty("os.arch")
|
||||||
|
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
|
||||||
|
|
||||||
|
if(os.isWindows()) {
|
||||||
|
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
|
||||||
|
configure(rt, jvmArgsWindows)
|
||||||
|
}
|
||||||
|
else if(os.isLinux())
|
||||||
|
{
|
||||||
|
if(arch.equals("amd64")) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
|
||||||
|
}
|
||||||
|
else if(arch.equals("aarch64")) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
|
||||||
|
}
|
||||||
|
configure(rt, jvmArgsLinux)
|
||||||
|
}
|
||||||
|
else if(os.isMacOsX())
|
||||||
|
{
|
||||||
|
if(arch.equals("amd64")) {
|
||||||
|
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
|
||||||
|
}
|
||||||
|
else if(arch.equals("aarch64")) {
|
||||||
|
rt.extension.targetPlatform(targetOsxAarch64, javaHome)
|
||||||
|
}
|
||||||
|
configure(rt, jvmArgsLinux)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Windows release package.
|
||||||
|
*
|
||||||
|
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||||
|
*
|
||||||
|
* Usage: gradle runtimeZipWindows
|
||||||
|
* Usage: gradlew runtimeZipWindows (windows - using gradlew)
|
||||||
|
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
|
||||||
|
*/
|
||||||
|
tasks.register('runtimeZipWindows', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||||
|
if(file(jdk_windows_x86_64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetWindowsX86_64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_windows_x86_64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(rt, jvmArgsWindows)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Linux & OSX release packages.
|
||||||
|
*
|
||||||
|
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||||
|
*
|
||||||
|
* Usage: gradle runtimeZipOthers
|
||||||
|
* Usage: gradlew runtimeZipOthers (windows - using gradlew)
|
||||||
|
* Usage: ./gradlew runtimeZipOthers (linux - using gradlew)
|
||||||
|
*/
|
||||||
|
tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||||
|
//Linux aarch64
|
||||||
|
if(file(jdk_linux_aarch64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetLinuxAarch64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_linux_aarch64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Linux x86-64
|
||||||
|
if(file(jdk_linux_x86_64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxX86_64, jdk_linux_x86_64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetLinuxX86_64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_linux_x86_64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//OSX aarch64
|
||||||
|
if(file(jdk_osx_aarch64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetOsxAarch64, jdk_osx_aarch64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetOsxAarch64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_osx_aarch64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//OSX x86-64
|
||||||
|
if(file(jdk_osx_x86_64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetOsxX86_64, jdk_osx_x86_64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetOsxX86_64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_osx_x86_64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(rt, jvmArgsLinux);
|
||||||
}
|
}
|
||||||
|
|||||||
31
gradle.properties
Normal file
31
gradle.properties
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#
|
||||||
|
# ******************************************************************************
|
||||||
|
# Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
|
||||||
|
# Version naming conventions:
|
||||||
|
# Alpha: maj.min.patch-alpha-build
|
||||||
|
# Beta: maj.min.patch-beta-build
|
||||||
|
# Release: maj.min.patch
|
||||||
|
#
|
||||||
|
# Example: 0.6.0-beta-5
|
||||||
|
#
|
||||||
|
# Note: this is so that the file names for the resultant release build products match the GitLab release asset
|
||||||
|
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||||
|
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||||
|
|
||||||
|
projectVersion=0.7.0-alpha-1
|
||||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,37 +19,31 @@
|
|||||||
package io.github.dsheirer.alias;
|
package io.github.dsheirer.alias;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.action.AliasAction;
|
import io.github.dsheirer.alias.action.AliasAction;
|
||||||
import io.github.dsheirer.alias.action.AliasActionType;
|
|
||||||
import io.github.dsheirer.alias.action.beep.BeepAction;
|
import io.github.dsheirer.alias.action.beep.BeepAction;
|
||||||
import io.github.dsheirer.alias.action.clip.ClipAction;
|
import io.github.dsheirer.alias.action.clip.ClipAction;
|
||||||
import io.github.dsheirer.alias.action.script.ScriptAction;
|
import io.github.dsheirer.alias.action.script.ScriptAction;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.AliasIDType;
|
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
||||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
|
||||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
|
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||||
import io.github.dsheirer.alias.id.record.Record;
|
import io.github.dsheirer.alias.id.record.Record;
|
||||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||||
|
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class AliasFactory
|
public class AliasFactory
|
||||||
{
|
{
|
||||||
@@ -64,6 +58,11 @@ public class AliasFactory
|
|||||||
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
||||||
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
||||||
return copyBroadcast;
|
return copyBroadcast;
|
||||||
|
case DCS:
|
||||||
|
Dcs originalDcs = (Dcs)id;
|
||||||
|
Dcs copyDcs = new Dcs();
|
||||||
|
copyDcs.setDCSCode(originalDcs.getDCSCode());
|
||||||
|
return copyDcs;
|
||||||
case ESN:
|
case ESN:
|
||||||
Esn originalESN = (Esn)id;
|
Esn originalESN = (Esn)id;
|
||||||
Esn copyESN = new Esn();
|
Esn copyESN = new Esn();
|
||||||
@@ -80,6 +79,18 @@ public class AliasFactory
|
|||||||
Min copyMin = new Min();
|
Min copyMin = new Min();
|
||||||
copyMin.setMin(originalMin.getMin());
|
copyMin.setMin(originalMin.getMin());
|
||||||
return copyMin;
|
return copyMin;
|
||||||
|
case P25_FULLY_QUALIFIED_RADIO_ID:
|
||||||
|
P25FullyQualifiedRadio originalP25 = (P25FullyQualifiedRadio) id;
|
||||||
|
P25FullyQualifiedRadio copyP25 = new P25FullyQualifiedRadio(originalP25.getWacn(),
|
||||||
|
originalP25.getSystem(), originalP25.getValue());
|
||||||
|
copyP25.setOverlap(originalP25.overlapProperty().get());
|
||||||
|
return copyP25;
|
||||||
|
case P25_FULLY_QUALIFIED_TALKGROUP:
|
||||||
|
P25FullyQualifiedTalkgroup originalFqt = (P25FullyQualifiedTalkgroup) id;
|
||||||
|
P25FullyQualifiedTalkgroup copyFqt = new P25FullyQualifiedTalkgroup(originalFqt.getWacn(),
|
||||||
|
originalFqt.getSystem(), originalFqt.getValue());
|
||||||
|
copyFqt.setOverlap(originalFqt.overlapProperty().get());
|
||||||
|
return copyFqt;
|
||||||
case PRIORITY:
|
case PRIORITY:
|
||||||
Priority originalPriority = (Priority)id;
|
Priority originalPriority = (Priority)id;
|
||||||
Priority copyPriority = new Priority();
|
Priority copyPriority = new Priority();
|
||||||
@@ -187,6 +198,7 @@ public class AliasFactory
|
|||||||
copy.setGroup(original.getGroup());
|
copy.setGroup(original.getGroup());
|
||||||
copy.setColor(original.getColor());
|
copy.setColor(original.getColor());
|
||||||
copy.setIconName(original.getIconName());
|
copy.setIconName(original.getIconName());
|
||||||
|
copy.setRecordable(original.isRecordable());
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,65 +246,4 @@ public class AliasFactory
|
|||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AliasID getAliasID(AliasIDType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BROADCAST_CHANNEL:
|
|
||||||
return new BroadcastChannel();
|
|
||||||
case ESN:
|
|
||||||
return new Esn();
|
|
||||||
case FLEETSYNC:
|
|
||||||
return new FleetsyncID();
|
|
||||||
case LTR_NET_UID:
|
|
||||||
return new UniqueID();
|
|
||||||
case LOJACK:
|
|
||||||
return new LoJackFunctionAndID();
|
|
||||||
case MDC1200:
|
|
||||||
return new MDC1200ID();
|
|
||||||
case MIN:
|
|
||||||
return new Min();
|
|
||||||
case MPT1327:
|
|
||||||
return new MPT1327ID();
|
|
||||||
case NON_RECORDABLE:
|
|
||||||
return new NonRecordable();
|
|
||||||
case PRIORITY:
|
|
||||||
return new Priority();
|
|
||||||
case RADIO_ID:
|
|
||||||
return new Radio();
|
|
||||||
case RADIO_ID_RANGE:
|
|
||||||
return new RadioRange();
|
|
||||||
case RECORD:
|
|
||||||
return new Record();
|
|
||||||
case TALKGROUP:
|
|
||||||
return new Talkgroup();
|
|
||||||
case TALKGROUP_RANGE:
|
|
||||||
return new TalkgroupRange();
|
|
||||||
case SITE:
|
|
||||||
return new SiteID();
|
|
||||||
case STATUS:
|
|
||||||
return new UserStatusID();
|
|
||||||
case LEGACY_TALKGROUP:
|
|
||||||
return new LegacyTalkgroupID();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AliasAction getAliasAction(AliasActionType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BEEP:
|
|
||||||
return new BeepAction();
|
|
||||||
case CLIP:
|
|
||||||
return new ClipAction();
|
|
||||||
case SCRIPT:
|
|
||||||
return new ScriptAction();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,32 +21,34 @@ package io.github.dsheirer.alias;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
|
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||||
|
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
|
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
|
||||||
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
|
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
|
||||||
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneSequence;
|
import io.github.dsheirer.identifier.tone.ToneSequence;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -57,6 +59,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
||||||
@@ -67,6 +73,7 @@ public class AliasList
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
||||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
|
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
|
||||||
private Map<String,Alias> mESNMap = new HashMap<>();
|
private Map<String,Alias> mESNMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
||||||
@@ -152,6 +159,32 @@ public class AliasList
|
|||||||
|
|
||||||
talkgroupRangeAliasList.add(talkgroupRange, alias);
|
talkgroupRangeAliasList.add(talkgroupRange, alias);
|
||||||
break;
|
break;
|
||||||
|
case P25_FULLY_QUALIFIED_RADIO_ID:
|
||||||
|
P25FullyQualifiedRadio qualifiedRadio = (P25FullyQualifiedRadio) id;
|
||||||
|
|
||||||
|
RadioAliasList p25RadioAliasList = mRadioProtocolMap.get(qualifiedRadio.getProtocol());
|
||||||
|
|
||||||
|
if(p25RadioAliasList == null)
|
||||||
|
{
|
||||||
|
p25RadioAliasList = new RadioAliasList();
|
||||||
|
mRadioProtocolMap.put(qualifiedRadio.getProtocol(), p25RadioAliasList);
|
||||||
|
}
|
||||||
|
|
||||||
|
p25RadioAliasList.add(qualifiedRadio, alias);
|
||||||
|
break;
|
||||||
|
case P25_FULLY_QUALIFIED_TALKGROUP:
|
||||||
|
P25FullyQualifiedTalkgroup qualifiedTalkgroup = (P25FullyQualifiedTalkgroup) id;
|
||||||
|
|
||||||
|
TalkgroupAliasList p25TalkgroupAliasList = mTalkgroupProtocolMap.get(qualifiedTalkgroup.getProtocol());
|
||||||
|
|
||||||
|
if(p25TalkgroupAliasList == null)
|
||||||
|
{
|
||||||
|
p25TalkgroupAliasList = new TalkgroupAliasList();
|
||||||
|
mTalkgroupProtocolMap.put(qualifiedTalkgroup.getProtocol(), p25TalkgroupAliasList);
|
||||||
|
}
|
||||||
|
|
||||||
|
p25TalkgroupAliasList.add(qualifiedTalkgroup, alias);
|
||||||
|
break;
|
||||||
case RADIO_ID:
|
case RADIO_ID:
|
||||||
Radio radio = (Radio)id;
|
Radio radio = (Radio)id;
|
||||||
|
|
||||||
@@ -178,6 +211,12 @@ public class AliasList
|
|||||||
|
|
||||||
radioRangeAliasList.add(radioRange, alias);
|
radioRangeAliasList.add(radioRange, alias);
|
||||||
break;
|
break;
|
||||||
|
case DCS:
|
||||||
|
if(id instanceof Dcs dcs)
|
||||||
|
{
|
||||||
|
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ESN:
|
case ESN:
|
||||||
String esn = ((Esn)id).getEsn();
|
String esn = ((Esn)id).getEsn();
|
||||||
|
|
||||||
@@ -379,6 +418,8 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PATCH_GROUP:
|
case PATCH_GROUP:
|
||||||
|
List<Alias> aliases = new ArrayList<>();
|
||||||
|
|
||||||
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
||||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
@@ -386,8 +427,6 @@ public class AliasList
|
|||||||
|
|
||||||
if(patchGroupAliasList != null)
|
if(patchGroupAliasList != null)
|
||||||
{
|
{
|
||||||
List<Alias> aliases = new ArrayList<>();
|
|
||||||
|
|
||||||
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
||||||
|
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
@@ -395,19 +434,36 @@ public class AliasList
|
|||||||
aliases.add(alias);
|
aliases.add(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
|
for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
{
|
{
|
||||||
Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup);
|
Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup);
|
||||||
|
|
||||||
if(patchedAlias != null && !aliases.contains(patchedAlias))
|
if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias))
|
||||||
{
|
{
|
||||||
aliases.add(patchedAlias);
|
aliases.add(patchedTalkgroupAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return aliases;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
if(patchGroup.hasPatchedRadios())
|
||||||
|
{
|
||||||
|
RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol());
|
||||||
|
|
||||||
|
if(radioAliasList != null)
|
||||||
|
{
|
||||||
|
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
|
||||||
|
{
|
||||||
|
Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio);
|
||||||
|
|
||||||
|
if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias))
|
||||||
|
{
|
||||||
|
aliases.add(patchedRadioAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliases;
|
||||||
case RADIO:
|
case RADIO:
|
||||||
RadioIdentifier radio = (RadioIdentifier)identifier;
|
RadioIdentifier radio = (RadioIdentifier)identifier;
|
||||||
|
|
||||||
@@ -439,9 +495,9 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TONE:
|
case TONE:
|
||||||
if(identifier instanceof ToneIdentifier)
|
if(identifier instanceof ToneIdentifier toneIdentifier)
|
||||||
{
|
{
|
||||||
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
|
ToneSequence toneSequence = toneIdentifier.getValue();
|
||||||
|
|
||||||
if(toneSequence != null && toneSequence.hasTones())
|
if(toneSequence != null && toneSequence.hasTones())
|
||||||
{
|
{
|
||||||
@@ -454,6 +510,15 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(identifier instanceof DCSIdentifier dcsIdentifier)
|
||||||
|
{
|
||||||
|
DCSCode dcsCode = dcsIdentifier.getValue();
|
||||||
|
|
||||||
|
if(dcsCode != null)
|
||||||
|
{
|
||||||
|
return toList(mDCSCodeAliasMap.get(dcsCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,9 +530,7 @@ public class AliasList
|
|||||||
{
|
{
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
{
|
{
|
||||||
List<Alias> aliases = new ArrayList<>();
|
return Collections.singletonList(alias);
|
||||||
aliases.add(alias);
|
|
||||||
return aliases;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
@@ -590,6 +653,7 @@ public class AliasList
|
|||||||
*/
|
*/
|
||||||
public class TalkgroupAliasList
|
public class TalkgroupAliasList
|
||||||
{
|
{
|
||||||
|
private Map<String,Alias> mFullyQualifiedTalkgroupAliasMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mTalkgroupAliasMap = new TreeMap<>();
|
private Map<Integer,Alias> mTalkgroupAliasMap = new TreeMap<>();
|
||||||
private Map<TalkgroupRange, Alias> mTalkgroupRangeAliasMap = new HashMap<>();
|
private Map<TalkgroupRange, Alias> mTalkgroupRangeAliasMap = new HashMap<>();
|
||||||
|
|
||||||
@@ -599,6 +663,11 @@ public class AliasList
|
|||||||
|
|
||||||
public Alias getAlias(TalkgroupIdentifier identifier)
|
public Alias getAlias(TalkgroupIdentifier identifier)
|
||||||
{
|
{
|
||||||
|
if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti)
|
||||||
|
{
|
||||||
|
return mFullyQualifiedTalkgroupAliasMap.get(fqti.toString());
|
||||||
|
}
|
||||||
|
|
||||||
int value = identifier.getValue();
|
int value = identifier.getValue();
|
||||||
|
|
||||||
Alias mapValue = mTalkgroupAliasMap.get(value);
|
Alias mapValue = mTalkgroupAliasMap.get(value);
|
||||||
@@ -620,26 +689,55 @@ public class AliasList
|
|||||||
|
|
||||||
public void add(Talkgroup talkgroup, Alias alias)
|
public void add(Talkgroup talkgroup, Alias alias)
|
||||||
{
|
{
|
||||||
//Detect talkgroup collisions and set overlap flag for both
|
if(talkgroup instanceof P25FullyQualifiedTalkgroup fqt)
|
||||||
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
|
|
||||||
{
|
{
|
||||||
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
|
//Detect collisions
|
||||||
|
if(mFullyQualifiedTalkgroupAliasMap.containsKey(fqt.getHashKey()))
|
||||||
if(!existing.equals(alias))
|
|
||||||
{
|
{
|
||||||
talkgroup.setOverlap(true);
|
Alias existing = mFullyQualifiedTalkgroupAliasMap.get(fqt.getHashKey());
|
||||||
|
|
||||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
if(!existing.equals(alias))
|
||||||
{
|
{
|
||||||
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
|
fqt.setOverlap(true);
|
||||||
|
|
||||||
|
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||||
{
|
{
|
||||||
aliasID.setOverlap(true);
|
if(aliasID instanceof P25FullyQualifiedTalkgroup existingFqt &&
|
||||||
|
existingFqt.getHashKey().contentEquals(fqt.getHashKey()))
|
||||||
|
{
|
||||||
|
aliasID.setOverlap(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFullyQualifiedTalkgroupAliasMap.put(fqt.getHashKey(), alias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Detect talkgroup collisions and set overlap flag for both
|
||||||
|
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
|
||||||
|
{
|
||||||
|
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
|
||||||
|
|
||||||
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
|
if(!existing.equals(alias))
|
||||||
|
{
|
||||||
|
talkgroup.setOverlap(true);
|
||||||
|
|
||||||
|
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||||
|
{
|
||||||
|
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
|
||||||
|
{
|
||||||
|
aliasID.setOverlap(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(TalkgroupRange talkgroupRange, Alias alias)
|
public void add(TalkgroupRange talkgroupRange, Alias alias)
|
||||||
@@ -672,6 +770,7 @@ public class AliasList
|
|||||||
*/
|
*/
|
||||||
public class RadioAliasList
|
public class RadioAliasList
|
||||||
{
|
{
|
||||||
|
private Map<String,Alias> mFullyQualifiedRadioAliasMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mRadioAliasMap = new TreeMap<>();
|
private Map<Integer,Alias> mRadioAliasMap = new TreeMap<>();
|
||||||
private Map<RadioRange, Alias> mRadioRangeAliasMap = new HashMap<>();
|
private Map<RadioRange, Alias> mRadioRangeAliasMap = new HashMap<>();
|
||||||
|
|
||||||
@@ -681,6 +780,11 @@ public class AliasList
|
|||||||
|
|
||||||
public Alias getAlias(RadioIdentifier identifier)
|
public Alias getAlias(RadioIdentifier identifier)
|
||||||
{
|
{
|
||||||
|
if(identifier instanceof FullyQualifiedRadioIdentifier fqri)
|
||||||
|
{
|
||||||
|
return mFullyQualifiedRadioAliasMap.get(fqri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
int value = identifier.getValue();
|
int value = identifier.getValue();
|
||||||
|
|
||||||
Alias mapValue = mRadioAliasMap.get(value);
|
Alias mapValue = mRadioAliasMap.get(value);
|
||||||
@@ -702,26 +806,55 @@ public class AliasList
|
|||||||
|
|
||||||
public void add(Radio radio, Alias alias)
|
public void add(Radio radio, Alias alias)
|
||||||
{
|
{
|
||||||
//Detect collisions
|
if(radio instanceof P25FullyQualifiedRadio fqr)
|
||||||
if(mRadioAliasMap.containsKey(radio.getValue()))
|
|
||||||
{
|
{
|
||||||
Alias existing = mRadioAliasMap.get(radio.getValue());
|
//Detect collisions
|
||||||
|
if(mFullyQualifiedRadioAliasMap.containsKey(fqr.getHashKey()))
|
||||||
if(!existing.equals(alias))
|
|
||||||
{
|
{
|
||||||
radio.setOverlap(true);
|
Alias existing = mFullyQualifiedRadioAliasMap.get(fqr.getHashKey());
|
||||||
|
|
||||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
if(!existing.equals(alias))
|
||||||
{
|
{
|
||||||
if(aliasID instanceof Radio && ((Radio)aliasID).getValue() == radio.getValue())
|
fqr.setOverlap(true);
|
||||||
|
|
||||||
|
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||||
{
|
{
|
||||||
aliasID.setOverlap(true);
|
if(aliasID instanceof P25FullyQualifiedRadio existingFqr &&
|
||||||
|
existingFqr.getHashKey().contentEquals(fqr.getHashKey()))
|
||||||
|
{
|
||||||
|
aliasID.setOverlap(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFullyQualifiedRadioAliasMap.put(fqr.getHashKey(), alias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Detect collisions
|
||||||
|
if(mRadioAliasMap.containsKey(radio.getValue()))
|
||||||
|
{
|
||||||
|
Alias existing = mRadioAliasMap.get(radio.getValue());
|
||||||
|
|
||||||
mRadioAliasMap.put(radio.getValue(), alias);
|
if(!existing.equals(alias))
|
||||||
|
{
|
||||||
|
radio.setOverlap(true);
|
||||||
|
|
||||||
|
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||||
|
{
|
||||||
|
if(aliasID instanceof Radio existingRadio && (existingRadio.getValue() == radio.getValue()))
|
||||||
|
{
|
||||||
|
aliasID.setOverlap(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mRadioAliasMap.put(radio.getValue(), alias);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(RadioRange radioRange, Alias alias)
|
public void add(RadioRange radioRange, Alias alias)
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,23 +23,24 @@ import io.github.dsheirer.alias.id.AliasIDType;
|
|||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
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
|
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
|
||||||
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
|
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
|
||||||
* identifiers produced by channel decoder(s).
|
* identifiers produced by channel decoder(s).
|
||||||
*/
|
*/
|
||||||
|
@Component("aliasModel")
|
||||||
public class AliasModel
|
public class AliasModel
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class);
|
||||||
@@ -184,10 +185,8 @@ public class AliasModel
|
|||||||
*/
|
*/
|
||||||
public void addAliases(List<Alias> aliases)
|
public void addAliases(List<Alias> aliases)
|
||||||
{
|
{
|
||||||
for(Alias alias : aliases)
|
removeAliases(aliases);
|
||||||
{
|
mAliases.addAll(aliases);
|
||||||
addAlias(alias);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -241,6 +240,26 @@ public class AliasModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the list of aliases from this model and any alias lists that might contain each alias.
|
||||||
|
* @param aliases
|
||||||
|
*/
|
||||||
|
public void removeAliases(List<Alias> aliases)
|
||||||
|
{
|
||||||
|
if(aliases != null && !aliases.isEmpty())
|
||||||
|
{
|
||||||
|
mAliases.removeAll(aliases);
|
||||||
|
|
||||||
|
for(Alias alias: aliases)
|
||||||
|
{
|
||||||
|
if(hasAliasList(alias.getAliasListName()))
|
||||||
|
{
|
||||||
|
getAliasList(alias.getAliasListName()).removeAlias(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
|
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
|
||||||
* @param aliasListName to search
|
* @param aliasListName to search
|
||||||
|
|||||||
@@ -1,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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,19 +24,19 @@ import io.github.dsheirer.alias.Alias;
|
|||||||
import io.github.dsheirer.alias.action.AliasActionType;
|
import io.github.dsheirer.alias.action.AliasActionType;
|
||||||
import io.github.dsheirer.alias.action.RecurringAction;
|
import io.github.dsheirer.alias.action.RecurringAction;
|
||||||
import io.github.dsheirer.audio.AudioFormats;
|
import io.github.dsheirer.audio.AudioFormats;
|
||||||
import io.github.dsheirer.gui.preference.playback.ToneFrequency;
|
import io.github.dsheirer.gui.preference.audio.playback.ToneFrequency;
|
||||||
import io.github.dsheirer.gui.preference.playback.ToneUtil;
|
import io.github.dsheirer.gui.preference.audio.playback.ToneUtil;
|
||||||
import io.github.dsheirer.gui.preference.playback.ToneVolume;
|
import io.github.dsheirer.gui.preference.audio.playback.ToneVolume;
|
||||||
import io.github.dsheirer.message.IMessage;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioSystem;
|
import javax.sound.sampled.AudioSystem;
|
||||||
import javax.sound.sampled.Clip;
|
import javax.sound.sampled.Clip;
|
||||||
import javax.sound.sampled.DataLine;
|
import javax.sound.sampled.DataLine;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.ShortBuffer;
|
|
||||||
|
|
||||||
public class BeepAction extends RecurringAction
|
public class BeepAction extends RecurringAction
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.alias.id;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
|
|||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
||||||
@@ -37,11 +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.legacy.uniqueID.UniqueID;
|
||||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
|
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||||
import io.github.dsheirer.alias.id.record.Record;
|
import io.github.dsheirer.alias.id.record.Record;
|
||||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||||
|
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
@@ -54,6 +54,7 @@ import javafx.util.Callback;
|
|||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
||||||
|
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
|
||||||
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
||||||
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
||||||
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
||||||
@@ -62,6 +63,8 @@ import javafx.util.Callback;
|
|||||||
@JsonSubTypes.Type(value = Min.class, name = "min"),
|
@JsonSubTypes.Type(value = Min.class, name = "min"),
|
||||||
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
|
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
|
||||||
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
|
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
|
||||||
|
@JsonSubTypes.Type(value = P25FullyQualifiedRadio.class, name = "p25FullyQualifiedRadio"),
|
||||||
|
@JsonSubTypes.Type(value = P25FullyQualifiedTalkgroup.class, name = "p25FullyQualifiedTalkgroup"),
|
||||||
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
||||||
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
|
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
|
||||||
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
|
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.alias.id;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,11 +23,14 @@ import java.util.EnumSet;
|
|||||||
public enum AliasIDType
|
public enum AliasIDType
|
||||||
{
|
{
|
||||||
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
||||||
|
DCS("Digital Coded Squelch (DCS)"),
|
||||||
ESN("ESN"),
|
ESN("ESN"),
|
||||||
INVERT("Audio Inversion"),
|
INVERT("Audio Inversion"),
|
||||||
LOJACK("LoJack"),
|
LOJACK("LoJack"),
|
||||||
LTR_NET_UID("LTR-Net UID"),
|
LTR_NET_UID("LTR-Net UID"),
|
||||||
MIN("Passport MIN"),
|
MIN("Passport MIN"),
|
||||||
|
P25_FULLY_QUALIFIED_RADIO_ID("P25 Fully Qualified Radio ID"),
|
||||||
|
P25_FULLY_QUALIFIED_TALKGROUP("P25 Fully Qualified Talkgroup"),
|
||||||
PRIORITY("Audio Priority"),
|
PRIORITY("Audio Priority"),
|
||||||
RADIO_ID("Radio ID"),
|
RADIO_ID("Radio ID"),
|
||||||
RADIO_ID_RANGE("Radio ID Range"),
|
RADIO_ID_RANGE("Radio ID Range"),
|
||||||
@@ -58,7 +58,7 @@ public enum AliasIDType
|
|||||||
|
|
||||||
//Values used by the View-By alias editor
|
//Values used by the View-By alias editor
|
||||||
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
|
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
|
||||||
UNIT_STATUS, STATUS, TONES);
|
P25_FULLY_QUALIFIED_RADIO_ID, P25_FULLY_QUALIFIED_TALKGROUP, UNIT_STATUS, STATUS, TONES);
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|||||||
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.alias.id.dcs;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digital Coded Squelch (DCS) tone identifier
|
||||||
|
*/
|
||||||
|
public class Dcs extends AliasID implements Comparable<Dcs>
|
||||||
|
{
|
||||||
|
private DCSCode mDCSCode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AliasIDType getType()
|
||||||
|
{
|
||||||
|
return AliasIDType.DCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(AliasID id)
|
||||||
|
{
|
||||||
|
if(isValid() && id instanceof Dcs other)
|
||||||
|
{
|
||||||
|
return other.isValid() && getDCSCode().equals(other.getDCSCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCS code
|
||||||
|
* @return DCS code or null.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "code")
|
||||||
|
public DCSCode getDCSCode()
|
||||||
|
{
|
||||||
|
return mDCSCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the DCS code value.
|
||||||
|
* @param dcsCode to set
|
||||||
|
*/
|
||||||
|
public void setDCSCode(DCSCode dcsCode)
|
||||||
|
{
|
||||||
|
mDCSCode = dcsCode;
|
||||||
|
updateValueProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mDCSCode != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAudioIdentifier()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "DCS-Invalid - No Tone Selected";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Dcs o)
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
if(o.isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().compareTo(o.getDCSCode());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.alias.id.radio;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully qualified radio. Note: this is only for P25.
|
||||||
|
*/
|
||||||
|
public class P25FullyQualifiedRadio extends Radio
|
||||||
|
{
|
||||||
|
private int mWacn;
|
||||||
|
private int mSystem;
|
||||||
|
|
||||||
|
public P25FullyQualifiedRadio()
|
||||||
|
{
|
||||||
|
//No-arg JAXB constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param wacn for the radio
|
||||||
|
* @param system for the radio
|
||||||
|
* @param id for the radio
|
||||||
|
*/
|
||||||
|
public P25FullyQualifiedRadio(int wacn, int system, int id)
|
||||||
|
{
|
||||||
|
super(Protocol.APCO25, id);
|
||||||
|
mWacn = wacn;
|
||||||
|
mSystem = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AliasIDType getType()
|
||||||
|
{
|
||||||
|
return AliasIDType.P25_FULLY_QUALIFIED_RADIO_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WACN for this radio
|
||||||
|
* @return wacn
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "wacn")
|
||||||
|
public int getWacn()
|
||||||
|
{
|
||||||
|
return mWacn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the WACN for this radio
|
||||||
|
* @param wacn for this radio
|
||||||
|
*/
|
||||||
|
public void setWacn(int wacn)
|
||||||
|
{
|
||||||
|
mWacn = wacn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System for this radio
|
||||||
|
* @return system
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "system")
|
||||||
|
public int getSystem()
|
||||||
|
{
|
||||||
|
return mSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system for this radio
|
||||||
|
* @param system for this radio
|
||||||
|
*/
|
||||||
|
public void setSystem(int system)
|
||||||
|
{
|
||||||
|
mSystem = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append("Fully Qualified Radio ID:").append(getWacn());
|
||||||
|
sb.append(".").append(getSystem());
|
||||||
|
sb.append(".").append(getValue());
|
||||||
|
sb.append(" Protocol:").append((getProtocol()));
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append(" **NOT VALID**");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashable string key for use in a lookup map
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public String getHashKey()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(getWacn());
|
||||||
|
sb.append(".").append(getSystem());
|
||||||
|
sb.append(".").append(getValue());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.alias.id.talkgroup;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully qualified talkgroup. Note: this is only for P25.
|
||||||
|
*/
|
||||||
|
public class P25FullyQualifiedTalkgroup extends Talkgroup
|
||||||
|
{
|
||||||
|
private int mWacn;
|
||||||
|
private int mSystem;
|
||||||
|
|
||||||
|
public P25FullyQualifiedTalkgroup()
|
||||||
|
{
|
||||||
|
//No-arg JAXB constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param wacn for the radio
|
||||||
|
* @param system for the radio
|
||||||
|
* @param talkgroup value
|
||||||
|
*/
|
||||||
|
public P25FullyQualifiedTalkgroup(int wacn, int system, int talkgroup)
|
||||||
|
{
|
||||||
|
super(Protocol.APCO25, talkgroup);
|
||||||
|
mWacn = wacn;
|
||||||
|
mSystem = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AliasIDType getType()
|
||||||
|
{
|
||||||
|
return AliasIDType.P25_FULLY_QUALIFIED_TALKGROUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WACN for this radio
|
||||||
|
* @return wacn
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "wacn")
|
||||||
|
public int getWacn()
|
||||||
|
{
|
||||||
|
return mWacn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the WACN for this radio
|
||||||
|
* @param wacn for this radio
|
||||||
|
*/
|
||||||
|
public void setWacn(int wacn)
|
||||||
|
{
|
||||||
|
mWacn = wacn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System for this radio
|
||||||
|
* @return system
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "system")
|
||||||
|
public int getSystem()
|
||||||
|
{
|
||||||
|
return mSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system for this radio
|
||||||
|
* @param system for this radio
|
||||||
|
*/
|
||||||
|
public void setSystem(int system)
|
||||||
|
{
|
||||||
|
mSystem = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append("Fully Qualified Radio ID:").append(getWacn());
|
||||||
|
sb.append(".").append(getSystem());
|
||||||
|
sb.append(".").append(getValue());
|
||||||
|
sb.append(" Protocol:").append((getProtocol()));
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append(" **NOT VALID**");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashable string key for use in a lookup map
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public String getHashKey()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(getWacn());
|
||||||
|
sb.append(".").append(getSystem());
|
||||||
|
sb.append(".").append(getValue());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ import io.github.dsheirer.protocol.Protocol;
|
|||||||
*/
|
*/
|
||||||
public enum TalkgroupFormat
|
public enum TalkgroupFormat
|
||||||
{
|
{
|
||||||
|
AM("*****", 1, 0xFFFF, "1 to 65,535",
|
||||||
|
"AM valid value range is 1-65,535"),
|
||||||
APCO25("********", 0, 0xFFFF, "0 to 65,535",
|
APCO25("********", 0, 0xFFFF, "0 to 65,535",
|
||||||
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
||||||
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||||
@@ -122,6 +124,8 @@ public enum TalkgroupFormat
|
|||||||
|
|
||||||
switch(protocol)
|
switch(protocol)
|
||||||
{
|
{
|
||||||
|
case AM:
|
||||||
|
return AM;
|
||||||
case APCO25:
|
case APCO25:
|
||||||
return APCO25;
|
return APCO25;
|
||||||
case FLEETSYNC:
|
case FLEETSYNC:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.alias.id.talkgroup;
|
package io.github.dsheirer.alias.id.talkgroup;
|
||||||
@@ -23,21 +22,20 @@ package io.github.dsheirer.alias.id.talkgroup;
|
|||||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
||||||
|
import io.github.dsheirer.preference.identifier.talkgroup.AnalogTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
|
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
|
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
|
||||||
@@ -49,6 +47,10 @@ public class TalkgroupFormatter
|
|||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
|
AnalogTalkgroupFormatter analogTalkgroupFormatter = new AnalogTalkgroupFormatter();
|
||||||
|
mFormatterMap.put(Protocol.AM, analogTalkgroupFormatter);
|
||||||
|
mFormatterMap.put(Protocol.NBFM, analogTalkgroupFormatter);
|
||||||
|
|
||||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
||||||
@@ -57,7 +59,6 @@ public class TalkgroupFormatter
|
|||||||
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
||||||
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
|
|
||||||
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,12 +14,13 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.audio.call.AudioSegment;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
@@ -90,7 +90,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
|||||||
{
|
{
|
||||||
mAudioSegment.completeProperty().set(true);
|
mAudioSegment.completeProperty().set(true);
|
||||||
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
|
mIdentifierUpdateNotificationBroadcaster.removeListener(mAudioSegment);
|
||||||
mAudioSegment.decrementConsumerCount();
|
mAudioSegment.removeLease(getClass().toString());
|
||||||
mAudioSegment = null;
|
mAudioSegment = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,14 +106,14 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
|||||||
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
||||||
* listener(s).
|
* listener(s).
|
||||||
*/
|
*/
|
||||||
protected AudioSegment getAudioSegment()
|
public AudioSegment getAudioSegment()
|
||||||
{
|
{
|
||||||
synchronized(this)
|
synchronized(this)
|
||||||
{
|
{
|
||||||
if(mAudioSegment == null)
|
if(mAudioSegment == null)
|
||||||
{
|
{
|
||||||
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
|
mAudioSegment = new AudioSegment(mAliasList, getTimeslot());
|
||||||
mAudioSegment.incrementConsumerCount();
|
mAudioSegment.addLease(getClass().toString());
|
||||||
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
|
mAudioSegment.addIdentifiers(mIdentifierCollection.getIdentifiers());
|
||||||
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
|
mIdentifierUpdateNotificationBroadcaster.addListener(mAudioSegment);
|
||||||
|
|
||||||
@@ -124,7 +124,6 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
|||||||
|
|
||||||
if(mAudioSegmentListener != null)
|
if(mAudioSegmentListener != null)
|
||||||
{
|
{
|
||||||
mAudioSegment.incrementConsumerCount();
|
|
||||||
mAudioSegmentListener.receive(mAudioSegment);
|
mAudioSegmentListener.receive(mAudioSegment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,28 +134,35 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addAudio(float[] audioBuffer)
|
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
|
//If the current segment exceeds the max samples length, close it so that a new segment gets generated
|
||||||
//and then link the segments together
|
//and then link the segments together
|
||||||
if(mAudioSampleCount >= mMaxSegmentAudioSampleLength)
|
if(mAudioSampleCount >= mMaxSegmentAudioSampleLength)
|
||||||
{
|
{
|
||||||
AudioSegment previous = getAudioSegment();
|
AudioSegment previous = getAudioSegment();
|
||||||
closeAudioSegment();
|
closeAudioSegment();
|
||||||
audioSegment = getAudioSegment();
|
audioSegment = getAudioSegment();
|
||||||
audioSegment.linkTo(previous);
|
audioSegment.linkTo(previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
audioSegment.addAudio(audioBuffer);
|
audioSegment.addAudio(audioBuffer);
|
||||||
mAudioSampleCount += audioBuffer.length;
|
mAudioSampleCount += audioBuffer.length;
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
closeAudioSegment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
else
|
||||||
{
|
{
|
||||||
closeAudioSegment();
|
mLog.info("Attempt to add null audio from " + getClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
503
src/main/java/io/github/dsheirer/audio/AudioManager.java
Normal file
503
src/main/java/io/github/dsheirer/audio/AudioManager.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -43,6 +43,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||||
private static float[] sHighPassFilterCoefficients;
|
private static float[] sHighPassFilterCoefficients;
|
||||||
|
private final boolean mAudioFilterEnable;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
@@ -70,27 +71,33 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
*
|
*
|
||||||
* @param aliasList for aliasing identifiers
|
* @param aliasList for aliasing identifiers
|
||||||
|
* @param timeslot for this audio module
|
||||||
* @param maxAudioSegmentLength in milliseconds
|
* @param maxAudioSegmentLength in milliseconds
|
||||||
|
* @param audioFilterEnable to enable or disable high-pass audio filter
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
|
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, boolean audioFilterEnable)
|
||||||
{
|
{
|
||||||
super(aliasList, timeslot, maxAudioSegmentLength);
|
super(aliasList, timeslot, maxAudioSegmentLength);
|
||||||
|
mAudioFilterEnable = audioFilterEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
|
* @param aliasList for aliasing identifiers
|
||||||
|
* @param audioFilterEnable to enable or disable high-pass audio filter
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList)
|
public AudioModule(AliasList aliasList, boolean audioFilterEnable)
|
||||||
{
|
{
|
||||||
super(aliasList);
|
super(aliasList);
|
||||||
|
mAudioFilterEnable = audioFilterEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,7 +128,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||||
{
|
{
|
||||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
if(mAudioFilterEnable)
|
||||||
|
{
|
||||||
|
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
addAudio(audioBuffer);
|
addAudio(audioBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,11 +152,16 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
@Override
|
@Override
|
||||||
public void receive(SquelchStateEvent event)
|
public void receive(SquelchStateEvent event)
|
||||||
{
|
{
|
||||||
mSquelchState = event.getSquelchState();
|
SquelchState squelchState = event.getSquelchState();
|
||||||
|
|
||||||
if(mSquelchState == SquelchState.SQUELCH)
|
if(mSquelchState != squelchState)
|
||||||
{
|
{
|
||||||
closeAudioSegment();
|
mSquelchState = squelchState;
|
||||||
|
|
||||||
|
if(mSquelchState == SquelchState.SQUELCH)
|
||||||
|
{
|
||||||
|
closeAudioSegment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.call.AudioSegment;
|
||||||
import io.github.dsheirer.identifier.Form;
|
import io.github.dsheirer.identifier.Form;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
@@ -28,12 +29,11 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
|
import io.github.dsheirer.preference.duplicate.CallManagementPreference;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
import jakarta.annotation.PostConstruct;
|
||||||
import org.slf4j.LoggerFactory;
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -42,6 +42,9 @@ import java.util.concurrent.LinkedTransferQueue;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
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.
|
* Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method.
|
||||||
@@ -49,21 +52,32 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* Note: system in this context refers to the system name value that is used in channel configurations. All decoder
|
* 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.
|
* channels must share the same system name for call duplication detection.
|
||||||
*/
|
*/
|
||||||
|
@Component("duplicateCallDetector")
|
||||||
public class DuplicateCallDetector implements Listener<AudioSegment>
|
public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
||||||
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
|
private CallManagementPreference mCallManagementPreference;
|
||||||
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
||||||
|
@Resource
|
||||||
|
private UserPreferences mUserPreferences;
|
||||||
|
|
||||||
public DuplicateCallDetector(UserPreferences userPreferences)
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public DuplicateCallDetector()
|
||||||
{
|
{
|
||||||
mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference();
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void postConstruct()
|
||||||
|
{
|
||||||
|
mCallManagementPreference = mUserPreferences.getCallManagementPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive(AudioSegment audioSegment)
|
public void receive(AudioSegment audioSegment)
|
||||||
{
|
{
|
||||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled())
|
if(mCallManagementPreference.isDuplicateCallDetectionEnabled())
|
||||||
{
|
{
|
||||||
Identifier identifier = audioSegment.getIdentifierCollection()
|
Identifier identifier = audioSegment.getIdentifierCollection()
|
||||||
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||||
@@ -82,7 +96,9 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
mDetectorMap.put(system, detector);
|
mDetectorMap.put(system, detector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audioSegment.addLease(detector.getClass().toString());
|
||||||
detector.add(audioSegment);
|
detector.add(audioSegment);
|
||||||
|
audioSegment.removeLease(getClass().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +153,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
*/
|
*/
|
||||||
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
||||||
{
|
{
|
||||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
if(mCallManagementPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
||||||
{
|
{
|
||||||
//Step 1 check for duplicate TO values
|
//Step 1 check for duplicate TO values
|
||||||
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||||
@@ -149,7 +165,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByRadioEnabled())
|
if(mCallManagementPreference.isDuplicateCallDetectionByRadioEnabled())
|
||||||
{
|
{
|
||||||
//Step 2 check for duplicate FROM values
|
//Step 2 check for duplicate FROM values
|
||||||
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||||
@@ -242,7 +258,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
|
|
||||||
if(complete)
|
if(complete)
|
||||||
{
|
{
|
||||||
audioSegment.decrementConsumerCount();
|
audioSegment.removeLease(getClass().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return complete;
|
return complete;
|
||||||
@@ -271,7 +287,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
|||||||
if(isDuplicate(current, toCheck))
|
if(isDuplicate(current, toCheck))
|
||||||
{
|
{
|
||||||
toCheck.setDuplicate(true);
|
toCheck.setDuplicate(true);
|
||||||
toCheck.decrementConsumerCount();
|
toCheck.removeLease(getClass().toString());
|
||||||
duplicates.add(toCheck);
|
duplicates.add(toCheck);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.call.AudioSegment;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.call.AudioSegment;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,78 +19,82 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
import io.github.dsheirer.audio.AudioSegment;
|
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.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.IdentifierCollection;
|
||||||
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||||
import io.github.dsheirer.record.RecordFormat;
|
import io.github.dsheirer.record.RecordFormat;
|
||||||
import io.github.dsheirer.sample.Listener;
|
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import io.github.dsheirer.util.TimeStamp;
|
import io.github.dsheirer.util.TimeStamp;
|
||||||
import org.slf4j.Logger;
|
import jakarta.annotation.PostConstruct;
|
||||||
import org.slf4j.LoggerFactory;
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on
|
* Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on
|
||||||
* disk and enqueues the temporary recording for streaming.
|
* 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 final static Logger mLog = LoggerFactory.getLogger(AudioStreamingManager.class);
|
||||||
private LinkedTransferQueue<AudioSegment> mNewAudioSegments = new LinkedTransferQueue<>();
|
@Resource
|
||||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
private BroadcastModel mBroadcastModel;
|
||||||
private Listener<AudioRecording> mAudioRecordingListener;
|
@Resource
|
||||||
private BroadcastFormat mBroadcastFormat;
|
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
|
private LinkedTransferQueue<CompletedCall> mCompletedCallQueue = new LinkedTransferQueue<>();
|
||||||
|
private List<CompletedCall> mCompletedCalls = new ArrayList<>();
|
||||||
|
private BroadcastFormat mBroadcastFormat = BroadcastFormat.MP3;
|
||||||
private ScheduledFuture<?> mAudioSegmentProcessorFuture;
|
private ScheduledFuture<?> mAudioSegmentProcessorFuture;
|
||||||
private int mNextRecordingNumber = 1;
|
private int mNextRecordingNumber = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* 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
|
* Starts the scheduled audio segment processor
|
||||||
*/
|
*/
|
||||||
|
@PostConstruct
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
if(mAudioSegmentProcessorFuture == null)
|
if(mAudioSegmentProcessorFuture == null)
|
||||||
{
|
{
|
||||||
mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new CompletedCallProcessor(),
|
||||||
0, 250, TimeUnit.MILLISECONDS);
|
0, 250, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the scheduled audio segment processor
|
* Stops the scheduled audio segment processor
|
||||||
*/
|
*/
|
||||||
|
@PreDestroy
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
if(mAudioSegmentProcessorFuture != null)
|
if(mAudioSegmentProcessorFuture != null)
|
||||||
@@ -99,73 +103,148 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
|||||||
mAudioSegmentProcessorFuture = null;
|
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
|
* 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();
|
while(completedCall != null)
|
||||||
AudioSegment audioSegment;
|
|
||||||
while(it.hasNext())
|
|
||||||
{
|
{
|
||||||
audioSegment = it.next();
|
AudioSegment audioSegment = completedCall.audioSegment();
|
||||||
|
|
||||||
if(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference().isDuplicateStreamingSuppressionEnabled())
|
if(!(audioSegment.isDuplicate() && mUserPreferences.getCallManagementPreference().isDuplicateStreamingSuppressionEnabled()))
|
||||||
{
|
{
|
||||||
it.remove();
|
if(mBroadcastModel != null && audioSegment.hasBroadcastChannels())
|
||||||
audioSegment.decrementConsumerCount();
|
|
||||||
}
|
|
||||||
else if(audioSegment.completeProperty().get())
|
|
||||||
{
|
|
||||||
it.remove();
|
|
||||||
|
|
||||||
if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels())
|
|
||||||
{
|
{
|
||||||
Path path = getTemporaryRecordingPath();
|
IdentifierCollection identifiers =
|
||||||
long length = 0;
|
|
||||||
|
|
||||||
for(float[] audioBuffer: audioSegment.getAudioBuffers())
|
|
||||||
{
|
|
||||||
length += audioBuffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
|
|
||||||
IdentifierCollection identifierCollectionCopy =
|
|
||||||
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||||
|
|
||||||
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
|
if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
identifierCollectionCopy, audioSegment.getStartTimestamp(), length);
|
|
||||||
mAudioRecordingListener.receive(audioRecording);
|
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
{
|
||||||
mLog.error("Error recording temporary stream MP3");
|
if(mUserPreferences.getCallManagementPreference()
|
||||||
|
.getPatchGroupStreamingOption() == PatchGroupStreamingOption.TALKGROUPS)
|
||||||
|
{
|
||||||
|
//Decompose the patch group into the individual (patched) talkgroups and process the audio
|
||||||
|
//segment for each patched talkgroup.
|
||||||
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
|
List<Identifier> ids = new ArrayList<>();
|
||||||
|
ids.addAll(patchGroup.getPatchedTalkgroupIdentifiers());
|
||||||
|
ids.addAll(patchGroup.getPatchedRadioIdentifiers());
|
||||||
|
|
||||||
|
//If there are no patched radios/talkgroups, override user preference and stream as a patch group
|
||||||
|
if(ids.isEmpty() || audioSegment.getAliasList() == null)
|
||||||
|
{
|
||||||
|
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AliasList aliasList = audioSegment.getAliasList();
|
||||||
|
|
||||||
|
for(Identifier identifier: ids)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||||
|
Set<BroadcastChannel> broadcastChannels = new HashSet<>();
|
||||||
|
for(Alias alias: aliases)
|
||||||
|
{
|
||||||
|
broadcastChannels.addAll(alias.getBroadcastChannels());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!broadcastChannels.isEmpty())
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection decomposedIdentifiers =
|
||||||
|
new MutableIdentifierCollection(identifiers.getIdentifiers());
|
||||||
|
//Remove patch group TO identifier & replace with the patched talkgroup/radio
|
||||||
|
decomposedIdentifiers.remove(Role.TO);
|
||||||
|
decomposedIdentifiers.update(identifier);
|
||||||
|
processAudioSegment(audioSegment, decomposedIdentifiers, broadcastChannels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audioSegment.decrementConsumerCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audioSegment.removeLease(getClass().toString());
|
||||||
|
completedCall = mCompletedCallQueue.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes an audio segment for streaming by creating a temporary MP3 recording and submitting the recording
|
||||||
|
* to the specific broadcast channel(s).
|
||||||
|
* @param audioSegment to process for streaming
|
||||||
|
* @param identifierCollection to use for the streamed audio recording.
|
||||||
|
* @param broadcastChannels to receive the audio recording
|
||||||
|
*/
|
||||||
|
private void processAudioSegment(AudioSegment audioSegment, IdentifierCollection identifierCollection,
|
||||||
|
Set<BroadcastChannel> broadcastChannels)
|
||||||
|
{
|
||||||
|
Path path = getTemporaryRecordingPath();
|
||||||
|
long length = 0;
|
||||||
|
|
||||||
|
for(float[] audioBuffer: audioSegment.getAudioBuffers())
|
||||||
|
{
|
||||||
|
length += audioBuffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences, identifierCollection);
|
||||||
|
|
||||||
|
AudioRecording audioRecording = new AudioRecording(path, broadcastChannels, identifierCollection,
|
||||||
|
audioSegment.getStartTimestamp(), length);
|
||||||
|
mBroadcastModel.receive(audioRecording);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error recording temporary stream MP3");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,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
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
processAudioSegments();
|
processCompletedCalls();
|
||||||
}
|
}
|
||||||
catch(Throwable t)
|
catch(Throwable t)
|
||||||
{
|
{
|
||||||
mLog.error("Error processing audio segments for streaming", t);
|
mLog.error("Error processing completed calls for streaming", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||||
@@ -46,6 +48,8 @@ import java.net.SocketAddress;
|
|||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
|
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
|
||||||
|
@JsonSubTypes.Type(value = RdioScannerConfiguration.class, name="RdioScannerConfiguration"),
|
||||||
|
@JsonSubTypes.Type(value = OpenMHzConfiguration.class, name="OpenMHzConfiguration"),
|
||||||
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
|
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
|
||||||
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
||||||
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,6 +26,10 @@ import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
|
|||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
|
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.ShoutcastV1AudioBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster;
|
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster;
|
||||||
@@ -59,23 +63,26 @@ public class BroadcastFactory
|
|||||||
switch(configuration.getBroadcastServerType())
|
switch(configuration.getBroadcastServerType())
|
||||||
{
|
{
|
||||||
case BROADCASTIFY_CALL:
|
case BROADCASTIFY_CALL:
|
||||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration);
|
||||||
inputAudioFormat, mp3Setting, aliasModel);
|
case RDIOSCANNER_CALL:
|
||||||
|
return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration);
|
||||||
|
case OPENMHZ:
|
||||||
|
return new OpenMHzBroadcaster((OpenMHzConfiguration)configuration);
|
||||||
case BROADCASTIFY:
|
case BROADCASTIFY:
|
||||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
||||||
inputAudioFormat, mp3Setting, aliasModel);
|
inputAudioFormat, mp3Setting);
|
||||||
case ICECAST_TCP:
|
case ICECAST_TCP:
|
||||||
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
|
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
|
||||||
mp3Setting, aliasModel);
|
mp3Setting);
|
||||||
case ICECAST_HTTP:
|
case ICECAST_HTTP:
|
||||||
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
|
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
|
||||||
mp3Setting, aliasModel);
|
mp3Setting);
|
||||||
case SHOUTCAST_V1:
|
case SHOUTCAST_V1:
|
||||||
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
|
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
|
||||||
mp3Setting, aliasModel);
|
mp3Setting);
|
||||||
case SHOUTCAST_V2:
|
case SHOUTCAST_V2:
|
||||||
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
|
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
|
||||||
inputAudioFormat, mp3Setting, aliasModel);
|
inputAudioFormat, mp3Setting);
|
||||||
case UNKNOWN:
|
case UNKNOWN:
|
||||||
default:
|
default:
|
||||||
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
||||||
@@ -99,8 +106,12 @@ public class BroadcastFactory
|
|||||||
{
|
{
|
||||||
case BROADCASTIFY_CALL:
|
case BROADCASTIFY_CALL:
|
||||||
return new BroadcastifyCallConfiguration(format);
|
return new BroadcastifyCallConfiguration(format);
|
||||||
|
case RDIOSCANNER_CALL:
|
||||||
|
return new RdioScannerConfiguration(format);
|
||||||
case BROADCASTIFY:
|
case BROADCASTIFY:
|
||||||
return new BroadcastifyFeedConfiguration(format);
|
return new BroadcastifyFeedConfiguration(format);
|
||||||
|
case OPENMHZ:
|
||||||
|
return new OpenMHzConfiguration(format);
|
||||||
case ICECAST_HTTP:
|
case ICECAST_HTTP:
|
||||||
return new IcecastHTTPConfiguration(format);
|
return new IcecastHTTPConfiguration(format);
|
||||||
case ICECAST_TCP:
|
case ICECAST_TCP:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,12 +20,13 @@ package io.github.dsheirer.audio.broadcast;
|
|||||||
|
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -45,9 +46,11 @@ import javafx.collections.FXCollections;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
|
||||||
|
@Component("broadcastModel")
|
||||||
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(BroadcastModel.class);
|
private final static Logger mLog = LoggerFactory.getLogger(BroadcastModel.class);
|
||||||
@@ -68,28 +71,30 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
public static final String[] COLUMN_NAMES = new String[]
|
public static final String[] COLUMN_NAMES = new String[]
|
||||||
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "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 =
|
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
||||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||||
private List<AudioRecording> mRecordingQueue = new CopyOnWriteArrayList<>();
|
private List<AudioRecording> mRecordingQueue = new CopyOnWriteArrayList<>();
|
||||||
private Map<Integer,AbstractAudioBroadcaster> mBroadcasterMap = new HashMap<>();
|
private Map<Integer,AbstractAudioBroadcaster> mBroadcasterMap = new HashMap<>();
|
||||||
private IconModel mIconModel;
|
|
||||||
private AliasModel mAliasModel;
|
|
||||||
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
|
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
|
||||||
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
|
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
|
||||||
private UserPreferences mUserPreferences;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for managing Broadcast configurations and any associated broadcaster instances.
|
* 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
|
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
|
||||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
||||||
|
|
||||||
removeOrphanedTemporaryRecordings();
|
removeOrphanedTemporaryRecordings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,6 +32,8 @@ public enum BroadcastServerType
|
|||||||
|
|
||||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||||
ICECAST_TCP("Icecast (v2.3)", "images/icecast.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_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||||
UNKNOWN("Unknown", null);
|
UNKNOWN("Unknown", null);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public enum BroadcastState
|
|||||||
/**
|
/**
|
||||||
* Specified mount point is already in use
|
* Specified mount point is already in use
|
||||||
*/
|
*/
|
||||||
MOUNT_POINT_IN_USE("Mount Point In Use", true),
|
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network is unavailable or the server address cannot be resolved
|
* Network is unavailable or the server address cannot be resolved
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,10 +20,12 @@ package io.github.dsheirer.audio.broadcast;
|
|||||||
|
|
||||||
import io.github.dsheirer.icon.Icon;
|
import io.github.dsheirer.icon.Icon;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
|
||||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
import java.awt.Dimension;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
@@ -42,23 +44,15 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
private JTable mTable;
|
private JTable mTable;
|
||||||
private JTableColumnWidthMonitor mColumnWidthMonitor;
|
private JTableColumnWidthMonitor mColumnWidthMonitor;
|
||||||
private JScrollPane mScrollPane;
|
private JScrollPane mScrollPane;
|
||||||
|
@Resource
|
||||||
private BroadcastModel mBroadcastModel;
|
private BroadcastModel mBroadcastModel;
|
||||||
private UserPreferences mUserPreferences;
|
private String mPreferenceKey = "application.broadcast.status.panel";
|
||||||
private String mPreferenceKey;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* 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()
|
public JTable getTable()
|
||||||
@@ -66,21 +60,19 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
return mTable;
|
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]"));
|
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
|
||||||
|
|
||||||
mTable = new JTable(mBroadcastModel);
|
mTable = new JTable(mBroadcastModel);
|
||||||
|
mTable.setEnabled(false);
|
||||||
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)mTable.getDefaultRenderer(String.class);
|
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)mTable.getDefaultRenderer(String.class);
|
||||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
|
||||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
||||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
|
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);
|
mScrollPane = new JScrollPane(mTable);
|
||||||
|
|
||||||
add(mScrollPane);
|
add(mScrollPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for streaming management of patch groups. These options are used when streaming an audio call that is
|
||||||
|
* tagged to a patch group. The audio will either be streamed once and identified as the patch group, or it will
|
||||||
|
* be streamed multiple times, once for each individual patched talkgroup.
|
||||||
|
*/
|
||||||
|
public enum PatchGroupStreamingOption
|
||||||
|
{
|
||||||
|
PATCH_GROUP("Patch Group"),
|
||||||
|
TALKGROUPS("Individual Talkgroups");
|
||||||
|
|
||||||
|
private String mLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param label to display
|
||||||
|
*/
|
||||||
|
PatchGroupStreamingOption(String label)
|
||||||
|
{
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,13 +20,10 @@
|
|||||||
package io.github.dsheirer.audio.broadcast.broadcastify;
|
package io.github.dsheirer.audio.broadcast.broadcastify;
|
||||||
|
|
||||||
import com.google.common.net.HttpHeaders;
|
import com.google.common.net.HttpHeaders;
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
|
||||||
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
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.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||||
import io.github.dsheirer.identifier.Form;
|
import io.github.dsheirer.identifier.Form;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
@@ -38,9 +35,6 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -48,12 +42,13 @@ import java.net.http.HttpClient;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
||||||
@@ -82,10 +77,8 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
|||||||
/**
|
/**
|
||||||
* Constructs an instance of the broadcaster
|
* Constructs an instance of the broadcaster
|
||||||
* @param config to use
|
* @param config to use
|
||||||
* @param aliasModel for access to aliases
|
|
||||||
*/
|
*/
|
||||||
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, InputAudioFormat inputAudioFormat,
|
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config)
|
||||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
|
||||||
{
|
{
|
||||||
super(config);
|
super(config);
|
||||||
}
|
}
|
||||||
@@ -475,7 +468,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
|||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||||
{
|
{
|
||||||
sb.append(",").append(patched.getValue());
|
sb.append(",").append(patched.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast.icecast;
|
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.AudioStreamingBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||||
@@ -28,13 +27,11 @@ import io.github.dsheirer.audio.convert.MP3Setting;
|
|||||||
public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
|
public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
|
||||||
{
|
{
|
||||||
private IBroadcastMetadataUpdater mMetadataUpdater;
|
private IBroadcastMetadataUpdater mMetadataUpdater;
|
||||||
protected AliasModel mAliasModel;
|
|
||||||
|
|
||||||
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, InputAudioFormat inputAudioFormat,
|
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, InputAudioFormat inputAudioFormat,
|
||||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
MP3Setting mp3Setting)
|
||||||
{
|
{
|
||||||
super(broadcastConfiguration, inputAudioFormat, mp3Setting);
|
super(broadcastConfiguration, inputAudioFormat, mp3Setting);
|
||||||
mAliasModel = aliasModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +47,7 @@ public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
|
|||||||
{
|
{
|
||||||
if(mMetadataUpdater == null)
|
if(mMetadataUpdater == null)
|
||||||
{
|
{
|
||||||
mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration(), mAliasModel);
|
mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getConfiguration());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mMetadataUpdater;
|
return mMetadataUpdater;
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,23 +18,12 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast.icecast;
|
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.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
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.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.Role;
|
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
import jakarta.annotation.Resource;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -42,7 +31,8 @@ import java.net.URLEncoder;
|
|||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.util.List;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||||
{
|
{
|
||||||
@@ -50,6 +40,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
|||||||
private final static String UTF8 = "UTF-8";
|
private final static String UTF8 = "UTF-8";
|
||||||
private HttpClient mHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
|
private HttpClient mHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
|
||||||
private IcecastConfiguration mIcecastConfiguration;
|
private IcecastConfiguration mIcecastConfiguration;
|
||||||
|
@Resource
|
||||||
private AliasModel mAliasModel;
|
private AliasModel mAliasModel;
|
||||||
private boolean mConnectionLoggingSuppressed = false;
|
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
|
* 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.
|
* sequence, those updates will be queued and processed in the order received.
|
||||||
*/
|
*/
|
||||||
public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration, AliasModel aliasModel)
|
public IcecastBroadcastMetadataUpdater(IcecastConfiguration icecastConfiguration)
|
||||||
{
|
{
|
||||||
mIcecastConfiguration = icecastConfiguration;
|
mIcecastConfiguration = icecastConfiguration;
|
||||||
mAliasModel = aliasModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,6 +26,7 @@ import io.github.dsheirer.audio.convert.MP3Setting;
|
|||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -54,7 +55,8 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
||||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 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_0_OK_HEX_DUMP = "48 54 54 50 2F 31 2E 30 20 32 30 30 20 4F 4B";
|
||||||
|
@Resource
|
||||||
|
private AliasModel mAliasModel;
|
||||||
private NioSocketConnector mSocketConnector;
|
private NioSocketConnector mSocketConnector;
|
||||||
private IoSession mStreamingSession = null;
|
private IoSession mStreamingSession = null;
|
||||||
private Map<String,String> mHTTPHeaders;
|
private Map<String,String> mHTTPHeaders;
|
||||||
@@ -74,9 +76,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
* @param configuration for the Icecast stream
|
* @param configuration for the Icecast stream
|
||||||
*/
|
*/
|
||||||
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
||||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
MP3Setting mp3Setting)
|
||||||
{
|
{
|
||||||
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
|
super(configuration, inputAudioFormat, mp3Setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,6 +27,7 @@ import io.github.dsheirer.audio.convert.MP3Setting;
|
|||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
@@ -51,7 +52,8 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
||||||
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
|
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
|
||||||
private static final int WRITE_TIMEOUT_SECONDS = 5;
|
private static final int WRITE_TIMEOUT_SECONDS = 5;
|
||||||
|
@Resource
|
||||||
|
private AliasModel mAliasModel;
|
||||||
private NioSocketConnector mSocketConnector;
|
private NioSocketConnector mSocketConnector;
|
||||||
private IoSession mStreamingSession = null;
|
private IoSession mStreamingSession = null;
|
||||||
|
|
||||||
@@ -70,9 +72,9 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
* @param configuration for the Icecast stream
|
* @param configuration for the Icecast stream
|
||||||
*/
|
*/
|
||||||
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
||||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
MP3Setting mp3Setting)
|
||||||
{
|
{
|
||||||
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
|
super(configuration, inputAudioFormat, mp3Setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP headers used for posting to Rdio Scanner API
|
||||||
|
*/
|
||||||
|
public enum FormField
|
||||||
|
{
|
||||||
|
CALL("call"),
|
||||||
|
FREQ("freq"),
|
||||||
|
START_TIME("start_time"),
|
||||||
|
STOP_TIME("stop_time"),
|
||||||
|
CALL_LENGTH("call_length"),
|
||||||
|
TALKGROUP_NUM("talkgroup_num"),
|
||||||
|
EMERGENCY("emergency"),
|
||||||
|
API_KEY("api_key"),
|
||||||
|
SOURCE_LIST("source_list"),
|
||||||
|
FREQ_LIST("freq_list"),
|
||||||
|
PATCHES("patches");
|
||||||
|
|
||||||
|
private String mHeader;
|
||||||
|
|
||||||
|
FormField(String header)
|
||||||
|
{
|
||||||
|
mHeader = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeader()
|
||||||
|
{
|
||||||
|
return mHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,636 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||||
|
|
||||||
|
import com.google.common.net.HttpHeaders;
|
||||||
|
import io.github.dsheirer.alias.Alias;
|
||||||
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
|
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
||||||
|
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
|
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio broadcaster to push completed audio recordings to the OpenMHz call upload API.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class OpenMHzBroadcaster extends AbstractAudioBroadcaster<OpenMHzConfiguration>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzBroadcaster.class);
|
||||||
|
|
||||||
|
private static final String ENCODING_TYPE_MP3 = "mp3";
|
||||||
|
private static final String MULTIPART_TYPE = "multipart";
|
||||||
|
private static final String DEFAULT_SUBTYPE = "form-data";
|
||||||
|
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
|
||||||
|
private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
|
||||||
|
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||||
|
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
|
||||||
|
private HttpClient mHttpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_2)
|
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||||
|
.connectTimeout(Duration.ofSeconds(20))
|
||||||
|
.build();
|
||||||
|
private long mLastConnectionAttempt;
|
||||||
|
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
|
||||||
|
@Resource
|
||||||
|
private AliasModel mAliasModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of the broadcaster
|
||||||
|
* @param config to use
|
||||||
|
*/
|
||||||
|
public OpenMHzBroadcaster(OpenMHzConfiguration config)
|
||||||
|
{
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the audio recording processor thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
String response = testConnection(getBroadcastConfiguration());
|
||||||
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(response == "OK")
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else if (response == "No Response")
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.NO_SERVER);
|
||||||
|
mLog.error("Error connecting to OpenMHz server [Server not found or not reachable]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Error connecting to OpenMHz server on startup [" + response + "]");
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mAudioRecordingProcessorFuture == null)
|
||||||
|
{
|
||||||
|
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
|
||||||
|
0, 500, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the audio recording processor thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if(mAudioRecordingProcessorFuture != null)
|
||||||
|
{
|
||||||
|
mAudioRecordingProcessorFuture.cancel(true);
|
||||||
|
mAudioRecordingProcessorFuture = null;
|
||||||
|
dispose();
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares for disposal
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
|
||||||
|
while(audioRecording != null)
|
||||||
|
{
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
|
||||||
|
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
|
||||||
|
* the audio processor thread will persistently invoke this method to attempt a reconnect.
|
||||||
|
*/
|
||||||
|
private boolean connected()
|
||||||
|
{
|
||||||
|
if(getBroadcastState() != BroadcastState.CONNECTED &&
|
||||||
|
(System.currentTimeMillis() - mLastConnectionAttempt > mConnectionAttemptInterval))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
|
||||||
|
String response = testConnection(getBroadcastConfiguration());
|
||||||
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(response == "OK")
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else if (response == "No Response")
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.NO_SERVER);
|
||||||
|
mLog.error("Error reconnecting to OpenMHz server [Server not found or not reachable]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
mLog.error("Error reconnecting to OpenMHz server [" + response + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAudioQueueSize()
|
||||||
|
{
|
||||||
|
return mAudioRecordingQueue.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
mAudioRecordingQueue.offer(audioRecording);
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio recording is non-null and not too old, meaning that the age of the recording has not
|
||||||
|
* exceeded the max age value indicated in the broadcast configuration. Audio recordings that are too old will be
|
||||||
|
* deleted to ensure that the in-memory queue size doesn't blow up.
|
||||||
|
* @param audioRecording to test
|
||||||
|
* @return true if the recording is valid
|
||||||
|
*/
|
||||||
|
private boolean isValid(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
return audioRecording != null && System.currentTimeMillis() - audioRecording.getStartTime() <=
|
||||||
|
getBroadcastConfiguration().getMaximumRecordingAge();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes any enqueued audio recordings. This method employs asynchronous
|
||||||
|
* interaction with the server, so multiple audio recording uploads can occur simultaneously.
|
||||||
|
*/
|
||||||
|
private void processRecordingQueue()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
while(connected() && !mAudioRecordingQueue.isEmpty())
|
||||||
|
{
|
||||||
|
final AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||||
|
|
||||||
|
if(isValid(audioRecording) && audioRecording.getRecordingLength() > 0)
|
||||||
|
{
|
||||||
|
int durationSeconds = (int)(audioRecording.getRecordingLength() / 1E3f);
|
||||||
|
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
|
||||||
|
String talkgroup = getTo(audioRecording);
|
||||||
|
String radioId = getFrom(audioRecording);
|
||||||
|
Long frequency = getFrequency(audioRecording);
|
||||||
|
String patches = getPatches(audioRecording);
|
||||||
|
String talkgroupLabel = getTalkgroupLabel(audioRecording);
|
||||||
|
String talkgroupGroup = getTalkgroupGroup(audioRecording);
|
||||||
|
String systemLabel = getSystemLabel(audioRecording);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] audioBytes = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
audioBytes = Files.readAllBytes(audioRecording.getPath());
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("OpenMHz - audio recording file not found - ignoring upload");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(audioBytes != null)
|
||||||
|
{
|
||||||
|
String uri = getBroadcastConfiguration().getHost() + "/" + getBroadcastConfiguration().getSystemName() + "/upload";
|
||||||
|
|
||||||
|
OpenMHzBuilder bodyBuilder = new OpenMHzBuilder();
|
||||||
|
bodyBuilder
|
||||||
|
.addFile(audioBytes)
|
||||||
|
.addPart(FormField.FREQ, frequency)
|
||||||
|
.addPart(FormField.START_TIME, timestampSeconds)
|
||||||
|
.addPart(FormField.STOP_TIME, timestampSeconds)
|
||||||
|
.addPart(FormField.CALL_LENGTH, durationSeconds)
|
||||||
|
.addPart(FormField.TALKGROUP_NUM, talkgroup)
|
||||||
|
.addPart(FormField.EMERGENCY, 0)
|
||||||
|
.addPart(FormField.API_KEY, getBroadcastConfiguration().getApiKey())
|
||||||
|
// For future use if OpenMHz supports patch information
|
||||||
|
//.addPart(FormField.PATCHES, patches)
|
||||||
|
.addPart(FormField.SOURCE_LIST, "[{ \"pos\": 0.00, \"src\": " + radioId + "}]");
|
||||||
|
|
||||||
|
HttpRequest fileRequest = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
|
||||||
|
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||||
|
.POST(bodyBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mHttpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
|
||||||
|
.whenComplete((fileResponse, throwable1) -> {
|
||||||
|
if(throwable1 != null || fileResponse.statusCode() != 200)
|
||||||
|
{
|
||||||
|
if(throwable1 instanceof IOException || throwable1 instanceof CompletionException)
|
||||||
|
{
|
||||||
|
//We get socket reset exceptions occasionally when the remote server doesn't
|
||||||
|
//fully read our request and immediately responds.
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("OpenMHz API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("OpenMHz API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
if(fileResponse.statusCode() == 200)
|
||||||
|
{
|
||||||
|
incrementStreamedAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("OpenMHz API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Register an error for the file not found exception
|
||||||
|
mLog.error("OpenMHz API - upload file not found [" +
|
||||||
|
audioRecording.getPath().toString() + "]");
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Unknown Error", e);
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we're not connected and there are recordings in the queue, check the recording at the head of the queue
|
||||||
|
// and start age-off once the recordings become too old. The recordings should be time ordered in the queue.
|
||||||
|
AudioRecording audioRecording = mAudioRecordingQueue.peek();
|
||||||
|
|
||||||
|
while(audioRecording != null)
|
||||||
|
{
|
||||||
|
if(isValid(audioRecording))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Remove the recording from the queue, remove a replay, and peek at the next recording in the queue
|
||||||
|
mAudioRecordingQueue.poll();
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
incrementAgedOffAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
|
||||||
|
audioRecording = mAudioRecordingQueue.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a frequency value from the audio recording identifier collection.
|
||||||
|
*/
|
||||||
|
private static Long getFrequency(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
|
||||||
|
Form.CHANNEL_FREQUENCY, Role.ANY);
|
||||||
|
|
||||||
|
if(identifier instanceof ConfigurationLongIdentifier)
|
||||||
|
{
|
||||||
|
Long value = ((ConfigurationLongIdentifier)identifier).getValue();
|
||||||
|
|
||||||
|
if(value != null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Long.valueOf(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the FROM identifier or uses a default of zero(0)
|
||||||
|
*/
|
||||||
|
private static String getFrom(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
|
||||||
|
{
|
||||||
|
if(identifier instanceof RadioIdentifier)
|
||||||
|
{
|
||||||
|
return ((RadioIdentifier)identifier).getValue().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static String getTo(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
|
{
|
||||||
|
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
|
||||||
|
}
|
||||||
|
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||||
|
{
|
||||||
|
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||||
|
talkgroupIdentifier.getProtocol()));
|
||||||
|
}
|
||||||
|
else if(identifier instanceof RadioIdentifier radioIdentifier)
|
||||||
|
{
|
||||||
|
return radioIdentifier.getValue().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the Talkgroup Label from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first label as the primary talkgroup label.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getTalkgroupLabel(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
|
||||||
|
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(identifier != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(aliases.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the Talkgroup Group from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first group as the primary talkgroup group.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getTalkgroupGroup(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
|
||||||
|
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(identifier != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(aliases.get(0).getGroup());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the System Label from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first sytem as the primary talkgroup system.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getSystemLabel(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
List<Identifier> systems = audioRecording.getIdentifierCollection().getIdentifiers(Form.SYSTEM);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(!systems.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(systems.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the patched talkgroups
|
||||||
|
*/
|
||||||
|
public static String getPatches(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
|
{
|
||||||
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("[");
|
||||||
|
|
||||||
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
|
|
||||||
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests both the connection and configuration against the OpenMHz Call API service
|
||||||
|
* @param configuration containing API key and system id
|
||||||
|
* @return error string or null if test is successful
|
||||||
|
*/
|
||||||
|
public static String testConnection(OpenMHzConfiguration configuration)
|
||||||
|
{
|
||||||
|
String uri = configuration.getHost() + "/" + configuration.getSystemName() + "/authorize";
|
||||||
|
|
||||||
|
HttpClient httpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||||
|
.connectTimeout(Duration.ofSeconds(20))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(uri))
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_FORM_URLENCODED)
|
||||||
|
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString("api_key="+configuration.getApiKey()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse.BodyHandler<String> responseHandler = HttpResponse.BodyHandlers.ofString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpResponse<String> response = httpClient.send(request, responseHandler);
|
||||||
|
|
||||||
|
if (response.statusCode() == 200)
|
||||||
|
{
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
|
else if(response.statusCode() == 403)
|
||||||
|
{
|
||||||
|
return "Invalid API Key";
|
||||||
|
}
|
||||||
|
else if(response.statusCode() == 500)
|
||||||
|
{
|
||||||
|
return "Invalid System Name";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "No Response";
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
Throwable throwableCause = e.getCause();
|
||||||
|
|
||||||
|
if(throwableCause instanceof ConnectException)
|
||||||
|
{
|
||||||
|
return "No Response";
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.error("Exception connecting to OpenMHz server [" + e.toString() + "]");
|
||||||
|
return "Uknown Exception";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AudioRecordingProcessor implements Runnable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
processRecordingQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
mLog.debug("Starting ...");
|
||||||
|
|
||||||
|
OpenMHzConfiguration config = new OpenMHzConfiguration();
|
||||||
|
config.setHost("https://api.OpenMHz.com/call-upload-dev");
|
||||||
|
config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
|
||||||
|
config.setSystemName("systemx");
|
||||||
|
|
||||||
|
String response = testConnection(config);
|
||||||
|
mLog.error("Response: " + response);
|
||||||
|
|
||||||
|
if(response == "OK")
|
||||||
|
{
|
||||||
|
mLog.debug("Test Successful!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(response.contains("Invalid API Key"))
|
||||||
|
{
|
||||||
|
mLog.error("Invalid API Key");
|
||||||
|
}
|
||||||
|
else if(response.contains("Invalid System Name"))
|
||||||
|
{
|
||||||
|
mLog.error("Invalid System Name");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.debug("Response: " + response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Finished!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||||
|
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.lang.*;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for an HTTP body publisher to produce a OpenMHz call event
|
||||||
|
*/
|
||||||
|
public class OpenMHzBuilder
|
||||||
|
{
|
||||||
|
private static final String DASH_DASH = "--";
|
||||||
|
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
|
||||||
|
private List<Part> mParts = new ArrayList<>();
|
||||||
|
private byte[] audioBytes = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public OpenMHzBuilder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the static multi-part boundary string
|
||||||
|
*/
|
||||||
|
public String getBoundary()
|
||||||
|
{
|
||||||
|
return BOUNDARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Audio bytes part to the call
|
||||||
|
*/
|
||||||
|
public OpenMHzBuilder addFile(byte[] value)
|
||||||
|
{
|
||||||
|
audioBytes = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a string part to the call
|
||||||
|
*/
|
||||||
|
public OpenMHzBuilder addPart(FormField key, String value)
|
||||||
|
{
|
||||||
|
if(key != null && value != null)
|
||||||
|
{
|
||||||
|
mParts.add(new Part(key.getHeader(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a number part to the call
|
||||||
|
*/
|
||||||
|
public OpenMHzBuilder addPart(FormField key, Number value)
|
||||||
|
{
|
||||||
|
if(key != null && value != null)
|
||||||
|
{
|
||||||
|
mParts.add(new Part(key.getHeader(), value.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a form-data item
|
||||||
|
*/
|
||||||
|
private static String formatPart(Part part, String boundary)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||||
|
sb.append("Content-Disposition: form-data; name=\"").append(part.mKey).append("\"\r\n\r\n");
|
||||||
|
sb.append(part.getValue()).append("\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the audio file item
|
||||||
|
*/
|
||||||
|
private String formatFilePart(String boundary)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(audioBytes == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb= new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||||
|
sb.append("Content-Disposition: form-data; name=\"").append("call").append("\"; filename=\"audio.mp3\"\r\n\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the boundary closing item
|
||||||
|
*/
|
||||||
|
private static String getClosingBoundary(String boundary)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append(DASH_DASH).append("\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BodyPublisher for accessing the call form data
|
||||||
|
*/
|
||||||
|
public HttpRequest.BodyPublisher build()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(Part part: mParts)
|
||||||
|
{
|
||||||
|
sb.append(formatPart(part, BOUNDARY));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(formatFilePart(BOUNDARY));
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to create a ByteArray consisting of the Sting "parts" and the audio file bytes
|
||||||
|
*/
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
outputStream.write(sb.toString().getBytes());
|
||||||
|
|
||||||
|
if(audioBytes != null)
|
||||||
|
{
|
||||||
|
outputStream.write(audioBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = new StringBuilder();
|
||||||
|
sb.append("\r\n");
|
||||||
|
sb.append(getClosingBoundary(BOUNDARY));
|
||||||
|
|
||||||
|
outputStream.write(sb.toString().getBytes());
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
//mLog.error("Rdio Scanner API - unable to create POST reqeust.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpRequest.BodyPublishers.ofByteArray(outputStream.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(Part part: mParts)
|
||||||
|
{
|
||||||
|
sb.append(formatPart(part, BOUNDARY));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(getClosingBoundary(BOUNDARY));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key:Value pair holder
|
||||||
|
*/
|
||||||
|
public class Part
|
||||||
|
{
|
||||||
|
private String mKey;
|
||||||
|
private String mValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new part
|
||||||
|
* @param key value
|
||||||
|
* @param value item
|
||||||
|
*/
|
||||||
|
public Part(String key, String value)
|
||||||
|
{
|
||||||
|
mKey = key;
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey()
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streaming configuration for Rdio Scanner API.
|
||||||
|
*
|
||||||
|
* Note: this API is not a streaming audio service, rather a completed call push service. However, it fits nicely
|
||||||
|
* with the structure of the audio streaming subsystem in sdrtrunk
|
||||||
|
*/
|
||||||
|
public class OpenMHzConfiguration extends BroadcastConfiguration
|
||||||
|
{
|
||||||
|
public static final String PRODUCTION_ENDPOINT = "https://api.openmhz.com";
|
||||||
|
|
||||||
|
private StringProperty mSystemName = new SimpleStringProperty();
|
||||||
|
private StringProperty mApiKey = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for faster jackson
|
||||||
|
*/
|
||||||
|
public OpenMHzConfiguration()
|
||||||
|
{
|
||||||
|
this(BroadcastFormat.MP3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public constructor.
|
||||||
|
* @param format to use for audio recording (MP3)
|
||||||
|
*/
|
||||||
|
public OpenMHzConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
|
||||||
|
if(getHost() == null)
|
||||||
|
{
|
||||||
|
setHost(PRODUCTION_ENDPOINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
//The parent class binds this property, so we unbind it and rebind it here
|
||||||
|
mValid.unbind();
|
||||||
|
mValid.bind(Bindings.and(Bindings.and(Bindings.isNotNull(mSystemName), Bindings.isNotNull(mApiKey)),
|
||||||
|
Bindings.isNotNull(mHost)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System ID as a property
|
||||||
|
*/
|
||||||
|
public StringProperty systemIDProperty()
|
||||||
|
{
|
||||||
|
return mSystemName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API key as a property
|
||||||
|
*/
|
||||||
|
public StringProperty apiKeyProperty()
|
||||||
|
{
|
||||||
|
return mApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "api_key")
|
||||||
|
public String getApiKey()
|
||||||
|
{
|
||||||
|
return mApiKey.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the api key
|
||||||
|
* @param apiKey
|
||||||
|
*/
|
||||||
|
public void setApiKey(String apiKey)
|
||||||
|
{
|
||||||
|
mApiKey.setValue(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System name as provided by OpenMHz.com
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "system_name")
|
||||||
|
public String getSystemName()
|
||||||
|
{
|
||||||
|
return mSystemName.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system name provided by OpenMHz.com
|
||||||
|
*/
|
||||||
|
public void setSystemName(String systemName)
|
||||||
|
{
|
||||||
|
mSystemName.set(systemName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.OPENMHZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastConfiguration copyOf()
|
||||||
|
{
|
||||||
|
OpenMHzConfiguration copy = new OpenMHzConfiguration();
|
||||||
|
copy.setSystemName(getSystemName());
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
|
import io.github.dsheirer.gui.control.IntegerTextField;
|
||||||
|
import io.github.dsheirer.gui.playlist.streaming.AbstractBroadcastEditor;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
public class OpenMHzEditor extends AbstractBroadcastEditor<OpenMHzConfiguration>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzEditor.class);
|
||||||
|
private TextField mSystemNameTextField;
|
||||||
|
private IntegerTextField mMaxAgeTextField;
|
||||||
|
private TextField mApiKeyTextField;
|
||||||
|
private TextField mHostTextField;
|
||||||
|
private GridPane mEditorPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public OpenMHzEditor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItem(OpenMHzConfiguration item)
|
||||||
|
{
|
||||||
|
super.setItem(item);
|
||||||
|
|
||||||
|
getSystemNameTextField().setDisable(item == null);
|
||||||
|
getApiKeyTextField().setDisable(item == null);
|
||||||
|
getHostTextField().setDisable(item == null);
|
||||||
|
getMaxAgeTextField().setDisable(item == null);
|
||||||
|
|
||||||
|
if(item != null)
|
||||||
|
{
|
||||||
|
getSystemNameTextField().setText(item.getSystemName());
|
||||||
|
getApiKeyTextField().setText(item.getApiKey());
|
||||||
|
getHostTextField().setText(item.getHost());
|
||||||
|
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getSystemNameTextField().setText(null);
|
||||||
|
getApiKeyTextField().setText(null);
|
||||||
|
getHostTextField().setText(null);
|
||||||
|
getMaxAgeTextField().set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedProperty().set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save()
|
||||||
|
{
|
||||||
|
if(getItem() != null)
|
||||||
|
{
|
||||||
|
getItem().setSystemName(getSystemNameTextField().getText());
|
||||||
|
getItem().setHost(getHostTextField().getText());
|
||||||
|
getItem().setApiKey(getApiKeyTextField().getText());
|
||||||
|
getItem().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.OPENMHZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GridPane getEditorPane()
|
||||||
|
{
|
||||||
|
if(mEditorPane == null)
|
||||||
|
{
|
||||||
|
mEditorPane = new GridPane();
|
||||||
|
mEditorPane.setPadding(new Insets(10, 5, 10,10));
|
||||||
|
mEditorPane.setVgap(10);
|
||||||
|
mEditorPane.setHgap(5);
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
|
Label formatLabel = new Label("Format");
|
||||||
|
GridPane.setHalignment(formatLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(formatLabel, 0, row);
|
||||||
|
mEditorPane.getChildren().add(formatLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getFormatField(), 1, row);
|
||||||
|
mEditorPane.getChildren().add(getFormatField());
|
||||||
|
|
||||||
|
Label enabledLabel = new Label("Enabled");
|
||||||
|
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(enabledLabel, 2, row);
|
||||||
|
mEditorPane.getChildren().add(enabledLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getEnabledSwitch(), 3, row);
|
||||||
|
mEditorPane.getChildren().add(getEnabledSwitch());
|
||||||
|
|
||||||
|
Label systemLabel = new Label("Name");
|
||||||
|
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(systemLabel, 0, ++row);
|
||||||
|
mEditorPane.getChildren().add(systemLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getNameTextField(), 1, row);
|
||||||
|
mEditorPane.getChildren().add(getNameTextField());
|
||||||
|
|
||||||
|
Label apiKeyLabel = new Label("API Key");
|
||||||
|
GridPane.setHalignment(apiKeyLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(apiKeyLabel, 0, ++row);
|
||||||
|
mEditorPane.getChildren().add(apiKeyLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getApiKeyTextField(), 1, row);
|
||||||
|
mEditorPane.getChildren().add(getApiKeyTextField());
|
||||||
|
|
||||||
|
Label systemNameLabel = new Label("System Short Name");
|
||||||
|
GridPane.setHalignment(systemNameLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(systemNameLabel, 0, ++row);
|
||||||
|
mEditorPane.getChildren().add(systemNameLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getSystemNameTextField(), 1, row);
|
||||||
|
mEditorPane.getChildren().add(getSystemNameTextField());
|
||||||
|
|
||||||
|
Label hostLabel = new Label("OpenMHz Upload Server (API)");
|
||||||
|
GridPane.setHalignment(hostLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(hostLabel, 0, ++row);
|
||||||
|
mEditorPane.getChildren().add(hostLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getHostTextField(), 1, row);
|
||||||
|
mEditorPane.getChildren().add(getHostTextField());
|
||||||
|
|
||||||
|
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
|
||||||
|
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(maxAgeLabel, 0, ++row);
|
||||||
|
mEditorPane.getChildren().add(maxAgeLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getMaxAgeTextField(), 1, row);
|
||||||
|
mEditorPane.getChildren().add(getMaxAgeTextField());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return mEditorPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegerTextField getMaxAgeTextField()
|
||||||
|
{
|
||||||
|
if(mMaxAgeTextField == null)
|
||||||
|
{
|
||||||
|
mMaxAgeTextField = new IntegerTextField();
|
||||||
|
mMaxAgeTextField.setDisable(true);
|
||||||
|
mMaxAgeTextField.textProperty().addListener(mEditorModificationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMaxAgeTextField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextField getHostTextField()
|
||||||
|
{
|
||||||
|
if(mHostTextField == null)
|
||||||
|
{
|
||||||
|
mHostTextField = new TextField();
|
||||||
|
mHostTextField.setDisable(true);
|
||||||
|
mHostTextField.textProperty().addListener(mEditorModificationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mHostTextField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextField getApiKeyTextField()
|
||||||
|
{
|
||||||
|
if(mApiKeyTextField == null)
|
||||||
|
{
|
||||||
|
mApiKeyTextField = new TextField();
|
||||||
|
mApiKeyTextField.setDisable(true);
|
||||||
|
mApiKeyTextField.textProperty().addListener(mEditorModificationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mApiKeyTextField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextField getSystemNameTextField()
|
||||||
|
{
|
||||||
|
if(mSystemNameTextField == null)
|
||||||
|
{
|
||||||
|
mSystemNameTextField = new TextField();
|
||||||
|
mSystemNameTextField.setDisable(true);
|
||||||
|
mSystemNameTextField.textProperty().addListener(mEditorModificationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSystemNameTextField;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||||
|
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class OpenMHzFeedConfiguration extends IcecastTCPConfiguration
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzFeedConfiguration.class);
|
||||||
|
|
||||||
|
private int mFeedID;
|
||||||
|
|
||||||
|
public OpenMHzFeedConfiguration()
|
||||||
|
{
|
||||||
|
//No-arg constructor for JAXB
|
||||||
|
this(BroadcastFormat.MP3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenMHz configuration for Icecast 2.3.2 compatible servers
|
||||||
|
*
|
||||||
|
* @param format of output audio (MP3)
|
||||||
|
*/
|
||||||
|
public OpenMHzFeedConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
|
||||||
|
setBitRate(16);
|
||||||
|
setChannels(1);
|
||||||
|
setSampleRate(8000);
|
||||||
|
setInline(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OpenMHzFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
|
||||||
|
{
|
||||||
|
OpenMHzFeedConfiguration config = new OpenMHzFeedConfiguration(BroadcastFormat.MP3);
|
||||||
|
config.setName(userFeedBroadcast.getDescription());
|
||||||
|
config.setHost(userFeedBroadcast.getHostname());
|
||||||
|
config.setMountPoint(userFeedBroadcast.getMount());
|
||||||
|
config.setFeedID(userFeedBroadcast.getFeedId());
|
||||||
|
config.setPassword(userFeedBroadcast.getPassword());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
config.setPort(Integer.parseInt(userFeedBroadcast.getPort()));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error creating Rdio Scanner configuration from radio reference user feed instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastConfiguration copyOf()
|
||||||
|
{
|
||||||
|
OpenMHzFeedConfiguration copy = new OpenMHzFeedConfiguration(getBroadcastFormat());
|
||||||
|
|
||||||
|
//Broadcast Configuration Parameters
|
||||||
|
copy.setName(getName());
|
||||||
|
copy.setHost(getHost());
|
||||||
|
copy.setPort(getPort());
|
||||||
|
copy.setInline(getInline());
|
||||||
|
copy.setPassword(getPassword());
|
||||||
|
copy.setDelay(getDelay());
|
||||||
|
copy.setEnabled(false);
|
||||||
|
|
||||||
|
//Icecast Configuration Parameters
|
||||||
|
copy.setUserName(getUserName());
|
||||||
|
copy.setMountPoint(getMountPoint());
|
||||||
|
copy.setDescription(getDescription());
|
||||||
|
copy.setGenre(getGenre());
|
||||||
|
copy.setPublic(isPublic());
|
||||||
|
copy.setURL(getURL());
|
||||||
|
|
||||||
|
//OpenMHz Configuration Parameters
|
||||||
|
copy.setFeedID(getFeedID());
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.OPENMHZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "feed_id")
|
||||||
|
public int getFeedID()
|
||||||
|
{
|
||||||
|
return mFeedID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFeedID(int feedID)
|
||||||
|
{
|
||||||
|
mFeedID = feedID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP headers used for posting to Rdio Scanner API
|
||||||
|
*/
|
||||||
|
public enum FormField
|
||||||
|
{
|
||||||
|
AUDIO("audio"),
|
||||||
|
AUDIO_NAME("audioName"),
|
||||||
|
AUDIO_TYPE("audioType"),
|
||||||
|
DATE_TIME("dateTime"),
|
||||||
|
FREQUENCIES("frequencies"),
|
||||||
|
FREQUENCY("frequency"),
|
||||||
|
KEY("key"),
|
||||||
|
PATCHES("patches"),
|
||||||
|
SOURCE("source"),
|
||||||
|
SOURCES("sources"),
|
||||||
|
SYSTEM("system"),
|
||||||
|
SYSTEM_LABEL("systemLabel"),
|
||||||
|
TALKGROUP_ID("talkgroup"),
|
||||||
|
TALKGROUP_GROUP("talkgroupGroup"),
|
||||||
|
TALKGROUP_LABEL("talkgroupLabel"),
|
||||||
|
TALKGROUP_TAG("talkgroupTag"),
|
||||||
|
TEST("test");
|
||||||
|
|
||||||
|
private String mHeader;
|
||||||
|
|
||||||
|
FormField(String header)
|
||||||
|
{
|
||||||
|
mHeader = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeader()
|
||||||
|
{
|
||||||
|
return mHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,613 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import com.google.common.net.HttpHeaders;
|
||||||
|
import io.github.dsheirer.alias.Alias;
|
||||||
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
|
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
||||||
|
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
|
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio broadcaster to push completed audio recordings to the Rdio Scanner call upload API.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScannerConfiguration>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerBroadcaster.class);
|
||||||
|
|
||||||
|
private static final String ENCODING_TYPE_MP3 = "mp3";
|
||||||
|
private static final String MULTIPART_TYPE = "multipart";
|
||||||
|
private static final String DEFAULT_SUBTYPE = "form-data";
|
||||||
|
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
|
||||||
|
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||||
|
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
|
||||||
|
private HttpClient mHttpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_2)
|
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||||
|
.connectTimeout(Duration.ofSeconds(20))
|
||||||
|
.build();
|
||||||
|
private long mLastConnectionAttempt;
|
||||||
|
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
|
||||||
|
@Resource
|
||||||
|
private AliasModel mAliasModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of the broadcaster
|
||||||
|
* @param config to use
|
||||||
|
*/
|
||||||
|
public RdioScannerBroadcaster(RdioScannerConfiguration config)
|
||||||
|
{
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the audio recording processor thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
String response = testConnection(getBroadcastConfiguration());
|
||||||
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rdio Scanner API does not currently expose a test method.
|
||||||
|
*/
|
||||||
|
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Error connecting to Rdio Scanner server on startup [" + response + "]");
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mAudioRecordingProcessorFuture == null)
|
||||||
|
{
|
||||||
|
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
|
||||||
|
0, 500, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the audio recording processor thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if(mAudioRecordingProcessorFuture != null)
|
||||||
|
{
|
||||||
|
mAudioRecordingProcessorFuture.cancel(true);
|
||||||
|
mAudioRecordingProcessorFuture = null;
|
||||||
|
dispose();
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares for disposal
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
|
||||||
|
while(audioRecording != null)
|
||||||
|
{
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
|
||||||
|
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
|
||||||
|
* the audio processor thread will persistently invoke this method to attempt a reconnect.
|
||||||
|
*
|
||||||
|
* Rdio Scanner does not have a test API endpoint, so we look for the incomplete call response.
|
||||||
|
*/
|
||||||
|
private boolean connected()
|
||||||
|
{
|
||||||
|
if(getBroadcastState() != BroadcastState.CONNECTED &&
|
||||||
|
(System.currentTimeMillis() - mLastConnectionAttempt > mConnectionAttemptInterval))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
|
||||||
|
String response = testConnection(getBroadcastConfiguration());
|
||||||
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAudioQueueSize()
|
||||||
|
{
|
||||||
|
return mAudioRecordingQueue.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
mAudioRecordingQueue.offer(audioRecording);
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio recording is non-null and not too old, meaning that the age of the recording has not
|
||||||
|
* exceeded the max age value indicated in the broadcast configuration. Audio recordings that are too old will be
|
||||||
|
* deleted to ensure that the in-memory queue size doesn't blow up.
|
||||||
|
* @param audioRecording to test
|
||||||
|
* @return true if the recording is valid
|
||||||
|
*/
|
||||||
|
private boolean isValid(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
return audioRecording != null && System.currentTimeMillis() - audioRecording.getStartTime() <=
|
||||||
|
getBroadcastConfiguration().getMaximumRecordingAge();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes any enqueued audio recordings. This method employs asynchronous
|
||||||
|
* interaction with the server, so multiple audio recording uploads can occur simultaneously.
|
||||||
|
*/
|
||||||
|
private void processRecordingQueue()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
while(connected() && !mAudioRecordingQueue.isEmpty())
|
||||||
|
{
|
||||||
|
final AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||||
|
|
||||||
|
if(isValid(audioRecording) && audioRecording.getRecordingLength() > 0)
|
||||||
|
{
|
||||||
|
float durationSeconds = (float)(audioRecording.getRecordingLength() / 1E3f);
|
||||||
|
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
|
||||||
|
String talkgroup = getTo(audioRecording);
|
||||||
|
String radioId = getFrom(audioRecording);
|
||||||
|
Long frequency = getFrequency(audioRecording);
|
||||||
|
String patches = getPatches(audioRecording);
|
||||||
|
String talkgroupLabel = getTalkgroupLabel(audioRecording);
|
||||||
|
String talkgroupGroup = getTalkgroupGroup(audioRecording);
|
||||||
|
String systemLabel = getSystemLabel(audioRecording);
|
||||||
|
String path = audioRecording.getPath().toString();
|
||||||
|
// Remove TEMPORARY_STREAM_FILE_SUFFIX
|
||||||
|
String audioName = path.substring(path.substring(0, path.lastIndexOf("_")).lastIndexOf("_") + 1);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] audioBytes = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
audioBytes = Files.readAllBytes(audioRecording.getPath());
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Rdio Scanner API - audio recording file not found - ignoring upload");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(audioBytes != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
|
||||||
|
bodyBuilder.addPart(FormField.KEY, getBroadcastConfiguration().getApiKey())
|
||||||
|
.addPart(FormField.SYSTEM, getBroadcastConfiguration().getSystemID())
|
||||||
|
.addAudioName(audioName)
|
||||||
|
.addFile(audioBytes)
|
||||||
|
.addPart(FormField.DATE_TIME, timestampSeconds)
|
||||||
|
.addPart(FormField.TALKGROUP_ID, talkgroup)
|
||||||
|
.addPart(FormField.SOURCE, radioId)
|
||||||
|
.addPart(FormField.FREQUENCY, frequency)
|
||||||
|
.addPart(FormField.TALKGROUP_LABEL, talkgroupLabel)
|
||||||
|
.addPart(FormField.TALKGROUP_GROUP, talkgroupGroup)
|
||||||
|
.addPart(FormField.SYSTEM_LABEL, systemLabel)
|
||||||
|
.addPart(FormField.PATCHES, patches);
|
||||||
|
|
||||||
|
HttpRequest fileRequest = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(getBroadcastConfiguration().getHost()))
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
|
||||||
|
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||||
|
.POST(bodyBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mHttpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
|
||||||
|
.whenComplete((fileResponse, throwable1) -> {
|
||||||
|
if(throwable1 != null || fileResponse.statusCode() != 200)
|
||||||
|
{
|
||||||
|
if(throwable1 instanceof IOException || throwable1 instanceof CompletionException)
|
||||||
|
{
|
||||||
|
//We get socket reset exceptions occasionally when the remote server doesn't
|
||||||
|
//fully read our request and immediately responds.
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("Rdio Scanner API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("Rdio Scanner API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String fileResponseString = fileResponse.body();
|
||||||
|
|
||||||
|
if(fileResponseString.contains("Call imported successfully."))
|
||||||
|
{
|
||||||
|
incrementStreamedAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
else if(fileResponseString.contains("duplicate call rejected"))
|
||||||
|
{
|
||||||
|
//Rdio Scanner is telling us to skip audio upload - someone already uploaded it
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("Rdio Scanner API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Register an error for the file not found exception
|
||||||
|
mLog.error("Rdio Scanner API - upload file not found [" +
|
||||||
|
audioRecording.getPath().toString() + "]");
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Unknown Error", e);
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we're not connected and there are recordings in the queue, check the recording at the head of the queue
|
||||||
|
// and start age-off once the recordings become too old. The recordings should be time ordered in the queue.
|
||||||
|
AudioRecording audioRecording = mAudioRecordingQueue.peek();
|
||||||
|
|
||||||
|
while(audioRecording != null)
|
||||||
|
{
|
||||||
|
if(isValid(audioRecording))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Remove the recording from the queue, remove a replay, and peek at the next recording in the queue
|
||||||
|
mAudioRecordingQueue.poll();
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
incrementAgedOffAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
|
||||||
|
audioRecording = mAudioRecordingQueue.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a frequency value from the audio recording identifier collection.
|
||||||
|
*/
|
||||||
|
private static Long getFrequency(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
|
||||||
|
Form.CHANNEL_FREQUENCY, Role.ANY);
|
||||||
|
|
||||||
|
if(identifier instanceof ConfigurationLongIdentifier)
|
||||||
|
{
|
||||||
|
Long value = ((ConfigurationLongIdentifier)identifier).getValue();
|
||||||
|
|
||||||
|
if(value != null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Long.valueOf(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the FROM identifier or uses a default of zero(0)
|
||||||
|
*/
|
||||||
|
private static String getFrom(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
|
||||||
|
{
|
||||||
|
if(identifier instanceof RadioIdentifier)
|
||||||
|
{
|
||||||
|
return ((RadioIdentifier)identifier).getValue().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static String getTo(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
|
{
|
||||||
|
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
|
||||||
|
}
|
||||||
|
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||||
|
{
|
||||||
|
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||||
|
talkgroupIdentifier.getProtocol()));
|
||||||
|
}
|
||||||
|
else if(identifier instanceof RadioIdentifier radioIdentifier)
|
||||||
|
{
|
||||||
|
return radioIdentifier.getValue().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the Talkgroup Label from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first label as the primary talkgroup label.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getTalkgroupLabel(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(identifier != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(aliases.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the Talkgroup Group from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first group as the primary talkgroup group.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getTalkgroupGroup(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(identifier != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(aliases.get(0).getGroup());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the System Label from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first sytem as the primary talkgroup system.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getSystemLabel(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
List<Identifier> systems = audioRecording.getIdentifierCollection().getIdentifiers(Form.SYSTEM);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(!systems.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(systems.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the patched talkgroups
|
||||||
|
*/
|
||||||
|
public static String getPatches(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
|
{
|
||||||
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("[");
|
||||||
|
|
||||||
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
|
|
||||||
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests both the connection and configuration against the RdioScanner Call API service
|
||||||
|
* @param configuration containing API key and system id
|
||||||
|
* @return error string or null if test is successful
|
||||||
|
*/
|
||||||
|
public static String testConnection(RdioScannerConfiguration configuration)
|
||||||
|
{
|
||||||
|
HttpClient httpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||||
|
.connectTimeout(Duration.ofSeconds(20))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
|
||||||
|
bodyBuilder.addPart(FormField.KEY, configuration.getApiKey())
|
||||||
|
.addPart(FormField.SYSTEM, configuration.getSystemID())
|
||||||
|
.addPart(FormField.TEST, 1);
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(configuration.getHost()))
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
|
||||||
|
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||||
|
.header(HttpHeaders.ACCEPT, "*/*")
|
||||||
|
.POST(bodyBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse.BodyHandler<String> responseHandler = HttpResponse.BodyHandlers.ofString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpResponse<String> response = httpClient.send(request, responseHandler);
|
||||||
|
String responseBody = response.body();
|
||||||
|
return (responseBody != null ? responseBody : "(no response)") + " Status Code:" + response.statusCode();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
return e.getLocalizedMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AudioRecordingProcessor implements Runnable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
processRecordingQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
mLog.debug("Starting ...");
|
||||||
|
|
||||||
|
RdioScannerConfiguration config = new RdioScannerConfiguration();
|
||||||
|
config.setHost("https://api.RdioScanner.com/call-upload-dev");
|
||||||
|
config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
|
||||||
|
config.setSystemID(11);
|
||||||
|
|
||||||
|
String response = testConnection(config);
|
||||||
|
|
||||||
|
if(response == null)
|
||||||
|
{
|
||||||
|
mLog.debug("Test Successful!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(response.contains("1 Invalid-API-Key"))
|
||||||
|
{
|
||||||
|
mLog.error("Invalid API Key");
|
||||||
|
}
|
||||||
|
else if(response.contains("1 API-Key-Access-Denied"))
|
||||||
|
{
|
||||||
|
mLog.error("System ID not valid for API Key");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.debug("Response: " + response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Finished!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.lang.*;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for an HTTP body publisher to produce a RdioScanner call event
|
||||||
|
*/
|
||||||
|
public class RdioScannerBuilder
|
||||||
|
{
|
||||||
|
private static final String DASH_DASH = "--";
|
||||||
|
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
|
||||||
|
private List<Part> mParts = new ArrayList<>();
|
||||||
|
private byte[] audioBytes = null;
|
||||||
|
private String audioName = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the static multi-part boundary string
|
||||||
|
*/
|
||||||
|
public String getBoundary()
|
||||||
|
{
|
||||||
|
return BOUNDARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Audio file name part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addAudioName(String value)
|
||||||
|
{
|
||||||
|
audioName = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Audio bytes part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addFile(byte[] value)
|
||||||
|
{
|
||||||
|
audioBytes = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a string part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addPart(FormField key, String value)
|
||||||
|
{
|
||||||
|
if(key != null && value != null)
|
||||||
|
{
|
||||||
|
mParts.add(new Part(key.getHeader(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a number part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addPart(FormField key, Number value)
|
||||||
|
{
|
||||||
|
if(key != null && value != null)
|
||||||
|
{
|
||||||
|
mParts.add(new Part(key.getHeader(), value.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a form-data item
|
||||||
|
*/
|
||||||
|
private static String formatPart(Part part, String boundary)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||||
|
sb.append("Content-Disposition: form-data; name=\"").append(part.mKey).append("\"\r\n\r\n");
|
||||||
|
sb.append(part.getValue()).append("\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the audio file item
|
||||||
|
*/
|
||||||
|
private String formatFilePart(String boundary)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(audioBytes == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb= new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||||
|
sb.append("Content-Disposition: form-data; filename=\"").append(audioName);
|
||||||
|
sb.append("\"; name=\"").append("audio").append("\"\r\n\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the boundary closing item
|
||||||
|
*/
|
||||||
|
private static String getClosingBoundary(String boundary)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append(DASH_DASH).append("\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BodyPublisher for accessing the call form data
|
||||||
|
*/
|
||||||
|
public HttpRequest.BodyPublisher build()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(Part part: mParts)
|
||||||
|
{
|
||||||
|
sb.append(formatPart(part, BOUNDARY));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(formatFilePart(BOUNDARY));
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to create a ByteArray consisting of the Sting "parts" and the audio file bytes
|
||||||
|
*/
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
outputStream.write(sb.toString().getBytes());
|
||||||
|
|
||||||
|
if(audioBytes != null)
|
||||||
|
{
|
||||||
|
outputStream.write(audioBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = new StringBuilder();
|
||||||
|
sb.append("\r\n");
|
||||||
|
sb.append(getClosingBoundary(BOUNDARY));
|
||||||
|
|
||||||
|
outputStream.write(sb.toString().getBytes());
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
//mLog.error("Rdio Scanner API - unable to create POST reqeust.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpRequest.BodyPublishers.ofByteArray(outputStream.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(Part part: mParts)
|
||||||
|
{
|
||||||
|
sb.append(formatPart(part, BOUNDARY));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(getClosingBoundary(BOUNDARY));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key:Value pair holder
|
||||||
|
*/
|
||||||
|
public class Part
|
||||||
|
{
|
||||||
|
private String mKey;
|
||||||
|
private String mValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new part
|
||||||
|
* @param key value
|
||||||
|
* @param value item
|
||||||
|
*/
|
||||||
|
public Part(String key, String value)
|
||||||
|
{
|
||||||
|
mKey = key;
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey()
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streaming configuration for Rdio Scanner API.
|
||||||
|
*
|
||||||
|
* Note: this API is not a streaming audio service, rather a completed call push service. However, it fits nicely
|
||||||
|
* with the structure of the audio streaming subsystem in sdrtrunk
|
||||||
|
*/
|
||||||
|
public class RdioScannerConfiguration extends BroadcastConfiguration
|
||||||
|
{
|
||||||
|
private IntegerProperty mSystemID = new SimpleIntegerProperty();
|
||||||
|
private StringProperty mApiKey = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for faster jackson
|
||||||
|
*/
|
||||||
|
public RdioScannerConfiguration()
|
||||||
|
{
|
||||||
|
this(BroadcastFormat.MP3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public constructor.
|
||||||
|
* @param format to use for audio recording (MP3)
|
||||||
|
*/
|
||||||
|
public RdioScannerConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
|
||||||
|
//The parent class binds this property, so we unbind it and rebind it here
|
||||||
|
mValid.unbind();
|
||||||
|
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
|
||||||
|
Bindings.isNotNull(mHost)));
|
||||||
|
|
||||||
|
if(mHost.getValue() == null || mHost.getValue().isEmpty())
|
||||||
|
{
|
||||||
|
mHost.set("http://localhost");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System ID as a property
|
||||||
|
*/
|
||||||
|
public IntegerProperty systemIDProperty()
|
||||||
|
{
|
||||||
|
return mSystemID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API key as a property
|
||||||
|
*/
|
||||||
|
public StringProperty apiKeyProperty()
|
||||||
|
{
|
||||||
|
return mApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "api_key")
|
||||||
|
public String getApiKey()
|
||||||
|
{
|
||||||
|
return mApiKey.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the api key
|
||||||
|
* @param apiKey
|
||||||
|
*/
|
||||||
|
public void setApiKey(String apiKey)
|
||||||
|
{
|
||||||
|
mApiKey.setValue(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System ID as provided by RdioScanner.com
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "system_id")
|
||||||
|
public int getSystemID()
|
||||||
|
{
|
||||||
|
return mSystemID.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system ID provided by RdioScanner.com
|
||||||
|
*/
|
||||||
|
public void setSystemID(int systemID)
|
||||||
|
{
|
||||||
|
mSystemID.set(systemID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.RDIOSCANNER_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastConfiguration copyOf()
|
||||||
|
{
|
||||||
|
RdioScannerConfiguration copy = new RdioScannerConfiguration();
|
||||||
|
copy.setSystemID(getSystemID());
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||||
|
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class RdioScannerFeedConfiguration extends IcecastTCPConfiguration
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerFeedConfiguration.class);
|
||||||
|
|
||||||
|
private int mFeedID;
|
||||||
|
|
||||||
|
public RdioScannerFeedConfiguration()
|
||||||
|
{
|
||||||
|
//No-arg constructor for JAXB
|
||||||
|
this(BroadcastFormat.MP3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RdioScanner configuration for Icecast 2.3.2 compatible servers
|
||||||
|
*
|
||||||
|
* @param format of output audio (MP3)
|
||||||
|
*/
|
||||||
|
public RdioScannerFeedConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
|
||||||
|
setBitRate(16);
|
||||||
|
setChannels(1);
|
||||||
|
setSampleRate(8000);
|
||||||
|
setInline(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RdioScannerFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
|
||||||
|
{
|
||||||
|
RdioScannerFeedConfiguration config = new RdioScannerFeedConfiguration(BroadcastFormat.MP3);
|
||||||
|
config.setName(userFeedBroadcast.getDescription());
|
||||||
|
config.setHost(userFeedBroadcast.getHostname());
|
||||||
|
config.setMountPoint(userFeedBroadcast.getMount());
|
||||||
|
config.setFeedID(userFeedBroadcast.getFeedId());
|
||||||
|
config.setPassword(userFeedBroadcast.getPassword());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
config.setPort(Integer.parseInt(userFeedBroadcast.getPort()));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error creating Rdio Scanner configuration from radio reference user feed instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastConfiguration copyOf()
|
||||||
|
{
|
||||||
|
RdioScannerFeedConfiguration copy = new RdioScannerFeedConfiguration(getBroadcastFormat());
|
||||||
|
|
||||||
|
//Broadcast Configuration Parameters
|
||||||
|
copy.setName(getName());
|
||||||
|
copy.setHost(getHost());
|
||||||
|
copy.setPort(getPort());
|
||||||
|
copy.setInline(getInline());
|
||||||
|
copy.setPassword(getPassword());
|
||||||
|
copy.setDelay(getDelay());
|
||||||
|
copy.setEnabled(false);
|
||||||
|
|
||||||
|
//Icecast Configuration Parameters
|
||||||
|
copy.setUserName(getUserName());
|
||||||
|
copy.setMountPoint(getMountPoint());
|
||||||
|
copy.setDescription(getDescription());
|
||||||
|
copy.setGenre(getGenre());
|
||||||
|
copy.setPublic(isPublic());
|
||||||
|
copy.setURL(getURL());
|
||||||
|
|
||||||
|
//RdioScanner Configuration Parameters
|
||||||
|
copy.setFeedID(getFeedID());
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.RDIOSCANNER_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "feed_id")
|
||||||
|
public int getFeedID()
|
||||||
|
{
|
||||||
|
return mFeedID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFeedID(int feedID)
|
||||||
|
{
|
||||||
|
mFeedID = feedID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast.shoutcast.v1;
|
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.AudioStreamingBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
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.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
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.RuntimeIoException;
|
||||||
import org.apache.mina.core.buffer.IoBuffer;
|
import org.apache.mina.core.buffer.IoBuffer;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1AudioBroadcaster.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1AudioBroadcaster.class);
|
||||||
@@ -50,7 +48,6 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
|||||||
private NioSocketConnector mSocketConnector;
|
private NioSocketConnector mSocketConnector;
|
||||||
private IoSession mStreamingSession = null;
|
private IoSession mStreamingSession = null;
|
||||||
private IBroadcastMetadataUpdater mMetadataUpdater;
|
private IBroadcastMetadataUpdater mMetadataUpdater;
|
||||||
private AliasModel mAliasModel;
|
|
||||||
private long mLastConnectionAttempt = 0;
|
private long mLastConnectionAttempt = 0;
|
||||||
private AtomicBoolean mConnecting = new AtomicBoolean();
|
private AtomicBoolean mConnecting = new AtomicBoolean();
|
||||||
|
|
||||||
@@ -65,10 +62,9 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
|||||||
* @param configuration for the Shoutcast stream
|
* @param configuration for the Shoutcast stream
|
||||||
*/
|
*/
|
||||||
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, InputAudioFormat inputAudioFormat,
|
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, InputAudioFormat inputAudioFormat,
|
||||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
MP3Setting mp3Setting)
|
||||||
{
|
{
|
||||||
super(configuration, inputAudioFormat, mp3Setting);
|
super(configuration, inputAudioFormat, mp3Setting);
|
||||||
mAliasModel = aliasModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,7 +80,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
|||||||
{
|
{
|
||||||
if(mMetadataUpdater == null)
|
if(mMetadataUpdater == null)
|
||||||
{
|
{
|
||||||
mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getConfiguration(), mAliasModel);
|
mMetadataUpdater = new ShoutcastV1BroadcastMetadataUpdater(getConfiguration());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mMetadataUpdater;
|
return mMetadataUpdater;
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,6 +29,17 @@ import io.github.dsheirer.identifier.IdentifierClass;
|
|||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||||
import org.apache.mina.core.session.IoSession;
|
import org.apache.mina.core.session.IoSession;
|
||||||
@@ -41,23 +52,13 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1BroadcastMetadataUpdater.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV1BroadcastMetadataUpdater.class);
|
||||||
private final static String UTF8 = "UTF-8";
|
private final static String UTF8 = "UTF-8";
|
||||||
|
|
||||||
private ShoutcastV1Configuration mShoutcastV1Configuration;
|
private ShoutcastV1Configuration mShoutcastV1Configuration;
|
||||||
|
@Resource
|
||||||
private AliasModel mAliasModel;
|
private AliasModel mAliasModel;
|
||||||
private NioSocketConnector mSocketConnector;
|
private NioSocketConnector mSocketConnector;
|
||||||
private AtomicBoolean mUpdating = new AtomicBoolean();
|
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
|
* 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.
|
* sequence, those updates will be queued and processed in the order received.
|
||||||
*/
|
*/
|
||||||
public ShoutcastV1BroadcastMetadataUpdater(ShoutcastV1Configuration shoutcastV1Configuration, AliasModel aliasModel)
|
public ShoutcastV1BroadcastMetadataUpdater(ShoutcastV1Configuration shoutcastV1Configuration)
|
||||||
{
|
{
|
||||||
mShoutcastV1Configuration = shoutcastV1Configuration;
|
mShoutcastV1Configuration = shoutcastV1Configuration;
|
||||||
mAliasModel = aliasModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -49,6 +49,15 @@ import io.github.dsheirer.identifier.IdentifierCollection;
|
|||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.apache.mina.core.RuntimeIoException;
|
import org.apache.mina.core.RuntimeIoException;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
@@ -59,25 +68,17 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater
|
public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV2AudioStreamingBroadcaster.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ShoutcastV2AudioStreamingBroadcaster.class);
|
||||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
|
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
|
||||||
private int mMaxPayloadSize = 16377;
|
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AliasModel mAliasModel;
|
||||||
private NioSocketConnector mSocketConnector;
|
private NioSocketConnector mSocketConnector;
|
||||||
private IoSession mStreamingSession = null;
|
private IoSession mStreamingSession = null;
|
||||||
private AliasModel mAliasModel;
|
|
||||||
private long mLastConnectionAttempt = 0;
|
private long mLastConnectionAttempt = 0;
|
||||||
|
private int mMaxPayloadSize = 16377;
|
||||||
private AtomicBoolean mConnecting = new AtomicBoolean();
|
private AtomicBoolean mConnecting = new AtomicBoolean();
|
||||||
private LinkedTransferQueue<UltravoxMessage> mMetadataMessageQueue = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<UltravoxMessage> mMetadataMessageQueue = new LinkedTransferQueue<>();
|
||||||
|
|
||||||
@@ -94,10 +95,9 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
|||||||
* @param configuration for the Shoutcast V2 stream
|
* @param configuration for the Shoutcast V2 stream
|
||||||
*/
|
*/
|
||||||
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, InputAudioFormat inputAudioFormat,
|
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, InputAudioFormat inputAudioFormat,
|
||||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
MP3Setting mp3Setting)
|
||||||
{
|
{
|
||||||
super(configuration, inputAudioFormat, mp3Setting);
|
super(configuration, inputAudioFormat, mp3Setting);
|
||||||
mAliasModel = aliasModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShoutcastV2Configuration getConfiguration()
|
public ShoutcastV2Configuration getConfiguration()
|
||||||
|
|||||||
@@ -0,0 +1,461 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.call;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import io.github.dsheirer.alias.Alias;
|
||||||
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.audio.AudioFormats;
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import io.github.dsheirer.record.wave.AudioMetadata;
|
||||||
|
import io.github.dsheirer.record.wave.AudioMetadataUtils;
|
||||||
|
import io.github.dsheirer.record.wave.WaveWriter;
|
||||||
|
import io.github.dsheirer.sample.ConversionUtils;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages an active audio segment through to completion and provides options for refreshing the associated call
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public class ActiveAudioSegment
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ActiveAudioSegment.class);
|
||||||
|
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LOGGER);
|
||||||
|
private static final SimpleDateFormat DIRECTORY_SDF = new SimpleDateFormat("yyyyMMdd");
|
||||||
|
private static final SimpleDateFormat FILE_NAME_SDF = new SimpleDateFormat("yyyyMMdd_HHmmss");
|
||||||
|
private static int sFileNameCounter = 1;
|
||||||
|
|
||||||
|
private AudioSegment mAudioSegment;
|
||||||
|
private Call mCall;
|
||||||
|
private UserPreferences mUserPreferences;
|
||||||
|
private WaveWriter mWaveWriter;
|
||||||
|
private Path mPath;
|
||||||
|
private int mAudioBufferPointer = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param audioSegment
|
||||||
|
* @param call
|
||||||
|
* @param userPreferences for lookup of recording directory
|
||||||
|
*/
|
||||||
|
public ActiveAudioSegment(AudioSegment audioSegment, Call call, UserPreferences userPreferences)
|
||||||
|
{
|
||||||
|
mAudioSegment = audioSegment;
|
||||||
|
mAudioSegment.addLease(getClass().toString());
|
||||||
|
mCall = call;
|
||||||
|
mUserPreferences = userPreferences;
|
||||||
|
|
||||||
|
mPath = getAudioRecordingPath();
|
||||||
|
mCall.setFile(mPath.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs disposal workflow.
|
||||||
|
*/
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
if(mWaveWriter != null)
|
||||||
|
{
|
||||||
|
closeAudio();
|
||||||
|
}
|
||||||
|
mCall.setComplete(true);
|
||||||
|
mAudioSegment.removeLease(getClass().toString());
|
||||||
|
mAudioSegment = null;
|
||||||
|
mCall = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio Segment that is active.
|
||||||
|
* @return audio segment
|
||||||
|
*/
|
||||||
|
public AudioSegment getAudioSegment()
|
||||||
|
{
|
||||||
|
return mAudioSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call entity associated with this audio segment
|
||||||
|
* @return call entity
|
||||||
|
*/
|
||||||
|
public Call getCall()
|
||||||
|
{
|
||||||
|
return mCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this audio segment is active and continues to receive audio buffer and identifier updates.
|
||||||
|
* @return true if active or false if the audio segment is complete.
|
||||||
|
*/
|
||||||
|
public boolean isComplete()
|
||||||
|
{
|
||||||
|
return getAudioSegment().isComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to update/refresh the call entity with details from the audio segment and returns true if any details in
|
||||||
|
* the call entity were updated or false otherwise.
|
||||||
|
* @return status of the update, true if updated or false if no update.
|
||||||
|
*/
|
||||||
|
public boolean update()
|
||||||
|
{
|
||||||
|
boolean updated = false;
|
||||||
|
updated |= updateTo(mCall, mAudioSegment.getIdentifierCollection().getToIdentifier(), getAudioSegment().getAliasList());
|
||||||
|
updated |= updateFrom(mCall, mAudioSegment.getIdentifierCollection().getFromIdentifier(), getAudioSegment().getAliasList());
|
||||||
|
updated |= updateDuration(mCall, mAudioSegment.getDuration());
|
||||||
|
updated |= updateFlags(mCall, mAudioSegment);
|
||||||
|
updated |= processAudio();
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique audio recording file path.
|
||||||
|
* @return audio recording path.
|
||||||
|
*/
|
||||||
|
private Path getAudioRecordingPath()
|
||||||
|
{
|
||||||
|
Date now = new Date();
|
||||||
|
String dailyFolder = DIRECTORY_SDF.format(now);
|
||||||
|
Path recordingDirectory = mUserPreferences.getDirectoryPreference().getDirectoryCalls()
|
||||||
|
.resolve(dailyFolder);
|
||||||
|
|
||||||
|
if(!Files.exists(recordingDirectory))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Files.createDirectories(recordingDirectory);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
LOGGING_SUPPRESSOR.error(dailyFolder, 1, "Unable to create daily calls recording " +
|
||||||
|
"folder [" + dailyFolder + "]", ioe);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int recordingNumber = sFileNameCounter++;
|
||||||
|
String fileName = FILE_NAME_SDF.format(now) + "_call_" + recordingNumber + ".wav";
|
||||||
|
return recordingDirectory.resolve(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes any buffered audio to the wave recording.
|
||||||
|
* @return true if the call was updated with the audio recording path.
|
||||||
|
*/
|
||||||
|
private boolean processAudio()
|
||||||
|
{
|
||||||
|
boolean updated = false;
|
||||||
|
|
||||||
|
if(mAudioSegment.hasAudio())
|
||||||
|
{
|
||||||
|
if(mWaveWriter == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mWaveWriter = new WaveWriter(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, mPath);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
LOGGER.error("Error creating wave file: " + mPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<float[]> audioBuffers = mAudioSegment.getAudioBuffers();
|
||||||
|
int size = audioBuffers.size();
|
||||||
|
|
||||||
|
if(mWaveWriter != null && size > mAudioBufferPointer)
|
||||||
|
{
|
||||||
|
for(int x = mAudioBufferPointer; x < audioBuffers.size(); x++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mWaveWriter.writeData(ConversionUtils.convertToSigned16BitSamples(audioBuffers.get(x)));
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
LOGGING_SUPPRESSOR.error("Call Wave Writer", 3, "Error while recording call audio", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioBufferPointer = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes remaining audio and metadata to wave file and closes the recorder.
|
||||||
|
*/
|
||||||
|
public void closeAudio()
|
||||||
|
{
|
||||||
|
processAudio();
|
||||||
|
|
||||||
|
if(mWaveWriter != null)
|
||||||
|
{
|
||||||
|
Map<AudioMetadata,String> metadataMap = AudioMetadataUtils.getMetadataMap(mAudioSegment.getIdentifierCollection(),
|
||||||
|
mAudioSegment.getAliasList());
|
||||||
|
|
||||||
|
ByteBuffer listChunk = AudioMetadataUtils.getLISTChunk(metadataMap);
|
||||||
|
byte[] id3Bytes = AudioMetadataUtils.getMP3ID3(metadataMap);
|
||||||
|
ByteBuffer id3Chunk = AudioMetadataUtils.getID3Chunk(id3Bytes);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mWaveWriter.writeMetadata(listChunk, id3Chunk);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
LOGGING_SUPPRESSOR.error("Metadata Write Error", 3,
|
||||||
|
"Error writing call ID3 metadata to recording", ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mWaveWriter.close();
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
LOGGING_SUPPRESSOR.error("Recording Close Error", 3, "Error closing audio wave recording", ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
mWaveWriter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to create an initial call instance from an Audio Segment. Note: this static method is used by
|
||||||
|
* an external database controller so that the call event can be stored in the database before
|
||||||
|
* @param audioSegment
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Call create(AudioSegment audioSegment)
|
||||||
|
{
|
||||||
|
if(audioSegment == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Call call = new Call();
|
||||||
|
|
||||||
|
call.setEventTime(audioSegment.getStartTimestamp());
|
||||||
|
|
||||||
|
Identifier decoder = audioSegment.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
|
||||||
|
Form.DECODER_TYPE, Role.ANY);
|
||||||
|
|
||||||
|
if(decoder instanceof DecoderTypeConfigurationIdentifier dtci)
|
||||||
|
{
|
||||||
|
call.setProtocol(dtci.getValue().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier systemId = audioSegment.getIdentifierCollection()
|
||||||
|
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||||
|
|
||||||
|
if(systemId instanceof SystemConfigurationIdentifier sci)
|
||||||
|
{
|
||||||
|
call.setSystem(sci.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier siteId = audioSegment.getIdentifierCollection()
|
||||||
|
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SITE, Role.ANY);
|
||||||
|
|
||||||
|
if(siteId instanceof SiteConfigurationIdentifier siteci)
|
||||||
|
{
|
||||||
|
call.setSite(siteci.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier channelId = audioSegment.getIdentifierCollection()
|
||||||
|
.getIdentifier(IdentifierClass.CONFIGURATION, Form.CHANNEL, Role.ANY);
|
||||||
|
|
||||||
|
if(channelId instanceof ChannelNameConfigurationIdentifier cnci)
|
||||||
|
{
|
||||||
|
call.setChannel(cnci.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier frequency = audioSegment.getIdentifierCollection()
|
||||||
|
.getIdentifier(IdentifierClass.CONFIGURATION, Form.CHANNEL_FREQUENCY, Role.ANY);
|
||||||
|
|
||||||
|
if(frequency instanceof FrequencyConfigurationIdentifier fci)
|
||||||
|
{
|
||||||
|
//Convert Hertz to MegaHertz for the call value.
|
||||||
|
call.setFrequency(fci.getValue() / 1E6d);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFrom(call, audioSegment.getIdentifierCollection().getFromIdentifier(), audioSegment.getAliasList());
|
||||||
|
updateTo(call, audioSegment.getIdentifierCollection().getToIdentifier(), audioSegment.getAliasList());
|
||||||
|
updateDuration(call, audioSegment.getDuration());
|
||||||
|
updateFlags(call, audioSegment);
|
||||||
|
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the duplicate, record, stream and other call flags/info
|
||||||
|
* @param call to update
|
||||||
|
* @param audioSegment containing flags/info
|
||||||
|
* @return true if the call is updated.
|
||||||
|
*/
|
||||||
|
private static boolean updateFlags(Call call, AudioSegment audioSegment)
|
||||||
|
{
|
||||||
|
boolean updated = false;
|
||||||
|
|
||||||
|
if(call.isDuplicate() ^ audioSegment.isDuplicate())
|
||||||
|
{
|
||||||
|
call.setDuplicate(true);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(call.isRecord() ^ audioSegment.recordAudioProperty().get())
|
||||||
|
{
|
||||||
|
call.setRecord(true);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(call.isStream() ^ audioSegment.hasBroadcastChannels())
|
||||||
|
{
|
||||||
|
call.setStream(true);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(call.getMonitor() != audioSegment.monitorPriorityProperty().get())
|
||||||
|
{
|
||||||
|
call.setMonitor(audioSegment.monitorPriorityProperty().get());
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the call duration field
|
||||||
|
* @param call to update
|
||||||
|
* @param durationMs value
|
||||||
|
* @return true if the value was updated
|
||||||
|
*/
|
||||||
|
private static boolean updateDuration(Call call, long durationMs)
|
||||||
|
{
|
||||||
|
double durationSeconds = durationMs / 1000.0;
|
||||||
|
|
||||||
|
if(call.getDuration() != durationSeconds)
|
||||||
|
{
|
||||||
|
call.setDuration(durationSeconds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the FROM identifier value.
|
||||||
|
* @param call to update
|
||||||
|
* @param fromIdentifier value
|
||||||
|
* @param aliasList for alias lookups
|
||||||
|
* @return true if the value was updated
|
||||||
|
*/
|
||||||
|
private static boolean updateFrom(Call call, Identifier fromIdentifier, AliasList aliasList)
|
||||||
|
{
|
||||||
|
if(fromIdentifier == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(call.getFromId() == null || call.getFromId().contentEquals(fromIdentifier.toString()))
|
||||||
|
{
|
||||||
|
call.setFromId(fromIdentifier.toString());
|
||||||
|
|
||||||
|
if(aliasList != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(fromIdentifier);
|
||||||
|
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
call.setFromAlias(Joiner.on(";").join(aliases));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the TO identifier value.
|
||||||
|
* @param call to update
|
||||||
|
* @param toIdentifier value
|
||||||
|
* @return true if the value was updated
|
||||||
|
*/
|
||||||
|
private static boolean updateTo(Call call, Identifier toIdentifier, AliasList aliasList)
|
||||||
|
{
|
||||||
|
if(toIdentifier == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(call.getToId() == null || call.getToId().contentEquals(toIdentifier.toString()))
|
||||||
|
{
|
||||||
|
call.setToId(toIdentifier.toString());
|
||||||
|
|
||||||
|
if(aliasList != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(toIdentifier);
|
||||||
|
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
call.setToAlias(Joiner.on(";").join(aliases));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(toIdentifier.getForm())
|
||||||
|
{
|
||||||
|
case TALKGROUP -> call.setCallType("Talk Group");
|
||||||
|
case RADIO ->
|
||||||
|
{
|
||||||
|
LOGGER.info("Private Call detected - TO identifier: " + toIdentifier);
|
||||||
|
call.setCallType("Private");
|
||||||
|
}
|
||||||
|
case PATCH_GROUP -> call.setCallType("Patch Group");
|
||||||
|
default -> call.setCallType(toIdentifier.getForm().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.playbackfx.AudioPlaybackChannelController;
|
||||||
|
import io.github.dsheirer.audio.playbackfx.IAudioPlaybackStatusListener;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.scene.control.Separator;
|
||||||
|
import javafx.scene.control.ToggleButton;
|
||||||
|
import javafx.scene.control.ToggleGroup;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.media.MediaPlayer;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
|
import jiconfont.javafx.IconNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio playback controller.
|
||||||
|
*/
|
||||||
|
public class AudioPlaybackControlView extends HBox implements IAudioPlaybackStatusListener
|
||||||
|
{
|
||||||
|
private static final int ICON_SIZE = 16;
|
||||||
|
private ObjectProperty<MediaPlayer> mMediaPlayer = new SimpleObjectProperty<>();
|
||||||
|
private ToggleButton mAutoRepeatButton;
|
||||||
|
private ToggleGroup mPlaybackGroup;
|
||||||
|
private ToggleButton mPlayButton;
|
||||||
|
private ToggleButton mPauseButton;
|
||||||
|
private ToggleButton mStopButton;
|
||||||
|
private boolean mUpdating = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public AudioPlaybackControlView()
|
||||||
|
{
|
||||||
|
setSpacing(2);
|
||||||
|
Separator separator = new Separator(Orientation.VERTICAL);
|
||||||
|
separator.setPadding(new Insets(0, 5, 0 , 7));
|
||||||
|
getChildren().addAll(getStopButton(), getPlayButton(), getPauseButton(), getAutoRepeatButton());
|
||||||
|
mediaPlayerProperty().addListener((observable, oldValue, newValue) -> updateControls());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the IAudioPLaybackStatusListener method to click the stop button when end of media is detected.
|
||||||
|
* @param controller where this occurred.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void endOfMedia(AudioPlaybackChannelController controller)
|
||||||
|
{
|
||||||
|
getStopButton().setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void playbackStatusUpdated(AudioPlaybackChannelController controller, MediaPlayer.Status previousStatus,
|
||||||
|
MediaPlayer.Status currentStatus)
|
||||||
|
{
|
||||||
|
if(currentStatus != null)
|
||||||
|
{
|
||||||
|
switch(currentStatus)
|
||||||
|
{
|
||||||
|
case UNKNOWN:
|
||||||
|
break;
|
||||||
|
case READY:
|
||||||
|
break;
|
||||||
|
case PAUSED:
|
||||||
|
mUpdating = true;
|
||||||
|
getPauseButton().setSelected(true);
|
||||||
|
mUpdating = false;
|
||||||
|
break;
|
||||||
|
case PLAYING:
|
||||||
|
mUpdating = true;
|
||||||
|
getPlayButton().setSelected(true);
|
||||||
|
mUpdating = false;
|
||||||
|
break;
|
||||||
|
case STOPPED:
|
||||||
|
mUpdating = true;
|
||||||
|
getStopButton().setSelected(true);
|
||||||
|
mUpdating = false;
|
||||||
|
if(getAutoRepeatButton().isSelected())
|
||||||
|
{
|
||||||
|
getPlayButton().setSelected(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STALLED:
|
||||||
|
case HALTED:
|
||||||
|
case DISPOSED:
|
||||||
|
System.out.println("Playback is " + currentStatus);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the controls when the media player changes
|
||||||
|
*/
|
||||||
|
private void updateControls()
|
||||||
|
{
|
||||||
|
boolean disable = mediaPlayerProperty().get() == null;
|
||||||
|
getStopButton().setDisable(disable);
|
||||||
|
getPlayButton().setDisable(disable);
|
||||||
|
getPauseButton().setDisable(disable);
|
||||||
|
getAutoRepeatButton().setDisable(disable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media player property that can be bound to a media player controller.
|
||||||
|
*/
|
||||||
|
public ObjectProperty<MediaPlayer> mediaPlayerProperty()
|
||||||
|
{
|
||||||
|
return mMediaPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Playback toggle buttons group (play, stop, and pause)
|
||||||
|
*/
|
||||||
|
private ToggleGroup getPlaybackGroup()
|
||||||
|
{
|
||||||
|
if(mPlaybackGroup == null)
|
||||||
|
{
|
||||||
|
mPlaybackGroup = new ToggleGroup();
|
||||||
|
mPlaybackGroup.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> {
|
||||||
|
MediaPlayer mediaPlayer = mediaPlayerProperty().get();
|
||||||
|
|
||||||
|
if(!mUpdating && mediaPlayer != null)
|
||||||
|
{
|
||||||
|
if(getPlayButton().equals(selectedToggle))
|
||||||
|
{
|
||||||
|
mediaPlayer.play();
|
||||||
|
}
|
||||||
|
else if(getPauseButton().equals(selectedToggle))
|
||||||
|
{
|
||||||
|
mediaPlayer.pause();
|
||||||
|
}
|
||||||
|
else if(getStopButton().equals(selectedToggle))
|
||||||
|
{
|
||||||
|
mediaPlayer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPlaybackGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play button
|
||||||
|
*/
|
||||||
|
private ToggleButton getPlayButton()
|
||||||
|
{
|
||||||
|
if(mPlayButton == null)
|
||||||
|
{
|
||||||
|
mPlayButton = new ToggleButton();
|
||||||
|
mPlayButton.setToggleGroup(getPlaybackGroup());
|
||||||
|
mPlayButton.setTooltip(new Tooltip("Play Audio"));
|
||||||
|
mPlayButton.setDisable(true);
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mPlayButton.setGraphic(iconNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPlayButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause button
|
||||||
|
*/
|
||||||
|
private ToggleButton getPauseButton()
|
||||||
|
{
|
||||||
|
if(mPauseButton == null)
|
||||||
|
{
|
||||||
|
mPauseButton = new ToggleButton();
|
||||||
|
mPauseButton.setToggleGroup(getPlaybackGroup());
|
||||||
|
mPauseButton.setTooltip(new Tooltip("Pause Audio Playback"));
|
||||||
|
mPauseButton.setDisable(true);
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.PAUSE);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mPauseButton.setGraphic(iconNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPauseButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop button
|
||||||
|
*/
|
||||||
|
private ToggleButton getStopButton()
|
||||||
|
{
|
||||||
|
if(mStopButton == null)
|
||||||
|
{
|
||||||
|
mStopButton = new ToggleButton();
|
||||||
|
mStopButton.setToggleGroup(getPlaybackGroup());
|
||||||
|
mStopButton.setTooltip(new Tooltip("Stop Audio Playback"));
|
||||||
|
mStopButton.setDisable(true);
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.STOP);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mStopButton.setGraphic(iconNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mStopButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToggleButton getAutoRepeatButton()
|
||||||
|
{
|
||||||
|
if(mAutoRepeatButton == null)
|
||||||
|
{
|
||||||
|
mAutoRepeatButton = new ToggleButton();
|
||||||
|
mAutoRepeatButton.setTooltip(new Tooltip("Auto-repeat audio playback"));
|
||||||
|
mAutoRepeatButton.setDisable(true);
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.REPEAT);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mAutoRepeatButton.setGraphic(iconNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAutoRepeatButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.Alias;
|
import io.github.dsheirer.alias.Alias;
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
@@ -26,6 +29,14 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
|||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
@@ -35,15 +46,6 @@ import javafx.collections.ObservableSet;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
|
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
|
||||||
* a discrete (ie start/stop) audio event, or it may be a time-constrained portion of an ongoing continuous audio
|
* a discrete (ie start/stop) audio event, or it may be a time-constrained portion of an ongoing continuous audio
|
||||||
@@ -72,13 +74,13 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||||
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
||||||
private List<float[]> mAudioBuffers = new CopyOnWriteArrayList();
|
private List<float[]> mAudioBuffers = new CopyOnWriteArrayList();
|
||||||
private AtomicInteger mConsumerCount = new AtomicInteger();
|
|
||||||
private AliasList mAliasList;
|
private AliasList mAliasList;
|
||||||
private long mStartTimestamp = System.currentTimeMillis();
|
private long mStartTimestamp = System.currentTimeMillis();
|
||||||
private long mSampleCount = 0;
|
private long mSampleCount = 0;
|
||||||
private boolean mDisposing = false;
|
private boolean mDisposing = false;
|
||||||
private AudioSegment mLinkedAudioSegment;
|
private AudioSegment mLinkedAudioSegment;
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
|
private List<String> mLeases = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -92,6 +94,50 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
mIdentifierCollection.setTimeslot(timeslot);
|
mIdentifierCollection.setTimeslot(timeslot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a lease to this audio segment. This audio segment will remain alive until all leases are removed.
|
||||||
|
* @param lease to add
|
||||||
|
*/
|
||||||
|
public void addLease(String lease)
|
||||||
|
{
|
||||||
|
synchronized(mLeases)
|
||||||
|
{
|
||||||
|
if(mLeases.contains(lease))
|
||||||
|
{
|
||||||
|
mLog.warn("Attempt to obtain duplicate lease: " + lease);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLeases.add(lease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the lease from this audio segment. Once all leases are removed, the audio segment is disposed.
|
||||||
|
* @param lease to remove
|
||||||
|
*/
|
||||||
|
public void removeLease(String lease)
|
||||||
|
{
|
||||||
|
synchronized(mLeases)
|
||||||
|
{
|
||||||
|
if(mLeases.contains(lease))
|
||||||
|
{
|
||||||
|
mLeases.remove(lease);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.warn("Attempt to remove a non-existent lease: " + lease);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mLeases.isEmpty())
|
||||||
|
{
|
||||||
|
mLog.warn("Invoking dispose() on non-leased audio segment - last lease was: " + lease);
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeslot for this audio segment
|
* Timeslot for this audio segment
|
||||||
*/
|
*/
|
||||||
@@ -139,25 +185,12 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
* Indicates if this audio segment is completed.
|
||||||
* an audio segment is a duplicate.
|
* @return true if completed.
|
||||||
*/
|
*/
|
||||||
public BooleanProperty duplicateProperty()
|
public boolean isComplete()
|
||||||
{
|
{
|
||||||
return mDuplicate;
|
return mComplete.get();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An observable list of broadcast channels for this audio segment. Broadcast channels are added to this segment
|
|
||||||
* across the life-cycle of the segment. As each new alias identifier is added to the segment, any broadcast
|
|
||||||
* channels assigned to the alias are added to this list. The audio segment producer can also add broadcast
|
|
||||||
* channels to this list.
|
|
||||||
*
|
|
||||||
* @return observable set of broadcast channels
|
|
||||||
*/
|
|
||||||
public ObservableSet<BroadcastChannel> broadcastChannelsProperty()
|
|
||||||
{
|
|
||||||
return mBroadcastChannels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -262,7 +295,7 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
* Adds the collection of identifiers to this segment's identifier collection
|
* Adds the collection of identifiers to this segment's identifier collection
|
||||||
* @param identifiers to pre-load into this audio segment
|
* @param identifiers to pre-load into this audio segment
|
||||||
*/
|
*/
|
||||||
void addIdentifiers(Collection<Identifier> identifiers)
|
public void addIdentifiers(Collection<Identifier> identifiers)
|
||||||
{
|
{
|
||||||
for(Identifier identifier: identifiers)
|
for(Identifier identifier: identifiers)
|
||||||
{
|
{
|
||||||
@@ -290,24 +323,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
return mAudioBuffers.size();
|
return mAudioBuffers.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the audio buffer at the specified index
|
|
||||||
* @param index of the buffer to fetch
|
|
||||||
* @return audio buffer
|
|
||||||
* @throws IllegalArgumentException if requested index is not valid
|
|
||||||
*/
|
|
||||||
public float[] getAudioBuffer(int index)
|
|
||||||
{
|
|
||||||
if(0 <= index && index < getAudioBufferCount())
|
|
||||||
{
|
|
||||||
return mAudioBuffers.get(index);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Requested audio buffer at index [" + index + "] does not exist");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this audio segment has one or more audio buffers
|
* Indicates if this audio segment has one or more audio buffers
|
||||||
*/
|
*/
|
||||||
@@ -328,34 +343,6 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
mLinkedAudioSegment = null;
|
mLinkedAudioSegment = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Increments the consumer count to indicate that a consumer is currently processing this segment. When the
|
|
||||||
* consumer count returns to zero, this indicates that all consumers are finished with the audio segment and the
|
|
||||||
* resources can be reclaimed.
|
|
||||||
*
|
|
||||||
* Consumer count should only be increased by the producer of the audio segment, or if a consumer distributes the
|
|
||||||
* segment to additional consumers.
|
|
||||||
*/
|
|
||||||
public void incrementConsumerCount()
|
|
||||||
{
|
|
||||||
mConsumerCount.incrementAndGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrements the consumer count. Consumers of this audio segment should invoke this method to signal that they
|
|
||||||
* will no longer need this audio segment. When all consumers are finished with an audio segment, the audio
|
|
||||||
* segment resources will be reclaimed.
|
|
||||||
*/
|
|
||||||
public void decrementConsumerCount()
|
|
||||||
{
|
|
||||||
int count = mConsumerCount.decrementAndGet();
|
|
||||||
|
|
||||||
if(count <= 0)
|
|
||||||
{
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an audio buffer to this segment. Note the producer of the audio buffer should increment the user count
|
* Adds an audio buffer to this segment. Note the producer of the audio buffer should increment the user count
|
||||||
* of the buffer prior to adding it to this segment. This segment will decrement the audio buffer user count once
|
* of the buffer prior to adding it to this segment. This segment will decrement the audio buffer user count once
|
||||||
@@ -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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
@@ -41,11 +41,8 @@ public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster
|
|||||||
{
|
{
|
||||||
for(Listener<T> listener : getListeners())
|
for(Listener<T> listener : getListeners())
|
||||||
{
|
{
|
||||||
audioSegment.incrementConsumerCount();
|
audioSegment.addLease(listener.getClass().toString());
|
||||||
listener.receive(audioSegment);
|
listener.receive(audioSegment);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Decrement consumer counter for this broadcaster
|
|
||||||
audioSegment.decrementConsumerCount();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
495
src/main/java/io/github/dsheirer/audio/call/Call.java
Normal file
495
src/main/java/io/github/dsheirer/audio/call/Call.java
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.call;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.SequenceGenerator;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.Transient;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.LongProperty;
|
||||||
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call database entity.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name="calls", schema="sdrtrunk")
|
||||||
|
public class Call
|
||||||
|
{
|
||||||
|
public static final String COLUMN_CALL_TYPE = "callType";
|
||||||
|
public static final String COLUMN_CHANNEL = "channel";
|
||||||
|
public static final String COLUMN_DUPLICATE = "duplicate";
|
||||||
|
public static final String COLUMN_DURATION = "duration";
|
||||||
|
public static final String COLUMN_EVENT_TIME = "eventTime";
|
||||||
|
public static final String COLUMN_FILE = "file";
|
||||||
|
public static final String COLUMN_FREQUENCY = "frequency";
|
||||||
|
public static final String COLUMN_FROM_ALIAS = "fromAlias";
|
||||||
|
public static final String COLUMN_FROM_ID = "fromId";
|
||||||
|
public static final String COLUMN_ID = "id";
|
||||||
|
public static final String COLUMN_MONITOR = "monitor";
|
||||||
|
public static final String COLUMN_PROTOCOL = "protocol";
|
||||||
|
public static final String COLUMN_RECORD = "record";
|
||||||
|
public static final String COLUMN_STREAM = "stream";
|
||||||
|
public static final String COLUMN_TO_ALIAS = "toAlias";
|
||||||
|
public static final String COLUMN_TO_ID = "toId";
|
||||||
|
public static final String COLUMN_SITE = "site";
|
||||||
|
public static final String COLUMN_SYSTEM = "system";
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator")
|
||||||
|
@SequenceGenerator(name = "id_generator", allocationSize = 1)
|
||||||
|
@Column(name=COLUMN_ID)
|
||||||
|
private long id;
|
||||||
|
@Column(name=COLUMN_CALL_TYPE)
|
||||||
|
private String callType;
|
||||||
|
@Column(name=COLUMN_CHANNEL)
|
||||||
|
private String channel;
|
||||||
|
@Column(name=COLUMN_DUPLICATE)
|
||||||
|
private boolean duplicate;
|
||||||
|
@Column(name=COLUMN_DURATION)
|
||||||
|
private double duration;
|
||||||
|
@Column(name=COLUMN_EVENT_TIME)
|
||||||
|
private long eventTime;
|
||||||
|
@Column(name=COLUMN_FILE)
|
||||||
|
private String file;
|
||||||
|
@Column(name=COLUMN_FREQUENCY)
|
||||||
|
private double frequency;
|
||||||
|
@Column(name=COLUMN_FROM_ALIAS)
|
||||||
|
private String fromAlias;
|
||||||
|
@Column(name=COLUMN_FROM_ID)
|
||||||
|
private String fromId;
|
||||||
|
@Column(name=COLUMN_MONITOR)
|
||||||
|
private int monitor;
|
||||||
|
@Column(name=COLUMN_PROTOCOL)
|
||||||
|
private String protocol;
|
||||||
|
@Column(name=COLUMN_RECORD)
|
||||||
|
private boolean record;
|
||||||
|
@Column(name=COLUMN_SITE)
|
||||||
|
private String site;
|
||||||
|
@Column(name=COLUMN_STREAM)
|
||||||
|
private boolean stream;
|
||||||
|
@Column(name=COLUMN_SYSTEM)
|
||||||
|
private String system;
|
||||||
|
@Column(name=COLUMN_TO_ALIAS)
|
||||||
|
private String toAlias;
|
||||||
|
@Column(name=COLUMN_TO_ID)
|
||||||
|
private String toId;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private boolean mComplete = true;
|
||||||
|
@Transient
|
||||||
|
private LongProperty mLastUpdated = new SimpleLongProperty();
|
||||||
|
@Transient
|
||||||
|
private StringProperty mPlaybackChannel = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public Call()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record ID, auto-assigned by the database.
|
||||||
|
* @return id
|
||||||
|
*/
|
||||||
|
public long getId()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp for the event
|
||||||
|
* @return timestamp in milliseconds since epoch
|
||||||
|
*/
|
||||||
|
public long getEventTime()
|
||||||
|
{
|
||||||
|
return eventTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the timestamp
|
||||||
|
* @param eventTime in ms.
|
||||||
|
*/
|
||||||
|
public void setEventTime(long eventTime)
|
||||||
|
{
|
||||||
|
this.eventTime = eventTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From identifier
|
||||||
|
* @return from ID
|
||||||
|
*/
|
||||||
|
public String getFromId()
|
||||||
|
{
|
||||||
|
return fromId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the from identifier
|
||||||
|
* @param fromId ID
|
||||||
|
*/
|
||||||
|
public void setFromId(String fromId)
|
||||||
|
{
|
||||||
|
this.fromId = fromId;
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From alias
|
||||||
|
* @return from alias
|
||||||
|
*/
|
||||||
|
public String getFromAlias()
|
||||||
|
{
|
||||||
|
return fromAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the from alias value.
|
||||||
|
* @param fromAlias value
|
||||||
|
*/
|
||||||
|
public void setFromAlias(String fromAlias)
|
||||||
|
{
|
||||||
|
this.fromAlias = fromAlias;
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To identifier
|
||||||
|
* @return to
|
||||||
|
*/
|
||||||
|
public String getToId()
|
||||||
|
{
|
||||||
|
return toId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the to identifier
|
||||||
|
* @param toId value
|
||||||
|
*/
|
||||||
|
public void setToId(String toId)
|
||||||
|
{
|
||||||
|
this.toId = toId;
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To alias value
|
||||||
|
* @return alias value
|
||||||
|
*/
|
||||||
|
public String getToAlias()
|
||||||
|
{
|
||||||
|
return toAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the to alias value.
|
||||||
|
* @param toAlias value
|
||||||
|
*/
|
||||||
|
public void setToAlias(String toAlias)
|
||||||
|
{
|
||||||
|
this.toAlias = toAlias;
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System name
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String getSystem()
|
||||||
|
{
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system name
|
||||||
|
* @param system name
|
||||||
|
*/
|
||||||
|
public void setSystem(String system)
|
||||||
|
{
|
||||||
|
this.system = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Site name
|
||||||
|
* @return site
|
||||||
|
*/
|
||||||
|
public String getSite()
|
||||||
|
{
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the site name
|
||||||
|
* @param site name
|
||||||
|
*/
|
||||||
|
public void setSite(String site)
|
||||||
|
{
|
||||||
|
this.site = site;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel name
|
||||||
|
* @return channel
|
||||||
|
*/
|
||||||
|
public String getChannel()
|
||||||
|
{
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the channel name
|
||||||
|
* @param channel name
|
||||||
|
*/
|
||||||
|
public void setChannel(String channel)
|
||||||
|
{
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency for the channel
|
||||||
|
* @return frequency.
|
||||||
|
*/
|
||||||
|
public double getFrequency()
|
||||||
|
{
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the frequency value for the channel.
|
||||||
|
* @param frequency value.
|
||||||
|
*/
|
||||||
|
public void setFrequency(double frequency)
|
||||||
|
{
|
||||||
|
this.frequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File path for the call
|
||||||
|
* @return file path
|
||||||
|
*/
|
||||||
|
public String getFile()
|
||||||
|
{
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the file path for the call.
|
||||||
|
* @param file path
|
||||||
|
*/
|
||||||
|
public void setFile(String file)
|
||||||
|
{
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of the call in seconds
|
||||||
|
* @return duration seconds
|
||||||
|
*/
|
||||||
|
public double getDuration()
|
||||||
|
{
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the call duration.
|
||||||
|
* @param duration value in seconds
|
||||||
|
*/
|
||||||
|
public void setDuration(double duration)
|
||||||
|
{
|
||||||
|
this.duration = duration;
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radio protocol
|
||||||
|
* @return protocol
|
||||||
|
*/
|
||||||
|
public String getProtocol()
|
||||||
|
{
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the radio protocol
|
||||||
|
* @param protocol for the call
|
||||||
|
*/
|
||||||
|
public void setProtocol(String protocol)
|
||||||
|
{
|
||||||
|
this.protocol = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this call is complete and will receive no further updates.
|
||||||
|
* @return true if complete.
|
||||||
|
*/
|
||||||
|
public boolean isComplete()
|
||||||
|
{
|
||||||
|
return mComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the complete status for this call.
|
||||||
|
* @param complete true if complete
|
||||||
|
*/
|
||||||
|
public void setComplete(boolean complete)
|
||||||
|
{
|
||||||
|
mComplete = complete;
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call type
|
||||||
|
* @return call type
|
||||||
|
*/
|
||||||
|
public String getCallType()
|
||||||
|
{
|
||||||
|
return callType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the call type
|
||||||
|
* @param callType of call
|
||||||
|
*/
|
||||||
|
public void setCallType(String callType)
|
||||||
|
{
|
||||||
|
this.callType = callType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this call was flagged as a duplicate
|
||||||
|
* @return true if duplicate
|
||||||
|
*/
|
||||||
|
public boolean isDuplicate()
|
||||||
|
{
|
||||||
|
return duplicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duplicate flag for this call.
|
||||||
|
* @param duplicate flag
|
||||||
|
*/
|
||||||
|
public void setDuplicate(boolean duplicate)
|
||||||
|
{
|
||||||
|
this.duplicate = duplicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitor/listen priority
|
||||||
|
* @return priority where a -1 value is do not monitor.
|
||||||
|
*/
|
||||||
|
public int getMonitor()
|
||||||
|
{
|
||||||
|
return monitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the monitor/listen priority
|
||||||
|
* @param monitor priority
|
||||||
|
*/
|
||||||
|
public void setMonitor(int monitor)
|
||||||
|
{
|
||||||
|
this.monitor = monitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the call is recordable/retainable.
|
||||||
|
* @return is recordable
|
||||||
|
*/
|
||||||
|
public boolean isRecord()
|
||||||
|
{
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the record/retain flag.
|
||||||
|
* @param record flag
|
||||||
|
*/
|
||||||
|
public void setRecord(boolean record)
|
||||||
|
{
|
||||||
|
this.record = record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the call is streamable.
|
||||||
|
* @return streamable
|
||||||
|
*/
|
||||||
|
public boolean isStream()
|
||||||
|
{
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the stream flag
|
||||||
|
* @param stream flag
|
||||||
|
*/
|
||||||
|
public void setStream(boolean stream)
|
||||||
|
{
|
||||||
|
this.stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the last updated timestamp.
|
||||||
|
*/
|
||||||
|
private void updateTimestamp()
|
||||||
|
{
|
||||||
|
if(Platform.isFxApplicationThread())
|
||||||
|
{
|
||||||
|
lastUpdatedProperty().set(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Platform.runLater(() -> lastUpdatedProperty().set(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last updated timestamp. This value is updated each time any attribute in the call record is updated so that
|
||||||
|
* consumer processes can monitor this property to be notified of updates.
|
||||||
|
* @return timestamp of last update in milliseconds.
|
||||||
|
*/
|
||||||
|
public LongProperty lastUpdatedProperty()
|
||||||
|
{
|
||||||
|
return mLastUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current playback channel for this call.
|
||||||
|
* @return current playback channel or null if this call is not being played back.
|
||||||
|
*/
|
||||||
|
public StringProperty playbackChannelProperty()
|
||||||
|
{
|
||||||
|
return mPlaybackChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
return obj instanceof Call c && getId() == c.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.call;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls database repository
|
||||||
|
*/
|
||||||
|
public interface CallRepository extends JpaRepository<Call,Long>
|
||||||
|
{
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.toId like ?1 OR t.toAlias like ?1 OR t.fromId like ?1 OR t.fromAlias like ?1")
|
||||||
|
Page<Call> findByAnyIdWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.toId like ?1 OR t.toAlias like ?1")
|
||||||
|
Page<Call> findByToWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.fromId like ?1 OR t.fromAlias like ?1")
|
||||||
|
Page<Call> findByFromWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.callType like ?1")
|
||||||
|
Page<Call> findByCallTypeWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.channel like ?1")
|
||||||
|
Page<Call> findByChannelWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.protocol like ?1")
|
||||||
|
Page<Call> findByProtocolWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.site like ?1")
|
||||||
|
Page<Call> findBySiteWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT t FROM Call t WHERE t.system like ?1")
|
||||||
|
Page<Call> findBySystemWithPagination(String value, Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all calls between the timestamp range arguments.
|
||||||
|
* @param start timestamp
|
||||||
|
* @param end timestamp
|
||||||
|
* @return pageable set of calls.
|
||||||
|
*/
|
||||||
|
Page<Call> findCallsByEventTimeBetweenOrderByEventTime(long start, long end, Pageable pageable);
|
||||||
|
}
|
||||||
971
src/main/java/io/github/dsheirer/audio/call/CallView.java
Normal file
971
src/main/java/io/github/dsheirer/audio/call/CallView.java
Normal file
@@ -0,0 +1,971 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.call;
|
||||||
|
|
||||||
|
import io.github.dsheirer.alias.Alias;
|
||||||
|
import io.github.dsheirer.audio.AudioManager;
|
||||||
|
import io.github.dsheirer.audio.playbackfx.AudioPlaybackChannelController;
|
||||||
|
import io.github.dsheirer.audio.playbackfx.AudioPlaybackController;
|
||||||
|
import io.github.dsheirer.audio.playbackfx.PlaybackMode;
|
||||||
|
import io.github.dsheirer.gui.control.TimestampTableCellFactory;
|
||||||
|
import io.github.dsheirer.gui.javafx.table.TableColumnEditor;
|
||||||
|
import io.github.dsheirer.gui.javafx.table.TableViewColumnController;
|
||||||
|
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.LongProperty;
|
||||||
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import javafx.scene.control.Separator;
|
||||||
|
import javafx.scene.control.SplitPane;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import jiconfont.icons.elusive.Elusive;
|
||||||
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
|
import jiconfont.javafx.IconNode;
|
||||||
|
import org.controlsfx.control.textfield.TextFields;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio call events playback and audio event database control/view.
|
||||||
|
*/
|
||||||
|
public class CallView extends VBox implements IPageRequestListener
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CallView.class);
|
||||||
|
private static final Sort DEFAULT_SORT = Sort.by("eventTime").descending();
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AudioManager mAudioManager;
|
||||||
|
@Resource
|
||||||
|
private CallRepository mCallRepository;
|
||||||
|
@Resource
|
||||||
|
private AudioPlaybackController mAudioPlaybackController;
|
||||||
|
|
||||||
|
private CallEventListener mCallEventListener = new CallEventListener();
|
||||||
|
private SortOrderListener mSortOrderListener = new SortOrderListener();
|
||||||
|
private ObservableList<Call> mCalls = FXCollections.observableArrayList();
|
||||||
|
private TableView<Call> mCallTableView;
|
||||||
|
private TableViewColumnController mCallTableViewMonitor;
|
||||||
|
private ProgressIndicator mPlaceholderProgressIndicator;
|
||||||
|
private HBox mSearchBox;
|
||||||
|
private HBox mPagingBox;
|
||||||
|
private TextField mSearchField;
|
||||||
|
private PagingController mPagingController;
|
||||||
|
private ComboBox<Integer> mPageSizeComboBox;
|
||||||
|
private Label mCallsCountLabel;
|
||||||
|
private LongProperty mCallCount = new SimpleLongProperty();
|
||||||
|
private Sort mSort = DEFAULT_SORT;
|
||||||
|
private ComboBox<SearchColumn> mSearchColumnComboBox;
|
||||||
|
private CheckBox mSearchExactCheckBox;
|
||||||
|
private AudioPlaybackControlView mAudioPlaybackControlView;
|
||||||
|
private Button mAutoModeLeftButton;
|
||||||
|
private Button mAutoModeRightButton;
|
||||||
|
private Button mLockModeLeftButton;
|
||||||
|
private Button mLockModeRightButton;
|
||||||
|
private TableColumnEditor mTableColumnEditor;
|
||||||
|
private SplitPane mTableSplitPane;
|
||||||
|
private Button mConfigureColumnsButton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public CallView()
|
||||||
|
{
|
||||||
|
mCallCount.addListener((observable, oldValue, newValue) -> {
|
||||||
|
getCallsCountLabel().setText("Calls: " + newValue);
|
||||||
|
int pageCount = (int)(mCallCount.get() / getPageSize());
|
||||||
|
if(mCallCount.get() % getPageSize() != 0)
|
||||||
|
{
|
||||||
|
pageCount++;
|
||||||
|
}
|
||||||
|
getPagingController().update(getPagingController().getCurrentPage(), pageCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post instantiation/startup steps.
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void postConstruct()
|
||||||
|
{
|
||||||
|
//Register the audio playback control view as a playback status listener with the primary audio channel controller
|
||||||
|
mAudioPlaybackController.getPrimaryController().add(getAudioPlaybackControlView());
|
||||||
|
|
||||||
|
HBox controlBox = new HBox();
|
||||||
|
controlBox.setAlignment(Pos.CENTER);
|
||||||
|
controlBox.setSpacing(2);
|
||||||
|
controlBox.setPadding(new Insets(5, 5, 5, 5));
|
||||||
|
getAudioPlaybackControlView().setAlignment(Pos.CENTER_LEFT);
|
||||||
|
HBox.setHgrow(getSearchBox(), Priority.ALWAYS);
|
||||||
|
getSearchBox().setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
|
||||||
|
Separator separator1 = new Separator(Orientation.VERTICAL);
|
||||||
|
separator1.setPadding(new Insets(0, 10, 0, 10));
|
||||||
|
Separator separator2 = new Separator(Orientation.VERTICAL);
|
||||||
|
separator2.setPadding(new Insets(0, 10, 0, 10));
|
||||||
|
Label autoLabel = new Label("Auto:");
|
||||||
|
Label lockLabel = new Label("Lock:");
|
||||||
|
getSearchExactCheckBox().setPadding(new Insets(0, 10, 0, 0));
|
||||||
|
controlBox.getChildren().addAll(getAudioPlaybackControlView(), separator1, autoLabel, getAutoModeLeftButton(),
|
||||||
|
getAutoModeRightButton(), separator2, lockLabel, getLockModeLeftButton(), getLockModeRightButton(),
|
||||||
|
getSearchBox(), getConfigureColumnsButton());
|
||||||
|
|
||||||
|
getTableColumnEditor().closedProperty().addListener((observable, previouslyClosed, closed) -> {
|
||||||
|
if(closed && !previouslyClosed)
|
||||||
|
{
|
||||||
|
getTableSplitPane().getItems().remove(getTableColumnEditor());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
VBox.setVgrow(getTableSplitPane(), Priority.ALWAYS);
|
||||||
|
getChildren().addAll(controlBox, getTableSplitPane(), getPagingBox());
|
||||||
|
|
||||||
|
getPagingController().setPadding(new Insets(5, 5, 5, 5));
|
||||||
|
getPagingController().update(1, 20);
|
||||||
|
|
||||||
|
//Register for call event notifications
|
||||||
|
mAudioManager.add(mCallEventListener);
|
||||||
|
|
||||||
|
getPlaceholderProgressIndicator().setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-destruction or shutdown steps
|
||||||
|
*/
|
||||||
|
@PreDestroy
|
||||||
|
public void preDestroy()
|
||||||
|
{
|
||||||
|
//De-register from call event notifications
|
||||||
|
mAudioManager.remove(mCallEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows first page of results.
|
||||||
|
*/
|
||||||
|
private void showFirstPage()
|
||||||
|
{
|
||||||
|
showPage(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the page to view. This implements the IPageRequestListener interface.
|
||||||
|
* @param page number to display.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void showPage(int page)
|
||||||
|
{
|
||||||
|
if(page >= 1)
|
||||||
|
{
|
||||||
|
Platform.runLater(() -> getPlaceholderProgressIndicator().setVisible(true));
|
||||||
|
|
||||||
|
final String searchTerm = getSearchField().getText();
|
||||||
|
final SearchColumn searchColumn = getSearchColumnComboBox().getSelectionModel().getSelectedItem();
|
||||||
|
final boolean wildcard = !getSearchExactCheckBox().isSelected();
|
||||||
|
|
||||||
|
//Spin the query off onto a cached thread and then place the results back onto the FX thread.
|
||||||
|
ThreadPool.CACHED.submit(() ->
|
||||||
|
{
|
||||||
|
final PageRequest pageRequest = PageRequest.of(page - 1, getPageSize(), mSort);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Page<Call> callPage = null;
|
||||||
|
|
||||||
|
if(searchTerm != null && !searchTerm.isEmpty() && searchColumn != null)
|
||||||
|
{
|
||||||
|
final String wildcardSearch = (wildcard ? "%" : "") + searchTerm + (wildcard ? "%" : "");
|
||||||
|
|
||||||
|
switch(searchColumn)
|
||||||
|
{
|
||||||
|
case ANY_ID -> callPage = mCallRepository.findByAnyIdWithPagination(wildcardSearch, pageRequest);
|
||||||
|
case CALL_TYPE -> callPage = mCallRepository.findByCallTypeWithPagination(wildcardSearch, pageRequest);
|
||||||
|
case CHANNEL -> callPage = mCallRepository.findByChannelWithPagination(wildcardSearch, pageRequest);
|
||||||
|
case FROM -> callPage = mCallRepository.findByFromWithPagination(wildcardSearch, pageRequest);
|
||||||
|
case PROTOCOL -> callPage = mCallRepository.findByProtocolWithPagination(wildcardSearch, pageRequest);
|
||||||
|
case SITE -> callPage = mCallRepository.findBySiteWithPagination(wildcardSearch, pageRequest);
|
||||||
|
case SYSTEM -> callPage = mCallRepository.findBySystemWithPagination(wildcardSearch, pageRequest);
|
||||||
|
case TO -> callPage = mCallRepository.findByToWithPagination(wildcardSearch, pageRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callPage = mCallRepository.findAll(pageRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(callPage != null)
|
||||||
|
{
|
||||||
|
final Page<Call> finalCallPage = callPage;
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
mCalls.clear();
|
||||||
|
mCalls.addAll(finalCallPage.stream().toList());
|
||||||
|
getPlaceholderProgressIndicator().setVisible(false);
|
||||||
|
getPagingController().update(finalCallPage.getNumber() + 1, finalCallPage.getTotalPages());
|
||||||
|
mCallCount.set(finalCallPage.getTotalElements());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
LOGGER.error("Error submitting paging [" + page + "] query to database", t);
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
getPlaceholderProgressIndicator().setVisible(false);
|
||||||
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
|
if(getParent() != null)
|
||||||
|
{
|
||||||
|
alert.initOwner(getParent().getScene().getWindow());
|
||||||
|
}
|
||||||
|
alert.setHeaderText("Database Error!");
|
||||||
|
alert.setContentText("Error retrieving page " + page + " from the database - reason: " + t.getMessage());
|
||||||
|
alert.showAndWait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.warn("Invalid page [" + page + "] number request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the sort order for the table and resets the page to page 1.
|
||||||
|
*/
|
||||||
|
private void updateSortOrder()
|
||||||
|
{
|
||||||
|
mSort = null;
|
||||||
|
|
||||||
|
for(TableColumn<Call,?> tableColumn: getCallTableView().getSortOrder())
|
||||||
|
{
|
||||||
|
Sort columnSort = Sort.by(tableColumn.getId());
|
||||||
|
if(tableColumn.getSortType() == TableColumn.SortType.DESCENDING)
|
||||||
|
{
|
||||||
|
columnSort = columnSort.descending();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mSort == null)
|
||||||
|
{
|
||||||
|
mSort = columnSort;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSort = mSort.and(columnSort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mSort == null)
|
||||||
|
{
|
||||||
|
mSort = DEFAULT_SORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
showFirstPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current page size
|
||||||
|
*/
|
||||||
|
private int getPageSize()
|
||||||
|
{
|
||||||
|
return getPageSizeComboBox().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure column order and visibility button
|
||||||
|
*/
|
||||||
|
private Button getConfigureColumnsButton()
|
||||||
|
{
|
||||||
|
if(mConfigureColumnsButton == null)
|
||||||
|
{
|
||||||
|
mConfigureColumnsButton = new Button();
|
||||||
|
mConfigureColumnsButton.setTooltip(new Tooltip("Configure call table column ordering and visibility"));
|
||||||
|
IconNode icon = new IconNode(FontAwesome.COG);
|
||||||
|
mConfigureColumnsButton.setGraphic(icon);
|
||||||
|
mConfigureColumnsButton.setOnAction(event -> {
|
||||||
|
//Set the split pane to expose the column configuration panel.
|
||||||
|
if(!getTableSplitPane().getItems().contains(getTableColumnEditor()))
|
||||||
|
{
|
||||||
|
getTableSplitPane().getItems().add(getTableColumnEditor());
|
||||||
|
}
|
||||||
|
getTableColumnEditor().closedProperty().set(false);
|
||||||
|
double splitWidth = getTableSplitPane().getWidth();
|
||||||
|
double scale = 0.5;
|
||||||
|
if(splitWidth > 260)
|
||||||
|
{
|
||||||
|
scale = (splitWidth - 260) / splitWidth;
|
||||||
|
}
|
||||||
|
getTableSplitPane().setDividerPosition(0, scale);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mConfigureColumnsButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split pane for call table and table column editor
|
||||||
|
*/
|
||||||
|
private SplitPane getTableSplitPane()
|
||||||
|
{
|
||||||
|
if(mTableSplitPane == null)
|
||||||
|
{
|
||||||
|
mTableSplitPane = new SplitPane(getCallTableView());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTableSplitPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table column order/visibility editor
|
||||||
|
*/
|
||||||
|
private TableColumnEditor getTableColumnEditor()
|
||||||
|
{
|
||||||
|
if(mTableColumnEditor == null)
|
||||||
|
{
|
||||||
|
mTableColumnEditor = new TableColumnEditor(getCallTableView(), mCallTableViewMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTableColumnEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets playback mode to auto
|
||||||
|
*/
|
||||||
|
private Button getAutoModeLeftButton()
|
||||||
|
{
|
||||||
|
if(mAutoModeLeftButton == null)
|
||||||
|
{
|
||||||
|
mAutoModeLeftButton = new Button("Left");
|
||||||
|
mAutoModeLeftButton.setTooltip(new Tooltip("Set left audio channel to auto playback mode"));
|
||||||
|
AudioPlaybackChannelController left = mAudioPlaybackController.getChannelController(MixerChannel.LEFT);
|
||||||
|
mAutoModeLeftButton.disableProperty().bind(left.playbackModeProperty().isEqualTo(PlaybackMode.AUTO));
|
||||||
|
mAutoModeLeftButton.setOnAction(event -> {
|
||||||
|
final AudioPlaybackChannelController controller = mAudioPlaybackController.getChannelController(MixerChannel.LEFT);
|
||||||
|
if(controller != null)
|
||||||
|
{
|
||||||
|
getCallTableView().getSelectionModel().clearSelection();
|
||||||
|
controller.auto();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAutoModeLeftButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets playback mode to auto
|
||||||
|
*/
|
||||||
|
private Button getAutoModeRightButton()
|
||||||
|
{
|
||||||
|
if(mAutoModeRightButton == null)
|
||||||
|
{
|
||||||
|
mAutoModeRightButton = new Button("Right");
|
||||||
|
mAutoModeRightButton.setTooltip(new Tooltip("Set right audio channel to auto playback mode"));
|
||||||
|
AudioPlaybackChannelController right = mAudioPlaybackController.getChannelController(MixerChannel.RIGHT);
|
||||||
|
mAutoModeRightButton.disableProperty().bind(right.playbackModeProperty().isEqualTo(PlaybackMode.AUTO));
|
||||||
|
mAutoModeRightButton.setOnAction(event -> {
|
||||||
|
final AudioPlaybackChannelController controller = mAudioPlaybackController.getChannelController(MixerChannel.RIGHT);
|
||||||
|
if(controller != null)
|
||||||
|
{
|
||||||
|
getCallTableView().getSelectionModel().clearSelection();
|
||||||
|
controller.auto();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAutoModeRightButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets playback mode to auto - locked to the currently selected TO identifier to the left channel
|
||||||
|
*/
|
||||||
|
private Button getLockModeLeftButton()
|
||||||
|
{
|
||||||
|
if(mLockModeLeftButton == null)
|
||||||
|
{
|
||||||
|
mLockModeLeftButton = new Button("Left");
|
||||||
|
mLockModeLeftButton.setTooltip(new Tooltip("Lock the left audio channel for auto playback of selected call talkgroup"));
|
||||||
|
mLockModeLeftButton.setDisable(true);
|
||||||
|
mLockModeLeftButton.setOnAction(event -> {
|
||||||
|
final AudioPlaybackChannelController left = mAudioPlaybackController.getChannelController(MixerChannel.LEFT);
|
||||||
|
if(left != null)
|
||||||
|
{
|
||||||
|
final Call selected = getCallTableView().getSelectionModel().getSelectedItem();
|
||||||
|
getCallTableView().getSelectionModel().clearSelection();
|
||||||
|
getLockModeLeftButton().setDisable(true);
|
||||||
|
|
||||||
|
if(selected != null)
|
||||||
|
{
|
||||||
|
//If the right controller is locked for this same call, reset it to auto so that only 1 is locked
|
||||||
|
final AudioPlaybackChannelController right = mAudioPlaybackController.getChannelController(MixerChannel.RIGHT);
|
||||||
|
if(right.isLockedFor(selected))
|
||||||
|
{
|
||||||
|
right.auto();
|
||||||
|
}
|
||||||
|
|
||||||
|
left.lockAutoTo(selected.getToId(), selected.getSystem());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
left.auto();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLockModeLeftButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets playback mode to auto - locked to the currently selected TO identifier to the left channel
|
||||||
|
*/
|
||||||
|
private Button getLockModeRightButton()
|
||||||
|
{
|
||||||
|
if(mLockModeRightButton == null)
|
||||||
|
{
|
||||||
|
mLockModeRightButton = new Button("Right");
|
||||||
|
mLockModeRightButton.setTooltip(new Tooltip("Lock the left audio channel for auto playback of selected call talkgroup"));
|
||||||
|
mLockModeRightButton.setDisable(true);
|
||||||
|
mLockModeRightButton.setOnAction(event -> {
|
||||||
|
final AudioPlaybackChannelController right = mAudioPlaybackController.getChannelController(MixerChannel.RIGHT);
|
||||||
|
if(right != null)
|
||||||
|
{
|
||||||
|
final Call selected = getCallTableView().getSelectionModel().getSelectedItem();
|
||||||
|
getCallTableView().getSelectionModel().clearSelection();
|
||||||
|
getLockModeRightButton().setDisable(true);
|
||||||
|
|
||||||
|
if(selected != null)
|
||||||
|
{
|
||||||
|
//If the left controller is locked for this same call, reset it to auto so that only 1 is locked
|
||||||
|
final AudioPlaybackChannelController left = mAudioPlaybackController.getChannelController(MixerChannel.RIGHT);
|
||||||
|
if(left.isLockedFor(selected))
|
||||||
|
{
|
||||||
|
left.auto();
|
||||||
|
}
|
||||||
|
|
||||||
|
right.lockAutoTo(selected.getToId(), selected.getSystem());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
right.auto();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLockModeRightButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio playback control view.
|
||||||
|
*/
|
||||||
|
private AudioPlaybackControlView getAudioPlaybackControlView()
|
||||||
|
{
|
||||||
|
if(mAudioPlaybackControlView == null)
|
||||||
|
{
|
||||||
|
mAudioPlaybackControlView = new AudioPlaybackControlView();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAudioPlaybackControlView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls label to display the number of calls in the database.
|
||||||
|
*/
|
||||||
|
private Label getCallsCountLabel()
|
||||||
|
{
|
||||||
|
if(mCallsCountLabel == null)
|
||||||
|
{
|
||||||
|
mCallsCountLabel = new Label("Calls: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCallsCountLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles wildcard searching
|
||||||
|
* @return true for exact match or false for wildcard/fuzzy matching.
|
||||||
|
*/
|
||||||
|
private CheckBox getSearchExactCheckBox()
|
||||||
|
{
|
||||||
|
if(mSearchExactCheckBox == null)
|
||||||
|
{
|
||||||
|
mSearchExactCheckBox = new CheckBox("Exact Match");
|
||||||
|
mSearchExactCheckBox.setTooltip(new Tooltip("Toggle exact matching (checked) or wildcard/fuzzy " +
|
||||||
|
"matching (unchecked)"));
|
||||||
|
mSearchExactCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> showFirstPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSearchExactCheckBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page size selection combo box.
|
||||||
|
*
|
||||||
|
* Note: when the user changes the page size, we always request page 1 again.
|
||||||
|
*/
|
||||||
|
private ComboBox<Integer> getPageSizeComboBox()
|
||||||
|
{
|
||||||
|
if(mPageSizeComboBox == null)
|
||||||
|
{
|
||||||
|
ObservableList<Integer> pageSizes = FXCollections.observableArrayList();
|
||||||
|
pageSizes.addAll(25, 50, 100, 200);
|
||||||
|
mPageSizeComboBox = new ComboBox<>(pageSizes);
|
||||||
|
mPageSizeComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> showFirstPage());
|
||||||
|
mPageSizeComboBox.setTooltip(new Tooltip("Select the page size"));
|
||||||
|
mPageSizeComboBox.getSelectionModel().select(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPageSizeComboBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page size selection combo box.
|
||||||
|
*
|
||||||
|
* Note: when the user changes the page size, we always request page 1 again.
|
||||||
|
*/
|
||||||
|
private ComboBox<SearchColumn> getSearchColumnComboBox()
|
||||||
|
{
|
||||||
|
if(mSearchColumnComboBox == null)
|
||||||
|
{
|
||||||
|
mSearchColumnComboBox = new ComboBox<>(FXCollections.observableArrayList(SearchColumn.values()));
|
||||||
|
mSearchColumnComboBox.getSelectionModel().select(SearchColumn.ANY_ID);
|
||||||
|
mSearchColumnComboBox.setTooltip(new Tooltip("Select the field(s) to search against"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSearchColumnComboBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paging controls panel
|
||||||
|
*/
|
||||||
|
private HBox getPagingBox()
|
||||||
|
{
|
||||||
|
if(mPagingBox == null)
|
||||||
|
{
|
||||||
|
mPagingBox = new HBox();
|
||||||
|
mPagingBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
mPagingBox.setSpacing(3);
|
||||||
|
Label pageSizeLabel = new Label("Page Size:");
|
||||||
|
pageSizeLabel.setPadding(new Insets(0, 2, 0, 10));
|
||||||
|
getCallsCountLabel().setPadding(new Insets(0, 0, 0, 10));
|
||||||
|
mPagingBox.getChildren().addAll(getPagingController(), pageSizeLabel, getPageSizeComboBox(), getCallsCountLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPagingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call table view
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private TableView<Call> getCallTableView()
|
||||||
|
{
|
||||||
|
if(mCallTableView == null)
|
||||||
|
{
|
||||||
|
mCallTableView = new TableView<>(mCalls);
|
||||||
|
mCallTableView.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
TableColumn playbackColumn = new TableColumn();
|
||||||
|
playbackColumn.setSortable(false);
|
||||||
|
playbackColumn.setId("playback");
|
||||||
|
playbackColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
IconNode iconNode = new IconNode(Elusive.VOLUME_UP);
|
||||||
|
iconNode.setIconSize(16);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
playbackColumn.setGraphic(iconNode);
|
||||||
|
playbackColumn.setCellValueFactory(new PropertyValueFactory<>("playbackChannel"));
|
||||||
|
playbackColumn.setPrefWidth(40);
|
||||||
|
|
||||||
|
TableColumn eventTimeColumn = new TableColumn();
|
||||||
|
eventTimeColumn.setId("eventTime");
|
||||||
|
eventTimeColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
eventTimeColumn.setText("Date/Time");
|
||||||
|
eventTimeColumn.setCellValueFactory(new PropertyValueFactory<Call, Long>(Call.COLUMN_EVENT_TIME));
|
||||||
|
eventTimeColumn.setCellFactory(new TimestampTableCellFactory<Call>());
|
||||||
|
eventTimeColumn.setPrefWidth(160);
|
||||||
|
|
||||||
|
TableColumn durationColumn = new TableColumn();
|
||||||
|
durationColumn.setId("duration");
|
||||||
|
durationColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
durationColumn.setText("Duration");
|
||||||
|
durationColumn.setCellValueFactory(new PropertyValueFactory<Call, Double>(Call.COLUMN_DURATION));
|
||||||
|
durationColumn.setPrefWidth(70);
|
||||||
|
|
||||||
|
TableColumn callTypeColumn = new TableColumn();
|
||||||
|
callTypeColumn.setId("callType");
|
||||||
|
callTypeColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
callTypeColumn.setText("Type");
|
||||||
|
callTypeColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_CALL_TYPE));
|
||||||
|
callTypeColumn.setPrefWidth(85);
|
||||||
|
|
||||||
|
TableColumn toIdColumn = new TableColumn();
|
||||||
|
toIdColumn.setId("toId");
|
||||||
|
toIdColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
toIdColumn.setText("To");
|
||||||
|
toIdColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_TO_ID));
|
||||||
|
toIdColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn toAliasColumn = new TableColumn();
|
||||||
|
toAliasColumn.setId("toAlias");
|
||||||
|
toAliasColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
toAliasColumn.setText("To Alias");
|
||||||
|
toAliasColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_TO_ALIAS));
|
||||||
|
toAliasColumn.setPrefWidth(100);
|
||||||
|
|
||||||
|
TableColumn fromIdColumn = new TableColumn();
|
||||||
|
fromIdColumn.setId("fromId");
|
||||||
|
fromIdColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
fromIdColumn.setText("From");
|
||||||
|
fromIdColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_FROM_ID));
|
||||||
|
fromIdColumn.setPrefWidth(70);
|
||||||
|
|
||||||
|
TableColumn fromAliasColumn = new TableColumn();
|
||||||
|
fromAliasColumn.setId("fromAlias");
|
||||||
|
fromAliasColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
fromAliasColumn.setText("From Alias");
|
||||||
|
fromAliasColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_FROM_ALIAS));
|
||||||
|
fromAliasColumn.setPrefWidth(100);
|
||||||
|
|
||||||
|
TableColumn protocolColumn = new TableColumn();
|
||||||
|
protocolColumn.setId("protocol");
|
||||||
|
protocolColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
protocolColumn.setText("Protocol");
|
||||||
|
protocolColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_PROTOCOL));
|
||||||
|
protocolColumn.setPrefWidth(90);
|
||||||
|
|
||||||
|
TableColumn frequencyColumn = new TableColumn();
|
||||||
|
frequencyColumn.setId("frequency");
|
||||||
|
frequencyColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
frequencyColumn.setText("Frequency");
|
||||||
|
frequencyColumn.setCellValueFactory(new PropertyValueFactory<Call, Double>(Call.COLUMN_FREQUENCY));
|
||||||
|
frequencyColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn systemColumn = new TableColumn();
|
||||||
|
systemColumn.setId("system");
|
||||||
|
systemColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
systemColumn.setText("System");
|
||||||
|
systemColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_SYSTEM));
|
||||||
|
systemColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn siteColumn = new TableColumn();
|
||||||
|
siteColumn.setVisible(false);
|
||||||
|
siteColumn.setId("mSite");
|
||||||
|
siteColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
siteColumn.setText("Site");
|
||||||
|
siteColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_SITE));
|
||||||
|
siteColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn channelColumn = new TableColumn();
|
||||||
|
channelColumn.setVisible(false);
|
||||||
|
channelColumn.setId("channel");
|
||||||
|
channelColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
channelColumn.setText("Channel");
|
||||||
|
channelColumn.setCellValueFactory(new PropertyValueFactory<Call, String>(Call.COLUMN_CHANNEL));
|
||||||
|
channelColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn duplicateColumn = new TableColumn();
|
||||||
|
duplicateColumn.setVisible(false);
|
||||||
|
duplicateColumn.setId("duplicate");
|
||||||
|
duplicateColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
duplicateColumn.setText("Duplicate");
|
||||||
|
duplicateColumn.setCellValueFactory(new PropertyValueFactory<Call, Boolean>(Call.COLUMN_DUPLICATE));
|
||||||
|
duplicateColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn recordColumn = new TableColumn();
|
||||||
|
recordColumn.setVisible(false);
|
||||||
|
recordColumn.setId("record");
|
||||||
|
recordColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
recordColumn.setText("Record");
|
||||||
|
recordColumn.setCellValueFactory(new PropertyValueFactory<Call, Boolean>(Call.COLUMN_RECORD));
|
||||||
|
recordColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn streamColumn = new TableColumn();
|
||||||
|
streamColumn.setVisible(false);
|
||||||
|
streamColumn.setId("stream");
|
||||||
|
streamColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
streamColumn.setText("Stream");
|
||||||
|
streamColumn.setCellValueFactory(new PropertyValueFactory<Call, Boolean>(Call.COLUMN_STREAM));
|
||||||
|
streamColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
TableColumn monitorColumn = new TableColumn();
|
||||||
|
monitorColumn.setVisible(false);
|
||||||
|
monitorColumn.setId("monitor");
|
||||||
|
monitorColumn.sortTypeProperty().addListener(mSortOrderListener);
|
||||||
|
monitorColumn.setText("Monitor");
|
||||||
|
monitorColumn.setCellValueFactory(new PropertyValueFactory<Call, Integer>(Call.COLUMN_MONITOR));
|
||||||
|
monitorColumn.setCellFactory(new PriorityCellFactory());
|
||||||
|
monitorColumn.setPrefWidth(80);
|
||||||
|
|
||||||
|
mCallTableView.getSortOrder().addListener(mSortOrderListener);
|
||||||
|
mCallTableView.getColumns().addAll(playbackColumn, eventTimeColumn, durationColumn, callTypeColumn,
|
||||||
|
toIdColumn, toAliasColumn, fromIdColumn, fromAliasColumn, protocolColumn, frequencyColumn,
|
||||||
|
systemColumn, siteColumn, channelColumn, duplicateColumn, recordColumn, streamColumn, monitorColumn);
|
||||||
|
mCallTableView.setPlaceholder(getPlaceholderProgressIndicator());
|
||||||
|
mCallTableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, selectedCall) -> {
|
||||||
|
if(selectedCall != null)
|
||||||
|
{
|
||||||
|
mAudioPlaybackController.replay(MixerChannel.LEFT, selectedCall);
|
||||||
|
}
|
||||||
|
else if(oldValue != null && selectedCall == null)
|
||||||
|
{
|
||||||
|
//We were in replay but now we don't have a selected call
|
||||||
|
mAudioPlaybackController.auto(MixerChannel.LEFT, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioPlaybackChannelController left = mAudioPlaybackController.getChannelController(MixerChannel.LEFT);
|
||||||
|
AudioPlaybackChannelController right = mAudioPlaybackController.getChannelController(MixerChannel.RIGHT);
|
||||||
|
getLockModeLeftButton().setDisable(selectedCall == null || left.isLockedFor(selectedCall));
|
||||||
|
getLockModeRightButton().setDisable(selectedCall == null || right.isLockedFor(selectedCall));
|
||||||
|
});
|
||||||
|
getPlaceholderProgressIndicator().setVisible(true);
|
||||||
|
|
||||||
|
//Bind the playback control view to the primary audio channel playback controller
|
||||||
|
getAudioPlaybackControlView().mediaPlayerProperty()
|
||||||
|
.bind(mAudioPlaybackController.getPrimaryController().mediaPlayerProperty());
|
||||||
|
|
||||||
|
mCallTableViewMonitor = new TableViewColumnController(mCallTableView, "call_table_view");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCallTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paging controller
|
||||||
|
*/
|
||||||
|
public PagingController getPagingController()
|
||||||
|
{
|
||||||
|
if(mPagingController == null)
|
||||||
|
{
|
||||||
|
mPagingController = new PagingController(this::showPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPagingController;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Progress indicator to convey data loading status when the table content is being updated.
|
||||||
|
* @return progress indicator
|
||||||
|
*/
|
||||||
|
public ProgressIndicator getPlaceholderProgressIndicator()
|
||||||
|
{
|
||||||
|
if(mPlaceholderProgressIndicator == null)
|
||||||
|
{
|
||||||
|
mPlaceholderProgressIndicator = new ProgressIndicator();
|
||||||
|
mPlaceholderProgressIndicator.setVisible(false);
|
||||||
|
mPlaceholderProgressIndicator.setProgress(-1); //always spinning
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPlaceholderProgressIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HBox getSearchBox()
|
||||||
|
{
|
||||||
|
if(mSearchBox == null)
|
||||||
|
{
|
||||||
|
mSearchBox = new HBox();
|
||||||
|
mSearchBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
mSearchBox.setSpacing(5);
|
||||||
|
|
||||||
|
Label searchLabel = new Label("Search:");
|
||||||
|
searchLabel.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
mSearchBox.getChildren().addAll(searchLabel, getSearchField(), getSearchColumnComboBox(), getSearchExactCheckBox());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSearchBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextField getSearchField()
|
||||||
|
{
|
||||||
|
if(mSearchField == null)
|
||||||
|
{
|
||||||
|
mSearchField = TextFields.createClearableTextField();
|
||||||
|
mSearchField.setTooltip(new Tooltip("Type a value to search against the selected database field(s)"));
|
||||||
|
mSearchField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
showFirstPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSearchField;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors the calls table view for sort column and sort order changes to refresh the call event records.
|
||||||
|
*/
|
||||||
|
private class SortOrderListener implements ListChangeListener<TableColumn<Call, ?>>, ChangeListener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onChanged(Change<? extends TableColumn<Call, ?>> c)
|
||||||
|
{
|
||||||
|
updateSortOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||||
|
{
|
||||||
|
updateSortOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles call events from the AudioManager.
|
||||||
|
*/
|
||||||
|
private class CallEventListener implements ICallEventListener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void added(Call call)
|
||||||
|
{
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
//Only update the calls table if we're viewing page 1
|
||||||
|
if(getPagingController().getCurrentPage() == 1)
|
||||||
|
{
|
||||||
|
if(mCalls.size() == getPageSize())
|
||||||
|
{
|
||||||
|
mCalls.remove(mCalls.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mCalls.add(0, call);
|
||||||
|
|
||||||
|
getCallTableView().sort();
|
||||||
|
|
||||||
|
//If the user hasn't selected an item, scroll to the new item.
|
||||||
|
if(getCallTableView().getSelectionModel().getSelectedItem() != null)
|
||||||
|
{
|
||||||
|
getCallTableView().scrollTo(call);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mCallCount.set(mCallCount.get() + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updated(Call call)
|
||||||
|
{
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if(mCalls.contains(call))
|
||||||
|
{
|
||||||
|
mCalls.set(mCalls.indexOf(call), call);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void completed(Call call, AudioSegment audioSegment)
|
||||||
|
{
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if(mCalls.contains(call))
|
||||||
|
{
|
||||||
|
mCalls.set(mCalls.indexOf(call), call);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audioSegment.removeLease(getClass().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleted(Call call)
|
||||||
|
{
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if(mCalls.contains(call))
|
||||||
|
{
|
||||||
|
mCalls.remove(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
mCallCount.set(mCallCount.get() - 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PriorityCellFactory implements Callback<TableColumn<Alias, Integer>, TableCell<Alias, Integer>>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public TableCell<Alias, Integer> call(TableColumn<Alias, Integer> param)
|
||||||
|
{
|
||||||
|
TableCell tableCell = new TableCell<Alias, Integer>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Integer item, boolean empty)
|
||||||
|
{
|
||||||
|
if(empty)
|
||||||
|
{
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
}
|
||||||
|
else if(item == io.github.dsheirer.alias.id.priority.Priority.DO_NOT_MONITOR)
|
||||||
|
{
|
||||||
|
setText("Mute");
|
||||||
|
final IconNode iconNode = new IconNode(FontAwesome.VOLUME_OFF);
|
||||||
|
iconNode.setIconSize(20);
|
||||||
|
iconNode.setFill(Color.RED);
|
||||||
|
setGraphic(iconNode);
|
||||||
|
}
|
||||||
|
else if(item == io.github.dsheirer.alias.id.priority.Priority.DEFAULT_PRIORITY)
|
||||||
|
{
|
||||||
|
setText("Default");
|
||||||
|
final IconNode iconNode = new IconNode(FontAwesome.VOLUME_UP);
|
||||||
|
iconNode.setIconSize(20);
|
||||||
|
iconNode.setFill(Color.GREEN);
|
||||||
|
setGraphic(iconNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setText(item.toString());
|
||||||
|
final IconNode iconNode = new IconNode(FontAwesome.VOLUME_UP);
|
||||||
|
iconNode.setIconSize(20);
|
||||||
|
iconNode.setFill(Color.GREEN);
|
||||||
|
setGraphic(iconNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return tableCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completed call record
|
||||||
|
* @param call associated with the audio segment.
|
||||||
|
* @param audioSegment associated with the call.
|
||||||
|
*/
|
||||||
|
public record CompletedCall(Call call, AudioSegment audioSegment)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call event listener interface
|
||||||
|
*/
|
||||||
|
public interface ICallEventListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Call added event.
|
||||||
|
* @param call that was added
|
||||||
|
*/
|
||||||
|
void added(Call call);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call updated event.
|
||||||
|
* @param call that was updated
|
||||||
|
*/
|
||||||
|
void updated(Call call);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call completed event. This indicates that the call was completed and no further updates will be available.
|
||||||
|
* @param call that was completed.
|
||||||
|
* @param audioSegment for the call
|
||||||
|
*/
|
||||||
|
void completed(Call call, AudioSegment audioSegment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call that was deleted (age-off) from the database.
|
||||||
|
* @param call that was deleted.
|
||||||
|
*/
|
||||||
|
void deleted(Call call);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for receiving paging requests from user initiated actions.
|
||||||
|
*/
|
||||||
|
public interface IPageRequestListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Indicates the user requests to display a page number
|
||||||
|
* @param page number to display.
|
||||||
|
*/
|
||||||
|
void showPage(int page);
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
|
import jiconfont.javafx.IconNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a set of paging controls.
|
||||||
|
*/
|
||||||
|
public class PagingController extends HBox
|
||||||
|
{
|
||||||
|
private static final int ICON_SIZE = 20;
|
||||||
|
private NumberFormat mNumberFormat = NumberFormat.getIntegerInstance();
|
||||||
|
private Button mFirstButton;
|
||||||
|
private Button mLastButton;
|
||||||
|
private Button mNextButton;
|
||||||
|
private Button mPreviousButton;
|
||||||
|
private Label mPageLabel;
|
||||||
|
private int mPageCount = 1;
|
||||||
|
private int mCurrentPage = 1;
|
||||||
|
private IPageRequestListener mPageRequestListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param listener to receive page requests
|
||||||
|
*/
|
||||||
|
public PagingController(IPageRequestListener listener)
|
||||||
|
{
|
||||||
|
mPageRequestListener = listener;
|
||||||
|
setAlignment(Pos.CENTER);
|
||||||
|
setSpacing(0);
|
||||||
|
getChildren().addAll(getFirstButton(), getPreviousButton(), getPageLabel(), getNextButton(), getLastButton());
|
||||||
|
update(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current state of this paging controller.
|
||||||
|
* @param currentPage number to display.
|
||||||
|
* @param pageCount the total number of available pages.
|
||||||
|
*/
|
||||||
|
public void update(int currentPage, int pageCount)
|
||||||
|
{
|
||||||
|
mPageCount = pageCount;
|
||||||
|
mCurrentPage = currentPage;
|
||||||
|
getFirstButton().setDisable(mCurrentPage <= 1);
|
||||||
|
getFirstButton().setTooltip(mCurrentPage <= 1 ? null : new Tooltip("Move to page 1"));
|
||||||
|
getPreviousButton().setDisable(mCurrentPage <= 1);
|
||||||
|
getPreviousButton().setTooltip(mCurrentPage <= 1 ? null : new Tooltip("Move to page " + (mCurrentPage - 1)));
|
||||||
|
getNextButton().setDisable(mCurrentPage >= mPageCount);
|
||||||
|
getNextButton().setTooltip(mCurrentPage >= mPageCount ? null : new Tooltip("Move to page " + (mCurrentPage + 1)));
|
||||||
|
getLastButton().setDisable(mCurrentPage >= mPageCount);
|
||||||
|
getLastButton().setTooltip(mCurrentPage >= mPageCount ? null : new Tooltip("Move to page " + mPageCount));
|
||||||
|
getPageLabel().setText("Page " + mNumberFormat.format(mCurrentPage) + " of " + mNumberFormat.format(mPageCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total pages available
|
||||||
|
* @return page count
|
||||||
|
*/
|
||||||
|
public int getPageCount()
|
||||||
|
{
|
||||||
|
return mPageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current page number
|
||||||
|
* @return current page.
|
||||||
|
*/
|
||||||
|
public int getCurrentPage()
|
||||||
|
{
|
||||||
|
return mCurrentPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the listener that the user requests the page number
|
||||||
|
* @param page number requested
|
||||||
|
*/
|
||||||
|
private void request(int page)
|
||||||
|
{
|
||||||
|
if(mPageRequestListener != null)
|
||||||
|
{
|
||||||
|
mPageRequestListener.showPage(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next page
|
||||||
|
*/
|
||||||
|
private void next()
|
||||||
|
{
|
||||||
|
request(mCurrentPage + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous page
|
||||||
|
*/
|
||||||
|
private void previous()
|
||||||
|
{
|
||||||
|
request(mCurrentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First page
|
||||||
|
*/
|
||||||
|
private void first()
|
||||||
|
{
|
||||||
|
request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last page
|
||||||
|
*/
|
||||||
|
private void last()
|
||||||
|
{
|
||||||
|
request(mPageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current page status label.
|
||||||
|
*/
|
||||||
|
private Label getPageLabel()
|
||||||
|
{
|
||||||
|
if(mPageLabel == null)
|
||||||
|
{
|
||||||
|
mPageLabel = new Label("Page 1 of 1");
|
||||||
|
mPageLabel.setTooltip(new Tooltip("Current Page and Total Page Count"));
|
||||||
|
mPageLabel.setAlignment(Pos.CENTER);
|
||||||
|
mPageLabel.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
mPageLabel.setPadding(new Insets(0, 20, 0, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPageLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First page button.
|
||||||
|
*/
|
||||||
|
private Button getFirstButton()
|
||||||
|
{
|
||||||
|
if(mFirstButton == null)
|
||||||
|
{
|
||||||
|
mFirstButton = new Button();
|
||||||
|
mFirstButton.setTooltip(new Tooltip("Go To First Page"));
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.ANGLE_DOUBLE_LEFT);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mFirstButton.setGraphic(iconNode);
|
||||||
|
mFirstButton.setOnAction(event -> first());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFirstButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last page button
|
||||||
|
*/
|
||||||
|
private Button getLastButton()
|
||||||
|
{
|
||||||
|
if(mLastButton == null)
|
||||||
|
{
|
||||||
|
mLastButton = new Button();
|
||||||
|
mLastButton.setTooltip(new Tooltip("Go To Last Page"));
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.ANGLE_DOUBLE_RIGHT);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mLastButton.setGraphic(iconNode);
|
||||||
|
mLastButton.setOnAction(event -> last());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLastButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next page button
|
||||||
|
*/
|
||||||
|
private Button getNextButton()
|
||||||
|
{
|
||||||
|
if(mNextButton == null)
|
||||||
|
{
|
||||||
|
mNextButton = new Button();
|
||||||
|
mNextButton.setTooltip(new Tooltip("Go To Next Page"));
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.ANGLE_RIGHT);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mNextButton.setGraphic(iconNode);
|
||||||
|
mNextButton.setOnAction(event -> next());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mNextButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Previous page button.
|
||||||
|
*/
|
||||||
|
private Button getPreviousButton()
|
||||||
|
{
|
||||||
|
if(mPreviousButton == null)
|
||||||
|
{
|
||||||
|
mPreviousButton = new Button();
|
||||||
|
mPreviousButton.setTooltip(new Tooltip("Go To Previous Page"));
|
||||||
|
IconNode iconNode = new IconNode(FontAwesome.ANGLE_LEFT);
|
||||||
|
iconNode.setIconSize(ICON_SIZE);
|
||||||
|
iconNode.setFill(Color.BLACK);
|
||||||
|
mPreviousButton.setGraphic(iconNode);
|
||||||
|
mPreviousButton.setOnAction(event -> previous());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPreviousButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.call;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search columns
|
||||||
|
*/
|
||||||
|
public enum SearchColumn
|
||||||
|
{
|
||||||
|
ANY_ID("Any ID"),
|
||||||
|
TO("To / Alias"),
|
||||||
|
FROM("From / Alias"),
|
||||||
|
CHANNEL("Channel"),
|
||||||
|
PROTOCOL("Protocol"),
|
||||||
|
SITE("Site"),
|
||||||
|
SYSTEM("System"),
|
||||||
|
CALL_TYPE("Type");
|
||||||
|
|
||||||
|
private String mLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param label to display
|
||||||
|
*/
|
||||||
|
private SearchColumn(String label)
|
||||||
|
{
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display label
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,35 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio.codec.mbe;
|
package io.github.dsheirer.audio.codec.mbe;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.audio.AbstractAudioModule;
|
import io.github.dsheirer.audio.AbstractAudioModule;
|
||||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.IMessageListener;
|
import io.github.dsheirer.message.IMessageListener;
|
||||||
|
import io.github.dsheirer.preference.IPreferenceUpdateListener;
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import jmbe.iface.IAudioCodec;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jmbe.iface.IAudioCodecLibrary;
|
import jakarta.annotation.PreDestroy;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -44,33 +37,48 @@ import java.net.URLClassLoader;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import jmbe.iface.IAudioCodec;
|
||||||
|
import jmbe.iface.IAudioCodecLibrary;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
|
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
|
||||||
ISquelchStateListener
|
ISquelchStateListener, IPreferenceUpdateListener
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
||||||
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
|
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
|
||||||
private static List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
private static final List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
||||||
private IAudioCodec mAudioCodec;
|
private IAudioCodec mAudioCodec;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
|
private static Class sLoadedJmbeAudioConverterClass;
|
||||||
|
|
||||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||||
{
|
{
|
||||||
super(aliasList, timeslot, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
super(aliasList, timeslot, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
||||||
mUserPreferences = userPreferences;
|
mUserPreferences = userPreferences;
|
||||||
MyEventBus.getGlobalEventBus().register(this);
|
|
||||||
loadConverter();
|
loadConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init()
|
||||||
|
{
|
||||||
|
mUserPreferences.addUpdateListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
mUserPreferences.removeUpdateListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
super.dispose();
|
super.dispose();
|
||||||
MyEventBus.getGlobalEventBus().unregister(this);
|
|
||||||
mAudioCodec = null;
|
mAudioCodec = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IAudioCodec getAudioCodec()
|
public IAudioCodec getAudioCodec()
|
||||||
{
|
{
|
||||||
return mAudioCodec;
|
return mAudioCodec;
|
||||||
}
|
}
|
||||||
@@ -94,7 +102,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
*
|
*
|
||||||
* @param preferenceType that was updated
|
* @param preferenceType that was updated
|
||||||
*/
|
*/
|
||||||
@Subscribe
|
|
||||||
public void preferenceUpdated(PreferenceType preferenceType)
|
public void preferenceUpdated(PreferenceType preferenceType)
|
||||||
{
|
{
|
||||||
if(preferenceType == PreferenceType.JMBE_LIBRARY)
|
if(preferenceType == PreferenceType.JMBE_LIBRARY)
|
||||||
@@ -110,38 +117,67 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
protected abstract String getCodecName();
|
protected abstract String getCodecName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads audio frame processing chain. Constructs an imbe targetdataline
|
* Loads JMBE audio converter library class and then instantiates new converter instances from the loaded class.
|
||||||
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
|
|
||||||
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
|
|
||||||
*/
|
*/
|
||||||
protected void loadConverter()
|
protected void loadConverter()
|
||||||
{
|
{
|
||||||
IAudioCodec audioConverter = null;
|
IAudioCodec audioConverter = null;
|
||||||
|
|
||||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
if(sLoadedJmbeAudioConverterClass == null)
|
||||||
|
|
||||||
if(path != null)
|
|
||||||
{
|
{
|
||||||
try
|
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||||
|
|
||||||
|
if(path != null)
|
||||||
{
|
{
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
{
|
{
|
||||||
mLog.info("Loading JMBE library from [" + path.toString() + "]");
|
mLog.info("Loading JMBE library from [" + path + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
try
|
||||||
this.getClass().getClassLoader());
|
|
||||||
|
|
||||||
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
|
||||||
|
|
||||||
Object instance = classToLoad.getDeclaredConstructor().newInstance();
|
|
||||||
|
|
||||||
if(instance instanceof IAudioCodecLibrary)
|
|
||||||
{
|
{
|
||||||
IAudioCodecLibrary library = (IAudioCodecLibrary)instance;
|
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
||||||
|
this.getClass().getClassLoader());
|
||||||
|
|
||||||
|
sLoadedJmbeAudioConverterClass = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
||||||
|
}
|
||||||
|
catch(IllegalArgumentException iae)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(MalformedURLException mue)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e1)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sLoadedJmbeAudioConverterClass != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Object instance = sLoadedJmbeAudioConverterClass.getDeclaredConstructor().newInstance();
|
||||||
|
|
||||||
|
if(instance instanceof IAudioCodecLibrary library)
|
||||||
|
{
|
||||||
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
||||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||||
{
|
{
|
||||||
audioConverter = library.getAudioConverter(getCodecName());
|
audioConverter = library.getAudioConverter(getCodecName());
|
||||||
|
|
||||||
@@ -169,38 +205,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(NoSuchMethodException nsme)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(MalformedURLException mue)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(ClassNotFoundException e1)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(InvocationTargetException ite)
|
catch(InvocationTargetException ite)
|
||||||
{
|
{
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
@@ -225,6 +229,14 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(NoSuchMethodException nsme)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -235,13 +247,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(audioConverter != null)
|
mAudioCodec = audioConverter;
|
||||||
{
|
|
||||||
mAudioCodec = audioConverter;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mAudioCodec = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,40 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio.codec.mbe;
|
package io.github.dsheirer.audio.codec.mbe;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.audio.call.AudioSegment;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
|
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||||
|
import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder;
|
||||||
|
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||||
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import jmbe.iface.IAudioCodec;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,79 +45,89 @@ public class MBECallSequenceConverter
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
||||||
|
|
||||||
// public static void convert(Path input, Path output) throws IOException
|
/**
|
||||||
// {
|
* Converts the input MBE file to PCM audio and writes to the output wave file.
|
||||||
// InputStream inputStream = Files.newInputStream(input);
|
* @param input path to the MBE file.
|
||||||
// ObjectMapper mapper = new ObjectMapper();
|
* @param output path to write the WAVE recording.
|
||||||
// MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
* @throws IOException if there is an error.
|
||||||
// convert(sequence, output);
|
*/
|
||||||
// }
|
public static void convert(UserPreferences userPreferences, Path input, Path output) throws IOException
|
||||||
//
|
{
|
||||||
// public static void convert(MBECallSequence callSequence, Path outputPath)
|
InputStream inputStream = Files.newInputStream(input);
|
||||||
// {
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
// if(callSequence == null || callSequence.isEncrypted())
|
MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
||||||
// {
|
convert(userPreferences, sequence, output);
|
||||||
// throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
}
|
||||||
// }
|
|
||||||
//
|
public static void convert(UserPreferences userPreferences, MBECallSequence callSequence, Path outputPath)
|
||||||
// if(callSequence != null && !callSequence.isEncrypted())
|
{
|
||||||
// {
|
if(callSequence == null || callSequence.isEncrypted())
|
||||||
// ThumbDv.AudioProtocol protocol = ThumbDv.AudioProtocol.P25_PHASE2;
|
{
|
||||||
//
|
throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
||||||
// AudioPacketWaveRecorder recorder = new AudioPacketWaveRecorder(outputPath);
|
}
|
||||||
// recorder.start();
|
|
||||||
//
|
if(callSequence != null)
|
||||||
// long delayMillis = 0;
|
{
|
||||||
//
|
if(callSequence.getProtocol().equals(P25P1CallSequenceRecorder.PROTOCOL))
|
||||||
// try(ThumbDv thumbDv = new ThumbDv(protocol, recorder))
|
{
|
||||||
// {
|
P25P1AudioModule audioModule = new P25P1AudioModule(userPreferences, new AliasList("mbe generator"));
|
||||||
// thumbDv.start();
|
audioModule.setRecordAudio(true);
|
||||||
// for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
audioModule.start();
|
||||||
// {
|
|
||||||
// mLog.debug("Frame [" + voiceFrame.getFrame() + "] + Hex [" + AmbeResponse.toHex(voiceFrame.getFrameBytes()) + "]");
|
if(callSequence.getFromIdentifier() != null)
|
||||||
// thumbDv.decode(voiceFrame.getFrameBytes());
|
{
|
||||||
// delayMillis += 30;
|
int from = 0;
|
||||||
// }
|
|
||||||
//
|
try
|
||||||
// if(delayMillis > 0)
|
{
|
||||||
// {
|
from = Integer.parseInt(callSequence.getFromIdentifier());
|
||||||
// delayMillis += 1000;
|
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25RadioIdentifier.createFrom(from),
|
||||||
// try
|
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||||
// {
|
}
|
||||||
// Thread.sleep(delayMillis);
|
catch(Exception e)
|
||||||
// }
|
{
|
||||||
// catch(InterruptedException ie)
|
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||||
// {
|
}
|
||||||
//
|
}
|
||||||
// }
|
|
||||||
// }
|
if(callSequence.getToIdentifier() != null)
|
||||||
// }
|
{
|
||||||
// catch(IOException ioe)
|
int to = 0;
|
||||||
// {
|
|
||||||
// mLog.error("Error", ioe);
|
try
|
||||||
// }
|
{
|
||||||
//
|
to = Integer.parseInt(callSequence.getToIdentifier());
|
||||||
// recorder.stop(Paths.get(outputPath.toString().replace(".tmp", ".wav")), new WaveMetadata());
|
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25Talkgroup.create(to),
|
||||||
// }
|
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||||
// }
|
}
|
||||||
//
|
catch(Exception e)
|
||||||
// public static void main(String[] args)
|
{
|
||||||
// {
|
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||||
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_3_TS0_65084_6591007.mbe";
|
}
|
||||||
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_2_TS1_65035.mbe";
|
}
|
||||||
//
|
|
||||||
// Path input = Paths.get(mbe);
|
IAudioCodec codec = audioModule.getAudioCodec();
|
||||||
// Path output = Paths.get(mbe.replace(".mbe", ".tmp"));
|
|
||||||
//
|
for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
||||||
// mLog.info("Converting: " + mbe);
|
{
|
||||||
//
|
byte[] frameBytes = voiceFrame.getFrameBytes();
|
||||||
// try
|
float[] audio = codec.getAudio(frameBytes);
|
||||||
// {
|
audioModule.addAudio(audio);
|
||||||
// MBECallSequenceConverter.convert(input, output);
|
}
|
||||||
// }
|
|
||||||
// catch(IOException ioe)
|
AudioSegment audioSegment = audioModule.getAudioSegment();
|
||||||
// {
|
|
||||||
// mLog.error("Error", ioe);
|
try
|
||||||
// }
|
{
|
||||||
// }
|
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath, audioSegment.getIdentifierCollection());
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error writing audio segment, ioe");
|
||||||
|
}
|
||||||
|
|
||||||
|
audioModule.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.playback;
|
package io.github.dsheirer.audio.playback;
|
||||||
|
|
||||||
@@ -25,12 +24,12 @@ import io.github.dsheirer.alias.Alias;
|
|||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.AudioEvent;
|
import io.github.dsheirer.audio.AudioEvent;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
|
||||||
import io.github.dsheirer.icon.IconModel;
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.preference.IPreferenceUpdateListener;
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
|
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
|
||||||
@@ -40,13 +39,6 @@ import io.github.dsheirer.settings.ColorSetting;
|
|||||||
import io.github.dsheirer.settings.Setting;
|
import io.github.dsheirer.settings.Setting;
|
||||||
import io.github.dsheirer.settings.SettingChangeListener;
|
import io.github.dsheirer.settings.SettingChangeListener;
|
||||||
import io.github.dsheirer.settings.SettingsManager;
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
import net.miginfocom.swing.MigLayout;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
@@ -54,8 +46,15 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, SettingChangeListener
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, SettingChangeListener, IPreferenceUpdateListener
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioChannelPanel.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioChannelPanel.class);
|
||||||
@@ -66,53 +65,54 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
|
|||||||
public static final String PROPERTY_COLOR_MUTED = PROPERTY_PREFIX + "muted";
|
public static final String PROPERTY_COLOR_MUTED = PROPERTY_PREFIX + "muted";
|
||||||
public static final String PROPERTY_COLOR_VALUE = PROPERTY_PREFIX + "value";
|
public static final String PROPERTY_COLOR_VALUE = PROPERTY_PREFIX + "value";
|
||||||
|
|
||||||
private Font mFont = new Font(Font.MONOSPACED, Font.PLAIN, 16);
|
private AliasModel mAliasModel;
|
||||||
|
private IconModel mIconModel;
|
||||||
|
private SettingsManager mSettingsManager;
|
||||||
|
private UserPreferences mUserPreferences;
|
||||||
|
|
||||||
|
private Font mFont = new Font(Font.MONOSPACED, Font.PLAIN, 16);
|
||||||
private Color mBackgroundColor;
|
private Color mBackgroundColor;
|
||||||
private Color mLabelColor;
|
private Color mLabelColor;
|
||||||
private Color mMutedColor;
|
private Color mMutedColor;
|
||||||
private Color mValueColor;
|
private Color mValueColor;
|
||||||
|
|
||||||
private IconModel mIconModel;
|
|
||||||
private SettingsManager mSettingsManager;
|
|
||||||
private UserPreferences mUserPreferences;
|
|
||||||
private TalkgroupFormatPreference mTalkgroupFormatPreference;
|
private TalkgroupFormatPreference mTalkgroupFormatPreference;
|
||||||
private AudioOutput mAudioOutput;
|
private AudioOutput mAudioOutput;
|
||||||
|
|
||||||
private JLabel mMutedLabel = new JLabel("M");
|
private JLabel mMutedLabel = new JLabel("M");
|
||||||
private JLabel mChannelName = new JLabel(" ");
|
private JLabel mChannelName = new JLabel(" ");
|
||||||
private JLabel mIconLabel = new JLabel(" ");
|
private JLabel mIconLabel = new JLabel(" ");
|
||||||
private JLabel mIdentifierLabel = new JLabel("-----");
|
private JLabel mIdentifierLabel = new JLabel("-----");
|
||||||
private Identifier mIdentifier;
|
private Identifier mIdentifier;
|
||||||
private List<Alias> mAliases = Collections.EMPTY_LIST;
|
private List<Alias> mAliases = Collections.EMPTY_LIST;
|
||||||
private AliasModel mAliasModel;
|
|
||||||
private Lock mLock = new ReentrantLock();
|
private Lock mLock = new ReentrantLock();
|
||||||
|
|
||||||
public AudioChannelPanel(IconModel iconModel, UserPreferences userPreferences, SettingsManager settingsManager,
|
public AudioChannelPanel(UserPreferences userPreferences, SettingsManager settingsManager, AliasModel aliasModel,
|
||||||
AudioOutput audioOutput, AliasModel aliasModel)
|
IconModel iconModel, AudioOutput audioOutput)
|
||||||
{
|
{
|
||||||
mIconModel = iconModel;
|
|
||||||
mSettingsManager = settingsManager;
|
|
||||||
mSettingsManager.addListener(this);
|
|
||||||
mAliasModel = aliasModel;
|
|
||||||
mUserPreferences = userPreferences;
|
mUserPreferences = userPreferences;
|
||||||
mTalkgroupFormatPreference = mUserPreferences.getTalkgroupFormatPreference();
|
mSettingsManager = settingsManager;
|
||||||
|
mAliasModel = aliasModel;
|
||||||
mAudioOutput = audioOutput;
|
mAudioOutput = audioOutput;
|
||||||
|
mIconModel = iconModel;
|
||||||
|
mSettingsManager.addListener(this);
|
||||||
if(mAudioOutput != null)
|
if(mAudioOutput != null)
|
||||||
{
|
{
|
||||||
mAudioOutput.addAudioEventListener(this);
|
mAudioOutput.addAudioEventListener(this);
|
||||||
mAudioOutput.setIdentifierCollectionListener(new AudioMetadataProcessor());
|
mAudioOutput.setIdentifierCollectionListener(new AudioMetadataProcessor());
|
||||||
}
|
}
|
||||||
|
mUserPreferences.addUpdateListener(this);
|
||||||
|
mTalkgroupFormatPreference = mUserPreferences.getTalkgroupFormatPreference();
|
||||||
mBackgroundColor = SystemProperties.getInstance().get(PROPERTY_COLOR_BACKGROUND, Color.BLACK);
|
mBackgroundColor = SystemProperties.getInstance().get(PROPERTY_COLOR_BACKGROUND, Color.BLACK);
|
||||||
mLabelColor = SystemProperties.getInstance().get(PROPERTY_COLOR_LABEL, Color.LIGHT_GRAY);
|
mLabelColor = SystemProperties.getInstance().get(PROPERTY_COLOR_LABEL, Color.LIGHT_GRAY);
|
||||||
mMutedColor = SystemProperties.getInstance().get(PROPERTY_COLOR_MUTED, Color.RED);
|
mMutedColor = SystemProperties.getInstance().get(PROPERTY_COLOR_MUTED, Color.RED);
|
||||||
mValueColor = SystemProperties.getInstance().get(PROPERTY_COLOR_VALUE, Color.GREEN);
|
mValueColor = SystemProperties.getInstance().get(PROPERTY_COLOR_VALUE, Color.GREEN);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void preDestroy()
|
||||||
|
{
|
||||||
|
mUserPreferences.removeUpdateListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives preference update notifications via the event bus
|
* Receives preference update notifications via the event bus
|
||||||
* @param preferenceType that was updated
|
* @param preferenceType that was updated
|
||||||
@@ -128,9 +128,6 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
|
|||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
//Deregister from receiving preference update notifications
|
|
||||||
MyEventBus.getGlobalEventBus().unregister(this);
|
|
||||||
|
|
||||||
if(mAudioOutput != null)
|
if(mAudioOutput != null)
|
||||||
{
|
{
|
||||||
mAudioOutput.removeAudioEventListener(this);
|
mAudioOutput.removeAudioEventListener(this);
|
||||||
@@ -140,9 +137,6 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
|
|||||||
|
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
//Register to receive preference updates
|
|
||||||
MyEventBus.getGlobalEventBus().register(this);
|
|
||||||
|
|
||||||
setLayout(new MigLayout("align center center, insets 0 0 0 0",
|
setLayout(new MigLayout("align center center, insets 0 0 0 0",
|
||||||
"[][][align right]0[grow,fill]", ""));
|
"[][][align right]0[grow,fill]", ""));
|
||||||
setBackground(mBackgroundColor);
|
setBackground(mBackgroundColor);
|
||||||
@@ -183,14 +177,7 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
|
|||||||
break;
|
break;
|
||||||
case AUDIO_MUTED:
|
case AUDIO_MUTED:
|
||||||
case AUDIO_UNMUTED:
|
case AUDIO_UNMUTED:
|
||||||
EventQueue.invokeLater(new Runnable()
|
EventQueue.invokeLater(() -> mMutedLabel.setVisible(mAudioOutput.isMuted()));
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
mMutedLabel.setVisible(mAudioOutput.isMuted());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user