mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 15:47:25 -05:00
Compare commits
154 Commits
v0.5.0-alp
...
v0.5.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
e4a85a4ed7 | ||
|
|
f61a415b43 | ||
|
|
29ed3b4553 | ||
|
|
a87309e792 | ||
|
|
ddb5b5ef87 | ||
|
|
7a58bf85b6 | ||
|
|
8707eb2c88 | ||
|
|
08499ac73f | ||
|
|
70170ff70f | ||
|
|
bad5589c0d | ||
|
|
7de28519a1 | ||
|
|
bbd907514e | ||
|
|
4a68cd9407 | ||
|
|
dc327a2855 | ||
|
|
9af593a785 | ||
|
|
24f4c22cdd | ||
|
|
bc462b9652 | ||
|
|
047dec6da1 | ||
|
|
bf935555bb | ||
|
|
efe36b0682 | ||
|
|
a18d4498aa | ||
|
|
803dc2531c | ||
|
|
1b062bcd06 | ||
|
|
a24c3736f4 | ||
|
|
ac61b681e6 | ||
|
|
0866bfd555 | ||
|
|
79fed701d9 | ||
|
|
48a93990e8 | ||
|
|
414ba25d61 | ||
|
|
f67f90e8b0 | ||
|
|
5751c70212 | ||
|
|
4e15c426a8 | ||
|
|
ceda189260 | ||
|
|
fce0ae33e7 | ||
|
|
fc5c6ef841 | ||
|
|
d0033578d5 | ||
|
|
670ec3a097 | ||
|
|
8bfb1d5470 | ||
|
|
46a916a945 | ||
|
|
459e77390a | ||
|
|
f4ab46b79d | ||
|
|
3c58e98788 | ||
|
|
36de81248c | ||
|
|
925353d515 | ||
|
|
7c32897321 | ||
|
|
cbeda19d27 | ||
|
|
8148ca48af | ||
|
|
adcb55ffbc | ||
|
|
1152b13220 | ||
|
|
166383e1ed | ||
|
|
0d5d1b20e3 | ||
|
|
77ea43f61a | ||
|
|
f988344222 | ||
|
|
60489e1fef | ||
|
|
ac8aa17a80 | ||
|
|
25a42126df | ||
|
|
1b90e0b50c | ||
|
|
7f5ad7eb20 | ||
|
|
0e6713b6e2 | ||
|
|
96d3996bec | ||
|
|
0fb148d31e | ||
|
|
cebca28b19 | ||
|
|
af8cc0255e | ||
|
|
775ae00861 | ||
|
|
1b80f5f1b3 | ||
|
|
f9a7bbeddf | ||
|
|
58a0264565 | ||
|
|
6d2af95e9a | ||
|
|
bf46b19dc9 | ||
|
|
7749fd10f1 | ||
|
|
90bc321aca | ||
|
|
bad4fae1b8 | ||
|
|
c3fc435fdc | ||
|
|
2ab0c78728 | ||
|
|
187989f6b8 | ||
|
|
3b83412bcb | ||
|
|
968ea458da | ||
|
|
a9f4fbbdd4 | ||
|
|
6b3b45e9af | ||
|
|
2355dfce07 | ||
|
|
0e8c9591e4 | ||
|
|
54e4771f36 | ||
|
|
b2eb245feb | ||
|
|
e826314d06 | ||
|
|
0eec2e7ab8 | ||
|
|
5e9c239ea3 | ||
|
|
1fd9b0fbcb | ||
|
|
8413343e76 | ||
|
|
2a0427c559 | ||
|
|
5d9d232585 | ||
|
|
f0f0329f9b | ||
|
|
2a45849e6d | ||
|
|
aa89079e28 | ||
|
|
31b9ee5910 | ||
|
|
6a54fe101c | ||
|
|
87b3d34024 | ||
|
|
b8bbe9264a | ||
|
|
0c7dd192b0 | ||
|
|
8b487d79e4 | ||
|
|
cb83a8e870 | ||
|
|
0bc11a07cf | ||
|
|
560cbad5d7 | ||
|
|
87e9bf8749 | ||
|
|
770f58c668 | ||
|
|
4b806ecfde | ||
|
|
7303e06943 | ||
|
|
846b249fb3 | ||
|
|
67f23494e2 | ||
|
|
d7efb627d7 | ||
|
|
08bc0b9963 | ||
|
|
d1ed46860c | ||
|
|
38f694d262 | ||
|
|
75ef3217ac | ||
|
|
2847dff64a | ||
|
|
f0d9ef99ea | ||
|
|
67702096d7 | ||
|
|
88f124af9d | ||
|
|
d34460c4e3 |
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
|
||||
34
.github/workflows/gradle.yml
vendored
Normal file
34
.github/workflows/gradle.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: Java CI with Gradle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 19
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '19'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
|
||||
with:
|
||||
arguments: clean build
|
||||
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -2,6 +2,7 @@
|
||||
<dictionary name="denny">
|
||||
<words>
|
||||
<w>bahroo</w>
|
||||
<w>basebanded</w>
|
||||
<w>broadcastify</w>
|
||||
<w>correlator</w>
|
||||
<w>deinterleave</w>
|
||||
|
||||
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="false" project-jdk-name="15" project-jdk-type="JavaSDK">
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_19_PREVIEW" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
59
README.md
59
README.md
@@ -1,67 +1,24 @@
|
||||

|
||||
|
||||
# sdrtrunk
|
||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted_V0.3.0)
|
||||
* [User's Manual Version 0.3.0 and 0.4.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_V0.3.0)
|
||||
* [Playlist Editor User Manual Version 0.5.0](https://github.com/DSheirer/sdrtrunk/wiki/Playlist-Editor)
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||
* [Support Group](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||
* [Discord Support Channel](https://discord.gg/HJQaKYE)
|
||||
* [Support](https://github.com/DSheirer/sdrtrunk/wiki/Support)
|
||||
|
||||

|
||||
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot
|
||||

|
||||
**Figure 1:** sdrtrunk Application Screenshot
|
||||
|
||||
# End User Instructions:
|
||||
|
||||
If you simply want to download and run the program, please follow these instructions.
|
||||
|
||||
## Download the latest sdrtrunk release for your operating system
|
||||
|
||||
## Download the Latest Release
|
||||
All release versions of sdrtrunk are available from the [releases](https://github.com/DSheirer/sdrtrunk/releases) tab.
|
||||
|
||||
* **(alpha)** These versions are under development feature previews and likely to contain bugs and unexpected behavior.
|
||||
* **(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.
|
||||
|
||||
## Unzip the release
|
||||
|
||||
Use 7-zip or any zip utility to unzip the release file
|
||||
|
||||
## Start the application
|
||||
|
||||
Once unzipped, open a command prompt to where you unzipped the release. Change to the **/bin** directory and use the launch script to start the application:
|
||||
* **Windows** sdr-trunk.bat
|
||||
* **Linux/OSX** ./sdr-trunk
|
||||
|
||||
## Optional - P25 Audio
|
||||
If you're using sdrtrunk with a P25 trunked radio system, the [JMBE](https://github.com/DSheirer/sdrtrunk/wiki/JMBE) wiki page contains instructions for downloading the JMBE audio library source code and compiling the JMBE library. Once you have compiled the library, launch the sdrtrunk application. From the menu bar, choose **View >> Preferences**. In the **JMBE Audio Codec** section, update the path to where your compiled JMBE library is located. Any channels that are started after you set the path will be able to produce P25 audio.
|
||||
|
||||
## Minimum System Requirements
|
||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
||||
* **CPU:** 4-core
|
||||
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
|
||||
|
||||
# Developer Instructions:
|
||||
|
||||
If you're interested in modifying and/or compiling the source code, please follow these instructions to use gradle to compile the code.
|
||||
|
||||
## Build the project
|
||||
sdrtrunk uses the gradle build system. This requires OpenJDK 11 or higher installed on your local compuber. Use the gradle wrapper to build the source code:
|
||||
|
||||
### Linux
|
||||
```
|
||||
./gradlew clean build
|
||||
```
|
||||
### Windows
|
||||
```
|
||||
gradlew.bat clean build
|
||||
```
|
||||
|
||||
The **/build/distributions** folder will contain the zip file of the compiled program. Unzip it and launch the program from the scripts in the **/bin** directory.
|
||||
|
||||
## Development
|
||||
All dependencies/versions are controlled from build.gradle.
|
||||
To change the new release version tag of artifact - change property:
|
||||
```
|
||||
version = '0.5.0'
|
||||
```
|
||||
|
||||
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_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
|
||||
423
build.gradle
423
build.gradle
@@ -1,105 +1,179 @@
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instructions for building/compiling the sdrtrunk application.
|
||||
*
|
||||
* Prerequisites:
|
||||
* Install and configure an OpenJDK version 19+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
|
||||
* - Optional: install and configure Gradle 7.6+
|
||||
*
|
||||
* Scenario 1: run the application via gradle command line from the source code root directory:
|
||||
* command: ./gradlew run
|
||||
*
|
||||
* Scenario 2: create an application release for the current operating system
|
||||
* command: ./gradlew runtimeZipCurrent
|
||||
* Note: release image is located in the /build/image/ directory
|
||||
*
|
||||
* Scenario 3: create releases for Linux and OSX operating systems using downloaded JDKs
|
||||
* command: ./gradlew runtimeZipOthers
|
||||
* Note: release images are located in the /build/image/ directory
|
||||
*
|
||||
* Scenario 4: create release for Windows operating system using downloaded JDK
|
||||
* command: ./gradlew runtimeZipWindows
|
||||
* Note: release image is located in the /build/image/ directory
|
||||
*/
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.8'
|
||||
id 'org.beryx.runtime' version '1.11.4'
|
||||
id 'org.beryx.runtime' version '1.12.7'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.5.0-alpha5'
|
||||
sourceCompatibility = '13'
|
||||
version = '0.6.0-alpha5'
|
||||
|
||||
//Java 19 is required for this version of the Project Panama preview/incubator feature
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(19)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs 'src/main'
|
||||
test.java.srcDirs 'src/test'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.assertj:assertj-core:3.8.0'
|
||||
|
||||
// JUnit Tests
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.assertj:assertj-core:3.23.1'
|
||||
testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.0')
|
||||
testImplementation 'org.mockito:mockito-core:4.8.0'
|
||||
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||
|
||||
//Jitpack imports
|
||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||
implementation 'com.github.dnault:libresample4j:master'
|
||||
implementation 'com.github.DSheirer.jmbe:jmbe-api:1.0.0'
|
||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||
|
||||
//mavenCentral/jcenter imports
|
||||
implementation 'ch.qos.logback:logback-core:1.2.3'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
|
||||
implementation 'com.fazecast:jSerialComm:2.5.0'
|
||||
implementation 'ch.qos.logback:logback-classic:1.4.5'
|
||||
implementation 'ch.qos.logback:logback-core:1.4.5'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0'
|
||||
implementation 'com.fazecast:jSerialComm:2.9.3'
|
||||
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
||||
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
||||
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||
implementation 'com.github.wendykierp:JTransforms:3.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.guava:guava:27.0.1-jre'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation 'com.google.guava:guava:31.1-jre'
|
||||
implementation 'com.jidesoft:jide-oss:3.6.18'
|
||||
implementation 'com.miglayout:miglayout-swing:5.2'
|
||||
implementation 'com.miglayout:miglayout-swing:11.0'
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
implementation 'commons-io:commons-io:2.11.0'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.8'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.9'
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
|
||||
implementation 'org.apache.commons:commons-compress:1.20'
|
||||
implementation 'commons-io:commons-io:2.7'
|
||||
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||
implementation 'org.apache.commons:commons-compress:1.21'
|
||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'org.apache.mina:mina-core:2.1.3'
|
||||
implementation 'org.apache.mina:mina-http:2.1.3'
|
||||
implementation 'org.controlsfx:controlsfx:11.0.2'
|
||||
implementation 'org.rauschig:jarchivelib:1.0.0'
|
||||
|
||||
implementation 'org.slf4j:slf4j-api:1.7.25'
|
||||
implementation 'org.apache.commons:commons-csv:1.9.0'
|
||||
implementation 'org.apache.mina:mina-core:2.2.1'
|
||||
implementation 'org.apache.mina:mina-http:2.2.1'
|
||||
implementation 'org.controlsfx:controlsfx:11.1.2'
|
||||
implementation 'org.rauschig:jarchivelib:1.2.0'
|
||||
implementation 'org.slf4j:slf4j-api:2.0.5'
|
||||
implementation 'org.usb4java:libusb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
|
||||
//These must remain as compile options to ensure they are included in the build to produce multi-OS images
|
||||
compile "org.openjfx:javafx-base:15:win"
|
||||
compile "org.openjfx:javafx-base:15:linux"
|
||||
compile "org.openjfx:javafx-base:15:mac"
|
||||
compile "org.openjfx:javafx-controls:15:win"
|
||||
compile "org.openjfx:javafx-controls:15:linux"
|
||||
compile "org.openjfx:javafx-controls:15:mac"
|
||||
compile "org.openjfx:javafx-graphics:15:win"
|
||||
compile "org.openjfx:javafx-graphics:15:linux"
|
||||
compile "org.openjfx:javafx-graphics:15:mac"
|
||||
compile "org.openjfx:javafx-swing:15:win"
|
||||
compile "org.openjfx:javafx-swing:15:linux"
|
||||
compile "org.openjfx:javafx-swing:15:mac"
|
||||
}
|
||||
|
||||
def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
|
||||
|
||||
/**
|
||||
* This is needed for the JDK19 panama and vector API ... until it moves out of incubation or preview
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||
|
||||
if(os.isWindows()) {
|
||||
applicationDefaultJvmArgs = jvmArgsWindows
|
||||
}
|
||||
else {
|
||||
applicationDefaultJvmArgs = jvmArgsLinux
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes (
|
||||
'Implementation-Title' : 'sdrtrunk project',
|
||||
'Implementation-Version' : archiveVersion,
|
||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||
'Build-Timestamp' : new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||
'Build-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']}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
@@ -109,103 +183,172 @@ idea {
|
||||
}
|
||||
}
|
||||
|
||||
javafx {
|
||||
version = "15"
|
||||
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
|
||||
}
|
||||
/**
|
||||
* Runtime target OS and CPU image names. We append version here so that version details are at end of zip filename.
|
||||
*/
|
||||
String targetLinuxAarch64 = 'linux-aarch64-v' + version
|
||||
String targetLinuxX86_64 = 'linux-x86_64-v' + version
|
||||
String targetOsxAarch64 = 'osx-aarch64-v' + version
|
||||
String targetOsxX86_64 = 'osx-x86_64-v' + version
|
||||
String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes (
|
||||
'Implementation-Title' : 'sdrtrunk project',
|
||||
'Implementation-Version' : archiveVersion,
|
||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
||||
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
|
||||
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
||||
*/
|
||||
String jdk_base = '/home/denny/java_jdks/'
|
||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
|
||||
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
|
||||
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
||||
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
||||
|
||||
/**
|
||||
* Download URLs to download the JDK as part of the gradle build packaging process
|
||||
*/
|
||||
def jdk_download_base = "https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-"
|
||||
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-aarch64" + jdk_download_suffix
|
||||
def jdk_download_osx_aarch64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
|
||||
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
|
||||
|
||||
/**
|
||||
* Configures the runtime zip task with additional options/settings.
|
||||
* @param rt task to configure
|
||||
* @param jvmArgs to use (windows or linux)
|
||||
* @return configured task.
|
||||
*/
|
||||
def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
|
||||
//Use the correct set of JVM arguments
|
||||
rt.extension.launcherData.get().jvmArgs = jvmArgs
|
||||
|
||||
//Additional modules to include with auto-detected modules.
|
||||
//jdk.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
|
||||
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility')
|
||||
|
||||
//Use auto-detected modules and 'add' any specified modules.
|
||||
rt.extension.additive.set(true)
|
||||
|
||||
//Recommended options for reducing/shrinking runtime target image size
|
||||
rt.extension.addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
|
||||
|
||||
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Java Development Kit (JDK) locations. In order to build OS-specific images, these paths must point to the 'bin'
|
||||
* directory within a JDK for each of the specified architectures. These JDKs are used by the runtime and runtimeZip
|
||||
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
|
||||
* host OS using the installed JDK.
|
||||
* 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)
|
||||
*/
|
||||
def jdk_base = '/media/denny/WD250GB/java/bellsoft/'
|
||||
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()
|
||||
|
||||
def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-15'
|
||||
def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-15'
|
||||
def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-15.jdk'
|
||||
def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-15'
|
||||
|
||||
def file_jdk_linux_arm64 = new File(jdk_linux_arm64)
|
||||
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
|
||||
def file_jdk_osx_x86_64 = new File(jdk_osx_x86_64)
|
||||
def file_jdk_windows_x86_64 = new File(jdk_windows_x86_64)
|
||||
|
||||
def hasTargetJdk = file_jdk_linux_x86_64.exists() ||
|
||||
file_jdk_linux_arm64 ||
|
||||
file_jdk_osx_x86_64.exists() ||
|
||||
file_jdk_windows_x86_64.exists()
|
||||
|
||||
runtime {
|
||||
if(file_jdk_linux_arm64.exists())
|
||||
{
|
||||
targetPlatform('linux-arm64-v' + version, jdk_linux_arm64)
|
||||
if(os.isWindows()) {
|
||||
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
|
||||
configure(rt, jvmArgsWindows)
|
||||
}
|
||||
else
|
||||
else if(os.isLinux())
|
||||
{
|
||||
println("Skipping OS Image - Linux ARM 64-bit JDK was not found at " + jdk_linux_arm64)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if(file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
targetPlatform('osx-x86_64-v' + version, jdk_osx_x86_64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
targetPlatform('windows-x86_64-v' + version, jdk_windows_x86_64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
modules = ['java.desktop', 'java.naming', 'jdk.unsupported', 'jdk.unsupported.desktop', 'java.net.http',
|
||||
'java.sql', 'jdk.crypto.ec']
|
||||
imageZip = hasTargetJdk ? file("$buildDir/image/sdr-trunk.zip") : file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
configure(rt, jvmArgsWindows)
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy JavaFX OS-specific libraries from each JDK to each platform target directory
|
||||
* 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.runtime.doLast
|
||||
{
|
||||
delete(fileTree('build/image/sdr-trunk-linux-arm64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
|
||||
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)
|
||||
}
|
||||
}
|
||||
//Linux 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)
|
||||
}
|
||||
}
|
||||
|
||||
//JavaFX web and media modules are transitive dependencies of ControlsFX but are not required for this application
|
||||
delete(fileTree('build/image/sdr-trunk-linux-arm64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
configure(rt, jvmArgsLinux);
|
||||
}
|
||||
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
22
gradle/wrapper/gradle-wrapper.properties
vendored
22
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,25 @@
|
||||
#
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
306
gradlew
vendored
306
gradlew
vendored
@@ -1,94 +1,132 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,7 +135,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -105,79 +143,105 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
38
gradlew.bat
vendored
38
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,10 +25,14 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@@ -37,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -51,7 +55,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -61,38 +65,26 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.action;
|
||||
@@ -26,8 +25,8 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.EventQueue;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -92,7 +91,7 @@ public abstract class RecurringAction extends AliasAction
|
||||
*/
|
||||
private void performThreadedAction(final Alias alias, final IMessage message)
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(() -> performAction(alias, message), 0, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.execute(() -> performAction(alias, message));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.action.beep;
|
||||
@@ -72,7 +71,7 @@ public class BeepAction extends RecurringAction
|
||||
@Override
|
||||
public void performAction(Alias alias, IMessage message )
|
||||
{
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
|
||||
DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO);
|
||||
|
||||
if(!AudioSystem.isLineSupported(info))
|
||||
{
|
||||
@@ -83,7 +82,7 @@ public class BeepAction extends RecurringAction
|
||||
try
|
||||
{
|
||||
Clip clip = (Clip)AudioSystem.getLine(info);
|
||||
clip.open(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, mToneBytes, 0, mToneBytes.length);
|
||||
clip.open(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, mToneBytes, 0, mToneBytes.length);
|
||||
clip.start();
|
||||
}
|
||||
catch(Exception e)
|
||||
|
||||
@@ -46,6 +46,8 @@ public enum TalkgroupFormat
|
||||
"MDC-1200 valid value range is 1-65,535"),
|
||||
MPT1327("###-####", 1, 0x7FFFFF, "000-0001 to 127-8192",
|
||||
"<html>MPT-1327 valid ranges are 0-127(prefix)<br>and 1-8192(ident) (ie. 000-0001 to 127-8192)"),
|
||||
NBFM("*****", 1, 0xFFFF, "1 to 65,535",
|
||||
"NBFM valid value range is 1-65,535"),
|
||||
PASSPORT("*****", 1, 0xFFFF, "1 to 65,535",
|
||||
"Passport valid value range is 1-65,535"),
|
||||
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
|
||||
@@ -131,6 +133,8 @@ public enum TalkgroupFormat
|
||||
return MDC1200;
|
||||
case MPT1327:
|
||||
return MPT1327;
|
||||
case NBFM:
|
||||
return NBFM;
|
||||
case PASSPORT:
|
||||
return PASSPORT;
|
||||
default:
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupForm
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
@@ -56,6 +57,7 @@ public class TalkgroupFormatter
|
||||
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
||||
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
@@ -106,7 +105,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
||||
* listener(s).
|
||||
*/
|
||||
protected AudioSegment getAudioSegment()
|
||||
public AudioSegment getAudioSegment()
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
@@ -135,7 +134,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
}
|
||||
}
|
||||
|
||||
protected void addAudio(float[] audioBuffer)
|
||||
public void addAudio(float[] audioBuffer)
|
||||
{
|
||||
AudioSegment audioSegment = getAudioSegment();
|
||||
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
@@ -5,81 +24,107 @@ import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
/**
|
||||
* Audio formats
|
||||
*/
|
||||
public class AudioFormats
|
||||
{
|
||||
public static final AudioFormat.Encoding IMBE_ENCODING =
|
||||
new AudioFormat.Encoding( "IMBE" );
|
||||
|
||||
public static final boolean LITTLE_ENDIAN = false;
|
||||
public static final boolean BIG_ENDIAN = true;
|
||||
|
||||
public static final float IMBE_FRAME_RATE = 50;
|
||||
public static final float IMBE_SAMPLE_RATE = 50;
|
||||
public static final float PCM_8KHZ_RATE = 8000;
|
||||
public static final float PCM_48KHZ_RATE = 48000;
|
||||
|
||||
public static final int IMBE_FRAME_SIZE_BYTES = 18;
|
||||
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
|
||||
|
||||
public static final float PCM_8_KHZ_RATE = 8000;
|
||||
public static final float PCM_16_KHZ_RATE = 16000;
|
||||
public static final float PCM_22050_HZ_RATE = 22050;
|
||||
public static final float PCM_44100_HZ_RATE = 44100;
|
||||
|
||||
public static final int ONE_CHANNEL = 1;
|
||||
public static final int TWO_CHANNELS = 2;
|
||||
public static final int PCM_SAMPLE_SIZE_BITS = 16;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_MONO = 2;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_STEREO = 4;
|
||||
|
||||
public static AudioFormat IMBE_AUDIO_FORMAT =
|
||||
new AudioFormat( IMBE_ENCODING,
|
||||
IMBE_SAMPLE_RATE,
|
||||
IMBE_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
IMBE_FRAME_SIZE_BYTES,
|
||||
IMBE_FRAME_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_MONO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_MONO,
|
||||
PCM_8KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
public static final int PCM_SAMPLE_SIZE_16_BITS = 16;
|
||||
public static final int PCM_SAMPLE_SIZE_32_BITS = 32;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_MONO = 2;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_32_BIT_MONO = 4;
|
||||
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_STEREO = 4;
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_STEREO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_STEREO,
|
||||
PCM_8KHZ_RATE,
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_16000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_16_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_16_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_16000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_16_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_16_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_22050_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_22050_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_22050_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_22050_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_22050_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_22050_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_44100_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_44100_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
|
||||
PCM_44100_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_44100_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_44100_HZ_RATE,
|
||||
PCM_SAMPLE_SIZE_32_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
|
||||
PCM_44100_HZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_8000_HZ_16BITS_STEREO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_8_KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_16_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_16_BIT_STEREO,
|
||||
PCM_8_KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_48KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
ONE_CHANNEL,
|
||||
PCM_FRAME_SIZE_BYTES_MONO,
|
||||
PCM_48KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_STEREO =
|
||||
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
|
||||
PCM_48KHZ_RATE,
|
||||
PCM_SAMPLE_SIZE_BITS,
|
||||
TWO_CHANNELS,
|
||||
PCM_FRAME_SIZE_BYTES_STEREO,
|
||||
PCM_48KHZ_RATE,
|
||||
LITTLE_ENDIAN );
|
||||
|
||||
/**
|
||||
* Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
|
||||
*/
|
||||
public static final Line.Info MONO_SOURCE_DATALINE_INFO =
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_MONO);
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16_BIT_MONO);
|
||||
|
||||
/**
|
||||
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
|
||||
*/
|
||||
public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_STEREO);
|
||||
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16BITS_STEREO);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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;
|
||||
|
||||
@@ -25,13 +22,13 @@ import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateListener;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.sample.real.IRealBufferListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -41,8 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* Incorporates audio squelch state listener to control if audio packets are broadcast or ignored.
|
||||
*/
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IReusableBufferListener,
|
||||
Listener<ReusableFloatBuffer>
|
||||
public class AudioModule extends AbstractAudioModule implements ISquelchStateListener, IRealBufferListener,
|
||||
Listener<float[]>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||
private static float[] sHighPassFilterCoefficients;
|
||||
@@ -73,7 +70,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
}
|
||||
|
||||
private RealFIRFilter2 mHighPassFilter = new RealFIRFilter2(sHighPassFilterCoefficients);
|
||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||
|
||||
@@ -120,22 +117,17 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ReusableFloatBuffer audioBuffer)
|
||||
public void receive(float[] audioBuffer)
|
||||
{
|
||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||
{
|
||||
ReusableFloatBuffer highPassFilteredAudio = mHighPassFilter.filter(audioBuffer);
|
||||
addAudio(highPassFilteredAudio.getSamplesCopy());
|
||||
highPassFilteredAudio.decrementUserCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
audioBuffer.decrementUserCount();
|
||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||
addAudio(audioBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ReusableFloatBuffer> getReusableBufferListener()
|
||||
public Listener<float[]> getBufferListener()
|
||||
{
|
||||
//Redirect received reusable buffers to the receive(buffer) method
|
||||
return this;
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
@@ -26,15 +29,6 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@@ -43,6 +37,14 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
|
||||
@@ -138,6 +140,15 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio segment is completed.
|
||||
* @return true if completed.
|
||||
*/
|
||||
public boolean isComplete()
|
||||
{
|
||||
return mComplete.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
||||
* an audio segment is a duplicate.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class AudioSegmentBroadcaster<T extends AudioSegment> extends Broadcaster
|
||||
@Override
|
||||
public void broadcast(T audioSegment)
|
||||
{
|
||||
for(Listener<T> listener : mListeners)
|
||||
for(Listener<T> listener : getListeners())
|
||||
{
|
||||
audioSegment.incrementConsumerCount();
|
||||
listener.receive(audioSegment);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio;
|
||||
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
@@ -21,7 +24,10 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.Float;
|
||||
import java.lang.Math;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AudioUtils
|
||||
@@ -76,4 +82,42 @@ public class AudioUtils
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize audio samples
|
||||
*/
|
||||
public static List<float[]> normalize(List<float[]> audioBuffers)
|
||||
{
|
||||
float targetMax = 0.95f; // -0.44dBFS
|
||||
float max = 0.1f; // Initialize at 0.1 (-20dBFS) to limit maximum gain
|
||||
|
||||
for(float[] audioBuffer: audioBuffers)
|
||||
{
|
||||
for(float value: audioBuffer)
|
||||
{
|
||||
float newMax = Math.max(max, Math.abs(value));
|
||||
if(!Float.isNaN(newMax))
|
||||
{
|
||||
max = newMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(targetMax == max)
|
||||
{
|
||||
return audioBuffers;
|
||||
}
|
||||
|
||||
float gain = targetMax / max;
|
||||
|
||||
for(float[] audioBuffer: audioBuffers)
|
||||
{
|
||||
for(int i=0;i<audioBuffer.length;i++)
|
||||
{
|
||||
audioBuffer[i] *= gain;
|
||||
}
|
||||
}
|
||||
|
||||
return audioBuffers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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;
|
||||
|
||||
import io.github.dsheirer.audio.convert.AudioFrames;
|
||||
import io.github.dsheirer.audio.convert.ISilenceGenerator;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3FrameTools;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Queue;
|
||||
@@ -36,22 +32,31 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioStreamingBroadcaster.class);
|
||||
|
||||
public static final int PROCESSOR_RUN_INTERVAL_MS = 1000;
|
||||
private ScheduledFuture mRecordingQueueProcessorFuture;
|
||||
private ScheduledFuture<?> mRecordingQueueProcessorFuture;
|
||||
|
||||
private RecordingQueueProcessor mRecordingQueueProcessor = new RecordingQueueProcessor();
|
||||
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||
private ISilenceGenerator mSilenceGenerator;
|
||||
|
||||
private BroadcastFormat mBroadcastFormat;
|
||||
private long mDelay;
|
||||
private long mMaximumRecordingAge;
|
||||
private AtomicBoolean mStreaming = new AtomicBoolean();
|
||||
|
||||
protected boolean mInlineActive = false;
|
||||
protected int mInlineInterval;
|
||||
protected int mInlineRemaining = -1;
|
||||
|
||||
private int mTimeOverrun = 0;
|
||||
|
||||
/**
|
||||
* AudioBroadcaster for streaming audio recordings to a remote streaming audio server. Audio recordings are
|
||||
* generated by an internal StreamManager that converts an inbound stream of AudioPackets into a recording of the
|
||||
@@ -73,12 +78,14 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
* The last audio packet's metadata is automatically attached to the closed audio recording when it is enqueued for
|
||||
* broadcast. That metadata will be updated on the remote server once the audio recording is opened for streaming.
|
||||
*/
|
||||
public AudioStreamingBroadcaster(T broadcastConfiguration)
|
||||
public AudioStreamingBroadcaster(T broadcastConfiguration, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
|
||||
{
|
||||
super(broadcastConfiguration);
|
||||
mBroadcastFormat = broadcastConfiguration.getBroadcastFormat();
|
||||
mDelay = getBroadcastConfiguration().getDelay();
|
||||
mMaximumRecordingAge = getBroadcastConfiguration().getMaximumRecordingAge();
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat(),
|
||||
inputAudioFormat, mp3Setting);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -88,7 +95,7 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
/**
|
||||
* Broadcast binary audio data frames or sequences.
|
||||
*/
|
||||
protected abstract void broadcastAudio(byte[] audio);
|
||||
protected abstract void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection);
|
||||
|
||||
/**
|
||||
* Protocol-specific metadata updater
|
||||
@@ -204,13 +211,15 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
|
||||
super.setBroadcastState(state);
|
||||
|
||||
if(mBroadcastState.get() instanceof BroadcastState && ((BroadcastState)mBroadcastState.get()).isErrorState())
|
||||
if(mBroadcastState.get() != null && mBroadcastState.get().isErrorState())
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
if(!connected())
|
||||
{
|
||||
//Reset inline metadata
|
||||
mInlineRemaining = -1;
|
||||
//Remove all pending audio recordings
|
||||
while(!mAudioRecordingQueue.isEmpty())
|
||||
{
|
||||
@@ -242,7 +251,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
public boolean canConnect()
|
||||
{
|
||||
BroadcastState state = getBroadcastState();
|
||||
|
||||
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
||||
}
|
||||
|
||||
@@ -263,10 +271,8 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
public class RecordingQueueProcessor implements Runnable
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
private ByteArrayInputStream mInputStream;
|
||||
private long mFinalSilencePadding = 0;
|
||||
private int mBytesStreamedActual = 0;
|
||||
private int mBytesStreamedRequired = 0;
|
||||
private AudioFrames mInputFrames;
|
||||
private IdentifierCollection mInputIdentifierCollection;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
@@ -275,45 +281,35 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
{
|
||||
try
|
||||
{
|
||||
if(mInputStream == null || mInputStream.available() <= 0)
|
||||
{
|
||||
if(mFinalSilencePadding > 0)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
|
||||
mFinalSilencePadding = 0;
|
||||
}
|
||||
int timeSent = 0;
|
||||
|
||||
if(mInputFrames == null || !mInputFrames.hasNextFrame())
|
||||
{
|
||||
nextRecording();
|
||||
}
|
||||
|
||||
if(mInputStream != null)
|
||||
if(mInputFrames != null && mInputFrames.hasNextFrame())
|
||||
{
|
||||
//We need to stream at 13.888 fps (144 byte frame) to achieve 2000 Bps or 16 kbps
|
||||
mBytesStreamedRequired += 2000; //2000 bytes per second for 16 kbps data rate
|
||||
int bytesToStream = mBytesStreamedRequired - mBytesStreamedActual;
|
||||
|
||||
//Trim length to whole-frame intervals (144 byte frame)
|
||||
bytesToStream -= (bytesToStream % 144);
|
||||
|
||||
int length = FastMath.min(bytesToStream, mInputStream.available());
|
||||
|
||||
byte[] audio = new byte[length];
|
||||
|
||||
try
|
||||
while(mInputFrames.hasNextFrame() && timeSent < PROCESSOR_RUN_INTERVAL_MS)
|
||||
{
|
||||
mBytesStreamedActual += mInputStream.read(audio);
|
||||
|
||||
broadcastAudio(audio);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error reading from in-memory audio recording input stream", ioe);
|
||||
mInputFrames.nextFrame();
|
||||
broadcastAudio(mInputFrames.getCurrentFrame(), mInputIdentifierCollection);
|
||||
timeSent += mInputFrames.getCurrentFrameDuration();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if((mInputFrames == null || !mInputFrames.hasNextFrame()) && timeSent < PROCESSOR_RUN_INTERVAL_MS)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
|
||||
AudioFrames silenceFrames = mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS - mTimeOverrun - timeSent);
|
||||
while(silenceFrames.hasNextFrame())
|
||||
{
|
||||
silenceFrames.nextFrame();
|
||||
broadcastAudio(silenceFrames.getCurrentFrame(), null);
|
||||
timeSent += silenceFrames.getCurrentFrameDuration();
|
||||
}
|
||||
}
|
||||
|
||||
mTimeOverrun += timeSent - PROCESSOR_RUN_INTERVAL_MS;
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
@@ -329,12 +325,9 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
*/
|
||||
private void nextRecording()
|
||||
{
|
||||
mBytesStreamedActual = 0;
|
||||
mBytesStreamedRequired = 0;
|
||||
|
||||
boolean metadataUpdateRequired = false;
|
||||
|
||||
if(mInputStream != null)
|
||||
if(mInputFrames != null)
|
||||
{
|
||||
mStreamedAudioCount++;
|
||||
broadcast(new BroadcastEvent(AudioStreamingBroadcaster.this,
|
||||
@@ -342,7 +335,8 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
metadataUpdateRequired = true;
|
||||
}
|
||||
|
||||
mInputStream = null;
|
||||
mInputFrames = null;
|
||||
mInputIdentifierCollection = null;
|
||||
|
||||
//Peek at the next recording but don't remove it from the queue yet, so we can inspect the start time for
|
||||
//age limits and/or delay elapsed
|
||||
@@ -370,17 +364,17 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
{
|
||||
byte[] audio = Files.readAllBytes(nextRecording.getPath());
|
||||
|
||||
if(audio != null && audio.length > 0)
|
||||
if(audio.length > 0)
|
||||
{
|
||||
mInputStream = new ByteArrayInputStream(audio);
|
||||
|
||||
mFinalSilencePadding = PROCESSOR_RUN_INTERVAL_MS -
|
||||
(nextRecording.getRecordingLength() % PROCESSOR_RUN_INTERVAL_MS);
|
||||
|
||||
while(mFinalSilencePadding >= PROCESSOR_RUN_INTERVAL_MS)
|
||||
switch(mBroadcastFormat)
|
||||
{
|
||||
mFinalSilencePadding -= PROCESSOR_RUN_INTERVAL_MS;
|
||||
case MP3:
|
||||
mInputFrames = MP3FrameTools.split(audio);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported broadcast format [" + mBroadcastFormat + "]");
|
||||
}
|
||||
mInputIdentifierCollection = nextRecording.getIdentifierCollection();
|
||||
|
||||
if(connected())
|
||||
{
|
||||
@@ -396,7 +390,8 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
mLog.error("Stream [" + getBroadcastConfiguration().getName() + "] error reading temporary audio " +
|
||||
"stream recording [" + nextRecording.getPath().toString() + "] - skipping recording - ", ioe);
|
||||
|
||||
mInputStream = null;
|
||||
mInputFrames = null;
|
||||
mInputIdentifierCollection = null;
|
||||
metadataUpdateRequired = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -150,7 +150,7 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
|
||||
IdentifierCollection identifierCollectionCopy =
|
||||
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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;
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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;
|
||||
|
||||
@@ -33,10 +30,11 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcast
|
||||
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.ShoutcastV2Configuration;
|
||||
import io.github.dsheirer.audio.convert.IAudioConverter;
|
||||
import io.github.dsheirer.audio.convert.ISilenceGenerator;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -44,67 +42,50 @@ public class BroadcastFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BroadcastFactory.class);
|
||||
|
||||
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
||||
public static final boolean MP3_CONSTANT_BITRATE = false;
|
||||
|
||||
/**
|
||||
* Creates an audio streaming broadcaster for the configuration
|
||||
*
|
||||
* @param configuration describing the server and audio types
|
||||
* @return configured broadcaster or null
|
||||
*/
|
||||
public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
|
||||
public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel,
|
||||
UserPreferences userPreferences)
|
||||
{
|
||||
if(configuration != null)
|
||||
{
|
||||
IAudioConverter converter = getAudioConverter(configuration);
|
||||
InputAudioFormat inputAudioFormat = userPreferences.getMP3Preference().getAudioSampleRate();
|
||||
MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting();
|
||||
|
||||
if(converter != null)
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
switch(configuration.getBroadcastServerType())
|
||||
{
|
||||
case BROADCASTIFY_CALL:
|
||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration, aliasModel);
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration, aliasModel);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, aliasModel);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
||||
break;
|
||||
}
|
||||
case BROADCASTIFY_CALL:
|
||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case ICECAST_TCP:
|
||||
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case SHOUTCAST_V1:
|
||||
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
|
||||
mp3Setting, aliasModel);
|
||||
case SHOUTCAST_V2:
|
||||
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case UNKNOWN:
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an audio convert to convert from 8 kHz PCM audio to the specified format
|
||||
*
|
||||
* @param configuration containing the requested output audio format
|
||||
* @return audio convert or null
|
||||
*/
|
||||
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration)
|
||||
{
|
||||
switch(configuration.getBroadcastFormat())
|
||||
{
|
||||
case MP3:
|
||||
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
|
||||
default:
|
||||
mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a broadcastAudio configuration for the specified server type and format
|
||||
*
|
||||
@@ -137,12 +118,12 @@ public class BroadcastFactory
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
|
||||
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
|
||||
{
|
||||
switch(format)
|
||||
{
|
||||
case MP3:
|
||||
return new MP3SilenceGenerator();
|
||||
return new MP3SilenceGenerator(inputAudioFormat, mp3Setting);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized broadcast format [" + format +
|
||||
"] can't create silence generator");
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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;
|
||||
|
||||
@@ -29,13 +26,6 @@ import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
@@ -51,6 +41,12 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
||||
{
|
||||
@@ -61,7 +57,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
||||
|
||||
public static final int COLUMN_SERVER_ICON = 0;
|
||||
public static final int COLUMN_BROADCAST_SERVER_TYPE = 0;
|
||||
public static final int COLUMN_STREAM_NAME = 1;
|
||||
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
||||
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
||||
@@ -70,7 +66,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
||||
|
||||
public static final String[] COLUMN_NAMES = new String[]
|
||||
{"Streaming", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
||||
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
||||
|
||||
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||
@@ -80,6 +76,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
private AliasModel mAliasModel;
|
||||
private Broadcaster<BroadcastEvent> mBroadcastEventBroadcaster = new Broadcaster<>();
|
||||
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
/**
|
||||
* Model for managing Broadcast configurations and any associated broadcaster instances.
|
||||
@@ -88,6 +85,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mIconModel = iconModel;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
|
||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
||||
@@ -346,7 +344,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
deleteBroadcaster(configuredBroadcast);
|
||||
}
|
||||
|
||||
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
|
||||
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
|
||||
mAliasModel, mUserPreferences);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
@@ -575,15 +574,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_SERVER_ICON:
|
||||
String iconPath = configuredBroadcast.getBroadcastConfiguration()
|
||||
.getBroadcastServerType().getIconPath();
|
||||
|
||||
if(iconPath != null && mIconModel != null)
|
||||
{
|
||||
return mIconModel.getScaledIcon(iconPath, 14);
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||
return configuredBroadcast.getBroadcastServerType();
|
||||
case COLUMN_STREAM_NAME:
|
||||
return configuredBroadcast.getBroadcastConfiguration().getName();
|
||||
case COLUMN_BROADCASTER_STATUS:
|
||||
@@ -653,8 +645,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||
case COLUMN_BROADCASTER_ERROR_COUNT:
|
||||
return Integer.class;
|
||||
case COLUMN_SERVER_ICON:
|
||||
return ImageIcon.class;
|
||||
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||
return BroadcastServerType.class;
|
||||
case COLUMN_STREAM_NAME:
|
||||
default:
|
||||
return String.class;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* 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;
|
||||
|
||||
public enum BroadcastServerType
|
||||
@@ -23,17 +23,17 @@ public enum BroadcastServerType
|
||||
/**
|
||||
* Broadcastify feeds (ie streaming) service
|
||||
*/
|
||||
BROADCASTIFY("Broadcastify Feed", "/images/broadcastify.png"), //Icecast Server 2.3.2
|
||||
BROADCASTIFY("Broadcastify Feed", "images/broadcastify.png"), //Icecast Server 2.3.2
|
||||
|
||||
/**
|
||||
* Broadcastify calls - completed audio recording push service
|
||||
*/
|
||||
BROADCASTIFY_CALL("Broadcastify Call", "/images/broadcastify.png"),
|
||||
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||
|
||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
|
||||
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
|
||||
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
|
||||
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
|
||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||
UNKNOWN("Unknown", null);
|
||||
|
||||
private String mLabel;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* 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;
|
||||
|
||||
public enum BroadcastState
|
||||
@@ -103,10 +103,10 @@ public enum BroadcastState
|
||||
private String mLabel;
|
||||
private boolean mErrorState;
|
||||
|
||||
private BroadcastState(String label, boolean connected)
|
||||
BroadcastState(String label, boolean errorState)
|
||||
{
|
||||
mLabel = label;
|
||||
mErrorState = connected;
|
||||
mErrorState = errorState;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,23 +14,29 @@
|
||||
*
|
||||
* 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;
|
||||
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
/**
|
||||
* Table of broadcast streams and statuses.
|
||||
*/
|
||||
public class BroadcastStatusPanel extends JPanel
|
||||
{
|
||||
private JTable mTable;
|
||||
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
|
||||
private UserPreferences mUserPreferences;
|
||||
private String mPreferenceKey;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
|
||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
||||
|
||||
mScrollPane = new JScrollPane(mTable);
|
||||
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
|
||||
add(mScrollPane);
|
||||
}
|
||||
|
||||
public class ServerTypeRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public ServerTypeRenderer()
|
||||
{
|
||||
setOpaque(true);
|
||||
setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(value instanceof BroadcastServerType broadcastServerType)
|
||||
{
|
||||
component.setText(broadcastServerType.toString());
|
||||
Icon icon = new Icon("empty", broadcastServerType.getIconPath());
|
||||
ImageIcon imageIcon = icon.getIcon();
|
||||
ImageIcon scaledIcon = IconModel.getScaledIcon(imageIcon, 13);
|
||||
component.setIcon(scaledIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.setText(null);
|
||||
component.setIcon(null);
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer for the broadcast state column.
|
||||
*/
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,6 +25,8 @@ import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
@@ -66,6 +68,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
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 ScheduledFuture<?> mBroadcastifyTestFuture;
|
||||
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
|
||||
private HttpClient mHttpClient = HttpClient.newBuilder()
|
||||
@@ -81,7 +84,8 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
* @param config to use
|
||||
* @param aliasModel for access to aliases
|
||||
*/
|
||||
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, AliasModel aliasModel)
|
||||
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(config);
|
||||
}
|
||||
@@ -106,6 +110,12 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
|
||||
if(mBroadcastifyTestFuture == null && getBroadcastConfiguration().isTestEnabled())
|
||||
{
|
||||
// Test periodically so we don't get marked offline due to radio inactivity
|
||||
mBroadcastifyTestFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BroadcastifyCallTest(), getBroadcastConfiguration().getTestInterval(), getBroadcastConfiguration().getTestInterval(), TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if(mAudioRecordingProcessorFuture == null)
|
||||
{
|
||||
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
|
||||
@@ -119,6 +129,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
if(mBroadcastifyTestFuture != null)
|
||||
{
|
||||
mBroadcastifyTestFuture.cancel(true);
|
||||
mBroadcastifyTestFuture = null;
|
||||
}
|
||||
if(mAudioRecordingProcessorFuture != null)
|
||||
{
|
||||
mAudioRecordingProcessorFuture.cancel(true);
|
||||
@@ -128,6 +143,23 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
}
|
||||
}
|
||||
|
||||
public class BroadcastifyCallTest implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
String response = testConnection(getBroadcastConfiguration());
|
||||
if(response != null && response.toLowerCase().startsWith("ok"))
|
||||
{
|
||||
mLog.info("Broadcastify Calls keep-alive success");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("Broadcastify Calls keep-alive failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares for disposal
|
||||
*/
|
||||
@@ -415,40 +447,42 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
*/
|
||||
private static String getTo(AudioRecording audioRecording)
|
||||
{
|
||||
List<Identifier> toIdentifiers = audioRecording.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||
|
||||
if(toIdentifiers.size() >= 1)
|
||||
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
Identifier to = toIdentifiers.get(0);
|
||||
|
||||
if(to instanceof TalkgroupIdentifier)
|
||||
{
|
||||
TalkgroupIdentifier talkgroupIdentifier = (TalkgroupIdentifier)to;
|
||||
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||
return format(patchGroupIdentifier);
|
||||
}
|
||||
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||
{
|
||||
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||
talkgroupIdentifier.getProtocol()));
|
||||
}
|
||||
else if(to instanceof PatchGroupIdentifier)
|
||||
{
|
||||
PatchGroup patchGroup = ((PatchGroupIdentifier)to).getValue();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
else if(to instanceof RadioIdentifier)
|
||||
{
|
||||
return ((RadioIdentifier)to).getValue().toString();
|
||||
}
|
||||
}
|
||||
else if(identifier instanceof RadioIdentifier radioIdentifier)
|
||||
{
|
||||
return radioIdentifier.getValue().toString();
|
||||
}
|
||||
|
||||
return "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a patch group
|
||||
*/
|
||||
public static String format(PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests both the connection and configuration against the Broadcastify Call API service
|
||||
* @param configuration containing API key and system id
|
||||
|
||||
@@ -24,7 +24,9 @@ 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;
|
||||
@@ -42,6 +44,8 @@ public class BroadcastifyCallConfiguration extends BroadcastConfiguration
|
||||
|
||||
private IntegerProperty mSystemID = new SimpleIntegerProperty();
|
||||
private StringProperty mApiKey = new SimpleStringProperty();
|
||||
private BooleanProperty mTestEnabled= new SimpleBooleanProperty(false);
|
||||
private IntegerProperty mTestInterval = new SimpleIntegerProperty(15);
|
||||
|
||||
/**
|
||||
* Constructor for faster jackson
|
||||
@@ -128,6 +132,34 @@ public class BroadcastifyCallConfiguration extends BroadcastConfiguration
|
||||
return BroadcastServerType.BROADCASTIFY_CALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this instance should schedule tests after connecting
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "test_enabled")
|
||||
public boolean isTestEnabled()
|
||||
{
|
||||
return mTestEnabled.get();
|
||||
}
|
||||
|
||||
public void setTestEnabled(boolean enabled)
|
||||
{
|
||||
mTestEnabled.set(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the interval at which tests should be performed while enabled
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "test_interval")
|
||||
public int getTestInterval()
|
||||
{
|
||||
return mTestInterval.get();
|
||||
}
|
||||
|
||||
public void setTestInterval(int interval)
|
||||
{
|
||||
mTestInterval.set(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastConfiguration copyOf()
|
||||
{
|
||||
|
||||
@@ -54,6 +54,7 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
|
||||
setBitRate(16);
|
||||
setChannels(1);
|
||||
setSampleRate(8000);
|
||||
setInline(true);
|
||||
}
|
||||
|
||||
public static BroadcastifyFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
|
||||
@@ -86,9 +87,9 @@ public class BroadcastifyFeedConfiguration extends IcecastTCPConfiguration
|
||||
copy.setName(getName());
|
||||
copy.setHost(getHost());
|
||||
copy.setPort(getPort());
|
||||
copy.setInline(getInline());
|
||||
copy.setPassword(getPassword());
|
||||
copy.setDelay(getDelay());
|
||||
|
||||
copy.setEnabled(false);
|
||||
|
||||
//Icecast Configuration Parameters
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,23 +14,26 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
|
||||
public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
|
||||
{
|
||||
private IBroadcastMetadataUpdater mMetadataUpdater;
|
||||
private AliasModel mAliasModel;
|
||||
protected AliasModel mAliasModel;
|
||||
|
||||
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, AliasModel aliasModel)
|
||||
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(broadcastConfiguration);
|
||||
super(broadcastConfiguration, inputAudioFormat, mp3Setting);
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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.icecast;
|
||||
|
||||
@@ -26,6 +23,8 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
@@ -76,6 +75,12 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
*/
|
||||
public void update(IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(mIcecastConfiguration.hasInline())
|
||||
{
|
||||
// Never send out-of-band metadata if inline metadata is enabled.
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
try
|
||||
@@ -87,7 +92,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
sb.append("/admin/metadata?mode=updinfo&mount=");
|
||||
sb.append(URLEncoder.encode(mIcecastConfiguration.getMountPoint(), UTF8));
|
||||
sb.append("&charset=UTF%2d8");
|
||||
sb.append("&song=").append(URLEncoder.encode(getSong(identifierCollection), UTF8));
|
||||
sb.append("&song=").append(URLEncoder.encode(IcecastMetadata.getTitle(identifierCollection, mAliasModel), UTF8));
|
||||
}
|
||||
catch(UnsupportedEncodingException uee)
|
||||
{
|
||||
@@ -100,7 +105,7 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
final String metadataUpdateURL = sb.toString();
|
||||
URI uri = URI.create(metadataUpdateURL);
|
||||
|
||||
ThreadPool.SCHEDULED.submit(new Runnable()
|
||||
ThreadPool.CACHED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@@ -160,82 +165,4 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the song information for a metadata update
|
||||
*/
|
||||
private String getSong(IdentifierCollection identifierCollection)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(identifierCollection != null)
|
||||
{
|
||||
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
|
||||
|
||||
Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
||||
}
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
List<Identifier> toIdentifiers = identifierCollection.getIdentifiers(Role.TO);
|
||||
if(!toIdentifiers.isEmpty())
|
||||
{
|
||||
to = toIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
sb.append("TO:").append(to);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(to);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("TO:UNKNOWN");
|
||||
}
|
||||
|
||||
Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
||||
|
||||
if(from == null)
|
||||
{
|
||||
List<Identifier> fromIdentifiers = identifierCollection.getIdentifiers(Role.FROM);
|
||||
|
||||
if(!fromIdentifiers.isEmpty())
|
||||
{
|
||||
from = fromIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(from != null)
|
||||
{
|
||||
sb.append(" FROM:").append(from);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(from);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" FROM:UNKNOWN");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Scanning ....");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
|
||||
private int mChannels = 1;
|
||||
private int mSampleRate = 8000;
|
||||
private String mURL;
|
||||
private boolean mInline = true;
|
||||
|
||||
public IcecastConfiguration(BroadcastFormat format)
|
||||
{
|
||||
@@ -275,6 +276,37 @@ public abstract class IcecastConfiguration extends BroadcastConfiguration
|
||||
return mURL != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether metadata is sent inline or out-of-band.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "inline")
|
||||
public boolean getInline()
|
||||
{
|
||||
return mInline;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public int getInlineInterval()
|
||||
{
|
||||
if (hasBitRate())
|
||||
{
|
||||
// Interval = BitRate * 1000 / 8 = 1 second
|
||||
return getBitRate() * 125;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setInline(boolean inline)
|
||||
{
|
||||
mInline = inline;
|
||||
}
|
||||
|
||||
public boolean hasInline()
|
||||
{
|
||||
// Bitrate must be known to calculate metadata interval
|
||||
return mInline != false && hasBitRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.apache.mina.core.future.ConnectFuture;
|
||||
@@ -41,13 +49,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
||||
@@ -72,23 +73,51 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
*
|
||||
* @param configuration for the Icecast stream
|
||||
*/
|
||||
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, AliasModel aliasModel)
|
||||
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration, aliasModel);
|
||||
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
|
||||
{
|
||||
IoBuffer buffer = IoBuffer.allocate(audio.length);
|
||||
buffer.put(audio);
|
||||
buffer.flip();
|
||||
|
||||
if(mInlineActive)
|
||||
{
|
||||
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
|
||||
buffer.setAutoExpand(true);
|
||||
if (mInlineRemaining == -1)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
}
|
||||
int audioOffset = 0;
|
||||
while(audioOffset < audio.length)
|
||||
{
|
||||
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
|
||||
mInlineRemaining -= chunk.length;
|
||||
audioOffset += chunk.length;
|
||||
buffer.put(chunk);
|
||||
if(mInlineRemaining == 0)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
buffer.put(metadata);
|
||||
}
|
||||
}
|
||||
buffer.shrink();
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.put(audio);
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
mStreamingSession.write(buffer);
|
||||
}
|
||||
}
|
||||
@@ -114,8 +143,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||
|
||||
// mSocketConnector.getFilterChain().addLast("logger",
|
||||
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
|
||||
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
|
||||
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
|
||||
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
|
||||
|
||||
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
||||
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
||||
@@ -164,8 +194,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
@@ -240,6 +269,17 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
mHTTPHeaders.put(IcecastHeader.BITRATE.getValue(), String.valueOf(getConfiguration().getBitRate()));
|
||||
}
|
||||
|
||||
if(getConfiguration().hasInline())
|
||||
{
|
||||
mInlineActive = true;
|
||||
mInlineInterval = getConfiguration().getInlineInterval();
|
||||
mHTTPHeaders.put(IcecastHeader.METAINT.getValue(), String.valueOf(mInlineInterval));
|
||||
}
|
||||
else
|
||||
{
|
||||
mInlineActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
return mHTTPHeaders;
|
||||
@@ -276,10 +316,49 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
|
||||
* OK response and then play like everything is well and good.
|
||||
*/
|
||||
if(cause instanceof ArrayIndexOutOfBoundsException &&
|
||||
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
|
||||
if(cause instanceof ArrayIndexOutOfBoundsException)
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
String hexDump = ((ProtocolDecoderException)throwable).getHexdump();
|
||||
|
||||
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpDumpMessage message = new HttpDumpMessage(hexDump);
|
||||
|
||||
if(message.hasHttpResponseCode())
|
||||
{
|
||||
switch(message.getHttpResponseCode())
|
||||
{
|
||||
case 403: //Forbidden
|
||||
if(message.toString().contains("Mountpoint in use"))
|
||||
{
|
||||
mLog.error("Stream [" + getStreamName() + "] - unable to connect - mountpoint in use");
|
||||
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
|
||||
disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
disconnect();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("HTTP protocol decoder error - message:\n\n" + message);
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -335,4 +414,97 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for parsing HTTP response messages from Icecast 2.4.2+
|
||||
*/
|
||||
public class HttpDumpMessage
|
||||
{
|
||||
private String mHexDump;
|
||||
private String mMessage;
|
||||
private int mHttpResponseCode = -1;
|
||||
|
||||
public HttpDumpMessage(String hexDump)
|
||||
{
|
||||
mHexDump = hexDump;
|
||||
|
||||
String[] split = mHexDump.split(" ");
|
||||
byte[] bytes = new byte[split.length];
|
||||
|
||||
int pointer = 0;
|
||||
for(String a : split)
|
||||
{
|
||||
try
|
||||
{
|
||||
int value = Integer.parseInt(a, 16);
|
||||
bytes[pointer++] = (byte) (value & 0xFF);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
pointer++;
|
||||
}
|
||||
}
|
||||
|
||||
mMessage = new String(bytes);
|
||||
|
||||
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||
Matcher m = pattern.matcher(mMessage);
|
||||
|
||||
if(m.find())
|
||||
{
|
||||
try
|
||||
{
|
||||
mHttpResponseCode = Integer.parseInt(m.group(1));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Unable to parse HTTP response code that was matched from message: " + m.group(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if an HTTP response code was parsed from the message
|
||||
* @return true if it was parsed.
|
||||
*/
|
||||
public boolean hasHttpResponseCode()
|
||||
{
|
||||
return mHttpResponseCode != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response code parsed from the hexdump message
|
||||
* @return value.
|
||||
*/
|
||||
public int getHttpResponseCode()
|
||||
{
|
||||
return mHttpResponseCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String text = "HTTP/1.0 403 Forbidden";
|
||||
|
||||
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||
|
||||
Matcher m = pattern.matcher(text);
|
||||
|
||||
if(m.find())
|
||||
{
|
||||
mLog.info("Matching Group Count: " + m.groupCount());
|
||||
mLog.info("Match 0: " + m.group(0));
|
||||
mLog.info("Match 1: " + m.group(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("No Match");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public class IcecastHTTPConfiguration extends IcecastConfiguration
|
||||
copy.setName(getName());
|
||||
copy.setHost(getHost());
|
||||
copy.setPort(getPort());
|
||||
copy.setInline(getInline());
|
||||
copy.setPassword(getPassword());
|
||||
copy.setDelay(getDelay());
|
||||
copy.setEnabled(false);
|
||||
|
||||
@@ -33,6 +33,7 @@ public enum IcecastHeader
|
||||
PUBLIC("ice-public"),
|
||||
URL("ice-url"),
|
||||
USER_AGENT("User-Agent"),
|
||||
METAINT("icy-metaint"),
|
||||
|
||||
UNKNOWN("unknown");
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.icecast;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
public class IcecastMetadata
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastMetadata.class);
|
||||
|
||||
/**
|
||||
* Creates the title for a metadata update
|
||||
*/
|
||||
public static String getTitle(IdentifierCollection identifierCollection, AliasModel AliasModel)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(identifierCollection != null)
|
||||
{
|
||||
AliasList aliasList = AliasModel.getAliasList(identifierCollection);
|
||||
|
||||
Identifier to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
to = identifierCollection.getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
||||
}
|
||||
|
||||
if(to == null)
|
||||
{
|
||||
List<Identifier> toIdentifiers = identifierCollection.getIdentifiers(Role.TO);
|
||||
if(!toIdentifiers.isEmpty())
|
||||
{
|
||||
to = toIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
sb.append("TO:").append(to);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(to);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("TO:UNKNOWN");
|
||||
}
|
||||
|
||||
Identifier from = identifierCollection.getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
||||
|
||||
if(from == null)
|
||||
{
|
||||
List<Identifier> fromIdentifiers = identifierCollection.getIdentifiers(Role.FROM);
|
||||
|
||||
if(!fromIdentifiers.isEmpty())
|
||||
{
|
||||
from = fromIdentifiers.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(from != null)
|
||||
{
|
||||
sb.append(" FROM:").append(from);
|
||||
|
||||
List<Alias> aliases = aliasList.getAliases(from);
|
||||
|
||||
if(aliases != null && !aliases.isEmpty())
|
||||
{
|
||||
sb.append(" ").append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" FROM:UNKNOWN");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Scanning...");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String formatInline(String title)
|
||||
{
|
||||
title = "StreamTitle='" + title + "';";
|
||||
int chunks = (int)Math.ceil((title.length() + 1) / 16.0d);
|
||||
int nulls = (chunks * 16) - title.length();
|
||||
char[] padding = new char[nulls];
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((char)chunks).append(title).append(padding);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,16 +14,26 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.future.ConnectFuture;
|
||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||
@@ -33,17 +43,10 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.UnresolvedAddressException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
||||
private final static String TERMINATOR = "\r\n";
|
||||
private final static String TERMINATOR = "\n";
|
||||
private final static String SEPARATOR = ":";
|
||||
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
|
||||
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
|
||||
@@ -66,20 +69,45 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
*
|
||||
* @param configuration for the Icecast stream
|
||||
*/
|
||||
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, AliasModel aliasModel)
|
||||
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration, aliasModel);
|
||||
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
|
||||
{
|
||||
mStreamingSession.write(audio);
|
||||
if(mInlineActive)
|
||||
{
|
||||
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
|
||||
if (mInlineRemaining == -1)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
}
|
||||
int audioOffset = 0;
|
||||
while(audioOffset < audio.length)
|
||||
{
|
||||
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
|
||||
mInlineRemaining -= chunk.length;
|
||||
audioOffset += chunk.length;
|
||||
mStreamingSession.write(chunk);
|
||||
if(mInlineRemaining == 0)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
mStreamingSession.write(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mStreamingSession.write(audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +195,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
@@ -185,7 +213,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
//Only set broadcast state to disconnected if it's not already in an error state, to prevent a restart. We
|
||||
//want to preserve the error state that got us here, so the user can see it.
|
||||
if(!getBroadcastState().isErrorState())
|
||||
{
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
}
|
||||
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -235,7 +269,18 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
.append(getConfiguration().getDescription()).append(TERMINATOR);
|
||||
}
|
||||
|
||||
sb.append(TERMINATOR).append(TERMINATOR);
|
||||
if(getConfiguration().hasInline())
|
||||
{
|
||||
mInlineActive = true;
|
||||
mInlineInterval = getConfiguration().getInlineInterval();
|
||||
sb.append(IcecastHeader.METAINT.getValue()).append(SEPARATOR)
|
||||
.append(String.valueOf(mInlineInterval))
|
||||
.append(TERMINATOR);
|
||||
}
|
||||
else
|
||||
{
|
||||
mInlineActive = false;
|
||||
}
|
||||
|
||||
session.write(sb.toString());
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public class IcecastTCPConfiguration extends IcecastConfiguration
|
||||
copy.setName(getName());
|
||||
copy.setHost(getHost());
|
||||
copy.setPort(getPort());
|
||||
copy.setInline(getInline());
|
||||
copy.setPassword(getPassword());
|
||||
copy.setDelay(getDelay());
|
||||
copy.setEnabled(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.icecast.codec;
|
||||
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,14 +14,17 @@
|
||||
*
|
||||
* 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.shoutcast.v1;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.apache.mina.core.RuntimeIoException;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
@@ -37,7 +40,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
@@ -62,9 +64,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
*
|
||||
* @param configuration for the Shoutcast stream
|
||||
*/
|
||||
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, AliasModel aliasModel)
|
||||
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration);
|
||||
super(configuration, inputAudioFormat, mp3Setting);
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
@@ -91,7 +94,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
|
||||
{
|
||||
@@ -179,7 +182,7 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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.shoutcast.v1;
|
||||
|
||||
@@ -53,7 +50,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||
@@ -138,7 +134,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
|
||||
if(updateRequest != null)
|
||||
{
|
||||
ThreadPool.SCHEDULED.schedule(new Runnable()
|
||||
ThreadPool.CACHED.submit(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@@ -176,7 +172,7 @@ public class ShoutcastV1BroadcastMetadataUpdater implements IBroadcastMetadataUp
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0l, TimeUnit.SECONDS);
|
||||
});
|
||||
}
|
||||
|
||||
//Fetch next metadata update to send
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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.shoutcast.v2;
|
||||
|
||||
@@ -43,6 +40,8 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageF
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageType;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMetadata;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxProtocolFactory;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
@@ -67,7 +66,6 @@ import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcaster implements IBroadcastMetadataUpdater
|
||||
@@ -95,9 +93,10 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
||||
*
|
||||
* @param configuration for the Shoutcast V2 stream
|
||||
*/
|
||||
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, AliasModel aliasModel)
|
||||
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(configuration);
|
||||
super(configuration, inputAudioFormat, mp3Setting);
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
||||
* Broadcasts the audio frame or sequence
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastAudio(byte[] audio)
|
||||
protected void broadcastAudio(byte[] audio, IdentifierCollection identifierCollection)
|
||||
{
|
||||
//Dispatch any queued metadata messages
|
||||
UltravoxMessage metadataMessage = mMetadataMessageQueue.poll();
|
||||
@@ -225,7 +224,7 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
|
||||
ThreadPool.CACHED.submit(runnable);
|
||||
}
|
||||
|
||||
return connected();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* 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.shoutcast.v2.ultravox;
|
||||
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* 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.shoutcast.v2.ultravox;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* 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.shoutcast.v2.ultravox;
|
||||
|
||||
public class UltravoxMessageFactory
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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.codec.mbe;
|
||||
@@ -32,11 +29,6 @@ import io.github.dsheirer.message.IMessageListener;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import jmbe.iface.IAudioCodec;
|
||||
import jmbe.iface.IAudioCodecLibrary;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@@ -44,15 +36,20 @@ import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
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,
|
||||
ISquelchStateListener
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
||||
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 UserPreferences mUserPreferences;
|
||||
private final UserPreferences mUserPreferences;
|
||||
private static Class sLoadedJmbeAudioConverterClass;
|
||||
|
||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||
{
|
||||
@@ -70,7 +67,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
mAudioCodec = null;
|
||||
}
|
||||
|
||||
protected IAudioCodec getAudioCodec()
|
||||
public IAudioCodec getAudioCodec()
|
||||
{
|
||||
return mAudioCodec;
|
||||
}
|
||||
@@ -110,38 +107,67 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
protected abstract String getCodecName();
|
||||
|
||||
/**
|
||||
* Loads audio frame processing chain. Constructs an imbe targetdataline
|
||||
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
|
||||
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
|
||||
* Loads JMBE audio converter library class and then instantiates new converter instances from the loaded class.
|
||||
*/
|
||||
protected void loadConverter()
|
||||
{
|
||||
IAudioCodec audioConverter = null;
|
||||
|
||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||
|
||||
if(path != null)
|
||||
if(sLoadedJmbeAudioConverterClass == null)
|
||||
{
|
||||
try
|
||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||
|
||||
if(path != null)
|
||||
{
|
||||
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()},
|
||||
this.getClass().getClassLoader());
|
||||
|
||||
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
||||
|
||||
Object instance = classToLoad.getDeclaredConstructor().newInstance();
|
||||
|
||||
if(instance instanceof IAudioCodecLibrary)
|
||||
try
|
||||
{
|
||||
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 &&
|
||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||
{
|
||||
audioConverter = library.getAudioConverter(getCodecName());
|
||||
|
||||
@@ -169,38 +195,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)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
@@ -225,6 +219,14 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
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
|
||||
{
|
||||
@@ -235,13 +237,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
}
|
||||
}
|
||||
|
||||
if(audioConverter != null)
|
||||
{
|
||||
mAudioCodec = audioConverter;
|
||||
}
|
||||
else
|
||||
{
|
||||
mAudioCodec = null;
|
||||
}
|
||||
mAudioCodec = audioConverter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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.codec.mbe;
|
||||
@@ -27,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.encryption.EncryptionKey;
|
||||
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||
|
||||
@@ -132,6 +130,18 @@ public class MBECallSequence
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the from radio identifier
|
||||
* @param identifier for from entity
|
||||
*/
|
||||
public void setFromIdentifier(Identifier identifier)
|
||||
{
|
||||
if(identifier != null)
|
||||
{
|
||||
setFromIdentifier(identifier.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio identifier that originated the call
|
||||
*
|
||||
@@ -156,6 +166,18 @@ public class MBECallSequence
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TO identifier
|
||||
* @param identifier for the TO entity
|
||||
*/
|
||||
public void setToIdentifier(Identifier identifier)
|
||||
{
|
||||
if(identifier != null)
|
||||
{
|
||||
setToIdentifier(identifier.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio identifier that received the call
|
||||
*
|
||||
|
||||
@@ -1,27 +1,46 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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.codec.mbe;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder;
|
||||
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import jmbe.iface.IAudioCodec;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.DirectoryFileFilter;
|
||||
import org.apache.commons.io.filefilter.SuffixFileFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -32,79 +51,130 @@ public class MBECallSequenceConverter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
||||
|
||||
// public static void convert(Path input, Path output) throws IOException
|
||||
// {
|
||||
// InputStream inputStream = Files.newInputStream(input);
|
||||
// ObjectMapper mapper = new ObjectMapper();
|
||||
// MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
||||
// convert(sequence, output);
|
||||
// }
|
||||
//
|
||||
// public static void convert(MBECallSequence callSequence, Path outputPath)
|
||||
// {
|
||||
// if(callSequence == null || callSequence.isEncrypted())
|
||||
// {
|
||||
// throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
||||
// }
|
||||
//
|
||||
// if(callSequence != null && !callSequence.isEncrypted())
|
||||
// {
|
||||
// ThumbDv.AudioProtocol protocol = ThumbDv.AudioProtocol.P25_PHASE2;
|
||||
//
|
||||
// AudioPacketWaveRecorder recorder = new AudioPacketWaveRecorder(outputPath);
|
||||
// recorder.start();
|
||||
//
|
||||
// long delayMillis = 0;
|
||||
//
|
||||
// try(ThumbDv thumbDv = new ThumbDv(protocol, recorder))
|
||||
// {
|
||||
// thumbDv.start();
|
||||
// for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
||||
// {
|
||||
// mLog.debug("Frame [" + voiceFrame.getFrame() + "] + Hex [" + AmbeResponse.toHex(voiceFrame.getFrameBytes()) + "]");
|
||||
// thumbDv.decode(voiceFrame.getFrameBytes());
|
||||
// delayMillis += 30;
|
||||
// }
|
||||
//
|
||||
// if(delayMillis > 0)
|
||||
// {
|
||||
// delayMillis += 1000;
|
||||
// try
|
||||
// {
|
||||
// Thread.sleep(delayMillis);
|
||||
// }
|
||||
// catch(InterruptedException ie)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch(IOException ioe)
|
||||
// {
|
||||
// mLog.error("Error", ioe);
|
||||
// }
|
||||
//
|
||||
// recorder.stop(Paths.get(outputPath.toString().replace(".tmp", ".wav")), new WaveMetadata());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void main(String[] args)
|
||||
// {
|
||||
// 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);
|
||||
// Path output = Paths.get(mbe.replace(".mbe", ".tmp"));
|
||||
//
|
||||
// mLog.info("Converting: " + mbe);
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// MBECallSequenceConverter.convert(input, output);
|
||||
// }
|
||||
// catch(IOException ioe)
|
||||
// {
|
||||
// mLog.error("Error", ioe);
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* Converts the input MBE file to PCM audio and writes to the output wave file.
|
||||
* @param input path to the MBE file.
|
||||
* @param output path to write the WAVE recording.
|
||||
* @throws IOException if there is an error.
|
||||
*/
|
||||
public static void convert(Path input, Path output) throws IOException
|
||||
{
|
||||
InputStream inputStream = Files.newInputStream(input);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
||||
convert(sequence, output);
|
||||
}
|
||||
|
||||
public static void convert(MBECallSequence callSequence, Path outputPath)
|
||||
{
|
||||
if(callSequence == null || callSequence.isEncrypted())
|
||||
{
|
||||
throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
||||
}
|
||||
|
||||
if(callSequence != null)
|
||||
{
|
||||
if(callSequence.getProtocol().equals(P25P1CallSequenceRecorder.PROTOCOL))
|
||||
{
|
||||
P25P1AudioModule audioModule = new P25P1AudioModule(new UserPreferences(), new AliasList("mbe generator"));
|
||||
audioModule.setRecordAudio(true);
|
||||
audioModule.start();
|
||||
|
||||
if(callSequence.getFromIdentifier() != null)
|
||||
{
|
||||
int from = 0;
|
||||
|
||||
try
|
||||
{
|
||||
from = Integer.parseInt(callSequence.getFromIdentifier());
|
||||
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25RadioIdentifier.createFrom(from),
|
||||
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
if(callSequence.getToIdentifier() != null)
|
||||
{
|
||||
int to = 0;
|
||||
|
||||
try
|
||||
{
|
||||
to = Integer.parseInt(callSequence.getToIdentifier());
|
||||
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25Talkgroup.create(to),
|
||||
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
IAudioCodec codec = audioModule.getAudioCodec();
|
||||
|
||||
for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
||||
{
|
||||
byte[] frameBytes = voiceFrame.getFrameBytes();
|
||||
float[] audio = codec.getAudio(frameBytes);
|
||||
audioModule.addAudio(audio);
|
||||
}
|
||||
|
||||
AudioSegment audioSegment = audioModule.getAudioSegment();
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error writing audio segment, ioe");
|
||||
}
|
||||
|
||||
audioModule.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
boolean all = true;
|
||||
|
||||
String path = "/home/denny/SDRTrunk/recordings";
|
||||
Path input = Paths.get(path);
|
||||
|
||||
if(all)
|
||||
{
|
||||
Collection<File> mbeFiles = FileUtils.listFiles(input.toFile(), new SuffixFileFilter(".mbe"), DirectoryFileFilter.DIRECTORY);
|
||||
|
||||
for(File inputFile: mbeFiles)
|
||||
{
|
||||
Path output = Paths.get(inputFile.getAbsolutePath().replace(".mbe", ".wav"));
|
||||
mLog.info("Converting: " + inputFile);
|
||||
try
|
||||
{
|
||||
MBECallSequenceConverter.convert(inputFile.toPath(), output);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Path output = Paths.get(path.replace(".mbe", ".wav"));
|
||||
mLog.info("Converting: " + path);
|
||||
|
||||
try
|
||||
{
|
||||
MBECallSequenceConverter.convert(input, output);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
133
src/main/java/io/github/dsheirer/audio/convert/AudioFrames.java
Normal file
133
src/main/java/io/github/dsheirer/audio/convert/AudioFrames.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public abstract class AudioFrames
|
||||
{
|
||||
protected final int audioDuration;
|
||||
protected final List<byte[]> audioFrames;
|
||||
protected int mCurrentFrame = -1;
|
||||
|
||||
/**
|
||||
* Create new AudioFrame
|
||||
* @param audioDuration total duration in milliseconds
|
||||
* @param audioFrames series of audio frames, split on frame boundaries
|
||||
*/
|
||||
public AudioFrames(int audioDuration, List<byte[]> audioFrames)
|
||||
{
|
||||
this.audioDuration = audioDuration;
|
||||
this.audioFrames = audioFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total duration of frames
|
||||
* @return total duration in milliseconds
|
||||
*/
|
||||
public int getDuration()
|
||||
{
|
||||
return audioDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all frames
|
||||
* @return list of byte arrays, with each element representing a single frame of audio
|
||||
*/
|
||||
public List<byte[]> getFrames()
|
||||
{
|
||||
return audioFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current frame
|
||||
* @return byte array with the current frame of audio
|
||||
*/
|
||||
public byte[] getCurrentFrame()
|
||||
{
|
||||
return audioFrames.get(mCurrentFrame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current frame duration
|
||||
* @return frame duration in milliseconds
|
||||
*/
|
||||
public abstract int getCurrentFrameDuration();
|
||||
|
||||
/**
|
||||
* Indicates if there is a frame before the current one
|
||||
* @return true if the frame exists, otherwise false
|
||||
*/
|
||||
public boolean hasPrevFrame()
|
||||
{
|
||||
int prevFrame = mCurrentFrame - 1;
|
||||
return prevFrame >= 0 && prevFrame < audioFrames.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there is a frame after the current one
|
||||
* @return true if the frame exists, otherwise false
|
||||
*/
|
||||
public boolean hasNextFrame()
|
||||
{
|
||||
int nextFrame = mCurrentFrame + 1;
|
||||
return nextFrame >= 0 && nextFrame < audioFrames.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to the previous frame
|
||||
*/
|
||||
public void prevFrame()
|
||||
{
|
||||
mCurrentFrame -= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to the next frame
|
||||
*/
|
||||
public void nextFrame()
|
||||
{
|
||||
mCurrentFrame += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart from beginning
|
||||
* Must call nextFrame() to get first frame
|
||||
*/
|
||||
public void restart()
|
||||
{
|
||||
mCurrentFrame = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek by the specified time
|
||||
* @param duration_ms time in milliseconds, positive or negative
|
||||
*/
|
||||
public void seek(int duration_ms)
|
||||
{
|
||||
int step = duration_ms < 0 ? -1 : 1;
|
||||
int actual_ms = 0;
|
||||
while(FastMath.abs(actual_ms) < FastMath.abs(duration_ms))
|
||||
{
|
||||
mCurrentFrame += step;
|
||||
actual_ms += getCurrentFrameDuration();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
/**
|
||||
* MP3 Channel Mode
|
||||
*
|
||||
* see: http://www.mp3-tech.org/programmer/frame_header.html
|
||||
*/
|
||||
public enum ChannelMode
|
||||
{
|
||||
STEREO("STEREO"),
|
||||
JOINT_STEREO("JOINT STEREO"),
|
||||
DUAL_CHANNEL("DUAL CHANNEL"),
|
||||
MONO("MONO");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
ChannelMode(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public static ChannelMode fromValue(int value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case 0:
|
||||
return STEREO;
|
||||
case 1:
|
||||
return JOINT_STEREO;
|
||||
case 2:
|
||||
return DUAL_CHANNEL;
|
||||
case 3:
|
||||
default:
|
||||
return MONO;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
* 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.convert;
|
||||
|
||||
import java.util.List;
|
||||
@@ -25,11 +25,11 @@ public interface IAudioConverter
|
||||
/**
|
||||
* Converts the PCM audio packets to converted audio format. May produce partial audio frame data.
|
||||
*/
|
||||
public byte[] convert(List<float[]> audioBuffers);
|
||||
List<byte[]> convert(List<float[]> audioBuffers);
|
||||
|
||||
/**
|
||||
* Finalizes audio conversion by fully converting any partial frames left in the buffer and returning the
|
||||
* remaining bytes to produce a full frame.
|
||||
*/
|
||||
public byte[] flush();
|
||||
List<byte[]> flush();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,11 +14,13 @@
|
||||
*
|
||||
* 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.convert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISilenceGenerator
|
||||
{
|
||||
byte[] generate(long duration);
|
||||
AudioFrames generate(long duration);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
/**
|
||||
* Enumeration of MP3 input audio formats for mono audio.
|
||||
*/
|
||||
public enum InputAudioFormat
|
||||
{
|
||||
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
|
||||
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz (default)"),
|
||||
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz"),
|
||||
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
|
||||
|
||||
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
|
||||
SR_32_16000(AudioFormats.PCM_SIGNED_16000_HZ_32_BIT_MONO, "32-Bit 16000 Hz"),
|
||||
SR_32_22050(AudioFormats.PCM_SIGNED_22050_HZ_32_BIT_MONO, "32-Bit 22050 Hz"),
|
||||
SR_32_44100(AudioFormats.PCM_SIGNED_44100_HZ_32_BIT_MONO, "32-Bit 44100 Hz");
|
||||
|
||||
private AudioFormat mAudioFormat;
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Set that includes sample rates of 8 or 16 kHz
|
||||
*/
|
||||
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16 = EnumSet.of(SR_8000, SR_32_8000, SR_16000, SR_32_16000);
|
||||
|
||||
/**
|
||||
* Set that includes sample rates of 8, 16, or 22 kHz
|
||||
*/
|
||||
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16_22 = EnumSet.of(SR_8000, SR_32_8000, SR_16000,
|
||||
SR_32_16000, SR_22050, SR_32_22050);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param audioFormat for the specified sample rate
|
||||
*/
|
||||
InputAudioFormat(AudioFormat audioFormat, String label)
|
||||
{
|
||||
mAudioFormat = audioFormat;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default sample rate
|
||||
*/
|
||||
public static InputAudioFormat getDefault()
|
||||
{
|
||||
return SR_16000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio format for the specified sample rate entry
|
||||
*/
|
||||
public AudioFormat getAudioFormat()
|
||||
{
|
||||
return mAudioFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample rate for the format
|
||||
*/
|
||||
public double getSampleRate()
|
||||
{
|
||||
return getAudioFormat().getSampleRate();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.resample.RealResampler;
|
||||
import net.sourceforge.lame.lowlevel.LameEncoder;
|
||||
import net.sourceforge.lame.mp3.MPEGMode;
|
||||
|
||||
/**
|
||||
* Factory class to create and configure LAME MP3 encoder
|
||||
*/
|
||||
public class LameFactory
|
||||
{
|
||||
/**
|
||||
* Creates a LAME MP3 encoder using the specified input sample rate and MP3 settings
|
||||
* @param input sample rate
|
||||
* @param setting to apply to the encoder
|
||||
* @return lame encoder
|
||||
*/
|
||||
public static LameEncoder getLameEncoder(InputAudioFormat input, MP3Setting setting)
|
||||
{
|
||||
return switch(setting)
|
||||
{
|
||||
case CBR_16 -> new LameEncoder(input.getAudioFormat(), 16, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, false);
|
||||
case CBR_32 -> new LameEncoder(input.getAudioFormat(), 32, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, false);
|
||||
case ABR_56 -> new LameEncoder(input.getAudioFormat(), 56, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, true);
|
||||
case VBR_5 -> new LameEncoder(input.getAudioFormat(), LameEncoder.DEFAULT_BITRATE, MPEGMode.MONO, 5, true);
|
||||
case VBR_7 -> new LameEncoder(input.getAudioFormat(), LameEncoder.DEFAULT_BITRATE, MPEGMode.MONO, 7, true);
|
||||
default -> throw new IllegalArgumentException("Unrecognized MP3 setting: " + setting);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resampler to resample from 8 kHz default audio sample rate to the specified sample rate.
|
||||
* @param sampleRate to resample to
|
||||
* @return resampler.
|
||||
*/
|
||||
public static RealResampler getResampler(InputAudioFormat sampleRate)
|
||||
{
|
||||
return switch(sampleRate)
|
||||
{
|
||||
case SR_16000, SR_32_16000 -> new RealResampler(8000, 16000, 4096, 512);
|
||||
case SR_22050, SR_32_22050 -> new RealResampler(8000, 22050, 4096, 512);
|
||||
case SR_44100, SR_32_44100 -> new RealResampler(8000, 44100, 4096, 512);
|
||||
default -> throw new IllegalArgumentException("Unrecognized sample rate for resampling: " + sampleRate);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,113 +14,151 @@
|
||||
*
|
||||
* 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.convert;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import io.github.dsheirer.audio.AudioUtils;
|
||||
import io.github.dsheirer.dsp.filter.resample.RealResampler;
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.sourceforge.lame.lowlevel.LameEncoder;
|
||||
import net.sourceforge.lame.mp3.Lame;
|
||||
import net.sourceforge.lame.mp3.MPEGMode;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Converts PCM audio packets to Mono, MP3 encoded audio
|
||||
*/
|
||||
public class MP3AudioConverter implements IAudioConverter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( MP3AudioConverter.class );
|
||||
public static final int AUDIO_QUALITY = Lame.QUALITY_LOW;
|
||||
private LameEncoder mEncoder;
|
||||
private ByteArrayOutputStream mMP3Stream = new ByteArrayOutputStream();
|
||||
private byte[] mMP3Buffer;
|
||||
private RealResampler mResampler;
|
||||
private ByteArrayOutputStream mMP3EncodedFramesStream = new ByteArrayOutputStream();
|
||||
private byte[] mOutputFramesBuffer;
|
||||
private InputAudioFormat mInputAudioFormat;
|
||||
|
||||
private boolean mNormalizeAudio;
|
||||
|
||||
/**
|
||||
* Converts PCM 8kHz 16-bit Little Endian audio packets to Mono, Low Quality MP3 compressed audio.
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param bitRate for converted MP3 audio
|
||||
* @param variableBitRate (VBR) true or false for constant bit rate (CBR)
|
||||
* @param inputAudioFormat for the desired input sample rate and bit size (resampled from default 8 kHz as needed)
|
||||
* @param setting to configure the LAME encoder
|
||||
* @param normalizeAudio to normalize the audio gain prior to encoding
|
||||
*/
|
||||
public MP3AudioConverter(int bitRate, boolean variableBitRate)
|
||||
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
|
||||
{
|
||||
mEncoder = new LameEncoder(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
|
||||
bitRate, MPEGMode.MONO, AUDIO_QUALITY, variableBitRate);
|
||||
mInputAudioFormat = inputAudioFormat;
|
||||
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
||||
mNormalizeAudio = normalizeAudio;
|
||||
|
||||
mMP3Buffer = new byte[mEncoder.getPCMBufferSize()];
|
||||
//Ensure input audio sample rate is supported for the MP3 setting and update as necessary - should never happen.
|
||||
if((mInputAudioFormat.getSampleRate() - mEncoder.getEffectiveSampleRate()) > 1.0)
|
||||
{
|
||||
mInputAudioFormat = InputAudioFormat.getDefault();
|
||||
mLog.warn("MP3 setting [" + setting + "] does not support requested input audio sample rate [" +
|
||||
inputAudioFormat + "] - using default sample rate [" + mInputAudioFormat + "]");
|
||||
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
||||
}
|
||||
|
||||
//Resampling is only required if desired input sample rate is not system default of 8kHz
|
||||
if(mInputAudioFormat != InputAudioFormat.SR_8000 && mInputAudioFormat != InputAudioFormat.SR_32_8000)
|
||||
{
|
||||
mResampler = LameFactory.getResampler(inputAudioFormat);
|
||||
}
|
||||
|
||||
mOutputFramesBuffer = new byte[mEncoder.getPCMBufferSize()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the list of PCM audio packets to MP3 encoded.
|
||||
* @param audioPackets of PCM audio sampled at 8 kHz
|
||||
* @return encoded MP3 audio
|
||||
*/
|
||||
public List<byte[]> convert(List<float[]> audioPackets)
|
||||
{
|
||||
List<byte[]> converted = new ArrayList<>();
|
||||
|
||||
if(mNormalizeAudio)
|
||||
{
|
||||
audioPackets = AudioUtils.normalize(audioPackets);
|
||||
}
|
||||
|
||||
if(mResampler != null)
|
||||
{
|
||||
audioPackets = mResampler.resample(audioPackets);
|
||||
}
|
||||
|
||||
for(int x = 0; x < audioPackets.size(); x++)
|
||||
{
|
||||
byte[] bytesToEncode = null;
|
||||
|
||||
if(mInputAudioFormat.getAudioFormat().getSampleSizeInBits() == 16)
|
||||
{
|
||||
bytesToEncode = ConversionUtils.convertToSigned16BitSamples(audioPackets.get(x)).array();
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesToEncode = ConversionUtils.convertToSigned32BitSamples(audioPackets.get(x)).array();
|
||||
}
|
||||
|
||||
int bytesToEncodePointer = 0;
|
||||
|
||||
int inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length);
|
||||
int outputChunkSize = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while(bytesToEncodePointer < bytesToEncode.length)
|
||||
{
|
||||
outputChunkSize = mEncoder.encodeBuffer(bytesToEncode, bytesToEncodePointer, inputChunkSize, mOutputFramesBuffer);
|
||||
bytesToEncodePointer += inputChunkSize;
|
||||
inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length - bytesToEncodePointer);
|
||||
|
||||
if(outputChunkSize > 0)
|
||||
{
|
||||
converted.add(Arrays.copyOf(mOutputFramesBuffer, outputChunkSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
int finalChunkSize = mEncoder.encodeFinish(mOutputFramesBuffer);
|
||||
|
||||
if(finalChunkSize > 0)
|
||||
{
|
||||
converted.add(Arrays.copyOf(mOutputFramesBuffer, finalChunkSize));
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] convert(List<float[]> audioPackets)
|
||||
{
|
||||
mMP3Stream.reset();
|
||||
|
||||
byte[] pcmBytes = AudioUtils.convertTo16BitSamples(audioPackets);
|
||||
|
||||
int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
|
||||
|
||||
int mp3BufferSize = 0;
|
||||
|
||||
int pcmBytesPosition = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
|
||||
{
|
||||
pcmBytesPosition += pcmBufferSize;
|
||||
pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
|
||||
mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
|
||||
}
|
||||
|
||||
return mMP3Stream.toByteArray();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] convertAudio(List<float[]> audioBuffers)
|
||||
{
|
||||
mMP3Stream.reset();
|
||||
|
||||
byte[] pcmBytes = AudioUtils.convert(audioBuffers);
|
||||
|
||||
int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
|
||||
|
||||
int mp3BufferSize = 0;
|
||||
|
||||
int pcmBytesPosition = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
|
||||
{
|
||||
pcmBytesPosition += pcmBufferSize;
|
||||
pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
|
||||
mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
|
||||
}
|
||||
|
||||
return mMP3Stream.toByteArray();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("There was an error converting audio to MP3: " + e.getMessage());
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] flush()
|
||||
public List<byte[]> flush()
|
||||
{
|
||||
byte[] lastPartialFrame = new byte[mEncoder.getMP3BufferSize()];
|
||||
|
||||
int length = mEncoder.encodeFinish(lastPartialFrame);
|
||||
|
||||
return Arrays.copyOf(lastPartialFrame, length);
|
||||
byte[] frame = Arrays.copyOf(lastPartialFrame, length);
|
||||
|
||||
if(frame.length == 0)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Collections.singletonList(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MP3AudioFrames extends AudioFrames
|
||||
{
|
||||
|
||||
/**
|
||||
* Create new MP3AudioFrame
|
||||
* @param audioDuration total duration in milliseconds
|
||||
* @param audioFrames series of audio frames, split on frame boundaries
|
||||
*/
|
||||
public MP3AudioFrames(int audioDuration, List<byte[]> audioFrames)
|
||||
{
|
||||
super(audioDuration, audioFrames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current frame duration
|
||||
* @return frame duration in milliseconds
|
||||
*/
|
||||
public int getCurrentFrameDuration()
|
||||
{
|
||||
return MP3Header.getFrameDuration(getCurrentFrame(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
public class MP3BitRate
|
||||
{
|
||||
private final static int[] MPEG_1_L3 = new int[]{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320};
|
||||
private final static int[] MPEG_2_L3 = new int[]{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};
|
||||
private final static int[] MPEG_2_5_L3 = new int[]{0, 8, 16, 24, 32, 40, 48, 56, 64};
|
||||
|
||||
public static int getLayer3BitRate(MPEGVersion version, int value)
|
||||
{
|
||||
switch(version)
|
||||
{
|
||||
case V_1:
|
||||
if(0 <= value && value < MPEG_1_L3.length)
|
||||
{
|
||||
return MPEG_1_L3[value];
|
||||
}
|
||||
break;
|
||||
case V_2:
|
||||
if(0 <= value && value < MPEG_2_L3.length)
|
||||
{
|
||||
return MPEG_2_L3[value];
|
||||
}
|
||||
break;
|
||||
case V_2_5:
|
||||
if(0 <= value && value < MPEG_2_5_L3.length)
|
||||
{
|
||||
return MPEG_2_5_L3[value];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,76 +14,58 @@
|
||||
*
|
||||
* 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.convert;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MP3FrameInspector
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MP3FrameInspector.class);
|
||||
|
||||
public static void inspect(byte[] frames)
|
||||
public static void inspect(List<byte[]> frames)
|
||||
{
|
||||
if(frames == null || frames.length == 0)
|
||||
if(frames.isEmpty())
|
||||
{
|
||||
mLog.info("No Frame Data!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(frames.length % 144 != 0)
|
||||
for(byte[] frame: frames)
|
||||
{
|
||||
log("Frame byte array not multiple of 144 byte frame size - length:" + frames.length, frames);
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int frameCounter = 0;
|
||||
|
||||
//Check for erroneous frame sync
|
||||
for(int x = 0; x < frames.length - 1; x++)
|
||||
{
|
||||
if(frames[x] == (byte)0xFF && (frames[x + 1] & 0xE0) == 0xE0 && (x % 144) != 0)
|
||||
for(int x = 0; x < frame.length; x++)
|
||||
{
|
||||
log("Bad frame sync detected at byte " + x + " in frame " + (x / 144), frames);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(MP3Header.isValid(frame, x))
|
||||
{
|
||||
sb.append("\n").append(++frameCounter).append(": ");
|
||||
sb.append(MP3Header.inspect(frame, x)).append(" - ");
|
||||
}
|
||||
|
||||
//Check MP3 headers
|
||||
|
||||
// log("Frames ...", frames);
|
||||
}
|
||||
|
||||
private static void log(String message, byte[] frames)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(message).append("\n");
|
||||
|
||||
int frameCounter = 0;
|
||||
sb.append(frameCounter++).append(" ");
|
||||
|
||||
for(int x = 0; x < frames.length; x++)
|
||||
{
|
||||
if(x > 0 && x % 144 == 0)
|
||||
{
|
||||
sb.append("\n");
|
||||
sb.append(frameCounter++).append(" ");
|
||||
sb.append(String.format("%02X ", frame[x]));
|
||||
}
|
||||
|
||||
sb.append(String.format("%02X ", frames[x]));
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
|
||||
/* TODO: Rework for MP3SilenceGenerator returning IAudioFrames
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.info("Starting ...");
|
||||
MP3SilenceGenerator gen = new MP3SilenceGenerator();
|
||||
MP3SilenceGenerator gen = new MP3SilenceGenerator(InputAudioFormat.SR_8000, MP3Setting.getDefault());
|
||||
|
||||
byte[] audio = gen.generate(173);
|
||||
List<byte[]> audio = gen.generate(173);
|
||||
|
||||
inspect(audio);
|
||||
|
||||
mLog.info("Finished");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class MP3FrameTools {
|
||||
|
||||
private MP3FrameTools() {}
|
||||
|
||||
/**
|
||||
* Split MP3 audio at frame boundaries
|
||||
* @param input byte array of audio
|
||||
* @return list of byte arrays containing audio frames
|
||||
*/
|
||||
public static MP3AudioFrames split(byte[] input)
|
||||
{
|
||||
List<byte[]> mp3Frames = new ArrayList<>();
|
||||
int audioDuration = 0;
|
||||
|
||||
int offset = 0;
|
||||
while(offset < input.length)
|
||||
{
|
||||
if(MP3Header.hasSync(input, offset))
|
||||
{
|
||||
int framelen = MP3Header.getFrameLength(input, offset);
|
||||
mp3Frames.add(Arrays.copyOfRange(input, offset, offset + FastMath.min(framelen, input.length - offset)));
|
||||
audioDuration += MP3Header.getFrameDuration(input, offset);
|
||||
offset += framelen;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new MP3AudioFrames(audioDuration, mp3Frames);
|
||||
}
|
||||
|
||||
}
|
||||
184
src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
Normal file
184
src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.audio.convert.MPEGLayer;
|
||||
import io.github.dsheirer.audio.convert.MPEGVersion;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* Utility class for inspecting/parsing MP3 frames
|
||||
*/
|
||||
public class MP3Header
|
||||
{
|
||||
private MP3Header() {}
|
||||
|
||||
/**
|
||||
* Indicates if the byte array at the offset has a valid MP3 frame sync and is at least 4 bytes long
|
||||
* @param frame array of bytes
|
||||
* @param offset into the array
|
||||
* @return true if the first two offset bytes contain a frame sync and length from offset is at least 4
|
||||
*/
|
||||
public static boolean isValid(byte[] frame, int offset)
|
||||
{
|
||||
return frame.length >= (offset + 4) && hasSync(frame, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the byte array at the offset has a valid MP3 frame sync
|
||||
* @param frame array of bytes
|
||||
* @param offset into the array
|
||||
* @return true if the first two offset bytes contain a frame sync
|
||||
*/
|
||||
public static boolean hasSync(byte[] frame, int offset)
|
||||
{
|
||||
return (frame[offset] & 0xFF) == 0xFF && (frame[offset + 1] & 0xE0) == 0xE0;
|
||||
}
|
||||
|
||||
/**
|
||||
* MPEG version for the frame
|
||||
*/
|
||||
public static MPEGVersion getMPEGVersion(byte[] frame, int offset)
|
||||
{
|
||||
return MPEGVersion.fromValue((frame[offset + 1] & 0x18) >> 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* MPEG layer for the frame. Normally layer 3 for audio.
|
||||
*/
|
||||
public static MPEGLayer getMPEGLayer(byte[] frame, int offset)
|
||||
{
|
||||
return MPEGLayer.fromValue((frame[offset + 1] & 0x6) >> 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* MP3 bit rate
|
||||
*/
|
||||
public static int getBitRate(byte[] frame, int offset)
|
||||
{
|
||||
return MP3BitRate.getLayer3BitRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0xF0) >> 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input audio sample rate.
|
||||
*/
|
||||
public static int getSampleRate(byte[] frame, int offset)
|
||||
{
|
||||
return MP3SampleRate.getSampleRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0x0C) >> 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel mode
|
||||
*/
|
||||
public static ChannelMode getChannelMode(byte[] frame, int offset)
|
||||
{
|
||||
return ChannelMode.fromValue((frame[offset + 3] & 0xC0) >> 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the frame has padding
|
||||
* @param frame
|
||||
* @param offset
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean isPadded(byte[] frame, int offset)
|
||||
{
|
||||
return (frame[offset + 2] & 0x02) == 0x02;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes in the frame
|
||||
* @param frame
|
||||
* @param offset
|
||||
* @return frame length in bytes
|
||||
*/
|
||||
public static int getFrameLength(byte[] frame, int offset)
|
||||
{
|
||||
return (getFrameSamples(frame, offset) / 8) * (getBitRate(frame, offset) * 1000) / getSampleRate(frame, offset) + (isPadded(frame, offset) ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of samples in the frame
|
||||
* @param frame
|
||||
* @param offset
|
||||
* @return length of frame in samples
|
||||
*/
|
||||
public static int getFrameSamples(byte[] frame, int offset)
|
||||
{
|
||||
switch(getMPEGVersion(frame, offset))
|
||||
{
|
||||
case V_1:
|
||||
switch(getMPEGLayer(frame, offset))
|
||||
{
|
||||
case LAYER1: return 384;
|
||||
case LAYER2: return 1152;
|
||||
case LAYER3: return 1152;
|
||||
default:
|
||||
}
|
||||
case V_2:
|
||||
case V_2_5:
|
||||
switch(getMPEGLayer(frame, offset))
|
||||
{
|
||||
case LAYER1: return 384;
|
||||
case LAYER2: return 1152;
|
||||
case LAYER3: return 576;
|
||||
default:
|
||||
}
|
||||
default:
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frame duration in milliseconds
|
||||
* @param frame
|
||||
* @param offset
|
||||
* @return duration in milliseconds
|
||||
*/
|
||||
public static int getFrameDuration(byte[] frame, int offset)
|
||||
{
|
||||
return (int)FastMath.round((double)getFrameSamples(frame, offset) / (double)getSampleRate(frame, offset) * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the frame bytes and provides details about the header.
|
||||
*/
|
||||
public static String inspect(byte[] frame, int offset)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("MP3 FRAME");
|
||||
|
||||
if(!isValid(frame, offset))
|
||||
{
|
||||
sb.append(" INVALID");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
sb.append(" VER:").append(getMPEGVersion(frame, offset));
|
||||
sb.append(" MODE:").append(getChannelMode(frame, offset));
|
||||
sb.append(" ").append(getMPEGLayer(frame, offset));
|
||||
sb.append(" BR:").append(getBitRate(frame, offset));
|
||||
sb.append(" SR:").append(getSampleRate(frame, offset));
|
||||
|
||||
//TODO: add MONO/STEREO
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
public class MP3SampleRate
|
||||
{
|
||||
private final static int[] MPEG_1 = new int[]{44100, 48000, 32000};
|
||||
private final static int[] MPEG_2 = new int[]{22050, 24000, 16000};
|
||||
private final static int[] MPEG2_5 = new int[]{11025, 12000, 8000};
|
||||
|
||||
public static int getSampleRate(MPEGVersion version, int value)
|
||||
{
|
||||
switch(version)
|
||||
{
|
||||
case V_1:
|
||||
if(0 <= value && value < MPEG_1.length)
|
||||
{
|
||||
return MPEG_1[value];
|
||||
}
|
||||
break;
|
||||
case V_2:
|
||||
if(0 <= value && value < MPEG_2.length)
|
||||
{
|
||||
return MPEG_2[value];
|
||||
}
|
||||
break;
|
||||
case V_2_5:
|
||||
if(0 <= value && value < MPEG2_5.length)
|
||||
{
|
||||
return MPEG2_5[value];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
113
src/main/java/io/github/dsheirer/audio/convert/MP3Setting.java
Normal file
113
src/main/java/io/github/dsheirer/audio/convert/MP3Setting.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import net.sourceforge.lame.mp3.Lame;
|
||||
|
||||
/**
|
||||
* MP3 encoder settings.
|
||||
*
|
||||
* A curated list of LAME audio encoder settings that are optimized for mono-channel voice that is originally
|
||||
* sampled at 8,000 Hz.
|
||||
*
|
||||
* See: https://svn.code.sf.net/p/lame/svn/trunk/lame/doc/html/usage.html
|
||||
*/
|
||||
public enum MP3Setting
|
||||
{
|
||||
CBR_16(false, 16, "CBR - Constant Bit Rate 16 kbps (default)"),
|
||||
CBR_32(false, 32, "CBR - Constant Bit Rate 32 kbps"),
|
||||
ABR_56(true, 56, "ABR - Average Bit Rate 56 kbps"),
|
||||
VBR_5(true, Lame.QUALITY_MIDDLE, "VBR - Variable Bit Rate - Better Quality"),
|
||||
VBR_7(true, Lame.QUALITY_LOW, "VBR - Variable Bit Rate - Good Quality");
|
||||
|
||||
private boolean mVariableBitRate;
|
||||
private int mSetting;
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param variableBitRate flag
|
||||
* @param setting for the entry
|
||||
* @param label to display
|
||||
*/
|
||||
MP3Setting(boolean variableBitRate, int setting, String label)
|
||||
{
|
||||
mVariableBitRate = variableBitRate;
|
||||
mSetting = setting;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default setting
|
||||
*/
|
||||
public static MP3Setting getDefault()
|
||||
{
|
||||
return MP3Setting.CBR_16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the entry is Constant Bit Rate (CBR), or Average/Variable Bit Rate (ABR or VBR)
|
||||
* @return true if CBR
|
||||
*/
|
||||
public boolean isVariableBitRate()
|
||||
{
|
||||
return mVariableBitRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* LAME encoder quality value
|
||||
*/
|
||||
public int getSetting()
|
||||
{
|
||||
return mSetting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input audio formats that are supported by the MP3Setting value.
|
||||
* @return list of supported input audio sample rates.
|
||||
*/
|
||||
public List<InputAudioFormat> getSupportedSampleRates()
|
||||
{
|
||||
switch(this)
|
||||
{
|
||||
case CBR_16:
|
||||
{
|
||||
return InputAudioFormat.SAMPLE_RATES_8_16.stream().toList();
|
||||
}
|
||||
case CBR_32:
|
||||
case VBR_7:
|
||||
{
|
||||
return InputAudioFormat.SAMPLE_RATES_8_16_22.stream().toList();
|
||||
}
|
||||
default:
|
||||
{
|
||||
return Arrays.stream(InputAudioFormat.values()).toList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -14,64 +14,89 @@
|
||||
*
|
||||
* 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.convert;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class MP3SilenceGenerator implements ISilenceGenerator
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MP3SilenceGenerator.class);
|
||||
public static final int MP3_BIT_RATE = 16;
|
||||
public static final boolean CONSTANT_BIT_RATE = false;
|
||||
|
||||
private MP3AudioConverter mGenerator = new MP3AudioConverter(MP3_BIT_RATE, CONSTANT_BIT_RATE);
|
||||
private byte[] mPreviousPartialFrameData;
|
||||
private MP3AudioConverter mMP3AudioConverter;
|
||||
private InputAudioFormat mInputAudioFormat;
|
||||
private byte[] mSilenceFrame;
|
||||
private int mSilenceFrameDuration;
|
||||
|
||||
/**
|
||||
* Generates MP3 audio silence frames
|
||||
*/
|
||||
public MP3SilenceGenerator()
|
||||
public MP3SilenceGenerator(InputAudioFormat inputAudioFormat, MP3Setting setting)
|
||||
{
|
||||
mInputAudioFormat = inputAudioFormat;
|
||||
mMP3AudioConverter = new MP3AudioConverter(inputAudioFormat, setting, false);
|
||||
generate_frame();
|
||||
}
|
||||
|
||||
public byte[] generate(long duration)
|
||||
/**
|
||||
* Generates a single silence frame
|
||||
* @return
|
||||
*/
|
||||
private void generate_frame()
|
||||
{
|
||||
int length = (int)(duration * 8); //8000 Hz sample rate
|
||||
//We generate silence at 8000 kHz, because it gets resampled by the silence generator to the target rate
|
||||
int length = (int)(InputAudioFormat.SR_8000.getSampleRate());
|
||||
|
||||
List<float[]> silenceBuffers = new ArrayList<>();
|
||||
silenceBuffers.add(new float[length]);
|
||||
byte[] frameData = mGenerator.convert(silenceBuffers);
|
||||
|
||||
frameData = merge(mPreviousPartialFrameData, frameData);
|
||||
|
||||
if(frameData != null && frameData.length > 0)
|
||||
int added = 0;
|
||||
while(added < length)
|
||||
{
|
||||
if(frameData.length < 144)
|
||||
{
|
||||
mPreviousPartialFrameData = frameData;
|
||||
return null;
|
||||
}
|
||||
else if((frameData.length % 144) == 0)
|
||||
{
|
||||
mPreviousPartialFrameData = null;
|
||||
return frameData;
|
||||
}
|
||||
else
|
||||
{
|
||||
int integralFrameLength = (int)(frameData.length / 144) * 144;
|
||||
mPreviousPartialFrameData = Arrays.copyOfRange(frameData, integralFrameLength, frameData.length);
|
||||
return Arrays.copyOf(frameData, integralFrameLength);
|
||||
}
|
||||
int chunk = Math.min(length - added, 256);
|
||||
silenceBuffers.add(new float[chunk]);
|
||||
added += chunk;
|
||||
}
|
||||
|
||||
return null;
|
||||
List<byte[]> mp3Audio = mMP3AudioConverter.convert(silenceBuffers);
|
||||
byte[] silence = new byte[0];
|
||||
for(byte[] mp3Chunk: mp3Audio)
|
||||
{
|
||||
silence = merge(silence, mp3Chunk);
|
||||
}
|
||||
|
||||
MP3AudioFrames mp3Frames = MP3FrameTools.split(silence);
|
||||
if(mp3Frames.hasNextFrame())
|
||||
{
|
||||
mp3Frames.nextFrame();
|
||||
mSilenceFrame = mp3Frames.getCurrentFrame();
|
||||
mSilenceFrameDuration = mp3Frames.getCurrentFrameDuration();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates silence frames
|
||||
* @param duration_ms in milliseconds
|
||||
* @return
|
||||
*/
|
||||
public MP3AudioFrames generate(long duration_ms)
|
||||
{
|
||||
List<byte[]> silenceFrames = new ArrayList<>();
|
||||
int silenceDuration = 0;
|
||||
|
||||
while(silenceDuration < duration_ms)
|
||||
{
|
||||
silenceFrames.add(mSilenceFrame);
|
||||
silenceDuration += mSilenceFrameDuration;
|
||||
}
|
||||
|
||||
return new MP3AudioFrames(silenceDuration, silenceFrames);
|
||||
}
|
||||
|
||||
private static byte[] merge(byte[] a, byte[] b)
|
||||
@@ -96,23 +121,30 @@ public class MP3SilenceGenerator implements ISilenceGenerator
|
||||
return c;
|
||||
}
|
||||
|
||||
/* TODO: Rework for MP3SilenceGenerator returning IAudioFrames
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
|
||||
for(long x = 243; x < 500; x ++)
|
||||
MP3SilenceGenerator generator = new MP3SilenceGenerator(InputAudioFormat.SR_44100, MP3Setting.VBR_7);
|
||||
|
||||
int x = 100;
|
||||
|
||||
// for(; x < 1000; x += 10)
|
||||
// {
|
||||
List<byte[]> silenceFrames = generator.generate(x);
|
||||
|
||||
mLog.debug("Generated [" + silenceFrames.size() + "] silence frames");
|
||||
|
||||
for(byte[] silence: silenceFrames)
|
||||
{
|
||||
MP3SilenceGenerator generator = new MP3SilenceGenerator();
|
||||
|
||||
byte[] silence = generator.generate(x);
|
||||
|
||||
if(silence != null && silence.length > 0)
|
||||
{
|
||||
mLog.debug("Silence:" + x + " Length:" + silence.length);
|
||||
MP3FrameInspector.inspect(silence);
|
||||
}
|
||||
mLog.debug("Silence:" + x + " Length:" + silence.length);
|
||||
}
|
||||
|
||||
MP3FrameInspector.inspect(silenceFrames);
|
||||
// }
|
||||
|
||||
mLog.debug("Finished");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
/**
|
||||
* MPEG Layer
|
||||
*
|
||||
* see: http://www.mp3-tech.org/programmer/frame_header.html
|
||||
*/
|
||||
public enum MPEGLayer
|
||||
{
|
||||
RESERVED("LAYER-RESERVED"),
|
||||
LAYER3("LAYER-3"),
|
||||
LAYER2("LAYER-2"),
|
||||
LAYER1("LAYER-1");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
MPEGLayer(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public static MPEGLayer fromValue(int value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case 1:
|
||||
return LAYER3;
|
||||
case 2:
|
||||
return LAYER2;
|
||||
case 3:
|
||||
return LAYER1;
|
||||
default:
|
||||
case 0:
|
||||
return RESERVED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
/**
|
||||
* MPEG Version
|
||||
*
|
||||
* see: http://www.mp3-tech.org/programmer/frame_header.html
|
||||
*/
|
||||
public enum MPEGVersion
|
||||
{
|
||||
V_2_5("2.5"),
|
||||
RESERVED("RESERVED"),
|
||||
V_2("2"),
|
||||
V_1("1");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
MPEGVersion(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public static MPEGVersion fromValue(int value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case 0:
|
||||
return V_2_5;
|
||||
case 2:
|
||||
return V_2;
|
||||
case 3:
|
||||
return V_1;
|
||||
default:
|
||||
return RESERVED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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.convert.thumbdv;
|
||||
@@ -36,8 +33,6 @@ import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechRes
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
|
||||
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -82,11 +77,10 @@ public class ThumbDv implements AutoCloseable
|
||||
private ScheduledFuture mAudioDecodeProcessorHandle;
|
||||
private LinkedTransferQueue<DecodeSpeechRequest> mDecodeSpeechRequests = new LinkedTransferQueue<>();
|
||||
private AudioProtocol mAudioProtocol;
|
||||
private Listener<ReusableFloatBuffer> mAudioBufferListener;
|
||||
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("ThumbDv");
|
||||
private Listener<float[]> mAudioBufferListener;
|
||||
private boolean mStarted;
|
||||
|
||||
public ThumbDv(AudioProtocol audioProtocol, Listener<ReusableFloatBuffer> listener)
|
||||
public ThumbDv(AudioProtocol audioProtocol, Listener<float[]> listener)
|
||||
{
|
||||
mAudioProtocol = audioProtocol;
|
||||
mAudioBufferListener = listener;
|
||||
@@ -209,8 +203,7 @@ public class ThumbDv implements AutoCloseable
|
||||
if(message instanceof DecodeSpeechResponse && mAudioBufferListener != null)
|
||||
{
|
||||
float[] samples = ((DecodeSpeechResponse)message).getSamples();
|
||||
ReusableFloatBuffer audioBuffer = mReusableBufferQueue.getBuffer(samples, System.currentTimeMillis());
|
||||
mAudioBufferListener.receive(audioBuffer);
|
||||
mAudioBufferListener.receive(samples);
|
||||
}
|
||||
else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null)
|
||||
{
|
||||
@@ -456,9 +449,8 @@ public class ThumbDv implements AutoCloseable
|
||||
|
||||
mLog.debug("Starting thumb dv thread(s)");
|
||||
|
||||
final Listener<ReusableFloatBuffer> listener = reusableAudioPacket -> {
|
||||
final Listener<float[]> listener = packet -> {
|
||||
mLog.info("Got an audio packet!");
|
||||
reusableAudioPacket.decrementUserCount();
|
||||
};
|
||||
|
||||
try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener))
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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.convert.thumbdv.message;
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is 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.convert.thumbdv.message.response;
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.invert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.FloatFIRFilter;
|
||||
import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
|
||||
/**
|
||||
* Based on the AudioType applied, produces normal, muted, or audio invert
|
||||
@@ -30,7 +31,7 @@ import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
public class AudioAdapter
|
||||
{
|
||||
private static int mGain = 5;
|
||||
private IOscillator mSineWaveGenerator;
|
||||
private IRealOscillator mSineWaveGenerator;
|
||||
private FloatFIRFilter mPostInversionFilter;
|
||||
private FloatFIRFilter mAudioHighPassFilter;
|
||||
private AudioType mAudioType = AudioType.NORMAL;
|
||||
@@ -39,7 +40,7 @@ public class AudioAdapter
|
||||
public AudioAdapter( int sampleRate )
|
||||
{
|
||||
mSampleRate = sampleRate;
|
||||
mSineWaveGenerator = new Oscillator( 3000, mSampleRate );
|
||||
mSineWaveGenerator = OscillatorFactory.getRealOscillator(3000, mSampleRate );
|
||||
mAudioHighPassFilter = new FloatFIRFilter(
|
||||
FilterFactory.getHighPass( mSampleRate, 200, 1600, 48,
|
||||
WindowType.HAMMING, true ), mGain );
|
||||
@@ -58,7 +59,7 @@ public class AudioAdapter
|
||||
|
||||
if( inversionFrequency != 0 )
|
||||
{
|
||||
mSineWaveGenerator.setFrequency( inversionFrequency );;
|
||||
mSineWaveGenerator.setFrequency( inversionFrequency );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,9 +90,7 @@ public class AudioAdapter
|
||||
default:
|
||||
retVal = (float)( mPostInversionFilter.get(
|
||||
mAudioHighPassFilter.get( sample ) *
|
||||
mSineWaveGenerator.inphase() ) );
|
||||
|
||||
mSineWaveGenerator.rotate();
|
||||
mSineWaveGenerator.generate(1)[0] ) );
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.invert;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.Filters;
|
||||
import io.github.dsheirer.dsp.filter.FloatFIRFilter;
|
||||
import io.github.dsheirer.dsp.mixer.IOscillator;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.IRealOscillator;
|
||||
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
|
||||
import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
|
||||
/**
|
||||
@@ -29,17 +29,33 @@ import io.github.dsheirer.sample.real.RealSampleListener;
|
||||
*/
|
||||
public class AudioInverter implements RealSampleListener
|
||||
{
|
||||
private IOscillator mSineWaveGenerator;
|
||||
private static final float[] LOW_PASS_FILTER = new float[] { -0.0052419787903715655f,
|
||||
-0.0003789909443307852f, 0.0012970137854134386f, 0.004210460544067679f,
|
||||
0.008221266221665309f, 0.012951576579356689f, 0.017794312853913318f,
|
||||
0.021959871931790928f, 0.024583800118549094f, 0.024878144443784132f,
|
||||
0.022296319647647538f, 0.016685495012933472f, 0.008390268267799136f,
|
||||
-0.001718264469387386f, -0.012288389660616603f, -0.02161633130497537f,
|
||||
-0.027882128567957774f, -0.02943377233328899f, -0.025006516193566884f,
|
||||
-0.014040074880106284f, 0.0032217508351258103f, 0.025660031586092195f,
|
||||
0.05136009984280478f, 0.0778336106888181f, 0.10230926486919244f,
|
||||
0.12209961841343948f, 0.13496813439824185f, 0.13943117253534673f,
|
||||
0.13496813439824185f, 0.12209961841343948f, 0.10230926486919244f,
|
||||
0.0778336106888181f, 0.05136009984280478f, 0.025660031586092195f,
|
||||
0.0032217508351258103f, -0.014040074880106284f, -0.025006516193566884f,
|
||||
-0.02943377233328899f, -0.027882128567957774f, -0.02161633130497537f,
|
||||
-0.012288389660616603f, -0.001718264469387386f, 0.008390268267799136f,
|
||||
0.016685495012933472f, 0.022296319647647538f, 0.024878144443784132f,
|
||||
0.024583800118549094f, 0.021959871931790928f, 0.017794312853913318f,
|
||||
0.012951576579356689f, 0.008221266221665309f, 0.004210460544067679f,
|
||||
0.0012970137854134386f, -0.0003789909443307852f, -0.0052419787903715655f };
|
||||
private IRealOscillator mSineWaveGenerator;
|
||||
private FloatFIRFilter mPostInversionLowPassFilter;
|
||||
private RealSampleListener mListener;
|
||||
|
||||
public AudioInverter( int inversionFrequency, int sampleRate )
|
||||
{
|
||||
mSineWaveGenerator = new Oscillator( inversionFrequency, sampleRate );
|
||||
|
||||
mPostInversionLowPassFilter = new FloatFIRFilter(
|
||||
Filters.FIRLP_55TAP_48000FS_3000FC.getCoefficients(), 1.04f );
|
||||
|
||||
mSineWaveGenerator = OscillatorFactory.getRealOscillator( inversionFrequency, sampleRate );
|
||||
mPostInversionLowPassFilter = new FloatFIRFilter(LOW_PASS_FILTER, 1.04f );
|
||||
mPostInversionLowPassFilter.setListener( new FilteredSampleProcessor() );
|
||||
}
|
||||
|
||||
@@ -60,10 +76,7 @@ public class AudioInverter implements RealSampleListener
|
||||
//the low pass filter
|
||||
if( mPostInversionLowPassFilter != null )
|
||||
{
|
||||
mPostInversionLowPassFilter.receive(
|
||||
sample * mSineWaveGenerator.inphase() );
|
||||
|
||||
mSineWaveGenerator.rotate();
|
||||
mPostInversionLowPassFilter.receive(sample * mSineWaveGenerator.generate(1)[0] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * 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 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.playback;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.audio.AudioEvent;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.controller.NamingThreadFactory;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
@@ -33,11 +30,13 @@ import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -51,10 +50,6 @@ import javax.sound.sampled.LineListener;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Audio output/playback channel for a single audio mixer channel. Providers support for playback of audio segments
|
||||
@@ -67,7 +62,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
private int mBufferStopThreshold;
|
||||
private Listener<IdentifierCollection> mIdentifierCollectionListener;
|
||||
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||
private ScheduledFuture<?> mProcessorFuture;
|
||||
private SourceDataLine mOutput;
|
||||
private Mixer mMixer;
|
||||
private MixerChannel mMixerChannel;
|
||||
@@ -76,17 +70,17 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
private AudioEvent mAudioStartEvent;
|
||||
private AudioEvent mAudioStopEvent;
|
||||
private boolean mCanProcessAudio = false;
|
||||
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||
private AudioSegment mCurrentAudioSegment;
|
||||
private AudioSegment mNextAudioSegment;
|
||||
private ReentrantLock mLock = new ReentrantLock();
|
||||
private int mCurrentBufferIndex = 0;
|
||||
private UserPreferences mUserPreferences;
|
||||
private BooleanProperty mEmptyProperty = new SimpleBooleanProperty(true);
|
||||
private IntegerProperty mAudioPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||
private ByteBuffer mAudioSegmentStartTone;
|
||||
private ByteBuffer mAudioSegmentPreemptTone;
|
||||
private ByteBuffer mAudioSegmentDropTone;
|
||||
private boolean mRunning = false;
|
||||
private ScheduledExecutorService mScheduledExecutorService;
|
||||
private ScheduledFuture<?> mProcessorFuture;
|
||||
private long mOutputLastTimestamp = 0;
|
||||
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
|
||||
|
||||
/**
|
||||
* Single audio channel playback with automatic starting and stopping of the
|
||||
@@ -107,6 +101,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
{
|
||||
mMixer = mixer;
|
||||
mMixerChannel = mixerChannel;
|
||||
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
|
||||
"sdrtrunk audio output " + mixerChannel.name()));
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
try
|
||||
@@ -150,7 +146,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
|
||||
//Run the queue processor task every 100 milliseconds or 10 times a second
|
||||
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
mProcessorFuture = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
0, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@@ -172,26 +168,17 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean property that indicates if this audio output has an audio segment queued or in process of playback.
|
||||
* Indicates if this audio output doesn't currently have any audio segments queued for playback.
|
||||
* @return true if empty
|
||||
*/
|
||||
public BooleanProperty emptyProperty()
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return mEmptyProperty;
|
||||
return mAudioSegmentQueue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio playback priority of the current audio segment or default audio priority if nothing is currently loaded.
|
||||
*/
|
||||
public IntegerProperty audioPriorityProperty()
|
||||
{
|
||||
return mAudioPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the audio segment for playback.
|
||||
*
|
||||
* Note: if a segment is currently playing and another audio segment is already queued for playback, invoking
|
||||
* this method will overwrite the queued segment with the argument.
|
||||
* Schedules the audio segment for playback. The audio segment user count should already be incremented by the
|
||||
* calling entity.
|
||||
*
|
||||
* @param audioSegment to schedule for playback.
|
||||
*/
|
||||
@@ -199,23 +186,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
{
|
||||
if(audioSegment != null)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
if(mNextAudioSegment != null)
|
||||
{
|
||||
mNextAudioSegment.decrementConsumerCount();
|
||||
mNextAudioSegment = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mNextAudioSegment = audioSegment;
|
||||
mEmptyProperty.set(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
//Audio segment use count has already been incremented by the external caller.
|
||||
mAudioSegmentQueue.add(audioSegment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +234,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
private void updateToneInsertionAudioClips()
|
||||
{
|
||||
mAudioSegmentStartTone = null;
|
||||
mAudioSegmentPreemptTone = null;
|
||||
mAudioSegmentDropTone = null;
|
||||
|
||||
float[] start = mUserPreferences.getPlaybackPreference().getStartTone();
|
||||
@@ -272,13 +243,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
mAudioSegmentStartTone = convert(start);
|
||||
}
|
||||
|
||||
float[] preempt = mUserPreferences.getPlaybackPreference().getPreemptTone();
|
||||
|
||||
if(preempt != null)
|
||||
{
|
||||
mAudioSegmentPreemptTone = convert(preempt);
|
||||
}
|
||||
|
||||
float[] drop = mUserPreferences.getPlaybackPreference().getDropTone();
|
||||
|
||||
if(drop != null)
|
||||
@@ -287,48 +251,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of a processed audio segment and notifies the originator that processing of the segment is complete
|
||||
* by decrementing the consumer count.
|
||||
* @param audioSegment to dispose
|
||||
*/
|
||||
private void dispose(AudioSegment audioSegment)
|
||||
{
|
||||
mAudioPriority.unbind();
|
||||
|
||||
if(audioSegment != null)
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
audioSegment.removeIdentifierUpdateNotificationListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tone indicating that a new audio segment is starting
|
||||
*/
|
||||
private ByteBuffer getAudioSegmentStartTone()
|
||||
{
|
||||
return mAudioSegmentStartTone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tone indicating that the current audio segment playback has been preempted for a higher priority
|
||||
* audio segment that is now starting.
|
||||
*/
|
||||
private ByteBuffer getAudioSegmentPreemptionTone()
|
||||
{
|
||||
return mAudioSegmentPreemptTone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tone indicating that the current audio segment playback has been dropped because the audio segment
|
||||
* has been flagged as Do Not Monitor after playback has started.
|
||||
*/
|
||||
private ByteBuffer getAudioSegmentDropTone()
|
||||
{
|
||||
return mAudioSegmentDropTone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the audio buffer data to the source data line. If the data line is not currently playing, write as
|
||||
* much of the buffer to the data line as will fit, start the dataline, and then finish writing the residual
|
||||
@@ -355,6 +277,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
|
||||
//Top off the buffer and check if we can start it
|
||||
wrote += mOutput.write(buffer.array(), 0, toWrite);
|
||||
mOutputLastTimestamp = System.currentTimeMillis();
|
||||
|
||||
checkStart();
|
||||
}
|
||||
@@ -363,147 +286,135 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
{
|
||||
//This will block until the buffer is fully written to the data line
|
||||
mOutput.write(buffer.array(), wrote, buffer.array().length - wrote);
|
||||
mOutputLastTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of the currently assigned audio segment.
|
||||
*/
|
||||
private void disposeCurrentAudioSegment()
|
||||
{
|
||||
if(mCurrentAudioSegment != null)
|
||||
{
|
||||
mCurrentAudioSegment.decrementConsumerCount();
|
||||
mCurrentAudioSegment.removeIdentifierUpdateNotificationListener(this);
|
||||
mCurrentAudioSegment = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the next audio segment from the queue.
|
||||
*/
|
||||
private void loadNextAudioSegment()
|
||||
{
|
||||
AudioSegment audioSegment = mAudioSegmentQueue.poll();
|
||||
|
||||
boolean verificationInProgress = (audioSegment != null);
|
||||
|
||||
while(verificationInProgress)
|
||||
{
|
||||
if(audioSegment != null)
|
||||
{
|
||||
//Throw away the audio segment if it has been flagged as do not monitor or is duplicate
|
||||
if(isThrowaway(audioSegment))
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
audioSegment = mAudioSegmentQueue.poll();
|
||||
}
|
||||
else
|
||||
{
|
||||
verificationInProgress = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
verificationInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentAudioSegment = audioSegment;
|
||||
|
||||
if(audioSegment != null)
|
||||
{
|
||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||
|
||||
//If we played any amount of the previous audio segment then play the audio segment start. We check this
|
||||
//to avoid repeatedly playing an audio segment start tone on the rare occasion that we have empty segments.
|
||||
if(mCurrentBufferIndex > 0)
|
||||
{
|
||||
playAudio(mAudioSegmentStartTone);
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentBufferIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the audio segment should be thrown away because it is marked as duplicate or do-not-monitor.
|
||||
* @param audioSegment to evaluate
|
||||
* @return true if the audio should be thrown away.
|
||||
*/
|
||||
private boolean isThrowaway(AudioSegment audioSegment)
|
||||
{
|
||||
return audioSegment != null &&
|
||||
(audioSegment.isDoNotMonitor() ||
|
||||
(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference()
|
||||
.isDuplicatePlaybackSuppressionEnabled()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage audio segment playback and process audio segment buffers. This method is designed to be called
|
||||
* by a threaded processor repeatedly to playback the current audio segment and check for and start a newly
|
||||
* assigned audio segment. It also handles starting and stopping the playback source data line to avoid audio
|
||||
* by a threaded processor repeatedly to playback the current audio segment and check for and start newly
|
||||
* added audio segments. It also handles starting and stopping the playback source data line to avoid audio
|
||||
* discontinuities due to buffer underruns.
|
||||
*/
|
||||
private void processAudio()
|
||||
{
|
||||
if(mNextAudioSegment != null)
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
//Remove an assigned segment that's subsequently flagged as duplicate or do not monitor before playback
|
||||
if(mNextAudioSegment.isDoNotMonitor() || (mNextAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
{
|
||||
mNextAudioSegment = null;
|
||||
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
mEmptyProperty.set(true);
|
||||
}
|
||||
}
|
||||
//For linked audio segments, allow the linked segment to complete first before assigning the next
|
||||
else if(mNextAudioSegment.isLinked())
|
||||
{
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
mCurrentAudioSegment = mNextAudioSegment;
|
||||
mNextAudioSegment = null;
|
||||
mCurrentBufferIndex = 0;
|
||||
|
||||
if(mCurrentAudioSegment != null)
|
||||
{
|
||||
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||
}
|
||||
else
|
||||
{
|
||||
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Insert audio segment start or audio priority preemption bonk tone
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
playAudio(getAudioSegmentStartTone());
|
||||
}
|
||||
else if(mCurrentBufferIndex > 0 &&
|
||||
(!mCurrentAudioSegment.completeProperty().get() ||
|
||||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount()))
|
||||
{
|
||||
playAudio(getAudioSegmentPreemptionTone());
|
||||
}
|
||||
else
|
||||
{
|
||||
playAudio(getAudioSegmentStartTone());
|
||||
}
|
||||
|
||||
//Close current audio segment
|
||||
dispose(mCurrentAudioSegment);
|
||||
mCurrentAudioSegment = mNextAudioSegment;
|
||||
mNextAudioSegment = null;
|
||||
mCurrentBufferIndex = 0;
|
||||
|
||||
if(mCurrentAudioSegment != null)
|
||||
{
|
||||
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||
}
|
||||
else
|
||||
{
|
||||
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
loadNextAudioSegment();
|
||||
}
|
||||
|
||||
if(mCurrentAudioSegment != null)
|
||||
//Reevaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
||||
if(isThrowaway(mCurrentAudioSegment))
|
||||
{
|
||||
//Check for completed audio segment or a segment flagged as duplicate or Do Not Monitor
|
||||
if(mCurrentAudioSegment.isDoNotMonitor() ||
|
||||
(mCurrentAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()) ||
|
||||
(mCurrentAudioSegment.completeProperty().get() && mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||
if(mCurrentBufferIndex > 0)
|
||||
{
|
||||
if(mCurrentAudioSegment.isDoNotMonitor())
|
||||
{
|
||||
playAudio(getAudioSegmentDropTone());
|
||||
}
|
||||
|
||||
dispose(mCurrentAudioSegment);
|
||||
mCurrentAudioSegment = null;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mNextAudioSegment == null)
|
||||
{
|
||||
mEmptyProperty.set(true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
return;
|
||||
playAudio(mAudioSegmentDropTone);
|
||||
}
|
||||
|
||||
//Process any new buffers that have been added to the audio segment. If a next audio segment gets assigned
|
||||
//while processing, exit the loop so that we can evaluate the next for higher priority preempt. If the next
|
||||
//segment is a linked segment, ignore it so that we can close out the current segment.
|
||||
while(mCurrentAudioSegment != null && (mNextAudioSegment == null || mNextAudioSegment.isLinked()) &&
|
||||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount() &&
|
||||
!mCurrentAudioSegment.isDoNotMonitor() && !(mCurrentAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
disposeCurrentAudioSegment();
|
||||
loadNextAudioSegment();
|
||||
}
|
||||
|
||||
while(mCurrentAudioSegment != null && mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount())
|
||||
{
|
||||
try
|
||||
{
|
||||
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
||||
|
||||
if(audioBuffer != null)
|
||||
{
|
||||
ByteBuffer audio = convert(audioBuffer);
|
||||
//This call blocks until all audio bytes are dumped into the data line.
|
||||
playAudio(audio);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error while processing audio for [" + mMixerChannel.name() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
//Check for completed and fully-played audio segment -- load next audio segment
|
||||
if(mCurrentAudioSegment != null &&
|
||||
mCurrentAudioSegment.isComplete() &&
|
||||
(mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||
{
|
||||
disposeCurrentAudioSegment();
|
||||
}
|
||||
|
||||
checkStop();
|
||||
@@ -523,25 +434,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
|
||||
mProcessorFuture = null;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mNextAudioSegment != null)
|
||||
{
|
||||
mNextAudioSegment.decrementConsumerCount();
|
||||
mNextAudioSegment = null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
dispose(mCurrentAudioSegment);
|
||||
mCurrentAudioSegment = null;
|
||||
|
||||
disposeCurrentAudioSegment();
|
||||
mAudioEventBroadcaster.clear();
|
||||
mIdentifierCollectionListener = null;
|
||||
|
||||
@@ -650,14 +543,28 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
if(mRunning)
|
||||
{
|
||||
//If the output buffer falls below the threshold then drain the output and stop playback
|
||||
if(mOutput.isRunning() && mOutput.available() >= mBufferStopThreshold)
|
||||
if(mOutput.isRunning())
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
mRunning = false;
|
||||
if(mOutput.available() >= mBufferStopThreshold)
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
mRunning = false;
|
||||
}
|
||||
//Detect when we have a partially filled output that will neither start nor stop and the last write
|
||||
//timestamp was more than the staleness threshold (500 ms) -- stop the output and discard stale contents
|
||||
else if(mOutput.available() < mBufferStopThreshold &&
|
||||
mOutput.available() > mBufferStartThreshold &&
|
||||
(System.currentTimeMillis() - mOutputLastTimestamp >= STALE_PLAYBACK_THRESHOLD_MS))
|
||||
{
|
||||
mOutput.stop();
|
||||
//Discard the stale buffer contents.
|
||||
mOutput.flush();
|
||||
mRunning = false;
|
||||
}
|
||||
}
|
||||
//If output playback stopped on its own because the buffer emptied, then cleanup
|
||||
else if(!mOutput.isRunning())
|
||||
else
|
||||
{
|
||||
mRunning = false;
|
||||
}
|
||||
@@ -735,16 +642,33 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
*/
|
||||
public class AudioSegmentProcessor implements Runnable
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
if(mProcessing.compareAndSet(false, true))
|
||||
{
|
||||
processAudio();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error while processing audio buffers", t);
|
||||
try
|
||||
{
|
||||
//TODO: Debug hooks - remove after testing
|
||||
if(mMixerChannel.name().equals("LEFT"))
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
else if(mMixerChannel.name().equals("RIGHT"))
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
processAudio();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error while processing audio buffers", t);
|
||||
}
|
||||
|
||||
mProcessing.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user